committed by
Alec Thomas
parent
aea859372c
commit
8e96da517d
@@ -103,19 +103,24 @@ func buildNode(v reflect.Value, cmd bool) *Node {
|
|||||||
if decoder == nil {
|
if decoder == nil {
|
||||||
fail("no decoder for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
fail("no decoder for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flag := !arg
|
||||||
|
|
||||||
value := Value{
|
value := Value{
|
||||||
Name: name,
|
Name: name,
|
||||||
Help: help,
|
Flag: flag,
|
||||||
Decoder: decoder,
|
Help: help,
|
||||||
Value: fv,
|
Decoder: decoder,
|
||||||
Field: ft,
|
Value: fv,
|
||||||
Required: !optional || required,
|
Field: ft,
|
||||||
|
|
||||||
|
// Flags are optional by default, and args are required by default.
|
||||||
|
Required: (flag && required) || (arg && !optional),
|
||||||
Format: format,
|
Format: format,
|
||||||
}
|
}
|
||||||
if arg {
|
if arg {
|
||||||
node.Positional = append(node.Positional, &value)
|
node.Positional = append(node.Positional, &value)
|
||||||
} else {
|
} else {
|
||||||
value.Flag = true
|
|
||||||
node.Flags = append(node.Flags, &Flag{
|
node.Flags = append(node.Flags, &Flag{
|
||||||
Value: value,
|
Value: value,
|
||||||
Short: short,
|
Short: short,
|
||||||
@@ -126,5 +131,20 @@ func buildNode(v reflect.Value, cmd bool) *Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan through argument positionals to ensure optional is never before a required
|
||||||
|
last := true
|
||||||
|
for _, p := range node.Positional {
|
||||||
|
if p.Flag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !last && p.Required {
|
||||||
|
fail("arguments can not be required after an optional: %v", p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
last = p.Required
|
||||||
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,27 +201,79 @@ func (k *Kong) applyNode(scan *Scanner, node *Node, flags []*Flag) (command []st
|
|||||||
return nil, fmt.Errorf("unexpected token %s", token)
|
return nil, fmt.Errorf("unexpected token %s", token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if positional < len(node.Positional) {
|
|
||||||
missing := []string{}
|
if err := checkMissingPositionals(positional, node.Positional); err != nil {
|
||||||
for ; positional < len(node.Positional); positional++ {
|
return nil, err
|
||||||
missing = append(missing, "<"+node.Positional[positional].Name+">")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
|
||||||
}
|
}
|
||||||
if len(node.Children) > 0 {
|
|
||||||
missing := []string{}
|
if err := checkMissingChildren(node.Children); err != nil {
|
||||||
for _, child := range node.Children {
|
return nil, err
|
||||||
if child.Argument != nil {
|
|
||||||
missing = append(missing, "<"+child.Argument.Name+">")
|
|
||||||
} else {
|
|
||||||
missing = append(missing, child.Command.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := chickMissingFlags(node.Children, flags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func chickMissingFlags(children []*Branch, flags []*Flag) error {
|
||||||
|
// Only check required missing fields at the last child.
|
||||||
|
if len(children) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
missing := []string{}
|
||||||
|
for _, flag := range flags {
|
||||||
|
if !flag.Required || flag.Set {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
missing = append(missing, flag.Name)
|
||||||
|
}
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("missing flags: %s", strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkMissingChildren(children []*Branch) error {
|
||||||
|
missing := []string{}
|
||||||
|
for _, child := range children {
|
||||||
|
if child.Argument != nil {
|
||||||
|
if !child.Argument.Argument.Required {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
missing = append(missing, "<"+child.Argument.Name+">")
|
||||||
|
} else {
|
||||||
|
missing = append(missing, child.Command.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're missing any positionals and they're required, return an error.
|
||||||
|
func checkMissingPositionals(positional int, values []*Value) error {
|
||||||
|
// All the positionals are in.
|
||||||
|
if positional == len(values) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're low on supplied positionals, but the missing one is optional.
|
||||||
|
if !values[positional].Required {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
missing := []string{}
|
||||||
|
for ; positional < len(values); positional++ {
|
||||||
|
missing = append(missing, "<"+values[positional].Name+">")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
||||||
|
}
|
||||||
|
|
||||||
func matchFlags(flags []*Flag, token Token, scan *Scanner, matcher func(f *Flag) bool) (err error) {
|
func matchFlags(flags []*Flag, token Token, scan *Scanner, matcher func(f *Flag) bool) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
msg := recover()
|
msg := recover()
|
||||||
@@ -238,6 +290,7 @@ func matchFlags(flags []*Flag, token Token, scan *Scanner, matcher func(f *Flag)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
flag.Set = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,3 +150,94 @@ func TestPropagatedFlags(t *testing.T) {
|
|||||||
require.Equal(t, "moo", cli.Flag1)
|
require.Equal(t, "moo", cli.Flag1)
|
||||||
require.Equal(t, true, cli.Command1.Flag2)
|
require.Equal(t, true, cli.Command1.Flag2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequiredFlag(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string `required:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := mustNew(t, &cli)
|
||||||
|
_, err := parser.Parse([]string{})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionalArg(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Arg string `arg:"" optional:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := mustNew(t, &cli)
|
||||||
|
_, err := parser.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiredArg(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Arg string `arg:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := mustNew(t, &cli)
|
||||||
|
_, err := parser.Parse([]string{})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidRequiredAfterOptional(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
ID int `arg:"" optional:""`
|
||||||
|
Name string `arg:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := New(&cli)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionalStructArg(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Name struct {
|
||||||
|
Name string `arg:"" optional:""`
|
||||||
|
Enabled bool
|
||||||
|
} `arg:"" optional:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := mustNew(t, &cli)
|
||||||
|
|
||||||
|
t.Run("WithFlag", func(t *testing.T) {
|
||||||
|
_, err := parser.Parse([]string{"gak", "--enabled"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "gak", cli.Name.Name)
|
||||||
|
require.Equal(t, true, cli.Name.Enabled)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithoutFlag", func(t *testing.T) {
|
||||||
|
_, err := parser.Parse([]string{"gak"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "gak", cli.Name.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithNothing", func(t *testing.T) {
|
||||||
|
_, err := parser.Parse([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMixedRequiredArgs(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Name string `arg:""`
|
||||||
|
ID int `arg:"" optional:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := mustNew(t, &cli)
|
||||||
|
|
||||||
|
t.Run("SingleRequired", func(t *testing.T) {
|
||||||
|
_, err := parser.Parse([]string{"gak", "5"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "gak", cli.Name)
|
||||||
|
require.Equal(t, 5, cli.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ExtraOptional", func(t *testing.T) {
|
||||||
|
_, err := parser.Parse([]string{"gak"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "gak", cli.Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type Value struct {
|
|||||||
Field reflect.StructField
|
Field reflect.StructField
|
||||||
Value reflect.Value
|
Value reflect.Value
|
||||||
Required bool
|
Required bool
|
||||||
|
Set bool // Used with Required to test if a value has been given.
|
||||||
Format string // Formatting directive, if applicable.
|
Format string // Formatting directive, if applicable.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user