github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/src/path/filepath/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 filepath
     6  
     7  import (
     8  	"errors"
     9  	"os"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  	"unicode/utf8"
    14  )
    15  
    16  // ErrBadPattern indicates a globbing pattern was malformed.
    17  var ErrBadPattern = errors.New("syntax error in pattern")
    18  
    19  // Match reports whether name matches the shell file name pattern.
    20  // The pattern syntax is:
    21  //
    22  //	pattern:
    23  //		{ term }
    24  //	term:
    25  //		'*'         matches any sequence of non-Separator characters
    26  //		'?'         matches any single non-Separator character
    27  //		'[' [ '^' ] { character-range } ']'
    28  //		            character class (must be non-empty)
    29  //		c           matches character c (c != '*', '?', '\\', '[')
    30  //		'\\' c      matches character c
    31  //
    32  //	character-range:
    33  //		c           matches character c (c != '\\', '-', ']')
    34  //		'\\' c      matches character c
    35  //		lo '-' hi   matches character c for lo <= c <= hi
    36  //
    37  // Match requires pattern to match all of name, not just a substring.
    38  // The only possible returned error is ErrBadPattern, when pattern
    39  // is malformed.
    40  //
    41  // On Windows, escaping is disabled. Instead, '\\' is treated as
    42  // path separator.
    43  //
    44  func Match(pattern, name string) (matched bool, err error) {
    45  Pattern:
    46  	for len(pattern) > 0 {
    47  		var star bool
    48  		var chunk string
    49  		star, chunk, pattern = scanChunk(pattern)
    50  		if star && chunk == "" {
    51  			// Trailing * matches rest of string unless it has a /.
    52  			return strings.Index(name, string(Separator)) < 0, nil
    53  		}
    54  		// Look for match at current position.
    55  		t, ok, err := matchChunk(chunk, name)
    56  		// if we're the last chunk, make sure we've exhausted the name
    57  		// otherwise we'll give a false result even if we could still match
    58  		// using the star
    59  		if ok && (len(t) == 0 || len(pattern) > 0) {
    60  			name = t
    61  			continue
    62  		}
    63  		if err != nil {
    64  			return false, err
    65  		}
    66  		if star {
    67  			// Look for match skipping i+1 bytes.
    68  			// Cannot skip /.
    69  			for i := 0; i < len(name) && name[i] != Separator; i++ {
    70  				t, ok, err := matchChunk(chunk, name[i+1:])
    71  				if ok {
    72  					// if we're the last chunk, make sure we exhausted the name
    73  					if len(pattern) == 0 && len(t) > 0 {
    74  						continue
    75  					}
    76  					name = t
    77  					continue Pattern
    78  				}
    79  				if err != nil {
    80  					return false, err
    81  				}
    82  			}
    83  		}
    84  		return false, nil
    85  	}
    86  	return len(name) == 0, nil
    87  }
    88  
    89  // scanChunk gets the next segment of pattern, which is a non-star string
    90  // possibly preceded by a star.
    91  func scanChunk(pattern string) (star bool, chunk, rest string) {
    92  	for len(pattern) > 0 && pattern[0] == '*' {
    93  		pattern = pattern[1:]
    94  		star = true
    95  	}
    96  	inrange := false
    97  	var i int
    98  Scan:
    99  	for i = 0; i < len(pattern); i++ {
   100  		switch pattern[i] {
   101  		case '\\':
   102  			if runtime.GOOS != "windows" {
   103  				// error check handled in matchChunk: bad pattern.
   104  				if i+1 < len(pattern) {
   105  					i++
   106  				}
   107  			}
   108  		case '[':
   109  			inrange = true
   110  		case ']':
   111  			inrange = false
   112  		case '*':
   113  			if !inrange {
   114  				break Scan
   115  			}
   116  		}
   117  	}
   118  	return star, pattern[0:i], pattern[i:]
   119  }
   120  
   121  // matchChunk checks whether chunk matches the beginning of s.
   122  // If so, it returns the remainder of s (after the match).
   123  // Chunk is all single-character operators: literals, char classes, and ?.
   124  func matchChunk(chunk, s string) (rest string, ok bool, err error) {
   125  	for len(chunk) > 0 {
   126  		if len(s) == 0 {
   127  			return
   128  		}
   129  		switch chunk[0] {
   130  		case '[':
   131  			// character class
   132  			r, n := utf8.DecodeRuneInString(s)
   133  			s = s[n:]
   134  			chunk = chunk[1:]
   135  			// We can't end right after '[', we're expecting at least
   136  			// a closing bracket and possibly a caret.
   137  			if len(chunk) == 0 {
   138  				err = ErrBadPattern
   139  				return
   140  			}
   141  			// possibly negated
   142  			negated := chunk[0] == '^'
   143  			if negated {
   144  				chunk = chunk[1:]
   145  			}
   146  			// parse all ranges
   147  			match := false
   148  			nrange := 0
   149  			for {
   150  				if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
   151  					chunk = chunk[1:]
   152  					break
   153  				}
   154  				var lo, hi rune
   155  				if lo, chunk, err = getEsc(chunk); err != nil {
   156  					return
   157  				}
   158  				hi = lo
   159  				if chunk[0] == '-' {
   160  					if hi, chunk, err = getEsc(chunk[1:]); err != nil {
   161  						return
   162  					}
   163  				}
   164  				if lo <= r && r <= hi {
   165  					match = true
   166  				}
   167  				nrange++
   168  			}
   169  			if match == negated {
   170  				return
   171  			}
   172  
   173  		case '?':
   174  			if s[0] == Separator {
   175  				return
   176  			}
   177  			_, n := utf8.DecodeRuneInString(s)
   178  			s = s[n:]
   179  			chunk = chunk[1:]
   180  
   181  		case '\\':
   182  			if runtime.GOOS != "windows" {
   183  				chunk = chunk[1:]
   184  				if len(chunk) == 0 {
   185  					err = ErrBadPattern
   186  					return
   187  				}
   188  			}
   189  			fallthrough
   190  
   191  		default:
   192  			if chunk[0] != s[0] {
   193  				return
   194  			}
   195  			s = s[1:]
   196  			chunk = chunk[1:]
   197  		}
   198  	}
   199  	return s, true, nil
   200  }
   201  
   202  // getEsc gets a possibly-escaped character from chunk, for a character class.
   203  func getEsc(chunk string) (r rune, nchunk string, err error) {
   204  	if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
   205  		err = ErrBadPattern
   206  		return
   207  	}
   208  	if chunk[0] == '\\' && runtime.GOOS != "windows" {
   209  		chunk = chunk[1:]
   210  		if len(chunk) == 0 {
   211  			err = ErrBadPattern
   212  			return
   213  		}
   214  	}
   215  	r, n := utf8.DecodeRuneInString(chunk)
   216  	if r == utf8.RuneError && n == 1 {
   217  		err = ErrBadPattern
   218  	}
   219  	nchunk = chunk[n:]
   220  	if len(nchunk) == 0 {
   221  		err = ErrBadPattern
   222  	}
   223  	return
   224  }
   225  
   226  // Glob returns the names of all files matching pattern or nil
   227  // if there is no matching file. The syntax of patterns is the same
   228  // as in Match. The pattern may describe hierarchical names such as
   229  // /usr/*/bin/ed (assuming the Separator is '/').
   230  //
   231  // Glob ignores file system errors such as I/O errors reading directories.
   232  // The only possible returned error is ErrBadPattern, when pattern
   233  // is malformed.
   234  func Glob(pattern string) (matches []string, err error) {
   235  	if !hasMeta(pattern) {
   236  		if _, err = os.Lstat(pattern); err != nil {
   237  			return nil, nil
   238  		}
   239  		return []string{pattern}, nil
   240  	}
   241  
   242  	dir, file := Split(pattern)
   243  	switch dir {
   244  	case "":
   245  		dir = "."
   246  	case string(Separator):
   247  		// nothing
   248  	default:
   249  		dir = dir[0 : len(dir)-1] // chop off trailing separator
   250  	}
   251  
   252  	if !hasMeta(dir) {
   253  		return glob(dir, file, nil)
   254  	}
   255  
   256  	var m []string
   257  	m, err = Glob(dir)
   258  	if err != nil {
   259  		return
   260  	}
   261  	for _, d := range m {
   262  		matches, err = glob(d, file, matches)
   263  		if err != nil {
   264  			return
   265  		}
   266  	}
   267  	return
   268  }
   269  
   270  // glob searches for files matching pattern in the directory dir
   271  // and appends them to matches. If the directory cannot be
   272  // opened, it returns the existing matches. New matches are
   273  // added in lexicographical order.
   274  func glob(dir, pattern string, matches []string) (m []string, e error) {
   275  	m = matches
   276  	fi, err := os.Stat(dir)
   277  	if err != nil {
   278  		return
   279  	}
   280  	if !fi.IsDir() {
   281  		return
   282  	}
   283  	d, err := os.Open(dir)
   284  	if err != nil {
   285  		return
   286  	}
   287  	defer d.Close()
   288  
   289  	names, _ := d.Readdirnames(-1)
   290  	sort.Strings(names)
   291  
   292  	for _, n := range names {
   293  		matched, err := Match(pattern, n)
   294  		if err != nil {
   295  			return m, err
   296  		}
   297  		if matched {
   298  			m = append(m, Join(dir, n))
   299  		}
   300  	}
   301  	return
   302  }
   303  
   304  // hasMeta reports whether path contains any of the magic characters
   305  // recognized by Match.
   306  func hasMeta(path string) bool {
   307  	// TODO(niemeyer): Should other magic characters be added here?
   308  	return strings.IndexAny(path, "*?[") >= 0
   309  }