module: add a MatchPrefixPatterns function, for matching GOPRIVATE, etc.
This CL exposes a new MatchPrefixPatterns function, extracted from
GlobsMatchPath in src/cmd/go/internal/str. Tool authors can use this to
identify non-public modules by matching against GOPRIVATE, as is
explicitly suggested by `go help module-private`.
Fixes golang/go#38725
Change-Id: I5be79b49c2c13f2d68c7421a06747a297f48f21a
Reviewed-on: https://go-review.googlesource.com/c/mod/+/239797
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/module/module.go b/module/module.go
index 6cd3728..3a8b080 100644
--- a/module/module.go
+++ b/module/module.go
@@ -97,6 +97,7 @@
import (
"fmt"
+ "path"
"sort"
"strings"
"unicode"
@@ -716,3 +717,49 @@
}
return string(buf), true
}
+
+// MatchPrefixPatterns reports whether any path prefix of target matches one of
+// the glob patterns (as defined by path.Match) in the comma-separated globs
+// list. This implements the algorithm used when matching a module path to the
+// GOPRIVATE environment variable, as described by 'go help module-private'.
+//
+// It ignores any empty or malformed patterns in the list.
+func MatchPrefixPatterns(globs, target string) bool {
+ for globs != "" {
+ // Extract next non-empty glob in comma-separated list.
+ var glob string
+ if i := strings.Index(globs, ","); i >= 0 {
+ glob, globs = globs[:i], globs[i+1:]
+ } else {
+ glob, globs = globs, ""
+ }
+ if glob == "" {
+ continue
+ }
+
+ // A glob with N+1 path elements (N slashes) needs to be matched
+ // against the first N+1 path elements of target,
+ // which end just before the N+1'th slash.
+ n := strings.Count(glob, "/")
+ prefix := target
+ // Walk target, counting slashes, truncating at the N+1'th slash.
+ for i := 0; i < len(target); i++ {
+ if target[i] == '/' {
+ if n == 0 {
+ prefix = target[:i]
+ break
+ }
+ n--
+ }
+ }
+ if n > 0 {
+ // Not enough prefix elements.
+ continue
+ }
+ matched, _ := path.Match(glob, prefix)
+ if matched {
+ return true
+ }
+ }
+ return false
+}
diff --git a/module/module_test.go b/module/module_test.go
index bdf38c3..1a6115f 100644
--- a/module/module_test.go
+++ b/module/module_test.go
@@ -340,3 +340,37 @@
}
}
}
+
+func TestMatchPrefixPatterns(t *testing.T) {
+ for _, test := range []struct {
+ globs, target string
+ want bool
+ }{
+ {"*/quote", "rsc.io/quote", true},
+ {"*/quo", "rsc.io/quote", false},
+ {"*/quo??", "rsc.io/quote", true},
+ {"*/quo*", "rsc.io/quote", true},
+ {"*quo*", "rsc.io/quote", false},
+ {"rsc.io", "rsc.io/quote", true},
+ {"*.io", "rsc.io/quote", true},
+ {"rsc.io/", "rsc.io/quote", false},
+ {"rsc", "rsc.io/quote", false},
+ {"rsc*", "rsc.io/quote", true},
+
+ {"rsc.io", "rsc.io/quote/v3", true},
+ {"*/quote", "rsc.io/quote/v3", true},
+ {"*/quote/*", "rsc.io/quote/v3", true},
+ {"*/v3", "rsc.io/quote/v3", false},
+ {"*/*/v3", "rsc.io/quote/v3", true},
+ {"*/*/*", "rsc.io/quote/v3", true},
+ {"*/*/*", "rsc.io/quote", false},
+
+ {"*/*/*,,", "rsc.io/quote", false},
+ {"*/*/*,,*/quote", "rsc.io/quote", true},
+ {",,*/quote", "rsc.io/quote", true},
+ } {
+ if got := MatchPrefixPatterns(test.globs, test.target); got != test.want {
+ t.Errorf("MatchPrefixPatterns(%q, %q) = %t, want %t", test.globs, test.target, got, test.want)
+ }
+ }
+}