From 7f261dec7e12a43b784b972bde696d82249988e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Fr=C3=B6ssman?= Date: Fri, 13 Nov 2020 04:11:09 +0100 Subject: [PATCH] ff: add WithConfigFileVia option (#71) --- parse.go | 25 ++++++++++++++++------ parse_test.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/parse.go b/parse.go index 1d86172..a0f7f2c 100644 --- a/parse.go +++ b/parse.go @@ -66,15 +66,20 @@ func Parse(fs *flag.FlagSet, args []string, options ...Option) error { provided[f.Name] = true }) + var configFile string + if c.configFileVia != nil { + configFile = *c.configFileVia + } + // Third priority: config file (host). - if c.configFile == "" && c.configFileFlagName != "" { + if configFile == "" && c.configFileFlagName != "" { if f := fs.Lookup(c.configFileFlagName); f != nil { - c.configFile = f.Value.String() + configFile = f.Value.String() } } - if parseConfig := c.configFile != "" && c.configFileParser != nil; parseConfig { - f, err := os.Open(c.configFile) + if parseConfig := configFile != "" && c.configFileParser != nil; parseConfig { + f, err := os.Open(configFile) switch { case err == nil: defer f.Close() @@ -117,7 +122,7 @@ func Parse(fs *flag.FlagSet, args []string, options ...Option) error { // Context contains private fields used during parsing. type Context struct { - configFile string + configFileVia *string configFileFlagName string configFileParser ConfigFileParser allowMissingConfigFile bool @@ -135,8 +140,16 @@ type Option func(*Context) // Because config files should generally be user-specifiable, this option // should be rarely used. Prefer WithConfigFileFlag. func WithConfigFile(filename string) Option { + return WithConfigFileVia(&filename) +} + +// WithConfigFileVia tells Parse to read the provided filename as a config file. +// Requires WithConfigFileParser, and overrides WithConfigFileFlag. +// This is useful for sharing a single root level flag for config files among +// multiple ffcli subcommands. +func WithConfigFileVia(filename *string) Option { return func(c *Context) { - c.configFile = filename + c.configFileVia = filename } } diff --git a/parse_test.go b/parse_test.go index 3001f16..2ca2ef3 100644 --- a/parse_test.go +++ b/parse_test.go @@ -1,11 +1,14 @@ package ff_test import ( + "context" + "flag" "os" "testing" "time" "github.com/peterbourgon/ff/v3" + "github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/fftest" ) @@ -254,3 +257,59 @@ func TestParseConfigFile(t *testing.T) { }) } } + +func TestParseConfigFileVia(t *testing.T) { + t.Parallel() + + var ( + rootFS = flag.NewFlagSet("root", flag.ContinueOnError) + config = rootFS.String("config-file", "", "") + i = rootFS.Int("i", 0, "") + s = rootFS.String("s", "", "") + subFS = flag.NewFlagSet("subcommand", flag.ContinueOnError) + d = subFS.Duration("d", time.Second, "") + b = subFS.Bool("b", false, "") + ) + + subCommand := &ffcli.Command{ + Name: "subcommand", + FlagSet: subFS, + Options: []ff.Option{ + ff.WithConfigFileParser(ff.PlainParser), + ff.WithConfigFileVia(config), + ff.WithIgnoreUndefined(true), + }, + Exec: func(ctx context.Context, args []string) error { return nil }, + } + + root := &ffcli.Command{ + Name: "root", + FlagSet: rootFS, + Options: []ff.Option{ + ff.WithConfigFileParser(ff.PlainParser), + ff.WithConfigFileFlag("config-file"), + ff.WithIgnoreUndefined(true), + }, + Exec: func(ctx context.Context, args []string) error { return nil }, + Subcommands: []*ffcli.Command{subCommand}, + } + + err := root.ParseAndRun(context.Background(), []string{"-config-file", "testdata/1.conf", "subcommand", "-b"}) + if err != nil { + t.Fatal(err) + } + + if want, have := time.Hour, *d; want != have { + t.Errorf("d: want %v, have %v", want, have) + } + if want, have := true, *b; want != have { + t.Errorf("b: want %v, have %v", want, have) + } + if want, have := "bar", *s; want != have { + t.Errorf("s: want %q, have %q", want, have) + } + if want, have := 99, *i; want != have { + t.Errorf("i: want %d, have %d", want, have) + } + +}