"cmd/go/internal/str"
"cmd/go/internal/trace"
"cmd/go/internal/vcs"
+ "cmd/internal/pkgpattern"
"cmd/internal/sys"
"golang.org/x/mod/modfile"
matchers := make([]func(string) bool, len(patterns))
for i, p := range patterns {
if strings.Contains(p, "...") {
- matchers[i] = search.MatchPattern(p)
+ matchers[i] = pkgpattern.MatchPattern(p)
}
}
return pkgs, nil
"strings"
"cmd/go/internal/search"
+ "cmd/internal/pkgpattern"
)
// MatchPackage(pattern, cwd)(p) reports whether package p matches pattern in the working directory cwd.
if pattern == "" {
return func(p *Package) bool { return p.Dir == dir }
}
- matchPath := search.MatchPattern(pattern)
+ matchPath := pkgpattern.MatchPattern(pattern)
return func(p *Package) bool {
// Compute relative path to dir and see if it matches the pattern.
rel, err := filepath.Rel(dir, p.Dir)
case pattern == "cmd":
return func(p *Package) bool { return p.Standard && strings.HasPrefix(p.ImportPath, "cmd/") }
default:
- matchPath := search.MatchPattern(pattern)
+ matchPath := pkgpattern.MatchPattern(pattern)
return func(p *Package) bool { return matchPath(p.ImportPath) }
}
}
"cmd/go/internal/modload"
"cmd/go/internal/search"
"cmd/go/internal/str"
+ "cmd/internal/pkgpattern"
"golang.org/x/mod/module"
)
version: version,
}
if strings.Contains(q.pattern, "...") {
- q.matchWildcard = search.MatchPattern(q.pattern)
- q.canMatchWildcardInModule = search.TreeCanMatchPattern(q.pattern)
+ q.matchWildcard = pkgpattern.MatchPattern(q.pattern)
+ q.canMatchWildcardInModule = pkgpattern.TreeCanMatchPattern(q.pattern)
}
if err := q.validate(); err != nil {
return q, err
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/modinfo"
"cmd/go/internal/search"
+ "cmd/internal/pkgpattern"
"golang.org/x/mod/module"
)
if arg == "all" {
match = func(string) bool { return true }
} else if strings.Contains(arg, "...") {
- match = search.MatchPattern(arg)
+ match = pkgpattern.MatchPattern(arg)
} else {
var v string
if mg == nil {
"cmd/go/internal/search"
"cmd/go/internal/str"
"cmd/go/internal/trace"
+ "cmd/internal/pkgpattern"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
}
var match func(mod module.Version, roots []string, isLocal bool) *search.Match
- matchPattern := search.MatchPattern(pattern)
+ matchPattern := pkgpattern.MatchPattern(pattern)
if i := strings.Index(pattern, "..."); i >= 0 {
base = pathpkg.Dir(pattern[:i+3])
"cmd/go/internal/par"
"cmd/go/internal/search"
"cmd/go/internal/trace"
+ "cmd/internal/pkgpattern"
"golang.org/x/mod/module"
)
isMatch := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
if !m.IsMeta() {
- isMatch = search.MatchPattern(m.Pattern())
- treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
+ isMatch = pkgpattern.MatchPattern(m.Pattern())
+ treeCanMatch = pkgpattern.TreeCanMatchPattern(m.Pattern())
}
var mu sync.Mutex
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
+ "cmd/internal/pkgpattern"
"fmt"
"go/build"
"io/fs"
"os"
"path"
"path/filepath"
- "regexp"
"strings"
)
match := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
if !m.IsMeta() {
- match = MatchPattern(m.pattern)
- treeCanMatch = TreeCanMatchPattern(m.pattern)
+ match = pkgpattern.MatchPattern(m.pattern)
+ treeCanMatch = pkgpattern.TreeCanMatchPattern(m.pattern)
}
have := map[string]bool{
cleanPattern = "." + string(os.PathSeparator) + cleanPattern
}
slashPattern := filepath.ToSlash(cleanPattern)
- match := MatchPattern(slashPattern)
+ match := pkgpattern.MatchPattern(slashPattern)
// Find directory to begin the scan.
// Could be smarter but this one optimization
}
}
-// TreeCanMatchPattern(pattern)(name) reports whether
-// name or children of name can possibly match pattern.
-// Pattern is the same limited glob accepted by matchPattern.
-func TreeCanMatchPattern(pattern string) func(name string) bool {
- wildCard := false
- if i := strings.Index(pattern, "..."); i >= 0 {
- wildCard = true
- pattern = pattern[:i]
- }
- return func(name string) bool {
- return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
- wildCard && strings.HasPrefix(name, pattern)
- }
-}
-
-// MatchPattern(pattern)(name) reports whether
-// name matches pattern. Pattern is a limited glob
-// pattern in which '...' means 'any string' and there
-// is no other special syntax.
-// Unfortunately, there are two special cases. Quoting "go help packages":
-//
-// First, /... at the end of the pattern can match an empty string,
-// so that net/... matches both net and packages in its subdirectories, like net/http.
-// Second, any slash-separated pattern element containing a wildcard never
-// participates in a match of the "vendor" element in the path of a vendored
-// package, so that ./... does not match packages in subdirectories of
-// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
-// Note, however, that a directory named vendor that itself contains code
-// is not a vendored package: cmd/vendor would be a command named vendor,
-// and the pattern cmd/... matches it.
-func MatchPattern(pattern string) func(name string) bool {
- // Convert pattern to regular expression.
- // The strategy for the trailing /... is to nest it in an explicit ? expression.
- // The strategy for the vendor exclusion is to change the unmatchable
- // vendor strings to a disallowed code point (vendorChar) and to use
- // "(anything but that codepoint)*" as the implementation of the ... wildcard.
- // This is a bit complicated but the obvious alternative,
- // namely a hand-written search like in most shell glob matchers,
- // is too easy to make accidentally exponential.
- // Using package regexp guarantees linear-time matching.
-
- const vendorChar = "\x00"
-
- if strings.Contains(pattern, vendorChar) {
- return func(name string) bool { return false }
- }
-
- re := regexp.QuoteMeta(pattern)
- re = replaceVendor(re, vendorChar)
- switch {
- case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
- re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
- case re == vendorChar+`/\.\.\.`:
- re = `(/vendor|/` + vendorChar + `/\.\.\.)`
- case strings.HasSuffix(re, `/\.\.\.`):
- re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
- }
- re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
-
- reg := regexp.MustCompile(`^` + re + `$`)
-
- return func(name string) bool {
- if strings.Contains(name, vendorChar) {
- return false
- }
- return reg.MatchString(replaceVendor(name, vendorChar))
- }
-}
-
-// replaceVendor returns the result of replacing
-// non-trailing vendor path elements in x with repl.
-func replaceVendor(x, repl string) string {
- if !strings.Contains(x, "vendor") {
- return x
- }
- elem := strings.Split(x, "/")
- for i := 0; i < len(elem)-1; i++ {
- if elem[i] == "vendor" {
- elem[i] = repl
- }
- }
- return strings.Join(elem, "/")
-}
-
// WarnUnmatched warns about patterns that didn't match any packages.
func WarnUnmatched(matches []*Match) {
for _, m := range matches {
return out
}
-// hasPathPrefix reports whether the path s begins with the
-// elements in prefix.
-func hasPathPrefix(s, prefix string) bool {
- switch {
- default:
- return false
- case len(s) == len(prefix):
- return s == prefix
- case len(s) > len(prefix):
- if prefix != "" && prefix[len(prefix)-1] == '/' {
- return strings.HasPrefix(s, prefix)
- }
- return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
- }
-}
-
// hasFilepathPrefix reports whether the path s begins with the
// elements in prefix.
func hasFilepathPrefix(s, prefix string) bool {
-// Copyright 2012 The Go Authors. All rights reserved.
+// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package search
+package pkgpattern
import (
"strings"
var matchPatternTests = `
pattern ...
match foo
-
+
pattern net
match net
not net/http
-
+
pattern net/http
match net/http
not net
-
+
pattern net...
match net net/http netchan
not not/http not/net/http
-
+
# Special cases. Quoting docs:
# First, /... at the end of the pattern can match an empty string,
pattern ./...
match ./vendor ./mycode/vendor
not ./vendor/foo ./mycode/vendor/foo
-
+
pattern ./vendor/...
match ./vendor/foo ./vendor/foo/vendor
not ./vendor/foo/vendor/bar
-
+
pattern mycode/vendor/...
match mycode/vendor mycode/vendor/foo mycode/vendor/foo/vendor
not mycode/vendor/foo/vendor/bar
-
+
pattern x/vendor/y
match x/vendor/y
not x/vendor
-
+
pattern x/vendor/y/...
match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor
not x/vendor/y/vendor/z
-
+
pattern .../vendor/...
match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor
`
})
}
+var matchSimplePatternTests = `
+ pattern ...
+ match foo
+
+ pattern .../bar/.../baz
+ match foo/bar/abc/baz
+
+ pattern net
+ match net
+ not net/http
+
+ pattern net/http
+ match net/http
+ not net
+
+ pattern net...
+ match net net/http netchan
+ not not/http not/net/http
+
+ # Special cases. Quoting docs:
+
+ # First, /... at the end of the pattern can match an empty string,
+ # so that net/... matches both net and packages in its subdirectories, like net/http.
+ pattern net/...
+ match net net/http
+ not not/http not/net/http netchan
+`
+
+func TestSimpleMatchPattern(t *testing.T) {
+ testPatterns(t, "MatchSimplePattern", matchSimplePatternTests, func(pattern, name string) bool {
+ return MatchSimplePattern(pattern)(name)
+ })
+}
+
var treeCanMatchPatternTests = `
pattern ...
match foo
-
+
pattern net
match net
not net/http
-
+
pattern net/http
match net net/http
-
+
pattern net...
match net netchan net/http
not not/http not/net/http
pattern net/...
match net net/http
not not/http netchan
-
+
pattern abc.../def
match abcxyz
not xyzabc
-
+
pattern x/y/z/...
match x x/y x/y/z x/y/z/w
-
+
pattern x/y/z
match x x/y x/y/z
not x/y/z/w
-
+
pattern x/.../y/z
match x/a/b/c
not y/x/a/b/c
--- /dev/null
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pkgpattern
+
+import (
+ "regexp"
+ "strings"
+)
+
+// Note: most of this code was originally part of the cmd/go/internal/search
+// package; it was migrated here in order to support the use case of
+// commands other than cmd/go that need to accept package pattern args.
+
+// TreeCanMatchPattern(pattern)(name) reports whether
+// name or children of name can possibly match pattern.
+// Pattern is the same limited glob accepted by MatchPattern.
+func TreeCanMatchPattern(pattern string) func(name string) bool {
+ wildCard := false
+ if i := strings.Index(pattern, "..."); i >= 0 {
+ wildCard = true
+ pattern = pattern[:i]
+ }
+ return func(name string) bool {
+ return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
+ wildCard && strings.HasPrefix(name, pattern)
+ }
+}
+
+// MatchPattern(pattern)(name) reports whether
+// name matches pattern. Pattern is a limited glob
+// pattern in which '...' means 'any string' and there
+// is no other special syntax.
+// Unfortunately, there are two special cases. Quoting "go help packages":
+//
+// First, /... at the end of the pattern can match an empty string,
+// so that net/... matches both net and packages in its subdirectories, like net/http.
+// Second, any slash-separated pattern element containing a wildcard never
+// participates in a match of the "vendor" element in the path of a vendored
+// package, so that ./... does not match packages in subdirectories of
+// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
+// Note, however, that a directory named vendor that itself contains code
+// is not a vendored package: cmd/vendor would be a command named vendor,
+// and the pattern cmd/... matches it.
+func MatchPattern(pattern string) func(name string) bool {
+ return matchPatternInternal(pattern, true)
+}
+
+// MatchSimplePattern returns a function that can be used to check
+// whether a given name matches a pattern, where pattern is a limited
+// glob pattern in which '...' means 'any string', with no other
+// special syntax. There is one special case for MatchPatternSimple:
+// according to the rules in "go help packages": a /... at the end of
+// the pattern can match an empty string, so that net/... matches both
+// net and packages in its subdirectories, like net/http.
+func MatchSimplePattern(pattern string) func(name string) bool {
+ return matchPatternInternal(pattern, false)
+}
+
+func matchPatternInternal(pattern string, vendorExclude bool) func(name string) bool {
+ // Convert pattern to regular expression.
+ // The strategy for the trailing /... is to nest it in an explicit ? expression.
+ // The strategy for the vendor exclusion is to change the unmatchable
+ // vendor strings to a disallowed code point (vendorChar) and to use
+ // "(anything but that codepoint)*" as the implementation of the ... wildcard.
+ // This is a bit complicated but the obvious alternative,
+ // namely a hand-written search like in most shell glob matchers,
+ // is too easy to make accidentally exponential.
+ // Using package regexp guarantees linear-time matching.
+
+ const vendorChar = "\x00"
+
+ if vendorExclude && strings.Contains(pattern, vendorChar) {
+ return func(name string) bool { return false }
+ }
+
+ re := regexp.QuoteMeta(pattern)
+ wild := `.*`
+ if vendorExclude {
+ wild = `[^` + vendorChar + `]*`
+ re = replaceVendor(re, vendorChar)
+ switch {
+ case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
+ re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
+ case re == vendorChar+`/\.\.\.`:
+ re = `(/vendor|/` + vendorChar + `/\.\.\.)`
+ }
+ }
+ if strings.HasSuffix(re, `/\.\.\.`) {
+ re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
+ }
+ re = strings.ReplaceAll(re, `\.\.\.`, wild)
+
+ reg := regexp.MustCompile(`^` + re + `$`)
+
+ return func(name string) bool {
+ if vendorExclude {
+ if strings.Contains(name, vendorChar) {
+ return false
+ }
+ name = replaceVendor(name, vendorChar)
+ }
+ return reg.MatchString(name)
+ }
+}
+
+// hasPathPrefix reports whether the path s begins with the
+// elements in prefix.
+func hasPathPrefix(s, prefix string) bool {
+ switch {
+ default:
+ return false
+ case len(s) == len(prefix):
+ return s == prefix
+ case len(s) > len(prefix):
+ if prefix != "" && prefix[len(prefix)-1] == '/' {
+ return strings.HasPrefix(s, prefix)
+ }
+ return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
+ }
+}
+
+// replaceVendor returns the result of replacing
+// non-trailing vendor path elements in x with repl.
+func replaceVendor(x, repl string) string {
+ if !strings.Contains(x, "vendor") {
+ return x
+ }
+ elem := strings.Split(x, "/")
+ for i := 0; i < len(elem)-1; i++ {
+ if elem[i] == "vendor" {
+ elem[i] = repl
+ }
+ }
+ return strings.Join(elem, "/")
+}