1// Copyright 2014 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package pathtools 16 17import ( 18 "errors" 19 "os" 20 "path/filepath" 21 "strings" 22) 23 24var GlobMultipleRecursiveErr = errors.New("pattern contains multiple **") 25var GlobLastRecursiveErr = errors.New("pattern ** as last path element") 26 27// Glob returns the list of files that match the given pattern along with the 28// list of directories that were searched to construct the file list. 29// The supported glob patterns are equivalent to filepath.Glob, with an 30// extension that recursive glob (** matching zero or more complete path 31// entries) is supported. 32func Glob(pattern string) (matches, dirs []string, err error) { 33 return GlobWithExcludes(pattern, nil) 34} 35 36// GlobWithExcludes returns the list of files that match the given pattern but 37// do not match the given exclude patterns, along with the list of directories 38// that were searched to construct the file list. The supported glob and 39// exclude patterns are equivalent to filepath.Glob, with an extension that 40// recursive glob (** matching zero or more complete path entries) is supported. 41func GlobWithExcludes(pattern string, excludes []string) (matches, dirs []string, err error) { 42 if filepath.Base(pattern) == "**" { 43 return nil, nil, GlobLastRecursiveErr 44 } else { 45 matches, dirs, err = glob(pattern, false) 46 } 47 48 if err != nil { 49 return nil, nil, err 50 } 51 52 matches, err = filterExcludes(matches, excludes) 53 if err != nil { 54 return nil, nil, err 55 } 56 57 return matches, dirs, nil 58} 59 60// glob is a recursive helper function to handle globbing each level of the pattern individually, 61// allowing searched directories to be tracked. Also handles the recursive glob pattern, **. 62func glob(pattern string, hasRecursive bool) (matches, dirs []string, err error) { 63 if !isWild(pattern) { 64 // If there are no wilds in the pattern, check whether the file exists or not. 65 // Uses filepath.Glob instead of manually statting to get consistent results. 66 pattern = filepath.Clean(pattern) 67 matches, err = filepath.Glob(pattern) 68 if err != nil { 69 return matches, dirs, err 70 } 71 72 if len(matches) == 0 { 73 // Some part of the non-wild pattern didn't exist. Add the last existing directory 74 // as a dependency. 75 var matchDirs []string 76 for len(matchDirs) == 0 { 77 pattern, _ = saneSplit(pattern) 78 matchDirs, err = filepath.Glob(pattern) 79 if err != nil { 80 return matches, dirs, err 81 } 82 } 83 dirs = append(dirs, matchDirs...) 84 } 85 return matches, dirs, err 86 } 87 88 dir, file := saneSplit(pattern) 89 90 if file == "**" { 91 if hasRecursive { 92 return matches, dirs, GlobMultipleRecursiveErr 93 } 94 hasRecursive = true 95 } 96 97 dirMatches, dirs, err := glob(dir, hasRecursive) 98 if err != nil { 99 return nil, nil, err 100 } 101 102 for _, m := range dirMatches { 103 if info, _ := os.Stat(m); info.IsDir() { 104 if file == "**" { 105 recurseDirs, err := walkAllDirs(m) 106 if err != nil { 107 return nil, nil, err 108 } 109 matches = append(matches, recurseDirs...) 110 } else { 111 dirs = append(dirs, m) 112 newMatches, err := filepath.Glob(filepath.Join(m, file)) 113 if err != nil { 114 return nil, nil, err 115 } 116 matches = append(matches, newMatches...) 117 } 118 } 119 } 120 121 return matches, dirs, nil 122} 123 124// Faster version of dir, file := filepath.Dir(path), filepath.File(path) with no allocations 125// Similar to filepath.Split, but returns "." if dir is empty and trims trailing slash if dir is 126// not "/". Returns ".", "" if path is "." 127func saneSplit(path string) (dir, file string) { 128 if path == "." { 129 return ".", "" 130 } 131 dir, file = filepath.Split(path) 132 switch dir { 133 case "": 134 dir = "." 135 case "/": 136 // Nothing 137 default: 138 dir = dir[:len(dir)-1] 139 } 140 return dir, file 141} 142 143func isWild(pattern string) bool { 144 return strings.ContainsAny(pattern, "*?[") 145} 146 147// Returns a list of all directories under dir 148func walkAllDirs(dir string) (dirs []string, err error) { 149 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 150 if err != nil { 151 return err 152 } 153 154 if info.Mode().IsDir() { 155 dirs = append(dirs, path) 156 } 157 return nil 158 }) 159 160 return dirs, err 161} 162 163// Filters the strings in matches based on the glob patterns in excludes. Hierarchical (a/*) and 164// recursive (**) glob patterns are supported. 165func filterExcludes(matches []string, excludes []string) ([]string, error) { 166 if len(excludes) == 0 { 167 return matches, nil 168 } 169 170 var ret []string 171matchLoop: 172 for _, m := range matches { 173 for _, e := range excludes { 174 exclude, err := match(e, m) 175 if err != nil { 176 return nil, err 177 } 178 if exclude { 179 continue matchLoop 180 } 181 } 182 ret = append(ret, m) 183 } 184 185 return ret, nil 186} 187 188// match returns true if name matches pattern using the same rules as filepath.Match, but supporting 189// hierarchical patterns (a/*) and recursive globs (**). 190func match(pattern, name string) (bool, error) { 191 if filepath.Base(pattern) == "**" { 192 return false, GlobLastRecursiveErr 193 } 194 195 for { 196 var patternFile, nameFile string 197 pattern, patternFile = saneSplit(pattern) 198 name, nameFile = saneSplit(name) 199 200 if patternFile == "**" { 201 return matchPrefix(pattern, filepath.Join(name, nameFile)) 202 } 203 204 if nameFile == "" && patternFile == "" { 205 return true, nil 206 } else if nameFile == "" || patternFile == "" { 207 return false, nil 208 } 209 210 match, err := filepath.Match(patternFile, nameFile) 211 if err != nil || !match { 212 return match, err 213 } 214 } 215} 216 217// matchPrefix returns true if the beginning of name matches pattern using the same rules as 218// filepath.Match, but supporting hierarchical patterns (a/*). Recursive globs (**) are not 219// supported, they should have been handled in match(). 220func matchPrefix(pattern, name string) (bool, error) { 221 if len(pattern) > 0 && pattern[0] == '/' { 222 if len(name) > 0 && name[0] == '/' { 223 pattern = pattern[1:] 224 name = name[1:] 225 } else { 226 return false, nil 227 } 228 } 229 230 for { 231 var patternElem, nameElem string 232 patternElem, pattern = saneSplitFirst(pattern) 233 nameElem, name = saneSplitFirst(name) 234 235 if patternElem == "." { 236 patternElem = "" 237 } 238 if nameElem == "." { 239 nameElem = "" 240 } 241 242 if patternElem == "**" { 243 return false, GlobMultipleRecursiveErr 244 } 245 246 if patternElem == "" { 247 return true, nil 248 } else if nameElem == "" { 249 return false, nil 250 } 251 252 match, err := filepath.Match(patternElem, nameElem) 253 if err != nil || !match { 254 return match, err 255 } 256 } 257} 258 259func saneSplitFirst(path string) (string, string) { 260 i := strings.IndexRune(path, filepath.Separator) 261 if i < 0 { 262 return path, "" 263 } 264 return path[:i], path[i+1:] 265} 266 267func GlobPatternList(patterns []string, prefix string) (globedList []string, depDirs []string, err error) { 268 var ( 269 matches []string 270 deps []string 271 ) 272 273 globedList = make([]string, 0) 274 depDirs = make([]string, 0) 275 276 for _, pattern := range patterns { 277 if isWild(pattern) { 278 matches, deps, err = Glob(filepath.Join(prefix, pattern)) 279 if err != nil { 280 return nil, nil, err 281 } 282 globedList = append(globedList, matches...) 283 depDirs = append(depDirs, deps...) 284 } else { 285 globedList = append(globedList, filepath.Join(prefix, pattern)) 286 } 287 } 288 return globedList, depDirs, nil 289} 290