diff --git a/json.go b/json_parser.go similarity index 100% rename from json.go rename to json_parser.go diff --git a/json_test.go b/json_parser_test.go similarity index 100% rename from json_test.go rename to json_parser_test.go diff --git a/options.go b/options.go deleted file mode 100644 index de09749..0000000 --- a/options.go +++ /dev/null @@ -1 +0,0 @@ -package ff diff --git a/parse.go b/parse.go index 0c53daf..a73c74a 100644 --- a/parse.go +++ b/parse.go @@ -1,9 +1,12 @@ package ff import ( + "embed" + "errors" "flag" "fmt" "io" + iofs "io/fs" "os" "strings" ) @@ -12,8 +15,6 @@ import ( // and calls the set function for each parsed flag pair. type ConfigFileParser func(r io.Reader, set func(name, value string) error) error -type lookupFunc func(fs *flag.FlagSet, name string) *flag.Flag - // Parse the flags in the flag set from the provided (presumably commandline) // args. Additional options may be provided to have Parse also read from a // config file, and/or environment variables, in that priority order. @@ -97,9 +98,9 @@ func Parse(fs *flag.FlagSet, args []string, options ...Option) error { } } - if c.configFileLookup == nil { - c.configFileLookup = func(fs *flag.FlagSet, name string) *flag.Flag { - return fs.Lookup(name) + if c.configFileOpenFunc == nil { + c.configFileOpenFunc = func(s string) (iofs.File, error) { + return os.Open(s) } } @@ -109,7 +110,7 @@ func Parse(fs *flag.FlagSet, args []string, options ...Option) error { parseConfigFile = haveConfigFile && haveParser ) if parseConfigFile { - f, err := os.Open(configFile) + f, err := c.configFileOpenFunc(configFile) switch { case err == nil: defer f.Close() @@ -151,7 +152,7 @@ func Parse(fs *flag.FlagSet, args []string, options ...Option) error { return err } - case os.IsNotExist(err) && c.allowMissingConfigFile: + case errors.Is(err, iofs.ErrNotExist) && c.allowMissingConfigFile: // no problem default: @@ -171,7 +172,7 @@ type Context struct { configFileVia *string configFileFlagName string configFileParser ConfigFileParser - configFileLookup lookupFunc + configFileOpenFunc func(string) (iofs.File, error) allowMissingConfigFile bool readEnvVars bool envVarPrefix string @@ -278,6 +279,15 @@ func WithIgnoreUndefined(ignore bool) Option { } } +// WithFilesystem tells Parse to use the provided filesystem when accessing +// files on disk, for example when reading a config file. By default, the host +// filesystem is used, via [os.Open]. +func WithFilesystem(fs embed.FS) Option { + return func(c *Context) { + c.configFileOpenFunc = fs.Open + } +} + var flagNameToEnvVar = strings.NewReplacer( "-", "_", ".", "_", diff --git a/parse_test.go b/parse_test.go index 9717f7b..b90420d 100644 --- a/parse_test.go +++ b/parse_test.go @@ -2,6 +2,7 @@ package ff_test import ( "context" + "embed" "flag" "os" "testing" @@ -12,6 +13,9 @@ import ( "github.com/peterbourgon/ff/v3/fftest" ) +//go:embed testdata/*.conf +var testdataConfigFS embed.FS + func TestParseBasics(t *testing.T) { t.Parallel() @@ -149,6 +153,11 @@ func TestParseBasics(t *testing.T) { opts: []ff.Option{ff.WithEnvVarNoPrefix()}, want: fftest.Vars{S: "xxx", F: 9.87}, }, + { + name: "WithFilesystem testdata/1.conf", + opts: []ff.Option{ff.WithFilesystem(testdataConfigFS), ff.WithConfigFile("testdata/1.conf"), ff.WithConfigFileParser(ff.PlainParser)}, + want: fftest.Vars{S: "bar", I: 99, B: true, D: 1 * time.Hour}, + }, } { t.Run(testcase.name, func(t *testing.T) { if testcase.file != "" {