diff --git a/README.md b/README.md index c860ee0..6807a87 100644 --- a/README.md +++ b/README.md @@ -159,14 +159,29 @@ type Config struct { The `flags` backend allows to load individual configuration keys from the command line. The default values are extracted from the struct fields values. +A `short` option is also supported. + +```go +type Config struct { + Host string `config:"host,short=h"` + Port uint32 `config:"port,short=p"` + Timeout time.Duration `config:"timeout"` +} +``` + + ```sh ./bin -h Usage of ./bin: -host string (default "127.0.0.1") + -h string + (default "127.0.0.1") -port int (default 5656) + -p int + (default 5656) -timeout duration (default 10s) ``` diff --git a/backend/flags/flags.go b/backend/flags/flags.go index 1dfe27c..0cbe135 100644 --- a/backend/flags/flags.go +++ b/backend/flags/flags.go @@ -32,48 +32,63 @@ func (b *Backend) LoadStruct(ctx context.Context, cfg *confita.StructConfig) err k := f.Value.Kind() switch { case f.Value.Type().String() == "time.Duration": - // define the flag and its default value - v := flag.Duration(f.Key, time.Duration(f.Default.Int()), "") + var val time.Duration + flag.DurationVar(&val, f.Key, time.Duration(f.Default.Int()), "") + if f.Short != "" { + flag.DurationVar(&val, f.Short, time.Duration(f.Default.Int()), "") + } // this function must be executed after the flag.Parse call. defer func() { // if the user has set the flag, save the value in the field. - if isFlagSet(f.Key) { - f.Value.SetInt(int64(*v)) + if isFlagSet(f) { + f.Value.SetInt(int64(val)) } }() case k == reflect.Bool: - v := flag.Bool(f.Key, f.Default.Bool(), "") + var val bool + flag.BoolVar(&val, f.Key, f.Default.Bool(), "") + if f.Short != "" { + flag.BoolVar(&val, f.Short, f.Default.Bool(), "") + } defer func() { - if isFlagSet(f.Key) { - f.Value.SetBool(*v) + if isFlagSet(f) { + f.Value.SetBool(val) } }() case k >= reflect.Int && k <= reflect.Int64: - v := flag.Int(f.Key, int(f.Default.Int()), "") + var val int + flag.IntVar(&val, f.Key, int(f.Default.Int()), "") + if f.Short != "" { + flag.IntVar(&val, f.Short, int(f.Default.Int()), "") + } defer func() { - if isFlagSet(f.Key) { - f.Value.SetInt(int64(*v)) + if isFlagSet(f) { + f.Value.SetInt(int64(val)) } }() case k >= reflect.Uint && k <= reflect.Uint64: v := flag.Uint(f.Key, uint(f.Default.Uint()), "") defer func() { - if isFlagSet(f.Key) { + if isFlagSet(f) { f.Value.SetUint(uint64(*v)) } }() case k >= reflect.Float32 && k <= reflect.Float64: v := flag.Float64(f.Key, f.Default.Float(), "") defer func() { - if isFlagSet(f.Key) { + if isFlagSet(f) { f.Value.SetFloat(*v) } }() case k == reflect.String: - v := flag.String(f.Key, f.Default.String(), "") + var val string + flag.StringVar(&val, f.Key, f.Default.String(), "") + if f.Short != "" { + flag.StringVar(&val, f.Short, f.Default.String(), "") + } defer func() { - if isFlagSet(f.Key) { - f.Value.SetString(*v) + if isFlagSet(f) { + f.Value.SetString(val) } }() default: @@ -112,10 +127,10 @@ func (b *Backend) Name() string { return "flags" } -func isFlagSet(name string) bool { - flagset := make(map[string]bool) - flag.Visit(func(f *flag.Flag) { flagset[f.Name] = true }) +func isFlagSet(config *confita.FieldConfig) bool { + flagset := make(map[*confita.FieldConfig]bool) + flag.Visit(func(f *flag.Flag) { flagset[config] = true }) - _, ok := flagset[name] + _, ok := flagset[config] return ok } diff --git a/backend/flags/flags_test.go b/backend/flags/flags_test.go index 82cb981..bf5be35 100644 --- a/backend/flags/flags_test.go +++ b/backend/flags/flags_test.go @@ -17,13 +17,13 @@ import ( type Config struct { A string `config:"a"` - Adef string `config:"a-def"` + Adef string `config:"a-def,short=ad"` B bool `config:"b"` - Bdef bool `config:"b-def"` + Bdef bool `config:"b-def,short=bd"` C time.Duration `config:"c"` - Cdef time.Duration `config:"c-def"` + Cdef time.Duration `config:"c-def,short=cd"` D int `config:"d"` - Ddef int `config:"d-def"` + Ddef int `config:"d-def,short=dd"` } func runHelper(t *testing.T, args ...string) *Config { @@ -65,6 +65,22 @@ func TestFlags(t *testing.T) { }) } +func TestFlagsShort(t *testing.T) { + cfg := runHelper(t, "-ad=hello", "-bd=true", "-cd=20s", "-dd=500") + require.Equal(t, "hello", cfg.Adef) + require.Equal(t, true, cfg.Bdef) + require.Equal(t, 20*time.Second, cfg.Cdef) + require.Equal(t, 500, cfg.Ddef) +} + +func TestFlagsMixed(t *testing.T) { + cfg := runHelper(t, "-ad=hello", "-b-def=true", "-cd=20s", "-d-def=500") + require.Equal(t, "hello", cfg.Adef) + require.Equal(t, true, cfg.Bdef) + require.Equal(t, 20*time.Second, cfg.Cdef) + require.Equal(t, 500, cfg.Ddef) +} + func TestHelperProcess(t *testing.T) { if os.Getenv("GO_HELPER_PROCESS") != "1" { return diff --git a/config.go b/config.go index 5b6f70e..0f9651c 100644 --- a/config.go +++ b/config.go @@ -129,6 +129,12 @@ func (l *Loader) parseStruct(ref reflect.Value) *StructConfig { for _, opt := range opts { if opt == "required" { f.Required = true + continue + } + + if strings.HasPrefix(opt, "short=") { + f.Short = opt[len("short="):] + continue } if strings.HasPrefix(opt, "backend=") { @@ -169,6 +175,10 @@ func (l *Loader) resolve(ctx context.Context, s *StructConfig) error { default: } + if len(foundFields) == len(s.Fields) { + break + } + if u, ok := b.(Unmarshaler); ok { err := u.Unmarshal(ctx, s.S) if err != nil { @@ -233,6 +243,7 @@ type StructConfig struct { // FieldConfig holds informations about a struct field. type FieldConfig struct { Name string + Short string Key string Value reflect.Value Default reflect.Value