-
Notifications
You must be signed in to change notification settings - Fork 5
/
autoflags.go
138 lines (133 loc) · 4.46 KB
/
autoflags.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Package autoflags provides a convenient way of exposing struct fields as
// command line flags. Each exposed field should have a special tag attached:
//
// var config = struct {
// Name string `flag:"name,user name"`
// Age uint `flag:"age"`
// Married bool // this won't be exposed
// }{
// // default values
// Name: "John Doe",
// Age: 34,
// }
//
// After declaring your flags and their default values as above, just register
// flags with autoflags.Define and call [flag.Parse] as usual:
//
// autoflags.Define(&config)
// flag.Parse()
//
// Now config struct has its fields populated from command line flags. Call the
// program with flags to override default values:
//
// progname -name "Jane Roe" -age 29
//
// Package autoflags understands all basic types supported by the [flag] package
// xxxVar functions: int, int64, uint, uint64, float64, bool, string,
// time.Duration. Types implementing [flag.Value] interface are also supported.
// Attaching a non-empty `flag` tag to field of an unsupported type would result in
// panic.
package autoflags // import "github.com/artyom/autoflags"
import (
"errors"
"flag"
"fmt"
"reflect"
"strings"
"time"
)
var (
// errPointerWanted is returned when passed argument is not a pointer
errPointerWanted = errors.New("autoflags: pointer expected")
// errInvalidArgument is returned when passed argument is nil pointer or
// pointer to a non-struct value
errInvalidArgument = errors.New("autoflags: non-nil pointer to struct expected")
// errInvalidFlagSet is returned when FlagSet argument passed to
// DefineFlagSet is nil
errInvalidFlagSet = errors.New("autoflags: non-nil FlagSet expected")
errInvalidField = errors.New("autoflags: field is of unsupported type")
)
// Define takes pointer to a struct and declares flags for its flag-tagged fields.
// Valid tags have one of the following formats:
//
// `flag:"flagname"`
// `flag:"flagname,usage string"`
//
// Define panics if given an unsupported/invalid argument (anything but a
// non-nil pointer to a struct) or if any config attribute with `flag` tag is of
// type unsupported by the flag package (consider implementing [flag.Value]
// interface for such attributes).
func Define(config interface{}) { DefineFlagSet(flag.CommandLine, config) }
// Parse is a shortcut for:
//
// autoflags.Define(&args)
// flag.Parse()
func Parse(config interface{}) { Define(config); flag.Parse() }
// DefineFlagSet takes pointer to a struct and declares flags for its flag-tagged
// fields on a given FlagSet. Valid tags have one of the following formats:
//
// `flag:"flagname"`
// `flag:"flagname,usage string"`
//
// DefineFlagSet panics if given an unsupported/invalid config argument
// (anything but a non-nil pointer to a struct) or if any config attribute with
// `flag` tag is of type unsupported by the flag package (consider implementing
// [flag.Value] interface for such attrubutes).
func DefineFlagSet(fs *flag.FlagSet, config interface{}) {
if fs == nil {
panic(errInvalidFlagSet)
}
st := reflect.ValueOf(config)
if st.Kind() != reflect.Ptr {
panic(errPointerWanted)
}
st = reflect.Indirect(st)
if !st.IsValid() || st.Type().Kind() != reflect.Struct {
panic(errInvalidArgument)
}
flagValueType := reflect.TypeOf((*flag.Value)(nil)).Elem()
for i := 0; i < st.NumField(); i++ {
typ := st.Type().Field(i)
var name, usage string
tag := typ.Tag.Get("flag")
if tag == "" {
continue
}
val := st.Field(i)
if !val.CanAddr() {
panic(errInvalidField)
}
flagData := strings.SplitN(tag, ",", 2)
switch len(flagData) {
case 1:
name = flagData[0]
case 2:
name, usage = flagData[0], flagData[1]
}
addr := val.Addr()
if addr.Type().Implements(flagValueType) {
fs.Var(addr.Interface().(flag.Value), name, usage)
continue
}
switch d := val.Interface().(type) {
case int:
fs.IntVar(addr.Interface().(*int), name, d, usage)
case int64:
fs.Int64Var(addr.Interface().(*int64), name, d, usage)
case uint:
fs.UintVar(addr.Interface().(*uint), name, d, usage)
case uint64:
fs.Uint64Var(addr.Interface().(*uint64), name, d, usage)
case float64:
fs.Float64Var(addr.Interface().(*float64), name, d, usage)
case bool:
fs.BoolVar(addr.Interface().(*bool), name, d, usage)
case string:
fs.StringVar(addr.Interface().(*string), name, d, usage)
case time.Duration:
fs.DurationVar(addr.Interface().(*time.Duration), name, d, usage)
default:
panic(fmt.Sprintf("autoflags: field with flag tag value %q is of unsupported type", name))
}
}
}