summaryrefslogtreecommitdiff
path: root/srv/cfg
diff options
context:
space:
mode:
Diffstat (limited to 'srv/cfg')
-rw-r--r--srv/cfg/cfg.go146
-rw-r--r--srv/cfg/cfg_test.go46
2 files changed, 181 insertions, 11 deletions
diff --git a/srv/cfg/cfg.go b/srv/cfg/cfg.go
index 685248c..8513e16 100644
--- a/srv/cfg/cfg.go
+++ b/srv/cfg/cfg.go
@@ -5,7 +5,10 @@ package cfg
import (
"context"
"flag"
+ "fmt"
"os"
+ "strconv"
+ "strings"
)
// Cfger is a component which can be used with Cfg to setup its initialization.
@@ -13,20 +16,70 @@ type Cfger interface {
SetupCfg(*Cfg)
}
+// Params are used to initialize a Cfg instance.
+type Params struct {
+
+ // Args are the command line arguments, excluding the command-name.
+ //
+ // Defaults to os.Args[1:]
+ Args []string
+
+ // Env is the process's environment variables.
+ //
+ // Defaults to the real environment variables.
+ Env map[string]string
+
+ // EnvPrefix indicates a string to prefix to all environment variable names
+ // that Cfg will read. Will be automatically suffixed with a "_" if given.
+ EnvPrefix string
+}
+
+func (p Params) withDefaults() Params {
+
+ if p.Args == nil {
+ p.Args = os.Args[1:]
+ }
+
+ if p.Env == nil {
+
+ p.Env = map[string]string{}
+
+ for _, envVar := range os.Environ() {
+
+ parts := strings.SplitN(envVar, "=", 2)
+
+ if len(parts) < 2 {
+ panic(fmt.Sprintf("envVar %q returned from os.Environ() somehow", envVar))
+ }
+
+ p.Env[parts[0]] = parts[1]
+ }
+ }
+
+ if p.EnvPrefix != "" {
+ p.EnvPrefix = strings.TrimSuffix(p.EnvPrefix, "_") + "_"
+ }
+
+ return p
+}
+
// Cfg is a wrapper around the stdlib's FlagSet and a set of initialization
// hooks.
type Cfg struct {
- *flag.FlagSet
+ params Params
+ flagSet *flag.FlagSet
hooks []func(ctx context.Context) error
- args []string
}
// New initializes and returns a new instance of *Cfg.
-func New() *Cfg {
+func New(params Params) *Cfg {
+
+ params = params.withDefaults()
+
return &Cfg{
- FlagSet: flag.NewFlagSet("", flag.ExitOnError),
- args: os.Args[1:],
+ params: params,
+ flagSet: flag.NewFlagSet("", flag.ExitOnError),
}
}
@@ -40,7 +93,7 @@ func (c *Cfg) OnInit(cb func(context.Context) error) {
// called. If one returns an error that error is returned and no further hooks
// are run.
func (c *Cfg) Init(ctx context.Context) error {
- if err := c.FlagSet.Parse(c.args); err != nil {
+ if err := c.flagSet.Parse(c.params.Args); err != nil {
return err
}
@@ -53,6 +106,77 @@ func (c *Cfg) Init(ctx context.Context) error {
return nil
}
+func (c *Cfg) envifyName(name string) string {
+ name = c.params.EnvPrefix + name
+ name = strings.Replace(name, "-", "_", -1)
+ name = strings.ToUpper(name)
+ return name
+}
+
+func envifyUsage(envName, usage string) string {
+ return fmt.Sprintf("%s (overrides %s)", usage, envName)
+}
+
+// StringVar is equivalent to flag.FlagSet's StringVar method, but will
+// additionally set up an environment variable for the parameter.
+func (c *Cfg) StringVar(p *string, name, value, usage string) {
+
+ envName := c.envifyName(name)
+
+ c.flagSet.StringVar(p, name, value, envifyUsage(envName, usage))
+
+ if val := c.params.Env[envName]; val != "" {
+ *p = val
+ }
+}
+
+// String is equivalent to flag.FlagSet's String method, but will additionally
+// set up an environment variable for the parameter.
+func (c *Cfg) String(name, value, usage string) *string {
+ p := new(string)
+ c.StringVar(p, name, value, usage)
+ return p
+}
+
+// IntVar is equivalent to flag.FlagSet's IntVar method, but will additionally
+// set up an environment variable for the parameter.
+func (c *Cfg) IntVar(p *int, name string, value int, usage string) {
+
+ envName := c.envifyName(name)
+
+ c.flagSet.IntVar(p, name, value, envifyUsage(envName, usage))
+
+ // if we can't parse the envvar now then just hold onto the error until
+ // Init, otherwise we'd have to panic here and that'd be ugly.
+ var err error
+
+ if valStr := c.params.Env[envName]; valStr != "" {
+
+ var val int
+ val, err = strconv.Atoi(valStr)
+
+ if err != nil {
+ err = fmt.Errorf(
+ "parsing envvar %q with value %q: %w",
+ envName, valStr, err,
+ )
+
+ } else {
+ *p = val
+ }
+ }
+
+ c.OnInit(func(context.Context) error { return err })
+}
+
+// Int is equivalent to flag.FlagSet's Int method, but will additionally set up
+// an environment variable for the parameter.
+func (c *Cfg) Int(name string, value int, usage string) *int {
+ p := new(int)
+ c.IntVar(p, name, value, usage)
+ return p
+}
+
// SubCmd should be called _after_ Init. Init will have consumed all arguments
// up until the first non-flag argument. This non-flag argument is a
// sub-command, and is returned by this method. This method also resets Cfg's
@@ -61,16 +185,16 @@ func (c *Cfg) Init(ctx context.Context) error {
// If there is no sub-command following the initial set of flags then this will
// return empty string.
func (c *Cfg) SubCmd() string {
- c.args = c.FlagSet.Args()
- if len(c.args) == 0 {
+ c.params.Args = c.flagSet.Args()
+ if len(c.params.Args) == 0 {
return ""
}
- subCmd := c.args[0]
+ subCmd := c.params.Args[0]
- c.FlagSet = flag.NewFlagSet(subCmd, flag.ExitOnError)
+ c.flagSet = flag.NewFlagSet(subCmd, flag.ExitOnError)
c.hooks = nil
- c.args = c.args[1:]
+ c.params.Args = c.params.Args[1:]
return subCmd
}
diff --git a/srv/cfg/cfg_test.go b/srv/cfg/cfg_test.go
new file mode 100644
index 0000000..7ccf94a
--- /dev/null
+++ b/srv/cfg/cfg_test.go
@@ -0,0 +1,46 @@
+package cfg
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStringVar(t *testing.T) {
+
+ cfg := New(Params{
+ Args: []string{"--foo=CLI"},
+ Env: map[string]string{"FOO": "ENV", "BAR": "ENV"},
+ })
+
+ var foo, bar, baz string
+
+ cfg.StringVar(&foo, "foo", "DEF", "")
+ cfg.StringVar(&bar, "bar", "DEF", "")
+ cfg.StringVar(&baz, "baz", "DEF", "")
+
+ assert.NoError(t, cfg.Init(context.Background()))
+ assert.Equal(t, "CLI", foo)
+ assert.Equal(t, "ENV", bar)
+ assert.Equal(t, "DEF", baz)
+}
+
+func TestIntVar(t *testing.T) {
+
+ cfg := New(Params{
+ Args: []string{"--foo=111"},
+ Env: map[string]string{"FOO": "222", "BAR": "222"},
+ })
+
+ var foo, bar, baz int
+
+ cfg.IntVar(&foo, "foo", 333, "")
+ cfg.IntVar(&bar, "bar", 333, "")
+ cfg.IntVar(&baz, "baz", 333, "")
+
+ assert.NoError(t, cfg.Init(context.Background()))
+ assert.Equal(t, 111, foo)
+ assert.Equal(t, 222, bar)
+ assert.Equal(t, 333, baz)
+}