Walk parent directories to find config file (#141)
Code is mostly borrowed from [gqlgen](https://github.com/99designs/gqlgen). The idea here is that I want to be able to store `genqlient.yaml` at the top-level, but my client code lives down in `graph/client/`. I put the `//go:generate` line in `graph/client/client.go`.
This commit is contained in:
+3
-1
@@ -1,6 +1,8 @@
|
|||||||
# genqlient.yaml is genqlient's configuration file. This genqlient.yaml is an
|
# genqlient.yaml is genqlient's configuration file. This genqlient.yaml is an
|
||||||
# example; use `go run github.com/Khan/genqlient --init` to generate a simple
|
# example; use `go run github.com/Khan/genqlient --init` to generate a simple
|
||||||
# starting point.
|
# starting point. By default, genqlient looks for the configuration file
|
||||||
|
# named [.]genqlient.y[a]ml in the current directory or any ancestor; or the
|
||||||
|
# filename may be given as an argument.
|
||||||
|
|
||||||
# The filename with the GraphQL schema (in SDL format), relative to
|
# The filename with the GraphQL schema (in SDL format), relative to
|
||||||
# genqlient.yaml.
|
# genqlient.yaml.
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var cfgFilenames = []string{".genqlient.yml", ".genqlient.yaml", "genqlient.yml", "genqlient.yaml"}
|
||||||
|
|
||||||
// Config represents genqlient's configuration, generally read from
|
// Config represents genqlient's configuration, generally read from
|
||||||
// genqlient.yaml.
|
// genqlient.yaml.
|
||||||
//
|
//
|
||||||
@@ -109,6 +111,17 @@ func ReadAndValidateConfig(filename string) (*Config, error) {
|
|||||||
return &config, nil
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadAndValidateConfigFromDefaultLocations looks for a config file in the
|
||||||
|
// current directory, and all parent directories walking up the tree. The
|
||||||
|
// closest config file will be returned.
|
||||||
|
func ReadAndValidateConfigFromDefaultLocations() (*Config, error) {
|
||||||
|
cfgFile, err := findCfg()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ReadAndValidateConfig(cfgFile)
|
||||||
|
}
|
||||||
|
|
||||||
func initConfig(filename string) error {
|
func initConfig(filename string) error {
|
||||||
// TODO(benkraft): Embed this config file into the binary, see
|
// TODO(benkraft): Embed this config file into the binary, see
|
||||||
// https://github.com/Khan/genqlient/issues/9.
|
// https://github.com/Khan/genqlient/issues/9.
|
||||||
@@ -126,3 +139,35 @@ func initConfig(filename string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findCfg searches for the config file in this directory and all parents up the tree
|
||||||
|
// looking for the closest match
|
||||||
|
func findCfg() (string, error) {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", errorf(nil, "unable to get working dir to findCfg: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := findCfgInDir(dir)
|
||||||
|
|
||||||
|
for cfg == "" && dir != filepath.Dir(dir) {
|
||||||
|
dir = filepath.Dir(dir)
|
||||||
|
cfg = findCfgInDir(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == "" {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findCfgInDir(dir string) string {
|
||||||
|
for _, cfgName := range cfgFilenames {
|
||||||
|
path := filepath.Join(dir, cfgName)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindCfg(t *testing.T) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
startDir string
|
||||||
|
expectedCfg string
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
"yaml in parent directory": {
|
||||||
|
startDir: cwd + "/testdata/find-config/parent/child",
|
||||||
|
expectedCfg: cwd + "/testdata/find-config/parent/genqlient.yaml",
|
||||||
|
},
|
||||||
|
"yaml in current directory": {
|
||||||
|
startDir: cwd + "/testdata/find-config/current",
|
||||||
|
expectedCfg: cwd + "/testdata/find-config/current/genqlient.yaml",
|
||||||
|
},
|
||||||
|
"no yaml": {
|
||||||
|
startDir: cwd + "/testdata/find-config/none/child",
|
||||||
|
expectedErr: os.ErrNotExist,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, os.Chdir(cwd), "Test cleanup failed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = os.Chdir(tc.startDir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
path, err := findCfg()
|
||||||
|
assert.Equal(t, tc.expectedCfg, path)
|
||||||
|
assert.Equal(t, tc.expectedErr, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindCfgInDir(t *testing.T) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
startDir string
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
"yaml": {
|
||||||
|
startDir: cwd + "/testdata/find-config/filenames/yaml",
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
"yml": {
|
||||||
|
startDir: cwd + "/testdata/find-config/filenames/yml",
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
".yaml": {
|
||||||
|
startDir: cwd + "/testdata/find-config/filenames/dotyaml",
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
".yml": {
|
||||||
|
startDir: cwd + "/testdata/find-config/filenames/dotyml",
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
"none": {
|
||||||
|
startDir: cwd + "/testdata/find-config/filenames/none",
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
path := findCfgInDir(tc.startDir)
|
||||||
|
if tc.found {
|
||||||
|
assert.NotEmpty(t, path)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-5
@@ -14,9 +14,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func readConfigGenerateAndWrite(configFilename string) error {
|
func readConfigGenerateAndWrite(configFilename string) error {
|
||||||
config, err := ReadAndValidateConfig(configFilename)
|
var config *Config
|
||||||
if err != nil {
|
var err error
|
||||||
return err
|
if configFilename != "" {
|
||||||
|
config, err = ReadAndValidateConfig(configFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config, err = ReadAndValidateConfigFromDefaultLocations()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generated, err := Generate(config)
|
generated, err := Generate(config)
|
||||||
@@ -42,7 +51,7 @@ func readConfigGenerateAndWrite(configFilename string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cliArgs struct {
|
type cliArgs struct {
|
||||||
ConfigFilename string `arg:"positional" placeholder:"CONFIG" default:"genqlient.yaml" help:"path to genqlient configuration (default genqlient.yaml)"`
|
ConfigFilename string `arg:"positional" placeholder:"CONFIG" default:"" help:"path to genqlient configuration (default: genqlient.yaml in current or any parent directory)"`
|
||||||
Init bool `arg:"--init" help:"write out and use a default config file"`
|
Init bool `arg:"--init" help:"write out and use a default config file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +76,12 @@ func Main() {
|
|||||||
var args cliArgs
|
var args cliArgs
|
||||||
arg.MustParse(&args)
|
arg.MustParse(&args)
|
||||||
if args.Init {
|
if args.Init {
|
||||||
err := initConfig(args.ConfigFilename)
|
filename := args.ConfigFilename
|
||||||
|
if filename == "" {
|
||||||
|
filename = "genqlient.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := initConfig(filename)
|
||||||
exitIfError(err)
|
exitIfError(err)
|
||||||
}
|
}
|
||||||
err := readConfigGenerateAndWrite(args.ConfigFilename)
|
err := readConfigGenerateAndWrite(args.ConfigFilename)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Default genqlient config; for full documentation see:
|
||||||
|
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
|
schema: schema.graphql
|
||||||
|
operations:
|
||||||
|
- genqlient.graphql
|
||||||
|
generated: generated.go
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Default genqlient config; for full documentation see:
|
||||||
|
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
|
schema: schema.graphql
|
||||||
|
operations:
|
||||||
|
- genqlient.graphql
|
||||||
|
generated: generated.go
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Default genqlient config; for full documentation see:
|
||||||
|
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
|
schema: schema.graphql
|
||||||
|
operations:
|
||||||
|
- genqlient.graphql
|
||||||
|
generated: generated.go
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Default genqlient config; for full documentation see:
|
||||||
|
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
|
schema: schema.graphql
|
||||||
|
operations:
|
||||||
|
- genqlient.graphql
|
||||||
|
generated: generated.go
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Default genqlient config; for full documentation see:
|
||||||
|
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
|
schema: schema.graphql
|
||||||
|
operations:
|
||||||
|
- genqlient.graphql
|
||||||
|
generated: generated.go
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Default genqlient config; for full documentation see:
|
||||||
|
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
|
schema: schema.graphql
|
||||||
|
operations:
|
||||||
|
- genqlient.graphql
|
||||||
|
generated: generated.go
|
||||||
Reference in New Issue
Block a user