Skip to content

Commit

Permalink
Added 'Choice' and custom argument support
Browse files Browse the repository at this point in the history
  • Loading branch information
ibraimgm committed Sep 25, 2019
1 parent ebb5f20 commit 3918829
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
46 changes: 46 additions & 0 deletions custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package libcmd

import (
"fmt"
"reflect"
"strings"
)

// CustomArg is the interface used to create customized argument types.
// As long as you can read and write to a string, you can use this.
//
// Note that a empty string ("") is assumed to be the zero value
// of your custom type
type CustomArg interface {
Get() string
Set(value string) error
}

var customArgType = reflect.TypeOf(new(CustomArg)).Elem()

type choiceString struct {
value *string
choices []string
}

func newChoice(target *string, choices []string) *choiceString {
return &choiceString{
value: target,
choices: choices,
}
}

func (c *choiceString) Get() string {
return *c.value
}

func (c *choiceString) Set(value string) error {
for _, s := range c.choices {
if s == value {
*c.value = value
return nil
}
}

return fmt.Errorf("'%s' is not a valid value (possible values: %s)", value, strings.Join(c.choices, ","))
}
27 changes: 27 additions & 0 deletions opt.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,22 @@ func (cmd *Cmd) Float64P(target *float64, long, short string, defaultValue float
cmd.addOpt(&optEntry{long: long, short: short, help: help, val: val})
}

// CustomP defines a new argument with custom type. During parsing, the argument
// is manipulated via Get and Set methods of the CustomArg interface.
func (cmd *Cmd) CustomP(target CustomArg, long, short, defaultValue, help string) {
val := varFromCustom(target, defaultValue)
cmd.addOpt(&optEntry{long: long, short: short, help: help, val: val})
}

// ChoiceP defines a new string argument, ith tha values limited by choices.
// After parsing, the argument value will be available in the specified pointer.
//
// If the defaultValue is always considered 'valid', even when not listed on
// the choices parameter.
func (cmd *Cmd) ChoiceP(target *string, choices []string, long, short, defaultValue, help string) {
cmd.CustomP(newChoice(target, choices), long, short, defaultValue, help)
}

// String defines a new string argument. After parsing, the argument value
// will be available in the returned pointer.
func (cmd *Cmd) String(long, short, defaultValue, help string) *string {
Expand Down Expand Up @@ -469,3 +485,14 @@ func (cmd *Cmd) Float64(long, short string, defaultValue float64, help string) *
cmd.Float64P(target, long, short, defaultValue, help)
return target
}

// Choice defines a new string argument, ith tha values limited by choices.
// After parsing, the argument value will be available in the returned pointer.
//
// If the defaultValue is always considered 'valid', even when not listed on
// the choices parameter.
func (cmd *Cmd) Choice(choices []string, long, short, defaultValue, help string) *string {
target := new(string)
cmd.ChoiceP(target, choices, long, short, defaultValue, help)
return target
}
34 changes: 34 additions & 0 deletions opt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,37 @@ func TestOptKeepValue(t *testing.T) {
compareValue(t, i, test.s2, *s2)
}
}

func TestChoice(t *testing.T) {
tests := []struct {
cmd []string
expected string
expectErr bool
}{
{cmd: []string{}},
{cmd: []string{"-c", "foo"}, expected: "foo"},
{cmd: []string{"-c", "bar"}, expected: "bar"},
{cmd: []string{"-c", "baz"}, expected: "baz"},
{cmd: []string{"-c", "hey"}, expectErr: true},
}

for i, test := range tests {
app := libcmd.NewApp("", "")
s := app.Choice([]string{"foo", "bar", "baz"}, "", "c", "", "")

err := app.RunArgs(test.cmd)
if !test.expectErr && err != nil {
t.Errorf("Case %d, error parsing args: %v", i, err)
continue
}

if test.expectErr && err == nil {
t.Errorf("Case %d, expected error but none received", i)
continue
}

if *s != test.expected {
t.Errorf("Case %d, wrong value: expected '%s', received '%s'", i, test.expected, *s)
}
}
}
25 changes: 25 additions & 0 deletions variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,20 @@ func varFromReflect(target, defaultValue reflect.Value) *variant {
}
}

func varFromCustom(target CustomArg, defaultValue string) *variant {
return &variant{
refValue: reflect.ValueOf(target),
defaultValue: reflect.ValueOf(defaultValue),
isStr: true,
}
}

func (v *variant) setValue(value string) error {
if v.refValue.Type().Implements(customArgType) {
ca, _ := v.refValue.Interface().(CustomArg)
return ca.Set(value)
}

converted, err := valueAsKind(value, v.refValue.Kind(), v.refValue.Type())
if err != nil {
return err
Expand All @@ -56,6 +69,18 @@ func (v *variant) useDefault() {
return
}

if v.refValue.Type().Implements(customArgType) {
ca, _ := v.refValue.Interface().(CustomArg)

if ca.Get() == "" {
return
}

str := v.defaultValue.String()
ca.Set(str) //nolint: errcheck
return
}

zero := reflect.Zero(v.refValue.Type())
defaultIsZero := zero.Interface() == v.defaultValue.Interface()
valueIsZero := zero.Interface() == v.refValue.Interface()
Expand Down

0 comments on commit 3918829

Please sign in to comment.