@@ -474,7 +474,7 @@ Tag | Description
|
||||
`envprefix:"X"` | Envar prefix for all sub-flags.
|
||||
`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur.
|
||||
`embed:""` | If present, this field's children will be embedded in the parent. Useful for composition.
|
||||
`passthrough:""` | If present, this positional argument stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`.
|
||||
`passthrough:""` | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed.
|
||||
`-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` ``
|
||||
|
||||
## Plugins
|
||||
|
||||
@@ -211,14 +211,28 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
|
||||
if child.Help == "" {
|
||||
child.Help = child.Argument.Help
|
||||
}
|
||||
} else if tag.HasDefault {
|
||||
if node.DefaultCmd != nil {
|
||||
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
|
||||
} else {
|
||||
if tag.HasDefault {
|
||||
if node.DefaultCmd != nil {
|
||||
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
|
||||
}
|
||||
if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
|
||||
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
|
||||
}
|
||||
node.DefaultCmd = child
|
||||
}
|
||||
if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
|
||||
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
|
||||
if tag.Passthrough {
|
||||
if len(child.Children) > 0 || len(child.Flags) > 0 {
|
||||
return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary())
|
||||
}
|
||||
if len(child.Positional) != 1 {
|
||||
return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary())
|
||||
}
|
||||
if !checkPassthroughArg(child.Positional[0].Target) {
|
||||
return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary())
|
||||
}
|
||||
child.Passthrough = true
|
||||
}
|
||||
node.DefaultCmd = child
|
||||
}
|
||||
node.Children = append(node.Children, child)
|
||||
|
||||
|
||||
+14
@@ -361,6 +361,10 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
flags = append(flags, group...)
|
||||
}
|
||||
|
||||
if node.Passthrough {
|
||||
c.endParsing()
|
||||
}
|
||||
|
||||
for !c.scan.Peek().IsEOL() {
|
||||
token := c.scan.Peek()
|
||||
switch token.Type {
|
||||
@@ -901,6 +905,16 @@ func checkEnum(value *Value, target reflect.Value) error {
|
||||
}
|
||||
}
|
||||
|
||||
func checkPassthroughArg(target reflect.Value) bool {
|
||||
typ := target.Type()
|
||||
switch typ.Kind() {
|
||||
case reflect.Slice:
|
||||
return typ.Elem().Kind() == reflect.String
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func checkXorDuplicates(paths []*Path) error {
|
||||
for _, path := range paths {
|
||||
seen := map[string]*Flag{}
|
||||
|
||||
@@ -1452,3 +1452,80 @@ func TestEnumValidation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughCmd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
flag string
|
||||
cmdArgs []string
|
||||
}{
|
||||
{
|
||||
"Simple",
|
||||
[]string{"--flag", "foobar", "command", "something"},
|
||||
"foobar",
|
||||
[]string{"something"},
|
||||
},
|
||||
{
|
||||
"DashDash",
|
||||
[]string{"--flag", "foobar", "command", "--", "something"},
|
||||
"foobar",
|
||||
[]string{"--", "something"},
|
||||
},
|
||||
{
|
||||
"Flag",
|
||||
[]string{"command", "--flag", "foobar"},
|
||||
"",
|
||||
[]string{"--flag", "foobar"},
|
||||
},
|
||||
{
|
||||
"FlagAndFlag",
|
||||
[]string{"--flag", "foobar", "command", "--flag", "foobar"},
|
||||
"foobar",
|
||||
[]string{"--flag", "foobar"},
|
||||
},
|
||||
{
|
||||
"NoArgs",
|
||||
[]string{"--flag", "foobar", "command"},
|
||||
"foobar",
|
||||
[]string(nil),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var cli struct {
|
||||
Flag string
|
||||
Command struct {
|
||||
Args []string `arg:"" optional:""`
|
||||
} `cmd:"" passthrough:""`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse(test.args)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.flag, cli.Flag)
|
||||
require.Equal(t, test.cmdArgs, cli.Command.Args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughCmdOnlyArgs(t *testing.T) {
|
||||
var cli struct {
|
||||
Command struct {
|
||||
Flag string
|
||||
Args []string `arg:"" optional:""`
|
||||
} `cmd:"" passthrough:""`
|
||||
}
|
||||
_, err := kong.New(&cli)
|
||||
require.EqualError(t, err, "<anonymous struct>.Command: passthrough command command [<args> ...] must not have subcommands or flags")
|
||||
}
|
||||
|
||||
func TestPassthroughCmdOnlyStringArgs(t *testing.T) {
|
||||
var cli struct {
|
||||
Command struct {
|
||||
Args []int `arg:"" optional:""`
|
||||
} `cmd:"" passthrough:""`
|
||||
}
|
||||
_, err := kong.New(&cli)
|
||||
require.EqualError(t, err, "<anonymous struct>.Command: passthrough command command [<args> ...] must contain exactly one positional argument of []string type")
|
||||
}
|
||||
|
||||
@@ -41,20 +41,21 @@ const (
|
||||
|
||||
// Node is a branch in the CLI. ie. a command or positional argument.
|
||||
type Node struct {
|
||||
Type NodeType
|
||||
Parent *Node
|
||||
Name string
|
||||
Help string // Short help displayed in summaries.
|
||||
Detail string // Detailed help displayed when describing command/arg alone.
|
||||
Group *Group
|
||||
Hidden bool
|
||||
Flags []*Flag
|
||||
Positional []*Positional
|
||||
Children []*Node
|
||||
DefaultCmd *Node
|
||||
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
|
||||
Tag *Tag
|
||||
Aliases []string
|
||||
Type NodeType
|
||||
Parent *Node
|
||||
Name string
|
||||
Help string // Short help displayed in summaries.
|
||||
Detail string // Detailed help displayed when describing command/arg alone.
|
||||
Group *Group
|
||||
Hidden bool
|
||||
Flags []*Flag
|
||||
Positional []*Positional
|
||||
Children []*Node
|
||||
DefaultCmd *Node
|
||||
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
|
||||
Tag *Tag
|
||||
Aliases []string
|
||||
Passthrough bool // Set to true to stop flag parsing when encountered.
|
||||
|
||||
Argument *Value // Populated when Type is ArgumentNode.
|
||||
}
|
||||
|
||||
@@ -235,8 +235,8 @@ func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo
|
||||
return fmt.Errorf("enum value is only valid if it is either required or has a valid default value")
|
||||
}
|
||||
passthrough := t.Has("passthrough")
|
||||
if passthrough && !t.Arg {
|
||||
return fmt.Errorf("passthrough only makes sense for positional arguments")
|
||||
if passthrough && !t.Arg && !t.Cmd {
|
||||
return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
|
||||
}
|
||||
t.Passthrough = passthrough
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user