@@ -15,14 +15,18 @@ func pathContainsGlobSyntax(_ path: String) -> Bool {
1515/// Glob type represents either an exact path or wildcard
1616public enum Glob : CustomStringConvertible {
1717 case path( String )
18- case regex( NSRegularExpression )
18+ case regex( String , NSRegularExpression )
1919
2020 public func matches( _ path: String ) -> Bool {
2121 switch self {
2222 case let . path( _path) :
2323 return _path == path
24- case let . regex( regex) :
25- let range = NSRange ( location: 0 , length: path. utf16. count)
24+ case let . regex( prefix, regex) :
25+ guard path. hasPrefix ( prefix) else {
26+ return false
27+ }
28+ let count = prefix. utf16. count
29+ let range = NSRange ( location: count, length: path. utf16. count - count)
2630 return regex. firstMatch ( in: path, options: [ ] , range: range) != nil
2731 }
2832 }
@@ -31,7 +35,7 @@ public enum Glob: CustomStringConvertible {
3135 switch self {
3236 case let . path( path) :
3337 return path
34- case let . regex( regex) :
38+ case let . regex( prefix , regex) :
3539 var result = regex. pattern. dropFirst ( ) . dropLast ( )
3640 . replacingOccurrences ( of: " ([^/]+)? " , with: " * " )
3741 . replacingOccurrences ( of: " (.+/)? " , with: " **/ " )
@@ -42,7 +46,7 @@ public enum Glob: CustomStringConvertible {
4246 let options = result [ range] . dropFirst ( ) . dropLast ( ) . components ( separatedBy: " | " )
4347 result. replaceSubrange ( range, with: " { \( options. joined ( separator: " , " ) ) } " )
4448 }
45- return result
49+ return prefix + result
4650 }
4751 }
4852}
@@ -70,7 +74,16 @@ public func expandGlobs(_ paths: String, in directory: String) -> [Glob] {
7074 // TODO: should we also handle cases where path includes tokens?
7175 return . path( path)
7276 }
73- var regex = " ^ \( path) $ "
77+ var prefix = " " , regex = " "
78+ let parts = path. components ( separatedBy: " / " )
79+ for (i, part) in parts. enumerated ( ) {
80+ if pathContainsGlobSyntax ( part) || part. contains ( " <<< " ) {
81+ regex = parts [ i... ] . joined ( separator: " / " )
82+ break
83+ }
84+ prefix += " \( part) / "
85+ }
86+ regex = " ^ \( regex) $ "
7487 . replacingOccurrences ( of: " [.+(){ \\ \\ |] " , with: " \\ \\ $0 " , options: . regularExpression)
7588 . replacingOccurrences ( of: " ? " , with: " [^/] " )
7689 . replacingOccurrences ( of: " **/ " , with: " (.+/)? " )
@@ -79,16 +92,15 @@ public func expandGlobs(_ paths: String, in directory: String) -> [Glob] {
7992 for (token, replacement) in tokens {
8093 regex = regex. replacingOccurrences ( of: token, with: replacement)
8194 }
82- return try ! . regex( NSRegularExpression ( pattern: regex, options: [ ] ) )
95+ return try ! . regex( prefix , NSRegularExpression ( pattern: regex, options: [ ] ) )
8396 }
8497}
8598
86- // NOTE: currently only used for testing
87- func matchGlobs( _ globs: [ Glob ] , in directory: String ) -> [ URL ] {
99+ func matchGlobs( _ globs: [ Glob ] , in directory: String ) throws -> [ URL ] {
88100 var urls = [ URL] ( )
89101 let keys : [ URLResourceKey ] = [ . isDirectoryKey]
90102 let manager = FileManager . default
91- func enumerate( _ directory: URL ) {
103+ func enumerate( _ directory: URL , with glob : Glob ) {
92104 guard let files = try ? manager. contentsOfDirectory (
93105 at: directory, includingPropertiesForKeys: keys, options: [ ]
94106 ) else {
@@ -97,15 +109,34 @@ func matchGlobs(_ globs: [Glob], in directory: String) -> [URL] {
97109 for url in files {
98110 let path = url. path
99111 var isDirectory : ObjCBool = false
100- if globs . contains ( where : { $0 . matches ( path) } ) {
112+ if glob . matches ( path) {
101113 urls. append ( url)
102114 } else if manager. fileExists ( atPath: path, isDirectory: & isDirectory) ,
103115 isDirectory. boolValue
104116 {
105- enumerate ( url)
117+ enumerate ( url, with: glob)
118+ }
119+ }
120+ }
121+ for glob in globs {
122+ switch glob {
123+ case let . path( path) :
124+ if manager. fileExists ( atPath: path) {
125+ urls. append ( URL ( fileURLWithPath: path) )
126+ } else {
127+ throw FormatError . options ( " File not found at \( glob) " )
128+ }
129+ case let . regex( path, _) :
130+ let count = urls. count
131+ if directory. hasPrefix ( path) {
132+ enumerate ( URL ( fileURLWithPath: directory) . standardized, with: glob)
133+ } else if path. hasPrefix ( directory) {
134+ enumerate ( URL ( fileURLWithPath: path) . standardized, with: glob)
135+ }
136+ if count == urls. count {
137+ throw FormatError . options ( " Glob did not match any files at \( glob) " )
106138 }
107139 }
108140 }
109- enumerate ( URL ( fileURLWithPath: directory) . standardized)
110141 return urls
111142}
0 commit comments