diff --git a/.golangci.yml b/.golangci.yml index 095c6226..0446dda9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -53,6 +53,7 @@ linters-settings: - golang.org/x/tools - gopkg.in/yaml.v2 - github.com/alexflint/go-arg + - github.com/bmatcuk/doublestar/v4 forbidigo: forbid: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ee43b5fb..239de388 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -24,6 +24,8 @@ When releasing a new version: ### New features: +- genqlient now supports double-star globs for schema and query files; see [`genqlient.yaml` docs](genqlient.yaml) for more. + ### Bug fixes: ## v0.7.0 diff --git a/docs/genqlient.yaml b/docs/genqlient.yaml index 55178a29..33a49c06 100644 --- a/docs/genqlient.yaml +++ b/docs/genqlient.yaml @@ -6,11 +6,15 @@ # The filename with the GraphQL schema (in SDL format), relative to # genqlient.yaml. -# This can also be a list of filenames, such as: +# This can also be a glob-pattern, or a list of filenames or globs, such as: # schema: # - user.graphql # - ./schema/*.graphql -# - ./another_directory/*/*.graphql +# - ./*/*.graphql # matches ./a/b.graphql, but not ./a/b/c.graphql +# - ./**/*.graphql # matches ./a.graphql, ./a/b/c.graphql, etc. +# The glob-pattern "**" is interpreted by github.com/bmatcuk/doublestar/v4, and +# matches zero or more path components (so you want **/*.graphql, not +# **.graphql). Each pattern must match at least one file, to avoid mistakes. schema: schema.graphql # Filename(s) or globs with the operations for which to generate code, relative @@ -20,7 +24,7 @@ schema: schema.graphql # Go files, in which case any string-literal starting with (optional # whitespace and) the string "# @genqlient" will be extracted as a query. # -# Like schema, this may be a single file or a list. +# Like schema, this may be a single filename or glob, or a list of those. operations: - genqlient.graphql - "pkg/*.go" diff --git a/generate/parse.go b/generate/parse.go index 6ef7656b..196f91aa 100644 --- a/generate/parse.go +++ b/generate/parse.go @@ -6,10 +6,12 @@ import ( goParser "go/parser" goToken "go/token" "os" + "path" "path/filepath" "strconv" "strings" + "github.com/bmatcuk/doublestar/v4" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/parser" "github.com/vektah/gqlparser/v2/validator" @@ -87,7 +89,10 @@ func getAndValidateQueries(basedir string, filenames StringList, schema *ast.Sch func expandFilenames(globs []string) ([]string, error) { uniqFilenames := make(map[string]bool, len(globs)) for _, glob := range globs { - matches, err := filepath.Glob(glob) + // SplitPattern in case the path is absolute or something; a valid path + // isn't necessarily a valid glob-pattern. + base, pattern := doublestar.SplitPattern(glob) + matches, err := doublestar.Glob(os.DirFS(base), pattern, doublestar.WithFilesOnly()) if err != nil { return nil, errorf(nil, "can't expand file-glob %v: %v", glob, err) } @@ -95,7 +100,7 @@ func expandFilenames(globs []string) ([]string, error) { return nil, errorf(nil, "%v did not match any files", glob) } for _, match := range matches { - uniqFilenames[match] = true + uniqFilenames[path.Join(base, match)] = true } } filenames := make([]string, 0, len(uniqFilenames)) diff --git a/generate/parse_test.go b/generate/parse_test.go index 500c7e84..e08d8a4c 100644 --- a/generate/parse_test.go +++ b/generate/parse_test.go @@ -6,12 +6,14 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/vektah/gqlparser/v2/ast" ) var ( - parseDataDir = "testdata/parsing" - parseErrorsDir = "testdata/parsing-errors" + parseDataDir = "testdata/parsing" + parseErrorsDir = "testdata/parsing-errors" + expandFilenamesDir = "testdata/expandFilenames" ) func sortQueries(queryDoc *ast.QueryDocument) { @@ -80,6 +82,44 @@ func removeComments(gotWithComments string) string { return got } +func filepathJoinAll(a string, bs []string) []string { + ret := make([]string, len(bs)) + for i, b := range bs { + ret[i] = filepath.Join(a, b) + } + return ret +} + +func TestExpandFilenames(t *testing.T) { + tests := []struct { + name string + globs []string + files []string + err bool + }{ + {"SingleFile", []string{"a/b/c"}, []string{"a/b/c"}, false}, + {"OneStar", []string{"a/*/c"}, []string{"a/b/c"}, false}, + {"StarExt", []string{"a/b/*"}, []string{"a/b/c", "a/b/c.d"}, false}, + {"TwoStar", []string{"**/c"}, []string{"a/b/c"}, false}, + {"TwoStarSuffix", []string{"**/*"}, []string{"a/b/c", "a/b/c.d"}, false}, + {"Repeated", []string{"a/b/c", "a/b/*"}, []string{"a/b/c", "a/b/c.d"}, false}, + {"Empty", []string{"bogus/*"}, nil, true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + files, err := expandFilenames(filepathJoinAll(expandFilenamesDir, test.globs)) + if test.err && err == nil { + t.Errorf("got %v, wanted error", files) + } else if !test.err && err != nil { + t.Errorf("got error %v, wanted %v", err, test.files) + } else { + assert.ElementsMatch(t, filepathJoinAll(expandFilenamesDir, test.files), files) + } + }) + } +} + // TestParseErrors tests that query-extraction from different language source files // produces appropriate errors if your query is invalid. func TestParseErrors(t *testing.T) { diff --git a/generate/testdata/expandFilenames/a/b/c b/generate/testdata/expandFilenames/a/b/c new file mode 100644 index 00000000..e69de29b diff --git a/generate/testdata/expandFilenames/a/b/c.d b/generate/testdata/expandFilenames/a/b/c.d new file mode 100644 index 00000000..e69de29b diff --git a/go.mod b/go.mod index 5741c420..c6fb5870 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alexflint/go-scalar v1.0.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect diff --git a/go.sum b/go.sum index 668fa62a..a3a0616f 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxj github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs= github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=