Differentiate between omitted and empty default.

Fixes #250.
This commit is contained in:
Alec Thomas
2021-12-13 19:03:23 +11:00
parent 9c9b8ab50b
commit 7533b11d06
6 changed files with 19 additions and 8 deletions
+2 -1
View File
@@ -211,7 +211,7 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
if child.Help == "" { if child.Help == "" {
child.Help = child.Argument.Help child.Help = child.Argument.Help
} }
} else if tag.Default != "" { } else if tag.HasDefault {
if node.DefaultCmd != nil { if node.DefaultCmd != nil {
return failField(v, ft, "can't have more than one default command under %s", node.Summary()) return failField(v, ft, "can't have more than one default command under %s", node.Summary())
} }
@@ -239,6 +239,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
Name: name, Name: name,
Help: tag.Help, Help: tag.Help,
OrigHelp: tag.Help, OrigHelp: tag.Help,
HasDefault: tag.HasDefault,
Default: tag.Default, Default: tag.Default,
DefaultValue: reflect.New(fv.Type()).Elem(), DefaultValue: reflect.New(fv.Type()).Elem(),
Mapper: mapper, Mapper: mapper,
+2 -2
View File
@@ -167,7 +167,7 @@ func (c *Context) Validate() error { // nolint: gocyclo
switch node := node.(type) { switch node := node.(type) {
case *Value: case *Value:
_, ok := os.LookupEnv(node.Tag.Env) _, ok := os.LookupEnv(node.Tag.Env)
if node.Enum != "" && (!node.Required || node.Default != "" || (node.Tag.Env != "" && ok)) { if node.Enum != "" && (!node.Required || node.HasDefault || (node.Tag.Env != "" && ok)) {
if err := checkEnum(node, node.Target); err != nil { if err := checkEnum(node, node.Target); err != nil {
return err return err
} }
@@ -175,7 +175,7 @@ func (c *Context) Validate() error { // nolint: gocyclo
case *Flag: case *Flag:
_, ok := os.LookupEnv(node.Tag.Env) _, ok := os.LookupEnv(node.Tag.Env)
if node.Enum != "" && (!node.Required || node.Default != "" || (node.Tag.Env != "" && ok)) { if node.Enum != "" && (!node.Required || node.HasDefault || (node.Tag.Env != "" && ok)) {
if err := checkEnum(node.Value, node.Target); err != nil { if err := checkEnum(node.Value, node.Target); err != nil {
return err return err
} }
+1 -1
View File
@@ -319,7 +319,7 @@ func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) er
} }
binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars)) binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
for _, flag := range node.Flags { for _, flag := range node.Flags {
if flag.Default == "" || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() { if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
continue continue
} }
method := getMethod(flag.Target, name) method := getMethod(flag.Target, name)
+7
View File
@@ -1432,6 +1432,13 @@ func TestEnumValidation(t *testing.T) {
}{}, }{},
false, false,
}, },
{
"EnumWithEmptyDefault",
&struct {
Flag string `enum:"one,two," default:""`
}{},
false,
},
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
+3 -2
View File
@@ -232,6 +232,7 @@ type Value struct {
Name string Name string
Help string Help string
OrigHelp string // Original help string, without interpolated variables. OrigHelp string // Original help string, without interpolated variables.
HasDefault bool
Default string Default string
DefaultValue reflect.Value DefaultValue reflect.Value
Enum string Enum string
@@ -357,7 +358,7 @@ func (v *Value) Reset() error {
return nil return nil
} }
} }
if v.Default != "" { if v.HasDefault {
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target) return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
} }
return nil return nil
@@ -404,7 +405,7 @@ func (f *Flag) FormatPlaceHolder() string {
if f.PlaceHolder != "" { if f.PlaceHolder != "" {
return f.PlaceHolder + tail return f.PlaceHolder + tail
} }
if f.Default != "" { if f.HasDefault {
if f.Value.Target.Kind() == reflect.String { if f.Value.Target.Kind() == reflect.String {
return strconv.Quote(f.Default) + tail return strconv.Quote(f.Default) + tail
} }
+4 -2
View File
@@ -20,6 +20,7 @@ type Tag struct {
Help string Help string
Type string Type string
TypeName string TypeName string
HasDefault bool
Default string Default string
Format string Format string
PlaceHolder string PlaceHolder string
@@ -182,9 +183,10 @@ func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo
} }
t.Required = required t.Required = required
t.Optional = optional t.Optional = optional
t.HasDefault = t.Has("default")
t.Default = t.Get("default") t.Default = t.Get("default")
// Arguments with defaults are always optional. // Arguments with defaults are always optional.
if t.Arg && t.Default != "" { if t.Arg && t.HasDefault {
t.Optional = true t.Optional = true
} else if t.Arg && !optional { // Arguments are required unless explicitly made optional. } else if t.Arg && !optional { // Arguments are required unless explicitly made optional.
t.Required = true t.Required = true
@@ -229,7 +231,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))
if t.Enum != "" && !(t.Required || t.Default != "") && 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")
} }
passthrough := t.Has("passthrough") passthrough := t.Has("passthrough")