forked from fsprojects/FAKE
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGlobbing.fs
134 lines (117 loc) · 4.94 KB
/
Globbing.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/// This module contains a file pattern globbing implementation.
module Fake.Globbing
open System
open System.Collections.Generic
open System.IO
open System.Text.RegularExpressions
type private SearchOption =
| Directory of string
| Drive of string
| Recursive
| FilePattern of string
let private checkSubDirs absolute (dir : string) root =
if dir.Contains "*" then Directory.EnumerateDirectories(root, dir, SearchOption.TopDirectoryOnly) |> Seq.toList
else
let path = Path.Combine(root, dir)
let di =
if absolute then new DirectoryInfo(dir)
else new DirectoryInfo(path)
if di.Exists then [ di.FullName ]
else []
let rec private buildPaths acc (input : SearchOption list) =
match input with
| [] -> acc
| Directory(name) :: t ->
let subDirs =
acc
|> List.map (checkSubDirs false name)
|> List.concat
buildPaths subDirs t
| Drive(name) :: t ->
let subDirs =
acc
|> List.map (checkSubDirs true name)
|> List.concat
buildPaths subDirs t
| Recursive :: [] ->
let dirs =
Seq.collect (fun dir -> Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories)) acc
|> Seq.toList
buildPaths (acc @ dirs) []
| Recursive :: t ->
let dirs =
Seq.collect (fun dir -> Directory.EnumerateDirectories(dir, "*", SearchOption.AllDirectories)) acc
|> Seq.toList
buildPaths (acc @ dirs) t
| FilePattern(pattern) :: t ->
Seq.collect (fun dir ->
if Directory.Exists(Path.Combine(dir, pattern))
then seq { yield Path.Combine(dir, pattern) }
else Directory.EnumerateFiles(dir, pattern)) acc |> Seq.toList
let private isDrive =
let regex = Regex(@"^[A-Za-z]:$", RegexOptions.Compiled)
fun dir -> regex.IsMatch dir
let inline private normalizePath (p : string) =
p.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar)
let inline private normalizeOutputPath (p : string) =
p.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar)
.TrimEnd(Path.DirectorySeparatorChar)
let internal getRoot (baseDirectory : string) (pattern : string) =
let baseDirectory = normalizePath baseDirectory
let normPattern = normalizePath pattern
let patternParts = normPattern.Split([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries)
let patternPathParts =
patternParts
|> Seq.takeWhile(fun p -> not (p.Contains("*")))
|> Seq.toArray
let globRoot =
// If we did not find any "*", then drop the last bit (it is a file name, not a pattern)
( if patternPathParts.Length = patternParts.Length then
patternPathParts.[0 .. patternPathParts.Length-2]
else patternPathParts )
|> String.concat (Path.DirectorySeparatorChar.ToString())
let globRoot =
// If we dropped "/" from the beginning of the path in the 'Split' call, put it back!
if normPattern.StartsWith("/") then "/" + globRoot
else globRoot
if Path.IsPathRooted globRoot then globRoot
else Path.Combine(baseDirectory, globRoot)
let internal search (baseDir : string) (input : string) =
let baseDir = normalizePath baseDir
let input = normalizePath input
let input = input.Replace(baseDir, "")
let filePattern = Path.GetFileName(input)
input.Split([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries)
|> Seq.map (function
| "**" -> Recursive
| a when a = filePattern -> FilePattern(a)
| a when isDrive a -> Directory(a + "\\")
| a -> Directory(a))
|> Seq.toList
|> buildPaths [ baseDir ]
|> List.map normalizeOutputPath
let internal isMatch pattern path : bool =
let pattern = normalizePath pattern
let path = normalizePath path
let escapedPattern = (Regex.Escape pattern)
let regexPattern =
let xTOy =
[
"dirwildcard", (@"\\\*\\\*(/|\\\\)", @"(.*(/|\\))?")
"stardotstar", (@"\\\*\\.\\\*", @"([^\\/]*)")
"wildcard", (@"\\\*", @"([^\\/]*)")
] |> List.map(fun (key, reg) ->
let pattern, replace = reg
let pattern = sprintf "(?<%s>%s)" key pattern
key, (pattern, replace)
)
let xTOyMap = xTOy |> Map.ofList
let replacePattern = xTOy |> List.map(fun x -> x |> snd |> fst) |> String.concat("|")
let replaced = Regex(replacePattern).Replace(escapedPattern, fun m ->
let matched = xTOy |> Seq.map(fst) |> Seq.find(fun n ->
m.Groups.Item(n).Success
)
(xTOyMap |> Map.tryFind matched).Value |> snd
)
"^" + replaced + "$"
Regex(regexPattern).IsMatch(path)