feat: add old "passthrough" behaviour back in as an option
`passthrough:""` or `passthrough:"all"` (the default) will pass through all further arguments including unrecognised flags. `passthrough:"partial"` will validate flags up until the `--` or the first positional argument, then pass through all subsequent flags and arguments.
This commit is contained in:
@@ -590,9 +590,13 @@ Both can coexist with standard Tag parsing.
|
|||||||
| `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 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. |
|
| `passthrough:"<mode>"`[^1] | 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:"-"` `` |
|
||||||
|
|
||||||
|
[^1]: `<mode>` can be `partial` or `all` (the default). `all` will pass through all arguments including flags, including
|
||||||
|
flags. `partial` will validate flags until the first positional argument is encountered, then pass through all remaining
|
||||||
|
positional arguments.
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example:
|
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example:
|
||||||
|
|||||||
@@ -281,17 +281,18 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
|
|||||||
}
|
}
|
||||||
|
|
||||||
value := &Value{
|
value := &Value{
|
||||||
Name: name,
|
Name: name,
|
||||||
Help: tag.Help,
|
Help: tag.Help,
|
||||||
OrigHelp: tag.Help,
|
OrigHelp: tag.Help,
|
||||||
HasDefault: tag.HasDefault,
|
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,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
Target: fv,
|
Target: fv,
|
||||||
Enum: tag.Enum,
|
Enum: tag.Enum,
|
||||||
Passthrough: tag.Passthrough,
|
Passthrough: tag.Passthrough,
|
||||||
|
PassthroughMode: tag.PassthroughMode,
|
||||||
|
|
||||||
// Flags are optional by default, and args are required by default.
|
// Flags are optional by default, and args are required by default.
|
||||||
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
|
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
|
||||||
|
|||||||
+2
-2
@@ -425,7 +425,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
|
|
||||||
case FlagToken:
|
case FlagToken:
|
||||||
if err := c.parseFlag(flags, token.String()); err != nil {
|
if err := c.parseFlag(flags, token.String()); err != nil {
|
||||||
if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].Passthrough {
|
if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].PassthroughMode == PassThroughModeAll {
|
||||||
c.scan.Pop()
|
c.scan.Pop()
|
||||||
c.scan.PushTyped(token.String(), PositionalArgumentToken)
|
c.scan.PushTyped(token.String(), PositionalArgumentToken)
|
||||||
} else {
|
} else {
|
||||||
@@ -435,7 +435,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
|
|
||||||
case ShortFlagToken:
|
case ShortFlagToken:
|
||||||
if err := c.parseFlag(flags, token.String()); err != nil {
|
if err := c.parseFlag(flags, token.String()); err != nil {
|
||||||
if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].Passthrough {
|
if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].PassthroughMode == PassThroughModeAll {
|
||||||
c.scan.Pop()
|
c.scan.Pop()
|
||||||
c.scan.PushTyped(token.String(), PositionalArgumentToken)
|
c.scan.PushTyped(token.String(), PositionalArgumentToken)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1803,6 +1803,35 @@ func TestPassthroughArgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPassthroughPartial(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string
|
||||||
|
Args []string `arg:"" optional:"" passthrough:"partial"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag", "foobar", "something"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foobar", cli.Flag)
|
||||||
|
assert.Equal(t, []string{"something"}, cli.Args)
|
||||||
|
_, err = p.Parse([]string{"--invalid", "foobar", "something"})
|
||||||
|
assert.EqualError(t, err, "unknown flag --invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassthroughAll(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string
|
||||||
|
Args []string `arg:"" optional:"" passthrough:"all"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag", "foobar", "something"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foobar", cli.Flag)
|
||||||
|
assert.Equal(t, []string{"something"}, cli.Args)
|
||||||
|
_, err = p.Parse([]string{"--invalid", "foobar", "something"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"--invalid", "foobar", "something"}, cli.Args)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPassthroughCmd(t *testing.T) {
|
func TestPassthroughCmd(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -239,23 +239,24 @@ func (n *Node) ClosestGroup() *Group {
|
|||||||
|
|
||||||
// A Value is either a flag or a variable positional argument.
|
// A Value is either a flag or a variable positional argument.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Flag *Flag // Nil if positional argument.
|
Flag *Flag // Nil if positional argument.
|
||||||
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
|
HasDefault bool
|
||||||
Default string
|
Default string
|
||||||
DefaultValue reflect.Value
|
DefaultValue reflect.Value
|
||||||
Enum string
|
Enum string
|
||||||
Mapper Mapper
|
Mapper Mapper
|
||||||
Tag *Tag
|
Tag *Tag
|
||||||
Target reflect.Value
|
Target reflect.Value
|
||||||
Required bool
|
Required bool
|
||||||
Set bool // Set to true when this value is set through some mechanism.
|
Set bool // Set to true when this value is set through some mechanism.
|
||||||
Format string // Formatting directive, if applicable.
|
Format string // Formatting directive, if applicable.
|
||||||
Position int // Position (for positional arguments).
|
Position int // Position (for positional arguments).
|
||||||
Passthrough bool // Set to true to stop flag parsing when encountered.
|
Passthrough bool // Deprecated: Use PassthroughMode instead. Set to true to stop flag parsing when encountered.
|
||||||
Active bool // Denotes the value is part of an active branch in the CLI.
|
PassthroughMode PassthroughMode //
|
||||||
|
Active bool // Denotes the value is part of an active branch in the CLI.
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnumMap returns a map of the enums in this value.
|
// EnumMap returns a map of the enums in this value.
|
||||||
|
|||||||
@@ -9,37 +9,50 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PassthroughMode indicates how parameters are passed through when "passthrough" is set.
|
||||||
|
type PassthroughMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PassThroughModeNone indicates passthrough mode is disabled.
|
||||||
|
PassThroughModeNone PassthroughMode = iota
|
||||||
|
// PassThroughModeAll indicates that all parameters, including flags, are passed through. It is the default.
|
||||||
|
PassThroughModeAll
|
||||||
|
// PassThroughModePartial will validate flags until the first positional argument is encountered, then pass through all remaining positional arguments.
|
||||||
|
PassThroughModePartial
|
||||||
|
)
|
||||||
|
|
||||||
// Tag represents the parsed state of Kong tags in a struct field tag.
|
// Tag represents the parsed state of Kong tags in a struct field tag.
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Ignored bool // Field is ignored by Kong. ie. kong:"-"
|
Ignored bool // Field is ignored by Kong. ie. kong:"-"
|
||||||
Cmd bool
|
Cmd bool
|
||||||
Arg bool
|
Arg bool
|
||||||
Required bool
|
Required bool
|
||||||
Optional bool
|
Optional bool
|
||||||
Name string
|
Name string
|
||||||
Help string
|
Help string
|
||||||
Type string
|
Type string
|
||||||
TypeName string
|
TypeName string
|
||||||
HasDefault bool
|
HasDefault bool
|
||||||
Default string
|
Default string
|
||||||
Format string
|
Format string
|
||||||
PlaceHolder string
|
PlaceHolder string
|
||||||
Envs []string
|
Envs []string
|
||||||
Short rune
|
Short rune
|
||||||
Hidden bool
|
Hidden bool
|
||||||
Sep rune
|
Sep rune
|
||||||
MapSep rune
|
MapSep rune
|
||||||
Enum string
|
Enum string
|
||||||
Group string
|
Group string
|
||||||
Xor []string
|
Xor []string
|
||||||
And []string
|
And []string
|
||||||
Vars Vars
|
Vars Vars
|
||||||
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
||||||
EnvPrefix string
|
EnvPrefix string
|
||||||
Embed bool
|
Embed bool
|
||||||
Aliases []string
|
Aliases []string
|
||||||
Negatable string
|
Negatable string
|
||||||
Passthrough bool
|
Passthrough bool // Deprecated: use PassthroughMode instead.
|
||||||
|
PassthroughMode PassthroughMode
|
||||||
|
|
||||||
// Storage for all tag keys for arbitrary lookups.
|
// Storage for all tag keys for arbitrary lookups.
|
||||||
items map[string][]string
|
items map[string][]string
|
||||||
@@ -289,6 +302,15 @@ func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo
|
|||||||
return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
|
return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
|
||||||
}
|
}
|
||||||
t.Passthrough = passthrough
|
t.Passthrough = passthrough
|
||||||
|
passthroughMode := t.Get("passthrough")
|
||||||
|
switch passthroughMode {
|
||||||
|
case "partial":
|
||||||
|
t.PassthroughMode = PassThroughModePartial
|
||||||
|
case "all", "":
|
||||||
|
t.PassthroughMode = PassThroughModeAll
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid passthrough mode %q, must be one of 'partial' or 'all'", passthroughMode)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user