github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/utils/glob/glob.go (about) 1 package glob 2 3 import ( 4 "regexp" 5 "strings" 6 ) 7 8 // Glob holds a Unix-style glob pattern in a compiled form for efficient 9 // matching against paths. 10 // 11 // Glob notation: 12 // - `?` matches a single char in a single path component 13 // - `*` matches zero or more chars in a single path component 14 // - `**` matches zero or more chars in zero or more components 15 // - any other sequence matches itself 16 type Glob struct { 17 pattern string // original glob pattern 18 regexp *regexp.Regexp // compiled regexp 19 } 20 21 const charPat = `[^/]` 22 23 func mustBuildRe(p string) *regexp.Regexp { 24 return regexp.MustCompile(`^/$|^(` + p + `+)?(/` + p + `+)*$`) 25 } 26 27 var globRe = mustBuildRe(`(` + charPat + `|[\*\?])`) 28 29 // Supports unix/ruby-style glob patterns: 30 // - `?` matches a single char in a single path component 31 // - `*` matches zero or more chars in a single path component 32 // - `**` matches zero or more chars in zero or more components 33 func translateGlob(pat string) (string, error) { 34 if !globRe.MatchString(pat) { 35 return "", Error(pat) 36 } 37 38 outs := make([]string, len(pat)) 39 i, double := 0, false 40 for _, c := range pat { 41 switch c { 42 default: 43 outs[i] = string(c) 44 double = false 45 case '.', '+', '-', '^', '$', '[', ']', '(', ')': 46 outs[i] = `\` + string(c) 47 double = false 48 case '?': 49 outs[i] = `[^/]` 50 double = false 51 case '*': 52 if double { 53 outs[i-1] = `.*` 54 } else { 55 outs[i] = `[^/]*` 56 } 57 double = !double 58 } 59 i++ 60 } 61 outs = outs[0:i] 62 63 return "^" + strings.Join(outs, "") + "$", nil 64 } 65 66 // CompileGlob translates pat into a form more convenient for 67 // matching against paths in the store. 68 func CompileGlob(pat string) (glob Glob, err error) { 69 pat = toSlash(pat) 70 s, err := translateGlob(pat) 71 if err != nil { 72 return 73 } 74 r, err := regexp.Compile(s) 75 if err != nil { 76 return 77 } 78 glob = Glob{pat, r} 79 return 80 } 81 82 // MustCompileGlob is like CompileGlob, but it panics if an error occurs, 83 // simplifying safe initialization of global variables holding glob patterns. 84 func MustCompileGlob(pat string) Glob { 85 g, err := CompileGlob(pat) 86 if err != nil { 87 panic(err) 88 } 89 return g 90 } 91 92 func (g Glob) String() string { 93 return g.pattern 94 } 95 96 func (g Glob) Match(path string) bool { 97 return g.regexp.MatchString(toSlash(path)) 98 } 99 100 type Error string 101 102 func (e Error) Error() string { 103 return "invalid glob pattern: " + string(e) 104 } 105 106 func toSlash(path string) string { 107 return strings.Replace(path, "\\", "/", -1) 108 }