Skip to content

Commit 6737e9c

Browse files
codysoylandjaffee
authored andcommitted
Add pflag support to LoadArgsEnv
1 parent e3a6ecf commit 6737e9c

File tree

3 files changed

+78
-27
lines changed

3 files changed

+78
-27
lines changed

com.go

+46-25
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,24 @@ func Flags(flags Flagger, main interface{}) error {
5757
return setFlags(newFlagTracker(flags), main, "")
5858
}
5959

60+
type flagSet struct {
61+
*flag.FlagSet
62+
}
63+
64+
func (f *flagSet) Flags() (flags []string) {
65+
f.VisitAll(func(f *flag.Flag) {
66+
flags = append(flags, f.Name)
67+
})
68+
return flags
69+
}
70+
71+
var _ = FlagNamer(&flagSet{})
72+
6073
// Run runs "main" which must be a pointer to a struct which implements the
6174
// Runner interface. It first calls Flags to set up command line flags based on
6275
// "main" (see the documentation for Flags).
6376
func Run(main interface{}) error {
64-
return RunArgs(flag.CommandLine, main, os.Args[1:])
77+
return RunArgs(&flagSet{flag.CommandLine}, main, os.Args[1:])
6578
}
6679

6780
var replacer *strings.Replacer = strings.NewReplacer("-", "_", ".", "_")
@@ -72,39 +85,35 @@ func envNorm(name string) string {
7285

7386
// loadEnv visits each flag in the FlagSet and sets its value based on
7487
// OS environment.
75-
func loadEnv(flagset *flag.FlagSet, prefix string) error {
76-
var err error
77-
78-
flagset.VisitAll(func(f *flag.Flag) {
79-
// skip rest if there is an error
80-
if err != nil {
81-
return
82-
}
83-
envString := envNorm(prefix + f.Name)
84-
val, ok := os.LookupEnv(envString)
85-
if ok {
86-
err = flagset.Set(f.Name, val)
87-
if err != nil {
88-
err = fmt.Errorf("couldn't set %s to %s from env %s: %v", f.Name, val, envString, err)
88+
func loadEnv(flagger Flagger, prefix string) (err error) {
89+
if namer, ok := flagger.(FlagNamer); ok {
90+
for _, name := range namer.Flags() {
91+
envString := envNorm(prefix + name)
92+
val, ok := os.LookupEnv(envString)
93+
if ok {
94+
err = flagger.Set(name, val)
95+
if err != nil {
96+
return fmt.Errorf("couldn't set %s to %s from env %s: %v", name, val, envString, err)
97+
}
8998
}
9099
}
91-
})
92-
return err
100+
} else {
101+
return fmt.Errorf("unable to load flags from environment: flagger does not implement FlagNamer")
102+
}
103+
return nil
93104
}
94105

95106
// LoadEnv calls LoadArgsEnv with args from the command line and the
96107
// default flag set.
97108
func LoadEnv(main interface{}, envPrefix string, parseElsewhere func(main interface{}) error) error {
98-
return LoadArgsEnv(flag.CommandLine, main, os.Args[1:], envPrefix, parseElsewhere)
109+
return LoadArgsEnv(&flagSet{flag.CommandLine}, main, os.Args[1:], envPrefix, parseElsewhere)
99110
}
100111

101112
// LoadArgsEnv uses Flags to define flags based on "main", then it
102113
// tries setting each flag's value from the OS environment based on a
103114
// prefix concatenated to the flag name. The flag name is normalized
104115
// by removing any dashes or dots and replacing them with
105-
// underscores. It differs from RunArgs in that it *must* take a
106-
// stdlib *FlagSet rather than the more generic Flagger. TODO: have a
107-
// way to use alternative flag implementations such as pflag.
116+
// underscores.
108117
//
109118
// One may also pass a "configElsewhere" function which can operate on
110119
// main arbitrarily. The purpose of this is to load config values from
@@ -118,7 +127,7 @@ func LoadEnv(main interface{}, envPrefix string, parseElsewhere func(main interf
118127
// can be configured (such as with a path to a config file). Once
119128
// configElsewhere runs, the environment and command line args are
120129
// re-set since they take higher precedence.
121-
func LoadArgsEnv(flags *flag.FlagSet, main interface{}, args []string, envPrefix string, configElsewhere func(main interface{}) error) error {
130+
func LoadArgsEnv(flags Flagger, main interface{}, args []string, envPrefix string, configElsewhere func(main interface{}) error) error {
122131
// setup flags
123132
err := Flags(flags, main)
124133
if err != nil {
@@ -247,9 +256,15 @@ func setFlags(flags *flagTracker, main interface{}, prefix string) error {
247256
continue
248257
case []string:
249258
// special case support for string slice with stdlib flags
250-
if stdflag, ok := flags.flagger.(*flag.FlagSet); !flags.pflag && ok {
251-
stdflag.Var(stringSliceValue{value: f.Addr().Interface().(*[]string)}, flagName, flagHelp(ft))
252-
continue
259+
if !flags.pflag {
260+
if stdflag, ok := flags.flagger.(*flag.FlagSet); ok {
261+
stdflag.Var(stringSliceValue{value: f.Addr().Interface().(*[]string)}, flagName, flagHelp(ft))
262+
continue
263+
}
264+
if stdflagcom, ok := flags.flagger.(*flagSet); ok {
265+
stdflagcom.Var(stringSliceValue{value: f.Addr().Interface().(*[]string)}, flagName, flagHelp(ft))
266+
continue
267+
}
253268
}
254269
}
255270

@@ -580,6 +595,7 @@ type Flagger interface {
580595
Uint64Var(p *uint64, name string, value uint64, usage string)
581596
Float64Var(p *float64, name string, value float64, usage string)
582597
DurationVar(p *time.Duration, name string, value time.Duration, usage string)
598+
Set(name string, value string) error
583599
}
584600

585601
// PFlagger is an extension of the Flagger interface which is implemented by the
@@ -612,6 +628,11 @@ type PFlagger interface {
612628
DurationVarP(p *time.Duration, name string, shorthand string, value time.Duration, usage string)
613629
}
614630

631+
// FlagNamer is an interface that Flaggers may use to list the available flags.
632+
type FlagNamer interface {
633+
Flags() []string
634+
}
635+
615636
// Runner must be implemented by things passed to the Run and RunArgs methods.
616637
type Runner interface {
617638
Run() error

com_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
)
1515

1616
func TestLoadEnv(t *testing.T) {
17-
fs := flag.NewFlagSet("", flag.ExitOnError)
17+
fs := &flagSet{flag.NewFlagSet("", flag.ExitOnError)}
1818

1919
// create initial instance with defaults
2020
mm := &test.SimpleMain{
@@ -116,7 +116,7 @@ func TestLoadArgsEnv(t *testing.T) {
116116
mustSetenv(t, "COMMANDEER_ONE", "envone")
117117
mustSetenv(t, "COMMANDEER_TWO", "32")
118118

119-
fs := flag.NewFlagSet("", flag.ExitOnError)
119+
fs := &flagSet{flag.NewFlagSet("", flag.ExitOnError)}
120120
err := LoadArgsEnv(fs, mm, []string{"-two=24", "-seven", "7.3", "-nine", "a,b,c"}, "COMMANDEER_", nil)
121121
if err != nil {
122122
t.Fatalf("LoadArgsEnv: %v", err)

pflag/com.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package pflag
2+
3+
import (
4+
"os"
5+
6+
"github.com/jaffee/commandeer"
7+
"github.com/spf13/pflag"
8+
)
9+
10+
// Validate interface
11+
var _ = commandeer.FlagNamer(&FlagSet{})
12+
13+
// FlagSet is an extension to *pflag.FlagSet that satisfies FlagNamer
14+
type FlagSet struct {
15+
*pflag.FlagSet
16+
}
17+
18+
// Flags returns a slice of flag names
19+
func (f *FlagSet) Flags() (flags []string) {
20+
f.VisitAll(func(f *pflag.Flag) {
21+
flags = append(flags, f.Name)
22+
})
23+
return flags
24+
}
25+
26+
// LoadEnv calls LoadArgsEnv with args from the command line and the
27+
// default flag set.
28+
func LoadEnv(main interface{}, envPrefix string, parseElsewhere func(main interface{}) error) error {
29+
return commandeer.LoadArgsEnv(&FlagSet{pflag.CommandLine}, main, os.Args[1:], envPrefix, parseElsewhere)
30+
}

0 commit comments

Comments
 (0)