mirror of
https://github.com/Luzifer/rconfig.git
synced 2024-12-30 08:01:19 +00:00
Add support for time.Time flags
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
913d4f1e88
commit
f4e07b507a
2 changed files with 148 additions and 13 deletions
110
config.go
110
config.go
|
@ -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
51
time_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue