...

Source file src/github.com/drone/envsubst/v2/path/match.go

Documentation: github.com/drone/envsubst/v2/path

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

View as plain text