Skip to content

Commit 0b26df4

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent 859b3ef commit 0b26df4

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

module/module.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ package module
9797

9898
import (
9999
"fmt"
100+
"path"
100101
"sort"
101102
"strings"
102103
"unicode"
@@ -716,3 +717,49 @@ func unescapeString(escaped string) (string, bool) {
716717
}
717718
return string(buf), true
718719
}
720+
721+
// MatchPrefixPatterns reports whether any path prefix of target matches one of
722+
// the glob patterns (as defined by path.Match) in the comma-separated globs
723+
// list. This implements the algorithm used when matching a module path to the
724+
// GOPRIVATE environment variable, as described by 'go help module-private'.
725+
//
726+
// It ignores any empty or malformed patterns in the list.
727+
func MatchPrefixPatterns(globs, target string) bool {
728+
for globs != "" {
729+
// Extract next non-empty glob in comma-separated list.
730+
var glob string
731+
if i := strings.Index(globs, ","); i >= 0 {
732+
glob, globs = globs[:i], globs[i+1:]
733+
} else {
734+
glob, globs = globs, ""
735+
}
736+
if glob == "" {
737+
continue
738+
}
739+
740+
// A glob with N+1 path elements (N slashes) needs to be matched
741+
// against the first N+1 path elements of target,
742+
// which end just before the N+1'th slash.
743+
n := strings.Count(glob, "/")
744+
prefix := target
745+
// Walk target, counting slashes, truncating at the N+1'th slash.
746+
for i := 0; i < len(target); i++ {
747+
if target[i] == '/' {
748+
if n == 0 {
749+
prefix = target[:i]
750+
break
751+
}
752+
n--
753+
}
754+
}
755+
if n > 0 {
756+
// Not enough prefix elements.
757+
continue
758+
}
759+
matched, _ := path.Match(glob, prefix)
760+
if matched {
761+
return true
762+
}
763+
}
764+
return false
765+
}

module/module_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,37 @@ func TestMatchPathMajor(t *testing.T) {
340340
}
341341
}
342342
}
343+
344+
func TestMatchPrefixPatterns(t *testing.T) {
345+
for _, test := range []struct {
346+
globs, target string
347+
want bool
348+
}{
349+
{"*/quote", "rsc.io/quote", true},
350+
{"*/quo", "rsc.io/quote", false},
351+
{"*/quo??", "rsc.io/quote", true},
352+
{"*/quo*", "rsc.io/quote", true},
353+
{"*quo*", "rsc.io/quote", false},
354+
{"rsc.io", "rsc.io/quote", true},
355+
{"*.io", "rsc.io/quote", true},
356+
{"rsc.io/", "rsc.io/quote", false},
357+
{"rsc", "rsc.io/quote", false},
358+
{"rsc*", "rsc.io/quote", true},
359+
360+
{"rsc.io", "rsc.io/quote/v3", true},
361+
{"*/quote", "rsc.io/quote/v3", true},
362+
{"*/quote/*", "rsc.io/quote/v3", true},
363+
{"*/v3", "rsc.io/quote/v3", false},
364+
{"*/*/v3", "rsc.io/quote/v3", true},
365+
{"*/*/*", "rsc.io/quote/v3", true},
366+
{"*/*/*", "rsc.io/quote", false},
367+
368+
{"*/*/*,,", "rsc.io/quote", false},
369+
{"*/*/*,,*/quote", "rsc.io/quote", true},
370+
{",,*/quote", "rsc.io/quote", true},
371+
} {
372+
if got := MatchPrefixPatterns(test.globs, test.target); got != test.want {
373+
t.Errorf("MatchPrefixPatterns(%q, %q) = %t, want %t", test.globs, test.target, got, test.want)
374+
}
375+
}
376+
}

0 commit comments

Comments
 (0)