@@ -474,7 +474,7 @@ Tag | Description
|
|||||||
`envprefix:"X"` | Envar prefix for all sub-flags.
|
`envprefix:"X"` | Envar prefix for all sub-flags.
|
||||||
`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur.
|
`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.
|
`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:"-"` ``
|
`-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` ``
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|||||||
@@ -211,14 +211,28 @@ 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.HasDefault {
|
} else {
|
||||||
if node.DefaultCmd != nil {
|
if tag.HasDefault {
|
||||||
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
|
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) {
|
if tag.Passthrough {
|
||||||
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
|
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)
|
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...)
|
flags = append(flags, group...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if node.Passthrough {
|
||||||
|
c.endParsing()
|
||||||
|
}
|
||||||
|
|
||||||
for !c.scan.Peek().IsEOL() {
|
for !c.scan.Peek().IsEOL() {
|
||||||
token := c.scan.Peek()
|
token := c.scan.Peek()
|
||||||
switch token.Type {
|
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 {
|
func checkXorDuplicates(paths []*Path) error {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
seen := map[string]*Flag{}
|
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.
|
// Node is a branch in the CLI. ie. a command or positional argument.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Type NodeType
|
Type NodeType
|
||||||
Parent *Node
|
Parent *Node
|
||||||
Name string
|
Name string
|
||||||
Help string // Short help displayed in summaries.
|
Help string // Short help displayed in summaries.
|
||||||
Detail string // Detailed help displayed when describing command/arg alone.
|
Detail string // Detailed help displayed when describing command/arg alone.
|
||||||
Group *Group
|
Group *Group
|
||||||
Hidden bool
|
Hidden bool
|
||||||
Flags []*Flag
|
Flags []*Flag
|
||||||
Positional []*Positional
|
Positional []*Positional
|
||||||
Children []*Node
|
Children []*Node
|
||||||
DefaultCmd *Node
|
DefaultCmd *Node
|
||||||
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
|
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
|
||||||
Tag *Tag
|
Tag *Tag
|
||||||
Aliases []string
|
Aliases []string
|
||||||
|
Passthrough bool // Set to true to stop flag parsing when encountered.
|
||||||
|
|
||||||
Argument *Value // Populated when Type is ArgumentNode.
|
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")
|
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")
|
||||||
if passthrough && !t.Arg {
|
if passthrough && !t.Arg && !t.Cmd {
|
||||||
return fmt.Errorf("passthrough only makes sense for positional arguments")
|
return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
|
||||||
}
|
}
|
||||||
t.Passthrough = passthrough
|
t.Passthrough = passthrough
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user