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  }