10 var ErrBadPattern = os.NewError("syntax error in pattern")
12 // Match returns true if name matches the shell file name pattern.
13 // The syntax used by pattern is:
18 // '*' matches any sequence of non-/ characters
19 // '?' matches any single non-/ character
20 // '[' [ '^' ] { character-range } ']'
21 // character class (must be non-empty)
22 // c matches character c (c != '*', '?', '\\', '[')
23 // '\\' c matches character c
26 // c matches character c (c != '\\', '-', ']')
27 // '\\' c matches character c
28 // lo '-' hi matches character c for lo <= c <= hi
30 // Match requires pattern to match all of name, not just a substring.
31 // The only possible error return is when pattern is malformed.
33 func Match(pattern, name string) (matched bool, err os.Error) {
35 for len(pattern) > 0 {
38 star, chunk, pattern = scanChunk(pattern)
39 if star && chunk == "" {
40 // Trailing * matches rest of string unless it has a /.
41 return strings.Index(name, "/") < 0, nil
43 // Look for match at current position.
44 t, ok, err := matchChunk(chunk, name)
45 // if we're the last chunk, make sure we've exhausted the name
46 // otherwise we'll give a false result even if we could still match
48 if ok && (len(t) == 0 || len(pattern) > 0) {
56 // Look for match skipping i+1 bytes.
58 for i := 0; i < len(name) && name[i] != '/'; i++ {
59 t, ok, err := matchChunk(chunk, name[i+1:])
61 // if we're the last chunk, make sure we exhausted the name
62 if len(pattern) == 0 && len(t) > 0 {
75 return len(name) == 0, nil
78 // scanChunk gets the next section of pattern, which is a non-star string
79 // possibly preceded by a star.
80 func scanChunk(pattern string) (star bool, chunk, rest string) {
81 for len(pattern) > 0 && pattern[0] == '*' {
88 for i = 0; i < len(pattern); i++ {
91 // error check handled in matchChunk: bad pattern.
92 if i+1 < len(pattern) {
106 return star, pattern[0:i], pattern[i:]
109 // matchChunk checks whether chunk matches the beginning of s.
110 // If so, it returns the remainder of s (after the match).
111 // Chunk is all single-character operators: literals, char classes, and ?.
112 func matchChunk(chunk, s string) (rest string, ok bool, err os.Error) {
120 r, n := utf8.DecodeRuneInString(s)
125 if len(chunk) > 0 && chunk[0] == '^' {
133 if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
138 if lo, chunk, err = getEsc(chunk); err != nil {
143 if hi, chunk, err = getEsc(chunk[1:]); err != nil {
147 if lo <= r && r <= hi {
152 if match != notNegated {
160 _, n := utf8.DecodeRuneInString(s)
173 if chunk[0] != s[0] {
183 // getEsc gets a possibly-escaped character from chunk, for a character class.
184 func getEsc(chunk string) (r int, nchunk string, err os.Error) {
185 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
189 if chunk[0] == '\\' {
196 r, n := utf8.DecodeRuneInString(chunk)
197 if r == utf8.RuneError && n == 1 {
201 if len(nchunk) == 0 {
207 // Glob returns the names of all files matching pattern or nil
208 // if there is no matching file. The syntax of patterns is the same
209 // as in Match. The pattern may describe hierarchical names such as
212 func Glob(pattern string) (matches []string) {
213 if !hasMeta(pattern) {
214 if _, err := os.Stat(pattern); err == nil {
215 return []string{pattern}
220 dir, file := Split(pattern)
227 dir = dir[0 : len(dir)-1] // chop off trailing '/'
231 for _, d := range Glob(dir) {
232 matches = glob(d, file, matches)
235 return glob(dir, file, nil)
240 // glob searches for files matching pattern in the directory dir
241 // and appends them to matches.
242 func glob(dir, pattern string, matches []string) []string {
243 fi, err := os.Stat(dir)
247 if !fi.IsDirectory() {
250 d, err := os.Open(dir, os.O_RDONLY, 0666)
256 names, err := d.Readdirnames(-1)
260 sort.SortStrings(names)
262 for _, n := range names {
263 matched, err := Match(pattern, n)
268 matches = append(matches, Join(dir, n))
274 // hasMeta returns true if path contains any of the magic characters
275 // recognized by Match.
276 func hasMeta(path string) bool {
277 return strings.IndexAny(path, "*?[") != -1