github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/utils/glob/glob.go (about)

     1  package glob
     2  
     3  import (
     4  	"regexp"
     5  	"strings"
     6  )
     7  
     8  // Glob holds a Unix-style glob pattern in a compiled form for efficient
     9  // matching against paths.
    10  //
    11  // Glob notation:
    12  //  - `?` matches a single char in a single path component
    13  //  - `*` matches zero or more chars in a single path component
    14  //  - `**` matches zero or more chars in zero or more components
    15  //  - any other sequence matches itself
    16  type Glob struct {
    17  	pattern string         // original glob pattern
    18  	regexp  *regexp.Regexp // compiled regexp
    19  }
    20  
    21  const charPat = `[^/]`
    22  
    23  func mustBuildRe(p string) *regexp.Regexp {
    24  	return regexp.MustCompile(`^/$|^(` + p + `+)?(/` + p + `+)*$`)
    25  }
    26  
    27  var globRe = mustBuildRe(`(` + charPat + `|[\*\?])`)
    28  
    29  // Supports unix/ruby-style glob patterns:
    30  //  - `?` matches a single char in a single path component
    31  //  - `*` matches zero or more chars in a single path component
    32  //  - `**` matches zero or more chars in zero or more components
    33  func translateGlob(pat string) (string, error) {
    34  	if !globRe.MatchString(pat) {
    35  		return "", Error(pat)
    36  	}
    37  
    38  	outs := make([]string, len(pat))
    39  	i, double := 0, false
    40  	for _, c := range pat {
    41  		switch c {
    42  		default:
    43  			outs[i] = string(c)
    44  			double = false
    45  		case '.', '+', '-', '^', '$', '[', ']', '(', ')':
    46  			outs[i] = `\` + string(c)
    47  			double = false
    48  		case '?':
    49  			outs[i] = `[^/]`
    50  			double = false
    51  		case '*':
    52  			if double {
    53  				outs[i-1] = `.*`
    54  			} else {
    55  				outs[i] = `[^/]*`
    56  			}
    57  			double = !double
    58  		}
    59  		i++
    60  	}
    61  	outs = outs[0:i]
    62  
    63  	return "^" + strings.Join(outs, "") + "$", nil
    64  }
    65  
    66  // CompileGlob translates pat into a form more convenient for
    67  // matching against paths in the store.
    68  func CompileGlob(pat string) (glob Glob, err error) {
    69  	pat = toSlash(pat)
    70  	s, err := translateGlob(pat)
    71  	if err != nil {
    72  		return
    73  	}
    74  	r, err := regexp.Compile(s)
    75  	if err != nil {
    76  		return
    77  	}
    78  	glob = Glob{pat, r}
    79  	return
    80  }
    81  
    82  // MustCompileGlob is like CompileGlob, but it panics if an error occurs,
    83  // simplifying safe initialization of global variables holding glob patterns.
    84  func MustCompileGlob(pat string) Glob {
    85  	g, err := CompileGlob(pat)
    86  	if err != nil {
    87  		panic(err)
    88  	}
    89  	return g
    90  }
    91  
    92  func (g Glob) String() string {
    93  	return g.pattern
    94  }
    95  
    96  func (g Glob) Match(path string) bool {
    97  	return g.regexp.MatchString(toSlash(path))
    98  }
    99  
   100  type Error string
   101  
   102  func (e Error) Error() string {
   103  	return "invalid glob pattern: " + string(e)
   104  }
   105  
   106  func toSlash(path string) string {
   107  	return strings.Replace(path, "\\", "/", -1)
   108  }