src/pkg/path/match.go - The Go Programming Language

Golang

Source file src/pkg/path/match.go

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