1
0
mirror of https://github.com/Luzifer/rconfig.git synced 2024-09-19 17:03:00 +00:00

Add support for time.Time flags

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-09-17 23:21:36 +02:00
parent 913d4f1e88
commit f4e07b507a
Signed by: luzifer
GPG Key ID: DC2729FDD34BE99E
2 changed files with 148 additions and 13 deletions

110
config.go
View File

@ -16,10 +16,25 @@ import (
validator "gopkg.in/validator.v2" validator "gopkg.in/validator.v2"
) )
type afterFunc func() error
var ( var (
autoEnv bool autoEnv bool
fs *pflag.FlagSet fs *pflag.FlagSet
variableDefaults map[string]string variableDefaults map[string]string
timeParserFormats = []string{
// Default constants
time.RFC3339Nano, time.RFC3339,
time.RFC1123Z, time.RFC1123,
time.RFC822Z, time.RFC822,
time.RFC850, time.RubyDate, time.UnixDate, time.ANSIC,
"2006-01-02 15:04:05.999999999 -0700 MST",
// More uncommon time formats
"2006-01-02 15:04:05", "2006-01-02 15:04:05Z07:00", // Simplified ISO time format
"01/02/2006 15:04:05", "01/02/2006 15:04:05Z07:00", // US time format
"02.01.2006 15:04:05", "02.01.2006 15:04:05Z07:00", // DE time format
}
) )
func init() { func init() {
@ -61,6 +76,11 @@ func Args() []string {
return fs.Args() return fs.Args()
} }
// AddTimeParserFormats adds custom formats to parse time.Time fields
func AddTimeParserFormats(f ...string) {
timeParserFormats = append(timeParserFormats, f...)
}
// AutoEnv enables or disables automated env variable guessing. If no `env` struct // AutoEnv enables or disables automated env variable guessing. If no `env` struct
// tag was set and AutoEnv is enabled the env variable name is derived from the // tag was set and AutoEnv is enabled the env variable name is derived from the
// name of the field: `MyFieldName` will get `MY_FIELD_NAME` // name of the field: `MyFieldName` will get `MY_FIELD_NAME`
@ -97,22 +117,37 @@ func parse(in interface{}, args []string) error {
} }
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
if err := execTags(in, fs); err != nil { afterFuncs, err := execTags(in, fs)
if err != nil {
return err return err
} }
return fs.Parse(args) if err := fs.Parse(args); err != nil {
return err
}
if afterFuncs != nil {
for _, f := range afterFuncs {
if err := f(); err != nil {
return err
}
}
}
return nil
} }
func execTags(in interface{}, fs *pflag.FlagSet) error { func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
if reflect.TypeOf(in).Kind() != reflect.Ptr { if reflect.TypeOf(in).Kind() != reflect.Ptr {
return errors.New("Calling parser with non-pointer") return nil, errors.New("Calling parser with non-pointer")
} }
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct { if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
return errors.New("Calling parser with pointer to non-struct") return nil, errors.New("Calling parser with pointer to non-struct")
} }
afterFuncs := []afterFunc{}
st := reflect.ValueOf(in).Elem() st := reflect.ValueOf(in).Elem()
for i := 0; i < st.NumField(); i++ { for i := 0; i < st.NumField(); i++ {
valField := st.Field(i) valField := st.Field(i)
@ -134,7 +169,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
v = time.Duration(0) v = time.Duration(0)
} else { } else {
return err return nil, err
} }
} }
@ -148,6 +183,53 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
valField.Set(reflect.ValueOf(v)) valField.Set(reflect.ValueOf(v))
} }
continue continue
case reflect.TypeOf(time.Time{}):
var sVar string
if typeField.Tag.Get("flag") != "" {
if len(parts) == 1 {
fs.StringVar(&sVar, parts[0], value, typeField.Tag.Get("description"))
} else {
fs.StringVarP(&sVar, parts[0], parts[1], value, typeField.Tag.Get("description"))
}
} else {
sVar = value
}
afterFuncs = append(afterFuncs, func(valField reflect.Value, sVar *string) func() error {
return func() error {
if *sVar == "" {
// No time, no problem
return nil
}
// Check whether we could have a timestamp
if ts, err := strconv.ParseInt(*sVar, 10, 64); err == nil {
t := time.Unix(ts, 0)
valField.Set(reflect.ValueOf(t))
return nil
}
// We haven't so lets walk through possible time formats
matched := false
for _, tf := range timeParserFormats {
if t, err := time.Parse(tf, *sVar); err == nil {
matched = true
valField.Set(reflect.ValueOf(t))
return nil
}
}
if !matched {
return fmt.Errorf("Value %q did not match expected time formats", *sVar)
}
return nil
}
}(valField, &sVar))
continue
} }
switch typeField.Type.Kind() { switch typeField.Type.Kind() {
@ -180,7 +262,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
vt = 0 vt = 0
} else { } else {
return err return nil, err
} }
} }
if typeField.Tag.Get("flag") != "" { if typeField.Tag.Get("flag") != "" {
@ -195,7 +277,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
vt = 0 vt = 0
} else { } else {
return err return nil, err
} }
} }
if typeField.Tag.Get("flag") != "" { if typeField.Tag.Get("flag") != "" {
@ -210,7 +292,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" { if value == "" {
vt = 0.0 vt = 0.0
} else { } else {
return err return nil, err
} }
} }
if typeField.Tag.Get("flag") != "" { if typeField.Tag.Get("flag") != "" {
@ -220,9 +302,11 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
} }
case reflect.Struct: case reflect.Struct:
if err := execTags(valField.Addr().Interface(), fs); err != nil { afs, err := execTags(valField.Addr().Interface(), fs)
return err if err != nil {
return nil, err
} }
afterFuncs = append(afterFuncs, afs...)
case reflect.Slice: case reflect.Slice:
switch typeField.Type.Elem().Kind() { switch typeField.Type.Elem().Kind() {
@ -231,7 +315,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
for _, v := range strings.Split(value, ",") { for _, v := range strings.Split(value, ",") {
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64) it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
if err != nil { if err != nil {
return err return nil, err
} }
def = append(def, int(it)) def = append(def, int(it))
} }
@ -258,7 +342,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
} }
} }
return nil return afterFuncs, nil
} }
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) { func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {

51
time_test.go Normal file
View File

@ -0,0 +1,51 @@
package rconfig
import (
"fmt"
"testing"
"time"
)
func TestParseTime(t *testing.T) {
type ts struct {
Test time.Time `flag:"time"`
TestS time.Time `flag:"other-time,o"`
TestDef time.Time `default:"2006-01-02T15:04:05.999999999Z"`
TestDE time.Time `default:"18.09.2018 20:25:31"`
}
var (
err error
args []string
cfg ts
)
for _, tf := range timeParserFormats {
expect := time.Now().Format(tf)
cfg = ts{}
args = []string{
fmt.Sprintf("--time=%s", expect),
"-o", expect,
}
if err = parse(&cfg, args); err != nil {
t.Fatalf("Time format %q did not parse: %s", tf, err)
}
for name, ti := range map[string]time.Time{
"Long flag": cfg.Test,
"Short flag": cfg.TestS,
"Default flag": cfg.TestDef,
"DE flag": cfg.TestDE,
} {
if ti.IsZero() {
t.Errorf("%s did parse to zero with format %q", name, tf)
}
}
if e := cfg.Test.Format(tf); e != expect {
t.Errorf("Parsed time %q did not match expectation %q", e, expect)
}
}
}