feat: rewrote glob to have a full doublestar impl
All checks were successful
default / ensure tests work (push) Successful in 35s
All checks were successful
default / ensure tests work (push) Successful in 35s
This implements a full double star glob implementation with it's own filesystem implementation.
This commit is contained in:
parent
3daeb3fc05
commit
24ad00274d
7 changed files with 1354 additions and 333 deletions
220
glob/glob_test.go
Normal file
220
glob/glob_test.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package glob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
pattern, f string
|
||||
match bool
|
||||
err error
|
||||
}
|
||||
|
||||
func escape(name string) string {
|
||||
// use a math division slash for correct visual
|
||||
return strings.ReplaceAll(name, "/", "∕")
|
||||
}
|
||||
|
||||
func TestPattern_Match(t *testing.T) {
|
||||
tests := []test{
|
||||
// Test cases not covered by path.Match
|
||||
{"main.go", "main.go", true, nil},
|
||||
{"main_test.go", "main_test.go", true, nil},
|
||||
{"foo/foo_test.go", "foo/foo_test.go", true, nil},
|
||||
{"?.go", "m.go", true, nil},
|
||||
{"*.go", "main.go", true, nil},
|
||||
{"**/*.go", "main.go", true, nil},
|
||||
{"*.go", "*.go", true, nil},
|
||||
|
||||
{"//", "", false, ErrBadPattern},
|
||||
{"foo//", "", false, ErrBadPattern},
|
||||
{"*?.go", "", false, ErrBadPattern},
|
||||
{"?*.go", "", false, ErrBadPattern},
|
||||
{"**?.go", "", false, ErrBadPattern},
|
||||
{"**f", "", false, ErrBadPattern},
|
||||
{"[a-", "", false, ErrBadPattern},
|
||||
{"[a-\\", "", false, ErrBadPattern},
|
||||
{"[\\", "", false, ErrBadPattern},
|
||||
|
||||
{"**/m.go", "foo.go", false, nil},
|
||||
{"**/m.go", "foo/a.go", false, nil},
|
||||
{"**/m.go", "m.go", true, nil},
|
||||
{"**/m.go", "foo/m.go", true, nil},
|
||||
{"**/m.go", "bar/m.go", true, nil},
|
||||
{"**/m.go", "foo/bar/m.go", true, nil},
|
||||
|
||||
{"ab[cde]", "abc", true, nil},
|
||||
{"ab[cde]", "abd", true, nil},
|
||||
{"ab[cde]", "abe", true, nil},
|
||||
{"ab[+-\\-]", "ab-", true, nil},
|
||||
{"ab[\\--a]", "ab-", true, nil},
|
||||
|
||||
{"[a-fA-F]", "a", true, nil},
|
||||
{"[a-fA-F]", "f", true, nil},
|
||||
{"[a-fA-F]", "A", true, nil},
|
||||
{"[a-fA-F]", "F", true, nil},
|
||||
|
||||
// The following test cases are taken from
|
||||
// https://github.com/golang/go/blob/master/src/path/match_test.go and are
|
||||
// provided here to test compatebility of the match implementation with the
|
||||
// test cases from the golang standard lib.
|
||||
{"abc", "abc", true, nil},
|
||||
{"*", "abc", true, nil},
|
||||
{"*c", "abc", true, nil},
|
||||
{"a*", "a", true, nil},
|
||||
{"a*", "abc", true, nil},
|
||||
{"a*", "ab/c", false, nil},
|
||||
{"a*/b", "abc/b", true, nil},
|
||||
{"a*/b", "a/c/b", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
|
||||
{"ab[c]", "abc", true, nil},
|
||||
{"ab[b-d]", "abc", true, nil},
|
||||
{"ab[e-g]", "abc", false, nil},
|
||||
{"ab[^c]", "abc", false, nil},
|
||||
{"ab[^b-d]", "abc", false, nil},
|
||||
{"ab[^e-g]", "abc", true, nil},
|
||||
{"a\\*b", "a*b", true, nil},
|
||||
{"a\\*b", "ab", false, nil},
|
||||
{"a?b", "a☺b", true, nil},
|
||||
{"a[^a]b", "a☺b", true, nil},
|
||||
{"a???b", "a☺b", false, nil},
|
||||
{"a[^a][^a][^a]b", "a☺b", false, nil},
|
||||
{"[a-ζ]*", "α", true, nil},
|
||||
{"*[a-ζ]", "A", false, nil},
|
||||
{"a?b", "a/b", false, nil},
|
||||
{"a*b", "a/b", false, nil},
|
||||
{"[\\]a]", "]", true, nil},
|
||||
{"[\\-]", "-", true, nil},
|
||||
{"[x\\-]", "x", true, nil},
|
||||
{"[x\\-]", "-", true, nil},
|
||||
{"[x\\-]", "z", false, nil},
|
||||
{"[\\-x]", "x", true, nil},
|
||||
{"[\\-x]", "-", true, nil},
|
||||
{"[\\-x]", "a", false, nil},
|
||||
{"[]a]", "]", false, ErrBadPattern},
|
||||
{"[-]", "-", false, ErrBadPattern},
|
||||
{"[x-]", "x", false, ErrBadPattern},
|
||||
{"[x-]", "-", false, ErrBadPattern},
|
||||
{"[x-]", "z", false, ErrBadPattern},
|
||||
{"[-x]", "x", false, ErrBadPattern},
|
||||
{"[-x]", "-", false, ErrBadPattern},
|
||||
{"[-x]", "a", false, ErrBadPattern},
|
||||
{"\\", "a", false, ErrBadPattern},
|
||||
{"[a-b-c]", "a", false, ErrBadPattern},
|
||||
{"[", "a", false, ErrBadPattern},
|
||||
{"[^", "a", false, ErrBadPattern},
|
||||
{"[^bc", "a", false, ErrBadPattern},
|
||||
{"a[", "a", false, ErrBadPattern},
|
||||
{"a[", "ab", false, ErrBadPattern},
|
||||
{"a[", "x", false, ErrBadPattern},
|
||||
{"a/b[", "x", false, ErrBadPattern},
|
||||
{"*x", "xxx", true, nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
pat, err := New(tt.pattern)
|
||||
if err != tt.err && !errors.Is(err, tt.err) {
|
||||
t.Errorf("New(%#q): wanted error %v but got %v", tt.pattern, tt.err, err)
|
||||
}
|
||||
|
||||
if pat != nil {
|
||||
match := pat.Match(tt.f)
|
||||
if match != tt.match {
|
||||
t.Errorf("New(%#q).Match(%#q): wanted match %v but got %v", tt.pattern, tt.f, tt.match, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPattern_MatchPrefix(t *testing.T) {
|
||||
tests := []test{
|
||||
{"**/*.go", "foo/", true, nil},
|
||||
{"**/*.go", "foo", true, nil},
|
||||
{"**/*.go", "foo/bar/", true, nil},
|
||||
{"**/*.go", "foo/bar", true, nil},
|
||||
{"*/*.go", "foo", true, nil},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc // capture range variable
|
||||
t.Run(fmt.Sprintf("%s (%s)", escape(tc.pattern), escape(tc.f)), func(t *testing.T) {
|
||||
pat, err := New(tc.pattern)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
got := pat.MatchPrefix(tc.f)
|
||||
|
||||
if got != tc.match {
|
||||
t.Errorf("got %v; want %v", got, tc.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPattern_GlobFS(t *testing.T) {
|
||||
fsys := fstest.MapFS{
|
||||
"go.mod": &fstest.MapFile{Mode: 0644},
|
||||
"go.sum": &fstest.MapFile{Mode: 0644},
|
||||
"cmd/main.go": &fstest.MapFile{Mode: 0644},
|
||||
"cmd/main_test.go": &fstest.MapFile{Mode: 0644},
|
||||
"internal/tool/tool.go": &fstest.MapFile{Mode: 0644},
|
||||
"internal/tool/tool_test.go": &fstest.MapFile{Mode: 0644},
|
||||
"internal/cli/cli.go": &fstest.MapFile{Mode: 0644},
|
||||
"internal/cli/cli_test.go": &fstest.MapFile{Mode: 0644},
|
||||
}
|
||||
|
||||
pat, err := New("**/*_test.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
files, err := pat.GlobFS(fsys, ".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect := []string{
|
||||
"cmd/main_test.go",
|
||||
"internal/cli/cli_test.go",
|
||||
"internal/tool/tool_test.go",
|
||||
}
|
||||
if !reflect.DeepEqual(expect, files) {
|
||||
t.Errorf("got %v; want %v", files, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPattern_String(t *testing.T) {
|
||||
tests := []string{
|
||||
"main.go",
|
||||
"*.go",
|
||||
"**/*.go",
|
||||
"foo/bar/*",
|
||||
"foo/?ar.go",
|
||||
"foo/[abc].go",
|
||||
"foo/[a-c].go",
|
||||
"foo/**/",
|
||||
"foo/*/bar.go",
|
||||
"foo/\\*bar.go",
|
||||
}
|
||||
for _, patstr := range tests {
|
||||
t.Run(escape(patstr), func(t *testing.T) {
|
||||
pat, err := New(patstr)
|
||||
if err != nil {
|
||||
t.Fatalf("New(%q) failed: %v", patstr, err)
|
||||
}
|
||||
if pat.String() != patstr {
|
||||
t.Fatalf("Pattern.String() = %q, want %q", pat.String(), patstr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue