1// Copyright 2022 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 5package pkgpattern 6 7import ( 8 "regexp" 9 "strings" 10) 11 12// Note: most of this code was originally part of the cmd/go/internal/search 13// package; it was migrated here in order to support the use case of 14// commands other than cmd/go that need to accept package pattern args. 15 16// TreeCanMatchPattern(pattern)(name) reports whether 17// name or children of name can possibly match pattern. 18// Pattern is the same limited glob accepted by MatchPattern. 19func TreeCanMatchPattern(pattern string) func(name string) bool { 20 wildCard := false 21 if i := strings.Index(pattern, "..."); i >= 0 { 22 wildCard = true 23 pattern = pattern[:i] 24 } 25 return func(name string) bool { 26 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 27 wildCard && strings.HasPrefix(name, pattern) 28 } 29} 30 31// MatchPattern(pattern)(name) reports whether 32// name matches pattern. Pattern is a limited glob 33// pattern in which '...' means 'any string' and there 34// is no other special syntax. 35// Unfortunately, there are two special cases. Quoting "go help packages": 36// 37// First, /... at the end of the pattern can match an empty string, 38// so that net/... matches both net and packages in its subdirectories, like net/http. 39// Second, any slash-separated pattern element containing a wildcard never 40// participates in a match of the "vendor" element in the path of a vendored 41// package, so that ./... does not match packages in subdirectories of 42// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 43// Note, however, that a directory named vendor that itself contains code 44// is not a vendored package: cmd/vendor would be a command named vendor, 45// and the pattern cmd/... matches it. 46func MatchPattern(pattern string) func(name string) bool { 47 return matchPatternInternal(pattern, true) 48} 49 50// MatchSimplePattern returns a function that can be used to check 51// whether a given name matches a pattern, where pattern is a limited 52// glob pattern in which '...' means 'any string', with no other 53// special syntax. There is one special case for MatchPatternSimple: 54// according to the rules in "go help packages": a /... at the end of 55// the pattern can match an empty string, so that net/... matches both 56// net and packages in its subdirectories, like net/http. 57func MatchSimplePattern(pattern string) func(name string) bool { 58 return matchPatternInternal(pattern, false) 59} 60 61func matchPatternInternal(pattern string, vendorExclude bool) func(name string) bool { 62 // Convert pattern to regular expression. 63 // The strategy for the trailing /... is to nest it in an explicit ? expression. 64 // The strategy for the vendor exclusion is to change the unmatchable 65 // vendor strings to a disallowed code point (vendorChar) and to use 66 // "(anything but that codepoint)*" as the implementation of the ... wildcard. 67 // This is a bit complicated but the obvious alternative, 68 // namely a hand-written search like in most shell glob matchers, 69 // is too easy to make accidentally exponential. 70 // Using package regexp guarantees linear-time matching. 71 72 const vendorChar = "\x00" 73 74 if vendorExclude && strings.Contains(pattern, vendorChar) { 75 return func(name string) bool { return false } 76 } 77 78 re := regexp.QuoteMeta(pattern) 79 wild := `.*` 80 if vendorExclude { 81 wild = `[^` + vendorChar + `]*` 82 re = replaceVendor(re, vendorChar) 83 switch { 84 case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): 85 re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` 86 case re == vendorChar+`/\.\.\.`: 87 re = `(/vendor|/` + vendorChar + `/\.\.\.)` 88 } 89 } 90 if strings.HasSuffix(re, `/\.\.\.`) { 91 re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` 92 } 93 re = strings.ReplaceAll(re, `\.\.\.`, wild) 94 95 reg := regexp.MustCompile(`^` + re + `$`) 96 97 return func(name string) bool { 98 if vendorExclude { 99 if strings.Contains(name, vendorChar) { 100 return false 101 } 102 name = replaceVendor(name, vendorChar) 103 } 104 return reg.MatchString(name) 105 } 106} 107 108// hasPathPrefix reports whether the path s begins with the 109// elements in prefix. 110func hasPathPrefix(s, prefix string) bool { 111 switch { 112 default: 113 return false 114 case len(s) == len(prefix): 115 return s == prefix 116 case len(s) > len(prefix): 117 if prefix != "" && prefix[len(prefix)-1] == '/' { 118 return strings.HasPrefix(s, prefix) 119 } 120 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 121 } 122} 123 124// replaceVendor returns the result of replacing 125// non-trailing vendor path elements in x with repl. 126func replaceVendor(x, repl string) string { 127 if !strings.Contains(x, "vendor") { 128 return x 129 } 130 elem := strings.Split(x, "/") 131 for i := 0; i < len(elem)-1; i++ { 132 if elem[i] == "vendor" { 133 elem[i] = repl 134 } 135 } 136 return strings.Join(elem, "/") 137} 138