From 899294dfd3f0deafc01364a00ab85f04c47a63b3 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 30 Jun 2023 00:43:01 +0800 Subject: [PATCH] :sparkles: feat: cflag - add new ext flag var types: IntVar, StrVar - IntVar, StrVar can be with value check func - add more unit test cases for other ext type --- cflag/cflag.go | 2 +- cflag/ext.go | 227 +++++++++++++++++++++++++++++++++------------ cflag/ext_test.go | 88 +++++++++++++++++- cflag/util_test.go | 6 ++ comdef/comdef.go | 6 ++ 5 files changed, 263 insertions(+), 66 deletions(-) diff --git a/cflag/cflag.go b/cflag/cflag.go index 954508f45..945580110 100644 --- a/cflag/cflag.go +++ b/cflag/cflag.go @@ -222,7 +222,7 @@ func (c *CFlags) MustParse(args []string) { } } -// Parse flags and run command +// Parse flags and run command func // // If args is nil, will parse os.Args func (c *CFlags) Parse(args []string) error { diff --git a/cflag/ext.go b/cflag/ext.go index bcba84b22..14ea10e11 100644 --- a/cflag/ext.go +++ b/cflag/ext.go @@ -20,23 +20,160 @@ type RepeatableFlag interface { IsRepeatable() bool } +// LimitInt limit int value range +func LimitInt(min, max int) comdef.IntCheckFunc { + return func(val int) error { + if val < min || val > max { + return fmt.Errorf("the value must be between %d and %d", min, max) + } + return nil + } +} + /************************************************************************* * options: some special flag vars * - implemented flag.Value interface *************************************************************************/ -// IntValue int, allow limit min and max value TODO -type IntValue struct { - val string //lint:ignore U1000 is TODO - // validate - Min, Max int +// IntVar int value can with a check func +// +// Limit min and max value: +// +// iv := cflag.IntValue{CheckFn: cflag.LimitInt(1, 10)} +// fs.IntVar(&iv, "int", 1, "the int value") +type IntVar struct { + val int + str string + // check func + CheckFn comdef.IntCheckFunc +} + +// NewIntVar create a new IntVar instance with check func +func NewIntVar(checkFn comdef.IntCheckFunc) IntVar { + return IntVar{CheckFn: checkFn} +} + +// Get value string +func (o *IntVar) Get() any { + return o.str +} + +// Set new value +func (o *IntVar) Set(value string) error { + intVal, err := strconv.Atoi(value) + if err != nil { + return err + } + + if o.CheckFn != nil { + if err = o.CheckFn(intVal); err != nil { + return err + } + } + + o.str = value + o.val = intVal + return nil +} + +// String value get +func (o *IntVar) String() string { + return o.str +} + +// String a special string +// +// Usage: +// +// // case 1: +// var names gcli.String +// c.VarOpt(&names, "names", "", "multi name by comma split") +// +// --names "tom,john,joy" +// names.Split(",") -> []string{"tom","john","joy"} +// +// // case 2: +// var ids gcli.String +// c.VarOpt(&ids, "ids", "", "multi id by comma split") +// +// --names "23,34,56" +// names.Ints(",") -> []int{23,34,56} +type String string + +// Get value +func (s *String) Get() any { + return s +} + +// Set value +func (s *String) Set(val string) error { + *s = String(val) + return nil +} + +// String input value to string +func (s *String) String() string { + return string(*s) +} + +// Strings split value to []string by sep ',' +func (s *String) Strings() []string { + return strutil.Split(string(*s), ",") +} + +// Split value to []string +func (s *String) Split(sep string) []string { + return strutil.Split(string(*s), sep) +} + +// Ints value to []int +func (s *String) Ints(sep string) []int { + return strutil.Ints(string(*s), sep) +} + +// StrVar string value can with a check func +// +// Usage: +// +// sv := cflag.StrVar{} +// fs.Var(&sv, "str", "the string value") +type StrVar struct { + val string + // check func + CheckFn comdef.StrCheckFunc +} + +// NewStrVar create a new StrVar with check func +func NewStrVar(checkFn comdef.StrCheckFunc) StrVar { + return StrVar{CheckFn: checkFn} +} + +// Get value string +func (o *StrVar) Get() any { + return o.val +} + +// Set new value +func (o *StrVar) Set(value string) error { + if o.CheckFn != nil { + if err := o.CheckFn(value); err != nil { + return err + } + } + + o.val = value + return nil +} + +// String value get +func (o *StrVar) String() string { + return o.val } // IntsString The ints-string flag. eg: --get 1,2,3 // // Implemented the flag.Value interface type IntsString struct { - val string //nolint:unused ints []int // value and size validate ValueFn func(val int) error @@ -45,7 +182,7 @@ type IntsString struct { // String input value to string func (o *IntsString) String() string { - return o.val + return arrutil.IntsToString(o.ints) } // Get value @@ -58,7 +195,7 @@ func (o *IntsString) Ints() []int { return o.ints } -// Set new value +// Set new value. eg: "12" func (o *IntsString) Set(value string) error { intVal, err := strconv.Atoi(value) if err != nil { @@ -90,6 +227,11 @@ func (s *Ints) String() string { return fmt.Sprintf("%v", *s) } +// Get value +func (s *Ints) Get() any { + return *s +} + // Set new value func (s *Ints) Set(value string) error { intVal, err := strconv.Atoi(value) @@ -99,6 +241,11 @@ func (s *Ints) Set(value string) error { return err } +// Ints value +func (s *Ints) Ints() []int { + return *s +} + // IsRepeatable on input func (s *Ints) IsRepeatable() bool { return true @@ -114,6 +261,11 @@ func (s *Strings) String() string { return strings.Join(*s, ",") } +// Get value +func (s *Strings) Get() any { + return s +} + // Set new value func (s *Strings) Set(value string) error { *s = append(*s, value) @@ -148,8 +300,13 @@ func (s *Booleans) IsRepeatable() bool { return true } -// EnumString The string flag list. +// EnumString limit input value is in the enum list. // implemented flag.Value interface +// +// Usage: +// +// var enumStr = cflag.NewEnumString("php", "go", "java") +// c.VarOpt(&enumStr, "lang", "", "input language name") type EnumString struct { val string enum []string @@ -177,7 +334,7 @@ func (s *EnumString) SetEnum(enum []string) { // WithEnum values func (s *EnumString) WithEnum(enum []string) *EnumString { - s.enum = enum + s.SetEnum(enum) return s } @@ -201,56 +358,6 @@ func (s *EnumString) Enum() []string { return s.enum } -// String a special string -// -// Usage: -// -// // case 1: -// var names gcli.String -// c.VarOpt(&names, "names", "", "multi name by comma split") -// -// --names "tom,john,joy" -// names.Split(",") -> []string{"tom","john","joy"} -// -// // case 2: -// var ids gcli.String -// c.VarOpt(&ids, "ids", "", "multi id by comma split") -// -// --names "23,34,56" -// names.Ints(",") -> []int{23,34,56} -type String string - -// Get value -func (s *String) Get() any { - return s -} - -// Set value -func (s *String) Set(val string) error { - *s = String(val) - return nil -} - -// String input value to string -func (s *String) String() string { - return string(*s) -} - -// Strings split value to []string by sep ',' -func (s *String) Strings() []string { - return strutil.Split(string(*s), ",") -} - -// Split value to []string -func (s *String) Split(sep string) []string { - return strutil.Split(string(*s), sep) -} - -// Ints value to []int -func (s *String) Ints(sep string) []int { - return strutil.Ints(string(*s), sep) -} - // KVString The kv-string flag, allow input multi. // // Implemented the flag.Value interface. diff --git a/cflag/ext_test.go b/cflag/ext_test.go index bb880b901..050a510ae 100644 --- a/cflag/ext_test.go +++ b/cflag/ext_test.go @@ -1,23 +1,65 @@ package cflag_test import ( + "errors" "testing" "github.com/gookit/goutil/cflag" "github.com/gookit/goutil/testutil/assert" ) -func TestEnumString_Set(t *testing.T) { - es := cflag.EnumString{} - es.SetEnum([]string{"php", "go"}) +func TestIntVar_methods(t *testing.T) { + iv := cflag.NewIntVar(cflag.LimitInt(1, 10)) + assert.Eq(t, 0, iv.Get()) + assert.NoErr(t, iv.Set("1")) + assert.Eq(t, 1, iv.Get()) + assert.Eq(t, "1", iv.String()) + + assert.Err(t, iv.Set("no-int")) + assert.Err(t, iv.Set("11")) +} + +func TestString_methods(t *testing.T) { + var s cflag.String + + assert.Eq(t, "", s.Get()) + assert.NoErr(t, s.Set("val")) + assert.Eq(t, "val", s.Get()) + assert.Eq(t, "val", s.String()) +} + +func TestStrVar_methods(t *testing.T) { + sv := cflag.StrVar{} + + assert.Eq(t, "", sv.Get()) + assert.NoErr(t, sv.Set("val")) + assert.Eq(t, "val", sv.Get()) + assert.Eq(t, "val", sv.String()) + + sv = cflag.NewStrVar(func(val string) error { + if val == "no" { + return errors.New("invalid value") + } + return nil + }) + assert.Err(t, sv.Set("no")) +} + +func TestEnumString_methods(t *testing.T) { + es := cflag.NewEnumString() + es.WithEnum([]string{"php", "go"}) + + assert.NotEmpty(t, es.Enum()) assert.Err(t, es.Set("no-match")) assert.NoErr(t, es.Set("go")) assert.Eq(t, "go", es.String()) + assert.Eq(t, "go", es.Get()) + assert.Eq(t, "php,go", es.EnumString()) } -func TestConfString_Set(t *testing.T) { +func TestConfString_methods(t *testing.T) { cs := cflag.ConfString{} cs.SetData(map[string]string{"key": "val"}) @@ -36,7 +78,7 @@ func TestConfString_Set(t *testing.T) { assert.Eq(t, 123, cs.Int("age")) } -func TestKVString_Set(t *testing.T) { +func TestKVString_methods(t *testing.T) { kv := cflag.NewKVString() assert.Empty(t, kv.Data()) @@ -51,3 +93,39 @@ func TestKVString_Set(t *testing.T) { assert.NoErr(t, kv.Set("name=inhere")) assert.Eq(t, "inhere", kv.Str("name")) } + +func TestInts_methods(t *testing.T) { + its := cflag.Ints{} + + assert.NoErr(t, its.Set("23")) + assert.NotEmpty(t, its.Get()) + assert.Eq(t, []int{23}, its.Ints()) + assert.Eq(t, "[23]", its.String()) +} + +func TestIntsString_methods(t *testing.T) { + its := cflag.IntsString{} + + assert.NoErr(t, its.Set("23")) + assert.Eq(t, []int{23}, its.Ints()) + assert.Eq(t, []int{23}, its.Get()) + + assert.NoErr(t, its.Set("34")) + assert.Eq(t, "[23,34]", its.String()) + + its.ValueFn = func(val int) error { + if val < 10 { + return errors.New("invalid value") + } + return nil + } + assert.Err(t, its.Set("3")) + + its.SizeFn = func(ln int) error { + if ln > 2 { + return errors.New("too many values") + } + return nil + } + assert.Err(t, its.Set("45")) +} diff --git a/cflag/util_test.go b/cflag/util_test.go index bb2b39f2b..a1b5447dc 100644 --- a/cflag/util_test.go +++ b/cflag/util_test.go @@ -20,6 +20,12 @@ func TestAddPrefix(t *testing.T) { func TestIsFlagHelpErr(t *testing.T) { assert.False(t, cflag.IsFlagHelpErr(nil)) assert.True(t, cflag.IsFlagHelpErr(flag.ErrHelp)) + + // IsGoodName + assert.True(t, cflag.IsGoodName("name")) + + // WrapColorForCode + assert.Eq(t, "hello keywords", cflag.WrapColorForCode("hello `keywords`")) } func TestSplitShortcut(t *testing.T) { diff --git a/comdef/comdef.go b/comdef/comdef.go index 56e663062..24a5275af 100644 --- a/comdef/comdef.go +++ b/comdef/comdef.go @@ -77,3 +77,9 @@ type StringHandleFunc func(s string) string func (fn StringHandleFunc) Handle(s string) string { return fn(s) } + +// IntCheckFunc check func +type IntCheckFunc func(val int) error + +// StrCheckFunc check func +type StrCheckFunc func(val string) error