@@ 1,30 @@
+[linters]
+ disable-all = true
+ enable = [
+ "deadcode",
+ "errcheck",
+ "gochecknoinits",
+ "gochecknoglobals",
+ "gofmt",
+ "golint",
+ "gosec",
+ "gosimple",
+ "govet",
+ "ineffassign",
+ "interfacer",
+ "misspell",
+ "nakedret",
+ "prealloc",
+ "staticcheck",
+ "structcheck",
+ "typecheck",
+ "unconvert",
+ "unparam",
+ "unused",
+ "varcheck",
+ ]
+
+[issues]
+ # regexps of issue texts to exclude
+ exclude = [
+ ]
@@ 1,95 @@
+// Package flag implements a command-line flags parser that uses
+// struct tags to configure supported flags and returns any error
+// it encounters, without printing anything automatically.
+//
+// It uses the stdlib's flag package internally, and as such shares
+// the same behaviour regarding short and long flags.
+package flag
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "reflect"
+ "strings"
+ "time"
+)
+
+// Parse parses args into v, using struct tags to detect flags.
+// The tag must be named "flag" and multiple flags may be set
+// for the same field using a comma-separated list.
+// v must be a pointer to a struct and the flags must be defined
+// on fields with a type of string, int, bool or time.Duration.
+//
+// If v has a SetArgs method, it is called with the list
+// of non-flag arguments.
+//
+// If v has a SetFlags method, it is called with the set of
+// flags that were set by args (a map[string]bool).
+//
+// It panics if v is not a pointer to a struct or if a flag is
+// defined with an unsupported type.
+func Parse(args []string, v interface{}) error {
+ // create a FlagSet that is silent and only returns any error
+ // it encounters.
+ fs := flag.NewFlagSet("", flag.ContinueOnError)
+ fs.SetOutput(ioutil.Discard)
+ fs.Usage = nil
+
+ durationType := reflect.TypeOf(time.Duration(0))
+
+ // extract the flags from the struct
+ val := reflect.ValueOf(v).Elem()
+ str := reflect.TypeOf(v).Elem()
+ count := val.NumField()
+ for i := 0; i < count; i++ {
+ fld := val.Field(i)
+ typ := str.Field(i)
+ names := strings.Split(typ.Tag.Get("flag"), ",")
+
+ for _, nm := range names {
+ if nm == "" {
+ continue
+ }
+ switch fld.Kind() {
+ case reflect.Bool:
+ fs.BoolVar(fld.Addr().Interface().(*bool), nm, fld.Bool(), "")
+ case reflect.String:
+ fs.StringVar(fld.Addr().Interface().(*string), nm, fld.String(), "")
+ case reflect.Int:
+ fs.IntVar(fld.Addr().Interface().(*int), nm, int(fld.Int()), "")
+ default:
+ switch typ.Type {
+ case durationType:
+ fs.DurationVar(fld.Addr().Interface().(*time.Duration), nm, fld.Interface().(time.Duration), "")
+ default:
+ panic(fmt.Sprintf("unsupported flag field kind: %s (%s: %s)", fld.Kind(), typ.Name, typ.Type))
+ }
+ }
+ }
+ }
+
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+
+ if sa, ok := v.(interface{ SetArgs([]string) }); ok {
+ args := fs.Args()
+ if len(args) == 0 {
+ args = nil
+ }
+ sa.SetArgs(args)
+ }
+ if sf, ok := v.(interface{ SetFlags(map[string]bool) }); ok {
+ set := make(map[string]bool)
+ fs.Visit(func(fl *flag.Flag) {
+ set[fl.Name] = true
+ })
+ if len(set) == 0 {
+ set = nil
+ }
+ sf.SetFlags(set)
+ }
+
+ return nil
+}
@@ 1,176 @@
+package flag
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+type F struct {
+ S string `flag:"s,string,long-string"`
+ I int `flag:"i,int"`
+ B bool `flag:"b"`
+ H bool `flag:"h,help"`
+ T time.Duration `flag:"t"`
+ N int
+ args []string
+ flags map[string]bool
+}
+
+func (f *F) SetArgs(args []string) {
+ f.args = args
+}
+
+func (f *F) SetFlags(flags map[string]bool) {
+ f.flags = flags
+}
+
+func TestParseFlags(t *testing.T) {
+ cases := []struct {
+ args []string
+ want *F
+ err string
+ }{
+ {
+ want: &F{},
+ },
+ {
+ args: []string{"toto"},
+ want: &F{
+ args: []string{"toto"},
+ },
+ },
+ {
+ args: []string{"-h"},
+ want: &F{
+ H: true,
+ flags: map[string]bool{"h": true},
+ },
+ },
+ {
+ args: []string{"-i", "10", "--int", "20"},
+ want: &F{
+ I: 20,
+ flags: map[string]bool{"i": true, "int": true},
+ },
+ },
+ {
+ args: []string{"-i", "10", "--int", "20"},
+ want: &F{
+ I: 20,
+ flags: map[string]bool{"i": true, "int": true},
+ },
+ },
+ {
+ args: []string{"-s", "a", "--string", "b", "-long-string", "c"},
+ want: &F{
+ S: "c",
+ flags: map[string]bool{"s": true, "string": true, "long-string": true},
+ },
+ },
+ {
+ args: []string{"-b", "--b", "-b"},
+ want: &F{
+ B: true,
+ flags: map[string]bool{"b": true},
+ },
+ },
+ {
+ args: []string{"-b", "-int", "1", "-string", "a", "arg1", "arg2"},
+ want: &F{
+ B: true,
+ I: 1,
+ S: "a",
+ args: []string{"arg1", "arg2"},
+ flags: map[string]bool{"b": true, "int": true, "string": true},
+ },
+ },
+ {
+ args: []string{"-n", "1"},
+ want: &F{},
+ err: "not defined: -n",
+ },
+ {
+ args: []string{"-t", "3s"},
+ want: &F{
+ T: 3 * time.Second,
+ flags: map[string]bool{"t": true},
+ },
+ },
+ {
+ args: []string{"-t", "nope"},
+ want: &F{},
+ err: "invalid value",
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(strings.Join(c.args, " "), func(t *testing.T) {
+ var f F
+ err := Parse(c.args, &f)
+
+ if c.err != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), c.err)
+ return
+ }
+
+ require.NoError(t, err)
+ require.Equal(t, c.want, &f)
+ })
+ }
+}
+
+func TestParseNoFlag(t *testing.T) {
+ type F struct {
+ V int
+ }
+ f := F{V: 4}
+ err := Parse([]string{"x"}, &f)
+ require.NoError(t, err)
+ require.Equal(t, 4, f.V)
+}
+
+type noFlagSetArgs struct {
+ args []string
+}
+
+func (n *noFlagSetArgs) SetArgs(args []string) {
+ n.args = args
+}
+
+func TestParseNoFlagSetArgs(t *testing.T) {
+ f := noFlagSetArgs{}
+ err := Parse([]string{"x"}, &f)
+ require.NoError(t, err)
+ require.Equal(t, []string{"x"}, f.args)
+}
+
+func TestParseArgsError(t *testing.T) {
+ type F struct {
+ X bool `flag:"x"`
+ }
+ f := F{}
+ err := Parse([]string{"-zz"}, &f)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "-zz")
+}
+
+func TestParseNotStructPointer(t *testing.T) {
+ var i int
+ require.Panics(t, func() {
+ _ = Parse([]string{"-h"}, i)
+ })
+}
+
+func TestParseUnsupportedFlagType(t *testing.T) {
+ type F struct {
+ C *bool `flag:"c"`
+ }
+ var f F
+ require.Panics(t, func() {
+ _ = Parse([]string{"-h"}, &f)
+ })
+}