diff --git a/changelog.adoc b/changelog.adoc index 4093643..475a684 100644 --- a/changelog.adoc +++ b/changelog.adoc @@ -8,6 +8,8 @@ Tested with Go 1.16, 1.17, 1.18, 1.19, 1.20 and 1.21. === New Features +* Add `opt.SetValue` to allow setting the value of an option. + * Add `opt.SuggestedValues` ModifyFn to allow setting autocompletion suggestions for an option. + Works just like the existing `opt.ValidValues` but it doesn't error out if the value is not in the list of suggestions. diff --git a/errors.go b/errors.go index f29f288..4614fa5 100644 --- a/errors.go +++ b/errors.go @@ -18,3 +18,6 @@ var ErrorHelpCalled = fmt.Errorf("help called") // ErrorParsing - Indicates that there was an error with cli args parsing var ErrorParsing = errors.New("") + +// ErrorNotFound - Generic not found error +var ErrorNotFound = fmt.Errorf("not found") diff --git a/internal/option/option.go b/internal/option/option.go index 45d0a8d..6986953 100644 --- a/internal/option/option.go +++ b/internal/option/option.go @@ -472,12 +472,14 @@ func (opt *Option) Save(a ...string) error { opt.SetFloat64Slice(append(*opt.pFloat64S, ff...)) return nil case StringMapType: - keyValue := strings.Split(a[0], "=") - if len(keyValue) < 2 { - // TODO: Create error type for use in tests with errors.Is - return fmt.Errorf(text.ErrorArgumentIsNotKeyValue, opt.UsedAlias) + for _, e := range a { + keyValue := strings.Split(e, "=") + if len(keyValue) < 2 { + // TODO: Create error type for use in tests with errors.Is + return fmt.Errorf(text.ErrorArgumentIsNotKeyValue, opt.UsedAlias) + } + opt.SetKeyValueToStringMap(keyValue[0], keyValue[1]) } - opt.SetKeyValueToStringMap(keyValue[0], keyValue[1]) return nil case IncrementType: opt.SetInt(opt.Int() + 1) diff --git a/public_api_test.go b/public_api_test.go index 640a3a0..8792e78 100644 --- a/public_api_test.go +++ b/public_api_test.go @@ -16,6 +16,7 @@ import ( "os" "path/filepath" "reflect" + "strings" "testing" "time" @@ -4148,3 +4149,78 @@ SYNOPSIS: } }) } + +func TestSetValue(t *testing.T) { + t.Run("not found error", func(t *testing.T) { + opt := getoptions.New() + opt.String("str", "") + err := opt.SetValue("x", "y") + if err == nil { + t.Errorf("no error") + } + if !strings.Contains(err.Error(), "not found") { + t.Errorf("wrong error: %s", err) + } + }) + t.Run("string", func(t *testing.T) { + opt := getoptions.New() + opt.String("str", "") + opt.SetValue("str", "hello") + if opt.Value("str") != "hello" { + t.Errorf("wrong value %v", opt.Value("str")) + } + }) + t.Run("bool", func(t *testing.T) { + opt := getoptions.New() + opt.Bool("bool", false) + opt.SetValue("bool") + if opt.Value("bool") != true { + t.Errorf("wrong value %v", opt.Value("bool")) + } + }) + t.Run("int", func(t *testing.T) { + opt := getoptions.New() + opt.Int("int", 0) + opt.SetValue("int", "123") + if opt.Value("int") != 123 { + t.Errorf("wrong value %v", opt.Value("int")) + } + }) + t.Run("float64", func(t *testing.T) { + opt := getoptions.New() + opt.Float64("float64", 0) + opt.SetValue("float64", "3.14") + if opt.Value("float64") != 3.14 { + t.Errorf("wrong value %v", opt.Value("float64")) + } + }) + t.Run("float64 error", func(t *testing.T) { + opt := getoptions.New() + opt.Float64("float64", 0) + err := opt.SetValue("float64", "x") + if err == nil { + t.Errorf("no error") + } + if !strings.Contains(err.Error(), "Can't convert string to float64: 'x'") { + t.Errorf("wrong error: %s", err) + } + }) + t.Run("stringSlice", func(t *testing.T) { + opt := getoptions.New() + opt.StringSlice("str", 1, 99) + opt.SetValue("str", "hello", "world") + opt.SetValue("str", "hello", "world") + if !reflect.DeepEqual(opt.Value("str"), []string{"hello", "world", "hello", "world"}) { + t.Errorf("wrong value %v", opt.Value("str")) + } + }) + t.Run("stringMap", func(t *testing.T) { + opt := getoptions.New() + opt.StringMap("str", 1, 99) + opt.SetValue("str", "hello=world", "goodbye=world") + opt.SetValue("str", "hola=mundo") + if !reflect.DeepEqual(opt.Value("str"), map[string]string{"hello": "world", "goodbye": "world", "hola": "mundo"}) { + t.Errorf("wrong value %v", opt.Value("str")) + } + }) +} diff --git a/user_options.go b/user_options.go index 896676a..f526ad9 100644 --- a/user_options.go +++ b/user_options.go @@ -185,6 +185,23 @@ func (gopt *GetOpt) Value(name string) interface{} { return nil } +// SetValue - Set the value of the given option using strings as an argument. +// +// Examples: +// +// opt.SetValue("bool") // boolean - sets to opposite of default +// opt.SetValue("int", "123") // int +// err := opt.SetValue("float64", "x") // error because "x" is not a valid float64 +// opt.SetValue("slice", "a", "b", "c") // []string +// opt.SetValue("slice", "d", "e", "f") // Can be called multiple times for options that allow it +// opt.SetValue("map", "hello=world", "hola=mundo") // map[string]string +func (gopt *GetOpt) SetValue(name string, value ...string) error { + if v, ok := gopt.programTree.ChildOptions[name]; ok { + return v.Save(value...) + } + return ErrorNotFound +} + // Bool - define a `bool` option and its aliases. // It returns a `*bool` pointing to the variable holding the result. // If the option is found, the result will be the opposite of the provided default.