bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/util/match.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package util 6 7 import ( 8 "errors" 9 "path/filepath" 10 "strings" 11 "unicode/utf8" 12 ) 13 14 const ( 15 Separator = filepath.Separator 16 ) 17 18 // ErrBadPattern indicates a globbing pattern was malformed. 19 var ErrBadPattern = errors.New("syntax error in pattern") 20 21 // Match returns true if name matches any of the patterns. 22 // The pattern syntax is: 23 // 24 // pattern: 25 // { term } [ '|' { term } '|' ... ] 26 // term: 27 // '*' matches any sequence of non-Separator characters 28 // '?' matches any single non-Separator character 29 // '[' [ '^' ] { character-range } ']' 30 // character class (must be non-empty) 31 // c matches character c (c != '*', '?', '\\', '[') 32 // '\\' c matches character c 33 // 34 // character-range: 35 // c matches character c (c != '\\', '-', ']') 36 // '\\' c matches character c 37 // lo '-' hi matches character c for lo <= c <= hi 38 // 39 // Match requires pattern to match all of name, not just a substring. 40 // The only possible returned error is ErrBadPattern, when pattern 41 // is malformed. 42 // 43 func Match(pattern, name string) (matched bool, err error) { 44 for _, s := range strings.Split(pattern, "|") { 45 matched, err = match(s, name) 46 if matched { 47 return 48 } 49 } 50 return 51 } 52 53 func match(pattern, name string) (matched bool, err error) { 54 Pattern: 55 for len(pattern) > 0 { 56 var star bool 57 var chunk string 58 star, chunk, pattern = scanChunk(pattern) 59 if star && chunk == "" { 60 // Trailing * matches rest of string unless it has a /. 61 return strings.Index(name, string(Separator)) < 0, nil 62 } 63 // Look for match at current position. 64 t, ok, err := matchChunk(chunk, name) 65 // if we're the last chunk, make sure we've exhausted the name 66 // otherwise we'll give a false result even if we could still match 67 // using the star 68 if ok && (len(t) == 0 || len(pattern) > 0) { 69 name = t 70 continue 71 } 72 if err != nil { 73 return false, err 74 } 75 if star { 76 // Look for match skipping i+1 bytes. 77 // Cannot skip /. 78 for i := 0; i < len(name) && name[i] != Separator; i++ { 79 t, ok, err := matchChunk(chunk, name[i+1:]) 80 if ok { 81 // if we're the last chunk, make sure we exhausted the name 82 if len(pattern) == 0 && len(t) > 0 { 83 continue 84 } 85 name = t 86 continue Pattern 87 } 88 if err != nil { 89 return false, err 90 } 91 } 92 } 93 return false, nil 94 } 95 return len(name) == 0, nil 96 } 97 98 // scanChunk gets the next segment of pattern, which is a non-star string 99 // possibly preceded by a star. 100 func scanChunk(pattern string) (star bool, chunk, rest string) { 101 for len(pattern) > 0 && pattern[0] == '*' { 102 pattern = pattern[1:] 103 star = true 104 } 105 inrange := false 106 var i int 107 Scan: 108 for i = 0; i < len(pattern); i++ { 109 switch pattern[i] { 110 case '\\': 111 // error check handled in matchChunk: bad pattern. 112 if i+1 < len(pattern) { 113 i++ 114 } 115 case '[': 116 inrange = true 117 case ']': 118 inrange = false 119 case '*': 120 if !inrange { 121 break Scan 122 } 123 } 124 } 125 return star, pattern[0:i], pattern[i:] 126 } 127 128 // matchChunk checks whether chunk matches the beginning of s. 129 // If so, it returns the remainder of s (after the match). 130 // Chunk is all single-character operators: literals, char classes, and ?. 131 func matchChunk(chunk, s string) (rest string, ok bool, err error) { 132 for len(chunk) > 0 { 133 if len(s) == 0 { 134 return 135 } 136 switch chunk[0] { 137 case '[': 138 // character class 139 r, n := utf8.DecodeRuneInString(s) 140 s = s[n:] 141 chunk = chunk[1:] 142 // We can't end right after '[', we're expecting at least 143 // a closing bracket and possibly a caret. 144 if len(chunk) == 0 { 145 err = ErrBadPattern 146 return 147 } 148 // possibly negated 149 negated := chunk[0] == '^' 150 if negated { 151 chunk = chunk[1:] 152 } 153 // parse all ranges 154 match := false 155 nrange := 0 156 for { 157 if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { 158 chunk = chunk[1:] 159 break 160 } 161 var lo, hi rune 162 if lo, chunk, err = getEsc(chunk); err != nil { 163 return 164 } 165 hi = lo 166 if chunk[0] == '-' { 167 if hi, chunk, err = getEsc(chunk[1:]); err != nil { 168 return 169 } 170 } 171 if lo <= r && r <= hi { 172 match = true 173 } 174 nrange++ 175 } 176 if match == negated { 177 return 178 } 179 180 case '?': 181 if s[0] == Separator { 182 return 183 } 184 _, n := utf8.DecodeRuneInString(s) 185 s = s[n:] 186 chunk = chunk[1:] 187 188 case '\\': 189 chunk = chunk[1:] 190 if len(chunk) == 0 { 191 err = ErrBadPattern 192 return 193 } 194 195 default: 196 if chunk[0] != s[0] { 197 return 198 } 199 s = s[1:] 200 chunk = chunk[1:] 201 } 202 } 203 return s, true, nil 204 } 205 206 // getEsc gets a possibly-escaped character from chunk, for a character class. 207 func getEsc(chunk string) (r rune, nchunk string, err error) { 208 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { 209 err = ErrBadPattern 210 return 211 } 212 if chunk[0] == '\\' { 213 chunk = chunk[1:] 214 if len(chunk) == 0 { 215 err = ErrBadPattern 216 return 217 } 218 } 219 r, n := utf8.DecodeRuneInString(chunk) 220 if r == utf8.RuneError && n == 1 { 221 err = ErrBadPattern 222 } 223 nchunk = chunk[n:] 224 if len(nchunk) == 0 { 225 err = ErrBadPattern 226 } 227 return 228 }