-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A generic mechanism for supporting new types (#23)
- Loading branch information
1 parent
9de0c49
commit 4ff212d
Showing
9 changed files
with
147 additions
and
242 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package flagsfiller | ||
|
||
/* | ||
The code in this file could be opened up in future if more complex implementation is needed | ||
*/ | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
// this is a list of addtional supported types(include struct), like time.Time, that walkFields() won't walk into, | ||
// the key is the is string returned by the getTypeName(<type>), | ||
// each supported type need to be added in this map in init() | ||
var extendedTypes = make(map[string]handlerFunc) | ||
|
||
type handlerFunc func(tag reflect.StructTag, fieldRef interface{}, | ||
hasDefaultTag bool, tagDefault string, | ||
flagSet *flag.FlagSet, renamed string, | ||
usage string, aliases string) error | ||
|
||
type flagVal[T any] interface { | ||
flag.Value | ||
StrConverter(string) (T, error) | ||
SetRef(*T) | ||
} | ||
|
||
func processGeneral[T any](fieldRef interface{}, val flagVal[T], | ||
hasDefaultTag bool, tagDefault string, | ||
flagSet *flag.FlagSet, renamed string, | ||
usage string, aliases string) (err error) { | ||
|
||
casted := fieldRef.(*T) | ||
if hasDefaultTag { | ||
*casted, err = val.StrConverter(tagDefault) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse default into %T: %w", *new(T), err) | ||
} | ||
} | ||
val.SetRef(casted) | ||
flagSet.Var(val, renamed, usage) | ||
if aliases != "" { | ||
for _, alias := range strings.Split(aliases, ",") { | ||
flagSet.Var(val, alias, usage) | ||
} | ||
} | ||
return nil | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
module github.com/itzg/go-flagsfiller | ||
|
||
go 1.16 | ||
go 1.19 | ||
|
||
require ( | ||
github.com/iancoleman/strcase v0.2.0 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,123 +1,33 @@ | ||
package flagsfiller | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"net" | ||
"strings" | ||
"reflect" | ||
) | ||
|
||
func init() { | ||
supportedStructList["net.IPNet"] = struct{}{} | ||
RegisterSimpleType(ipConverter) | ||
RegisterSimpleType(ipnetConverter) | ||
RegisterSimpleType(macConverter) | ||
} | ||
|
||
type ipValue struct { | ||
addr *net.IP | ||
} | ||
|
||
func (v *ipValue) String() string { | ||
if v.addr == nil { | ||
return fmt.Sprint(nil) | ||
} | ||
return v.addr.String() | ||
} | ||
|
||
func (v *ipValue) Set(s string) error { | ||
*v.addr = net.ParseIP(s) | ||
if *v.addr == nil { | ||
return fmt.Errorf("invalid ip addr %v", s) | ||
} | ||
return nil | ||
} | ||
|
||
func (f *FlagSetFiller) processIP(fieldRef interface{}, hasDefaultTag bool, tagDefault string, flagSet *flag.FlagSet, renamed string, usage string, aliases string) (err error) { | ||
casted, ok := fieldRef.(*net.IP) | ||
if !ok { | ||
return f.processCustom( | ||
fieldRef, | ||
func(s string) (interface{}, error) { | ||
value := net.ParseIP(s) | ||
if value == nil { | ||
return nil, fmt.Errorf("invalid IP address %s", s) | ||
} | ||
return value, nil | ||
}, | ||
hasDefaultTag, | ||
tagDefault, | ||
flagSet, | ||
renamed, | ||
usage, | ||
aliases, | ||
) | ||
} | ||
|
||
if hasDefaultTag { | ||
*casted = net.ParseIP(tagDefault) | ||
if *casted == nil { | ||
return fmt.Errorf("failed to parse default into net.IP: %s", tagDefault) | ||
} | ||
func ipConverter(s string, tag reflect.StructTag) (net.IP, error) { | ||
addr := net.ParseIP(s) | ||
if addr == nil { | ||
return nil, fmt.Errorf("%s is not a valid IP address", s) | ||
} | ||
flagSet.Var(&ipValue{casted}, renamed, usage) | ||
if aliases != "" { | ||
for _, alias := range strings.Split(aliases, ",") { | ||
flagSet.Var(&ipValue{casted}, alias, usage) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
type ipnetValue struct { | ||
prefix *net.IPNet | ||
} | ||
|
||
func (v *ipnetValue) String() string { | ||
if v.prefix == nil { | ||
return fmt.Sprint(nil) | ||
} | ||
return v.prefix.String() | ||
return addr, nil | ||
} | ||
|
||
func (v *ipnetValue) Set(s string) error { | ||
_, pr, err := net.ParseCIDR(s) | ||
func ipnetConverter(s string, tag reflect.StructTag) (net.IPNet, error) { | ||
_, prefix, err := net.ParseCIDR(s) | ||
if err != nil { | ||
return fmt.Errorf("invalid ip prefix %v", s) | ||
return net.IPNet{}, err | ||
} | ||
*v.prefix = *pr | ||
return nil | ||
return *prefix, nil | ||
} | ||
|
||
func (f *FlagSetFiller) processIPNet(fieldRef interface{}, hasDefaultTag bool, tagDefault string, flagSet *flag.FlagSet, renamed string, usage string, aliases string) (err error) { | ||
casted, ok := fieldRef.(*net.IPNet) | ||
if !ok { | ||
return f.processCustom( | ||
fieldRef, | ||
func(s string) (interface{}, error) { | ||
_, value, err := net.ParseCIDR(s) | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid IP prefix %s, %w", s, err) | ||
} | ||
return *value, nil | ||
}, | ||
hasDefaultTag, | ||
tagDefault, | ||
flagSet, | ||
renamed, | ||
usage, | ||
aliases, | ||
) | ||
} | ||
|
||
if hasDefaultTag { | ||
_, casted, err = net.ParseCIDR(tagDefault) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse default into net.IPNet: %s, %w", tagDefault, err) | ||
} | ||
} | ||
flagSet.Var(&ipnetValue{casted}, renamed, usage) | ||
if aliases != "" { | ||
for _, alias := range strings.Split(aliases, ",") { | ||
flagSet.Var(&ipnetValue{casted}, alias, usage) | ||
} | ||
} | ||
return nil | ||
func macConverter(s string, tag reflect.StructTag) (net.HardwareAddr, error) { | ||
return net.ParseMAC(s) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package flagsfiller | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"reflect" | ||
) | ||
|
||
// RegisterSimpleType register a new type, | ||
// should be called in init(), | ||
// see time.go and net.go for implementation examples | ||
func RegisterSimpleType[T any](c ConvertFunc[T]) { | ||
base := simpleType[T]{converter: c} | ||
extendedTypes[getTypeName(reflect.TypeOf(*new(T)))] = base.Process | ||
} | ||
|
||
// ConvertFunc is a function convert string s into a specific type T, the tag is the struct field tag, as addtional input. | ||
// see time.go and net.go for implementation examples | ||
type ConvertFunc[T any] func(s string, tag reflect.StructTag) (T, error) | ||
|
||
type simpleType[T any] struct { | ||
val *T | ||
tags reflect.StructTag | ||
converter ConvertFunc[T] | ||
} | ||
|
||
func newSimpleType[T any](c ConvertFunc[T], tag reflect.StructTag) simpleType[T] { | ||
return simpleType[T]{val: new(T), converter: c, tags: tag} | ||
} | ||
|
||
func (v *simpleType[T]) String() string { | ||
if v.val == nil { | ||
return fmt.Sprint(nil) | ||
} | ||
return fmt.Sprintf("%v", *v.val) | ||
} | ||
|
||
func (v *simpleType[T]) StrConverter(s string) (T, error) { | ||
return v.converter(s, v.tags) | ||
} | ||
|
||
func (v *simpleType[T]) Set(s string) error { | ||
var err error | ||
*v.val, err = v.converter(s, v.tags) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse %s into %T, %w", s, *(new(T)), err) | ||
} | ||
return nil | ||
} | ||
|
||
func (v *simpleType[T]) SetRef(t *T) { | ||
v.val = t | ||
} | ||
|
||
func (v *simpleType[T]) Process(tag reflect.StructTag, fieldRef interface{}, | ||
hasDefaultTag bool, tagDefault string, | ||
flagSet *flag.FlagSet, renamed string, | ||
usage string, aliases string) error { | ||
val := newSimpleType(v.converter, tag) | ||
return processGeneral[T](fieldRef, &val, hasDefaultTag, tagDefault, flagSet, renamed, usage, aliases) | ||
} |
Oops, something went wrong.