this seeks to work

master
Bel LaPointe 2019-03-23 15:29:31 -06:00
parent 3968519d0c
commit bcf1369f28
6 changed files with 346 additions and 16 deletions

72
arg.go
View File

@ -1,6 +1,10 @@
package args package args
import "time" import (
"fmt"
"strconv"
"time"
)
type Arg struct { type Arg struct {
Env string Env string
@ -17,34 +21,90 @@ func NewArg(argType Type, key, help string, def interface{}) *Arg {
Flag: key, Flag: key,
Help: help, Help: help,
Default: def, Default: def,
Value: def,
ArgType: argType, ArgType: argType,
} }
} }
func (a *Arg) GetInt(key string) int { func (a *Arg) GetInt() int {
if a.ArgType != INT { if a.ArgType != INT {
return -1 return -1
} }
if _, ok := a.Value.(int); !ok {
return -1
}
return a.Value.(int) return a.Value.(int)
} }
func (a *Arg) GetString(key string) string { func (a *Arg) GetString() string {
if a.ArgType != STRING { if a.ArgType != STRING {
return "" return ""
} }
if _, ok := a.Value.(string); !ok {
return ""
}
return a.Value.(string) return a.Value.(string)
} }
func (a *Arg) GetBool(key string) bool { func (a *Arg) GetBool() bool {
if a.ArgType != BOOL { if a.ArgType != BOOL {
return false return false
} }
if _, ok := a.Value.(bool); !ok {
return false
}
return a.Value.(bool) return a.Value.(bool)
} }
func (a *Arg) GetDuration(key string) time.Duration { func (a *Arg) GetDuration() time.Duration {
if a.ArgType != DURATION { if a.ArgType != DURATION {
return time.Duration(0) return time.Duration(-1)
}
if _, ok := a.Value.(time.Duration); !ok {
return time.Duration(-1)
} }
return a.Value.(time.Duration) return a.Value.(time.Duration)
} }
func (a *Arg) Set(value interface{}) error {
err := fmt.Errorf("incompatible set of type %T for arg $%v/-%v type %v", value, a.Env, a.Flag, a.ArgType)
switch a.ArgType {
case INT:
i, ok := value.(int)
if ok {
a.Value = i
err = nil
} else if s, ok := value.(string); !ok {
} else if i, err = strconv.Atoi(s); err == nil {
a.Value = i
err = nil
}
case STRING:
i, ok := value.(string)
if ok {
a.Value = i
err = nil
}
case BOOL:
i, ok := value.(bool)
if ok {
a.Value = i
err = nil
} else if s, ok := value.(string); !ok {
} else if i, err = strconv.ParseBool(s); err == nil {
a.Value = i
err = nil
}
case DURATION:
i, ok := value.(time.Duration)
if ok {
a.Value = i
err = nil
} else if s, ok := value.(string); !ok {
} else if i, err = time.ParseDuration(s); err == nil {
a.Value = i
err = nil
}
}
return err
}

137
arg_test.go Normal file
View File

@ -0,0 +1,137 @@
package args
import (
"testing"
"time"
)
func TestNewArg(t *testing.T) {
NewArg(INT, "key", "help", 5)
}
func TestGets(t *testing.T) {
cases := []struct {
t Type
v interface{}
ok bool
}{
{
t: STRING,
v: 5,
ok: false,
},
{
t: DURATION,
v: 5,
ok: false,
},
{
t: BOOL,
v: 5,
ok: false,
},
{
t: INT,
v: "5",
ok: false,
},
{
t: STRING,
v: "5",
ok: true,
},
{
t: DURATION,
v: time.Duration(5),
ok: true,
},
{
t: BOOL,
v: true,
ok: true,
},
{
t: INT,
v: 5,
ok: true,
},
}
for _, c := range cases {
a := NewArg(c.t, "key", "help", c.v)
switch c.t {
case INT:
if (a.GetInt() == c.v) != c.ok {
t.Errorf("failed to get int: want %v, got %v", c.v, a.GetInt())
}
case STRING:
if (a.GetString() == c.v) != c.ok {
t.Errorf("failed to get string: want %v, got %v", c.v, a.GetString())
}
case BOOL:
if (a.GetBool() == c.v) != c.ok {
t.Errorf("failed to get bool: want %v, got %v", c.v, a.GetBool())
}
case DURATION:
if (a.GetDuration() == c.v) != c.ok {
t.Errorf("failed to get duration: want %v, got %v", c.v, a.GetDuration())
}
}
}
}
func TestSets(t *testing.T) {
cases := []struct {
t Type
v interface{}
ok bool
}{
{
t: STRING,
v: 5,
ok: false,
},
{
t: DURATION,
v: 5,
ok: false,
},
{
t: BOOL,
v: 5,
ok: false,
},
{
t: INT,
v: "a",
ok: false,
},
{
t: STRING,
v: "5",
ok: true,
},
{
t: DURATION,
v: time.Duration(5),
ok: true,
},
{
t: BOOL,
v: true,
ok: true,
},
{
t: INT,
v: 5,
ok: true,
},
}
for i, c := range cases {
a := NewArg(c.t, "key", "help", nil)
if err := a.Set(c.v); (err == nil) != c.ok {
t.Errorf("[%d] failed to set %v: want ok=%v, got %v", i, c.t, c.ok, err)
}
}
}

View File

@ -2,7 +2,9 @@ package args
import ( import (
"errors" "errors"
"flag"
"os" "os"
"time"
) )
type ArgSet struct { type ArgSet struct {
@ -25,8 +27,8 @@ func (as *ArgSet) Get(key string) *Arg {
return nil return nil
} }
for i := range as.args { for i := range as.args {
if as.args[i].Key == key { if as.args[i].Env == key || as.args[i].Flag == key {
return as[i] return as.args[i]
} }
} }
return nil return nil
@ -46,7 +48,7 @@ func (as *ArgSet) Parse() error {
if err := as.setValueFromEnv(); err != nil { if err := as.setValueFromEnv(); err != nil {
return err return err
} }
if err := as.setValueFromFlag(); err != nil { if err := as.setValueFromFlags(); err != nil {
return err return err
} }
@ -54,25 +56,58 @@ func (as *ArgSet) Parse() error {
} }
func (as *ArgSet) setValueFromDefaults() error { func (as *ArgSet) setValueFromDefaults() error {
// TODO if not casted type, return err
for i := range as.args { for i := range as.args {
args[i].Value = args[i].Default if err := as.args[i].Set(as.args[i].Default); err != nil {
return err
}
} }
return nil return nil
} }
func (as *ArgSet) setValueFromEnv() error { func (as *ArgSet) setValueFromEnv() error {
// TODO if not casted type, return err
for i := range as.args { for i := range as.args {
key := args[i].Key key := as.args[i].Env
if v, ok := os.LookupEnv(key); ok { if v, ok := os.LookupEnv(key); ok {
args[i].Value = v if err := as.args[i].Set(v); err != nil {
return err
}
} }
} }
return nil return nil
} }
func (as *ArgSet) setValueFromFlags() error { func (as *ArgSet) setValueFromFlags() error {
// TODO if not casted type, return err fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
return errors.New("not impl") for i := range as.args {
arg := as.args[i]
switch arg.ArgType {
case INT:
arg.Default = fs.Int(arg.Flag, arg.Value.(int), arg.Help)
case STRING:
arg.Default = fs.String(arg.Flag, arg.Value.(string), arg.Help)
case BOOL:
arg.Default = fs.Bool(arg.Flag, arg.Value.(bool), arg.Help)
case DURATION:
arg.Default = fs.Duration(arg.Flag, arg.Value.(time.Duration), arg.Help)
default:
return errors.New("unknown type, cannot set from flag")
}
}
if err := fs.Parse(os.Args[1:]); err != nil {
return err
}
for i := range as.args {
arg := as.args[i]
switch arg.ArgType {
case INT:
arg.Value = *arg.Default.(*int)
case STRING:
arg.Value = *arg.Default.(*string)
case BOOL:
arg.Value = *arg.Default.(*bool)
case DURATION:
arg.Value = *arg.Default.(*time.Duration)
}
}
return nil
} }

60
argset_test.go Normal file
View File

@ -0,0 +1,60 @@
package args
import (
"os"
"testing"
)
func TestNewArgSet(t *testing.T) {
NewArgSet()
}
func TestAppend(t *testing.T) {
as := NewArgSet()
as.Append(INT, "key", "help", nil)
if len(as.args) != 1 || *as.args[0] != *NewArg(INT, "key", "help", nil) {
t.Errorf("argset.Append failed: got %v, want %v", *as.args[0], *NewArg(INT, "key", "help", nil))
}
}
func TestParse(t *testing.T) {
osArgs := append([]string{}, os.Args[:]...)
defer func() {
os.Args = osArgs
}()
os.Args = []string{"nothing", "-testkeyFlag", "5"}
os.Setenv("testkeyEnv", "9")
as := NewArgSet()
as.Append(INT, "testkeyFlag", "help", 1)
as.Append(INT, "testkeyEnv", "help", 2)
as.Append(INT, "testkeyDefault", "help", 3)
if err := as.Parse(); err != nil {
t.Fatalf("cannot parse legal: %v", err)
} else if arg := as.Get("testkeyFlag"); arg == nil || arg.Value != 5 {
t.Errorf("cannot set from flag: got %v", arg)
} else if arg := as.Get("testkeyEnv"); arg == nil || arg.Value != 9 {
t.Errorf("cannot set from env: got %v", arg)
} else if arg := as.Get("testkeyDefault"); arg == nil || arg.Value != 3 {
t.Errorf("cannot set from default: got %v", arg)
}
os.Args = []string{"nothing", "-testkey5", "bad"}
as = NewArgSet()
as.Append(INT, "testkey4", "help", "4")
if err := as.Parse(); err == nil {
t.Fatal(err)
}
}
func TestGet(t *testing.T) {
as := NewArgSet()
as.Append(INT, "testkey1", "help", 1)
as.Append(INT, "testkey2", "help", 2)
as.Append(INT, "testkey3", "help", 3)
if err := as.Parse(); err != nil {
t.Fatal(err)
}
if as.Get("testkey1") == nil {
t.Errorf("cannot get parsed at %q", "testkey1")
}
}

20
type.go
View File

@ -1,5 +1,10 @@
package args package args
import (
"fmt"
"time"
)
type Type int type Type int
const ( const (
@ -8,3 +13,18 @@ const (
BOOL = Type(iota) BOOL = Type(iota)
DURATION = Type(iota) DURATION = Type(iota)
) )
func (t Type) String() string {
var i interface{} = nil
switch t {
case INT:
i = 1
case STRING:
i = ""
case BOOL:
i = false
case DURATION:
i = time.Duration(0)
}
return fmt.Sprintf("%T", i)
}

18
type_test.go Normal file
View File

@ -0,0 +1,18 @@
package args
import "testing"
func TestType(t *testing.T) {
if INT.String() != "int" {
t.Errorf("wrong string for INT")
}
if STRING.String() != "string" {
t.Errorf("wrong string for STRING")
}
if BOOL.String() != "bool" {
t.Errorf("wrong string for BOOL")
}
if DURATION.String() != "time.Duration" {
t.Errorf("wrong string for DURATION")
}
}