Skip to content

Commit efb9cde

Browse files
committed
Add support for anonymous structures
1 parent baf5eba commit efb9cde

File tree

8 files changed

+216
-83
lines changed

8 files changed

+216
-83
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ And you can use your favorite flag or cli library!
4242
- [ ] Multiple ENV names
4343
- [x] Interface for user types.
4444
- [x] [Validation](https://godoc.org/github.com/octago/sflags/validator/govalidator#New) (using [govalidator](https://github.com/asaskevich/govalidator) package)
45-
45+
- [x] Anonymous nested structure support (anonymous structures flatten by default)
4646

4747
## Supported types in structures:
4848

@@ -214,6 +214,9 @@ func EnvDivider(val string)
214214
// Validator sets validator function for flags.
215215
// Check existed validators in sflags/validator package.
216216
func Validator(val ValidateFunc)
217+
218+
// Set to false if you don't want anonymous structure fields to be flatten.
219+
func Flatten(val bool)
217220
```
218221

219222

examples/anonymous/main.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
// This packages shows how to use sflags with flag library.
4+
5+
import (
6+
"flag"
7+
"fmt"
8+
"io/ioutil"
9+
"log"
10+
"os"
11+
12+
"github.com/davecgh/go-spew/spew"
13+
"github.com/octago/sflags/gen/gflag"
14+
)
15+
16+
type httpConfig struct {
17+
Host string
18+
}
19+
20+
type config struct {
21+
httpConfig
22+
}
23+
24+
func main() {
25+
cfg := &config{
26+
httpConfig: httpConfig{
27+
Host: "127.0.0.1",
28+
},
29+
}
30+
// Use gflags.ParseToDef if you want default `flag.CommandLine`
31+
fs, err := gflag.Parse(cfg)
32+
if err != nil {
33+
log.Fatalf("err: %v", err)
34+
}
35+
fs.Init("", flag.ContinueOnError)
36+
fs.SetOutput(ioutil.Discard)
37+
// You should run fs.Parse(os.Args[1:]), but this is an example.
38+
err = fs.Parse([]string{
39+
"-http-config-host", "localhost",
40+
})
41+
if err != nil {
42+
fmt.Printf("err: %v\n", err)
43+
}
44+
fmt.Println("Usage:")
45+
fs.SetOutput(os.Stdout)
46+
fs.PrintDefaults()
47+
fmt.Printf("cfg: %s\n", spew.Sdump(cfg))
48+
}

gen/gcli/gcli_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func TestParse(t *testing.T) {
110110
{
111111
name: "Test bad cfg value",
112112
cfg: "bad config",
113-
expErr1: errors.New("cfg must be a pointer to a structure"),
113+
expErr1: errors.New("object must be a pointer to struct or interface"),
114114
},
115115
}
116116
// forbid urfave/cli to exit

gen/gflag/gflag_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TestParse(t *testing.T) {
8888
{
8989
name: "Test bad cfg value",
9090
cfg: "bad config",
91-
expErr1: errors.New("cfg must be a pointer to a structure"),
91+
expErr1: errors.New("object must be a pointer to struct or interface"),
9292
},
9393
}
9494
for _, test := range tests {

gen/gkingpin/gkingpin_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func TestParse(t *testing.T) {
111111
{
112112
name: "Test bad cfg value",
113113
cfg: "bad config",
114-
expErr1: errors.New("cfg must be a pointer to a structure"),
114+
expErr1: errors.New("object must be a pointer to struct or interface"),
115115
},
116116
}
117117
for _, test := range tests {

gen/gpflag/gpflag_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func TestParse(t *testing.T) {
114114
{
115115
name: "Test bad cfg value",
116116
cfg: "bad config",
117-
expErr1: errors.New("cfg must be a pointer to a structure"),
117+
expErr1: errors.New("object must be a pointer to struct or interface"),
118118
},
119119
}
120120
for _, test := range tests {

parser.go

Lines changed: 92 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const (
1212
defaultEnvTag = "env"
1313
defaultFlagDivider = "-"
1414
defaultEnvDivider = "_"
15+
defaultFlatten = true
1516
)
1617

1718
// ValidateFunc describes a validation func,
@@ -27,9 +28,17 @@ type opts struct {
2728
envPrefix string
2829
flagDivider string
2930
envDivider string
31+
flatten bool
3032
validator ValidateFunc
3133
}
3234

35+
func (o opts) apply(optFuncs ...OptFunc) opts {
36+
for _, optFunc := range optFuncs {
37+
optFunc(&o)
38+
}
39+
return o
40+
}
41+
3342
// OptFunc sets values in opts structure.
3443
type OptFunc func(opt *opts)
3544

@@ -56,6 +65,10 @@ func EnvDivider(val string) OptFunc { return func(opt *opts) { opt.envDivider =
5665
// Check existed validators in sflags/validator package.
5766
func Validator(val ValidateFunc) OptFunc { return func(opt *opts) { opt.validator = val } }
5867

68+
// Flatten set flatten option.
69+
// Set to false if you don't want anonymous structure fields to be flatten.
70+
func Flatten(val bool) OptFunc { return func(opt *opts) { opt.flatten = val } }
71+
5972
func copyOpts(val opts) OptFunc { return func(opt *opts) { *opt = val } }
6073

6174
func hasOption(options []string, option string) bool {
@@ -67,6 +80,16 @@ func hasOption(options []string, option string) bool {
6780
return false
6881
}
6982

83+
func defOpts() opts {
84+
return opts{
85+
descTag: defaultDescTag,
86+
flagTag: defaultFlagTag,
87+
flagDivider: defaultFlagDivider,
88+
envDivider: defaultEnvDivider,
89+
flatten: defaultFlatten,
90+
}
91+
}
92+
7093
func parseFlagTag(field reflect.StructField, opt opts) *Flag {
7194
flag := Flag{}
7295
ignoreFlagPrefix := false
@@ -134,92 +157,94 @@ func parseEnv(flagName string, field reflect.StructField, opt opts) string {
134157
}
135158

136159
// ParseStruct parses structure and returns list of flags based on this structure.
137-
// This list of flags can be used by generators for
138-
// flag, kingpin, cobra, pflag, urfave/cli.
160+
// This list of flags can be used by generators for flag, kingpin, cobra, pflag, urfave/cli.
139161
func ParseStruct(cfg interface{}, optFuncs ...OptFunc) ([]*Flag, error) {
140162
// what we want is Ptr to Structure
141-
if reflect.ValueOf(cfg).Kind() != reflect.Ptr {
142-
return nil, errors.New("cfg must be a pointer to a structure")
163+
if cfg == nil {
164+
return nil, errors.New("object cannot be nil")
143165
}
144-
cfgValue := reflect.Indirect(reflect.ValueOf(cfg))
145-
if cfgValue.Kind() != reflect.Struct {
146-
return nil, errors.New("cfg must be a pointer to a structure")
166+
v := reflect.ValueOf(cfg)
167+
if v.Kind() != reflect.Ptr {
168+
return nil, errors.New("object must be a pointer to struct or interface")
147169
}
148-
opt := opts{
149-
descTag: defaultDescTag,
150-
flagTag: defaultFlagTag,
151-
flagDivider: defaultFlagDivider,
152-
envDivider: defaultEnvDivider,
170+
if v.IsNil() {
171+
return nil, errors.New("object cannot be nil")
153172
}
154-
for _, optFunc := range optFuncs {
155-
optFunc(&opt)
173+
switch e := v.Elem(); e.Kind() {
174+
case reflect.Struct:
175+
return parseStruct(e, optFuncs...), nil
176+
default:
177+
return nil, errors.New("object must be a pointer to struct or interface")
156178
}
179+
}
180+
181+
func parseVal(value reflect.Value, optFuncs ...OptFunc) ([]*Flag, Value) {
182+
// value is addressable, let's check if we can parse it
183+
if value.CanAddr() && value.Addr().CanInterface() {
184+
valueInterface := value.Addr().Interface()
185+
val := parseGenerated(valueInterface)
186+
if val != nil {
187+
return nil, val
188+
}
189+
// check if field implements Value interface
190+
if val, casted := valueInterface.(Value); casted {
191+
return nil, val
192+
}
193+
}
194+
195+
switch value.Kind() {
196+
case reflect.Ptr:
197+
if value.IsNil() {
198+
value.Set(reflect.New(value.Type().Elem()))
199+
}
200+
val := parseGeneratedPtrs(value.Addr().Interface())
201+
if val != nil {
202+
return nil, val
203+
}
204+
return parseVal(value.Elem(), optFuncs...)
205+
case reflect.Struct:
206+
flags := parseStruct(value, optFuncs...)
207+
return flags, nil
208+
}
209+
return nil, nil
210+
}
211+
212+
func parseStruct(value reflect.Value, optFuncs ...OptFunc) []*Flag {
213+
opt := defOpts().apply(optFuncs...)
157214

158215
flags := []*Flag{}
159216

160-
cfgType := cfgValue.Type()
217+
valueType := value.Type()
161218
fields:
162-
for i := 0; i < cfgType.NumField(); i++ {
163-
field := cfgType.Field(i)
164-
fieldValue := cfgValue.FieldByName(field.Name)
219+
for i := 0; i < value.NumField(); i++ {
220+
field := valueType.Field(i)
221+
fieldValue := value.Field(i)
222+
// skip unexported and non anonymous fields
223+
if field.PkgPath != "" && !field.Anonymous {
224+
continue fields
225+
}
165226

166227
flag := parseFlagTag(field, opt)
167228
if flag == nil {
168229
continue fields
169230
}
170231
flag.EnvName = parseEnv(flag.Name, field, opt)
171-
172232
flag.Usage = field.Tag.Get(opt.descTag)
173-
174-
if !(fieldValue.CanAddr() && fieldValue.Addr().CanInterface()) {
175-
continue fields
233+
prefix := flag.Name + opt.flagDivider
234+
if field.Anonymous && opt.flatten {
235+
prefix = opt.prefix
176236
}
177-
178-
fieldValueAddr := fieldValue.Addr().Interface()
179-
kind := fieldValue.Kind()
180-
181-
// if field is Ptr but it's nil then create new value for it.
182-
if kind == reflect.Ptr {
183-
if fieldValue.IsNil() {
184-
fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
185-
}
186-
}
187-
// check if field has required pointer type (**regex.Regexp f.e)
188-
if val := parseGeneratedPtrs(fieldValueAddr); val != nil {
189-
if opt.validator != nil {
190-
val = &validateValue{
191-
Value: val,
192-
validateFunc: func(val string) error {
193-
return opt.validator(val, field, cfg)
194-
},
195-
}
196-
}
197-
flag.Value = val
198-
flag.DefValue = val.String()
199-
flags = append(flags, flag)
200-
continue fields
201-
}
202-
// check if field is pointer to a value.
203-
if kind == reflect.Ptr {
204-
kind = fieldValue.Type().Elem().Kind()
205-
fieldValueAddr = fieldValue.Interface()
206-
}
207-
var val Value
208-
// check if field implements Value interface
209-
if fieldIsVal, casted := fieldValueAddr.(Value); casted {
210-
val = fieldIsVal
211-
}
212-
// check if field is from generated types
213-
if val == nil {
214-
val = parseGenerated(fieldValueAddr)
215-
}
216-
237+
nestedFlags, val := parseVal(fieldValue,
238+
copyOpts(opt),
239+
Prefix(prefix),
240+
)
241+
// field contains a simple value.
217242
if val != nil {
218243
if opt.validator != nil {
219244
val = &validateValue{
220245
Value: val,
221246
validateFunc: func(val string) error {
222-
return opt.validator(val, field, cfg)
247+
return opt.validator(val, field, value.Interface())
223248
},
224249
}
225250
}
@@ -228,21 +253,12 @@ fields:
228253
flags = append(flags, flag)
229254
continue fields
230255
}
231-
232-
// field is a nested structure
233-
switch kind {
234-
case reflect.Struct:
235-
subFlags, err := ParseStruct(fieldValueAddr,
236-
copyOpts(opt),
237-
Prefix(flag.Name+opt.flagDivider),
238-
)
239-
if err != nil {
240-
return nil, err
241-
}
242-
flags = append(flags, subFlags...)
256+
// field is a structure
257+
if len(nestedFlags) > 0 {
258+
flags = append(flags, nestedFlags...)
243259
continue fields
244260
}
245261

246262
}
247-
return flags, nil
263+
return flags
248264
}

0 commit comments

Comments
 (0)