Merging issue 280 with master changes (#296)
This commit is contained in:
committed by
GitHub
parent
9c8b401de0
commit
0c6a9f3a3d
@@ -444,6 +444,20 @@ var CLI struct {
|
|||||||
|
|
||||||
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
|
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
|
||||||
|
|
||||||
|
## Pointers
|
||||||
|
|
||||||
|
Pointers work like the underlying type, except that you can differentiate between the presence of the zero value and no value being supplied.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var CLI struct {
|
||||||
|
Foo *int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Would produce a nil value for `Foo` if no `--foo` argument is supplied, but would have a pointer to the value 0 if the argument `--foo=0` was supplied.
|
||||||
|
|
||||||
## Nested data structure
|
## Nested data structure
|
||||||
|
|
||||||
Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`:
|
Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`:
|
||||||
|
|||||||
+25
-1
@@ -663,6 +663,22 @@ func (c *Context) Apply() (string, error) {
|
|||||||
return strings.Join(path, " "), nil
|
return strings.Join(path, " "), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flipBoolValue(value reflect.Value) error {
|
||||||
|
if value.Kind() == reflect.Bool {
|
||||||
|
value.SetBool(!value.Bool())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Kind() == reflect.Ptr {
|
||||||
|
if !value.IsNil() {
|
||||||
|
return flipBoolValue(value.Elem())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot negate a value of %s", value.Type().String())
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||||
candidates := []string{}
|
candidates := []string{}
|
||||||
for _, flag := range flags {
|
for _, flag := range flags {
|
||||||
@@ -691,7 +707,10 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
|||||||
}
|
}
|
||||||
if flag.Negated {
|
if flag.Negated {
|
||||||
value := c.getValue(flag.Value)
|
value := c.getValue(flag.Value)
|
||||||
value.SetBool(!value.Bool())
|
err := flipBoolValue(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
flag.Value.Apply(value)
|
flag.Value.Apply(value)
|
||||||
}
|
}
|
||||||
c.Path = append(c.Path, &Path{Flag: flag})
|
c.Path = append(c.Path, &Path{Flag: flag})
|
||||||
@@ -891,6 +910,11 @@ func checkEnum(value *Value, target reflect.Value) error {
|
|||||||
case reflect.Map, reflect.Struct:
|
case reflect.Map, reflect.Struct:
|
||||||
return errors.New("enum can only be applied to a slice or value")
|
return errors.New("enum can only be applied to a slice or value")
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
if target.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return checkEnum(value, target.Elem())
|
||||||
default:
|
default:
|
||||||
enumSlice := value.EnumSlice()
|
enumSlice := value.EnumSlice()
|
||||||
v := fmt.Sprintf("%v", target)
|
v := fmt.Sprintf("%v", target)
|
||||||
|
|||||||
+231
@@ -1736,3 +1736,234 @@ func TestCumulativeArgumentNotLast(t *testing.T) {
|
|||||||
_, err := kong.New(&cli)
|
_, err := kong.New(&cli)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringPointer(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Foo *string
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--foo", "wtf"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.Foo)
|
||||||
|
require.Equal(t, "wtf", *cli.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringPointerNoValue(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Foo *string
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.Nil(t, cli.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringPointerDefault(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Foo *string `default:"stuff"`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.Foo)
|
||||||
|
require.Equal(t, "stuff", *cli.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringPointerAliasNoValue(t *testing.T) {
|
||||||
|
type Foo string
|
||||||
|
var cli struct {
|
||||||
|
F *Foo
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.Nil(t, cli.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringPointerAlias(t *testing.T) {
|
||||||
|
type Foo string
|
||||||
|
var cli struct {
|
||||||
|
F *Foo
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--f=value"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.F)
|
||||||
|
require.Equal(t, Foo("value"), *cli.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringPointerEmptyValue(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
F *string
|
||||||
|
G *string
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--f", "", "--g="})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.F)
|
||||||
|
require.NotNil(t, cli.G)
|
||||||
|
require.Equal(t, "", *cli.F)
|
||||||
|
require.Equal(t, "", *cli.G)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntPtr(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
F *int
|
||||||
|
G *int
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--f=6"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.F)
|
||||||
|
require.Nil(t, cli.G)
|
||||||
|
require.Equal(t, 6, *cli.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolPtr(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *bool
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--x"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.X)
|
||||||
|
require.Equal(t, true, *cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolPtrFalse(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *bool
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--x=false"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.X)
|
||||||
|
require.Equal(t, false, *cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolPtrNegated(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *bool `negatable:""`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--no-x"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.X)
|
||||||
|
require.Equal(t, false, *cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilNegatableBoolPtr(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *bool `negatable:""`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.Nil(t, cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolPtrNil(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *bool
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.Nil(t, cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsupportedPtr(t *testing.T) {
|
||||||
|
//nolint:structcheck,unused
|
||||||
|
type Foo struct {
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli struct {
|
||||||
|
F *Foo
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--f=whatever"})
|
||||||
|
require.Nil(t, ctx)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "--f: cannot find mapper for kong_test.Foo", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumPtr(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *string `enum:"A,B,C" default:"C"`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{"--x=A"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.X)
|
||||||
|
require.Equal(t, "A", *cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumPtrOmitted(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *string `enum:"A,B,C" default:"C"`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.NotNil(t, cli.X)
|
||||||
|
require.Equal(t, "C", *cli.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumPtrOmittedNoDefault(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
X *string `enum:"A,B,C"`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k)
|
||||||
|
ctx, err := k.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ctx)
|
||||||
|
require.Nil(t, cli.X)
|
||||||
|
}
|
||||||
|
|||||||
@@ -274,7 +274,8 @@ func (r *Registry) RegisterDefaults() *Registry {
|
|||||||
RegisterName("path", pathMapper(r)).
|
RegisterName("path", pathMapper(r)).
|
||||||
RegisterName("existingfile", existingFileMapper(r)).
|
RegisterName("existingfile", existingFileMapper(r)).
|
||||||
RegisterName("existingdir", existingDirMapper(r)).
|
RegisterName("existingdir", existingDirMapper(r)).
|
||||||
RegisterName("counter", counterMapper())
|
RegisterName("counter", counterMapper()).
|
||||||
|
RegisterKind(reflect.Ptr, ptrMapper(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
type boolMapper struct{}
|
type boolMapper struct{}
|
||||||
@@ -669,6 +670,22 @@ func existingDirMapper(r *Registry) MapperFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ptrMapper(r *Registry) MapperFunc {
|
||||||
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
|
elem := reflect.New(target.Type().Elem()).Elem()
|
||||||
|
nestedMapper := r.ForValue(elem)
|
||||||
|
if nestedMapper == nil {
|
||||||
|
return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String())
|
||||||
|
}
|
||||||
|
err := nestedMapper.Decode(ctx, elem)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target.Set(elem.Addr())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func counterMapper() MapperFunc {
|
func counterMapper() MapperFunc {
|
||||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
if ctx.Scan.Peek().Type == FlagValueToken {
|
if ctx.Scan.Peek().Type == FlagValueToken {
|
||||||
|
|||||||
@@ -169,9 +169,11 @@ func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) {
|
|||||||
func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo
|
func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo
|
||||||
var typeName string
|
var typeName string
|
||||||
var isBool bool
|
var isBool bool
|
||||||
|
var isBoolPtr bool
|
||||||
if typ != nil {
|
if typ != nil {
|
||||||
typeName = typ.Name()
|
typeName = typ.Name()
|
||||||
isBool = typ.Kind() == reflect.Bool
|
isBool = typ.Kind() == reflect.Bool
|
||||||
|
isBoolPtr = typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Bool
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
t.Cmd = t.Has("cmd")
|
t.Cmd = t.Has("cmd")
|
||||||
@@ -212,7 +214,7 @@ func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo
|
|||||||
t.EnvPrefix = t.Get("envprefix")
|
t.EnvPrefix = t.Get("envprefix")
|
||||||
t.Embed = t.Has("embed")
|
t.Embed = t.Has("embed")
|
||||||
negatable := t.Has("negatable")
|
negatable := t.Has("negatable")
|
||||||
if negatable && !isBool {
|
if negatable && !isBool && !isBoolPtr {
|
||||||
return fmt.Errorf("negatable can only be set on booleans")
|
return fmt.Errorf("negatable can only be set on booleans")
|
||||||
}
|
}
|
||||||
t.Negatable = negatable
|
t.Negatable = negatable
|
||||||
@@ -230,7 +232,7 @@ func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo
|
|||||||
}
|
}
|
||||||
t.PlaceHolder = t.Get("placeholder")
|
t.PlaceHolder = t.Get("placeholder")
|
||||||
t.Enum = t.Get("enum")
|
t.Enum = t.Get("enum")
|
||||||
scalarType := (typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map))
|
scalarType := (typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Ptr))
|
||||||
if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType {
|
if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType {
|
||||||
return fmt.Errorf("enum value is only valid if it is either required or has a valid default value")
|
return fmt.Errorf("enum value is only valid if it is either required or has a valid default value")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user