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. |
|
||||
| `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 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:"-"` `` |
|
||||
|
||||
[^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
|
||||
|
||||
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{
|
||||
Name: name,
|
||||
Help: tag.Help,
|
||||
OrigHelp: tag.Help,
|
||||
HasDefault: tag.HasDefault,
|
||||
Default: tag.Default,
|
||||
DefaultValue: reflect.New(fv.Type()).Elem(),
|
||||
Mapper: mapper,
|
||||
Tag: tag,
|
||||
Target: fv,
|
||||
Enum: tag.Enum,
|
||||
Passthrough: tag.Passthrough,
|
||||
Name: name,
|
||||
Help: tag.Help,
|
||||
OrigHelp: tag.Help,
|
||||
HasDefault: tag.HasDefault,
|
||||
Default: tag.Default,
|
||||
DefaultValue: reflect.New(fv.Type()).Elem(),
|
||||
Mapper: mapper,
|
||||
Tag: tag,
|
||||
Target: fv,
|
||||
Enum: tag.Enum,
|
||||
Passthrough: tag.Passthrough,
|
||||
PassthroughMode: tag.PassthroughMode,
|
||||
|
||||
// Flags are optional by default, and args are required by default.
|
||||
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:
|
||||
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.PushTyped(token.String(), PositionalArgumentToken)
|
||||
} else {
|
||||
@@ -435,7 +435,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
||||
|
||||
case ShortFlagToken:
|
||||
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.PushTyped(token.String(), PositionalArgumentToken)
|
||||
} 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) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -239,23 +239,24 @@ func (n *Node) ClosestGroup() *Group {
|
||||
|
||||
// A Value is either a flag or a variable positional argument.
|
||||
type Value struct {
|
||||
Flag *Flag // Nil if positional argument.
|
||||
Name string
|
||||
Help string
|
||||
OrigHelp string // Original help string, without interpolated variables.
|
||||
HasDefault bool
|
||||
Default string
|
||||
DefaultValue reflect.Value
|
||||
Enum string
|
||||
Mapper Mapper
|
||||
Tag *Tag
|
||||
Target reflect.Value
|
||||
Required bool
|
||||
Set bool // Set to true when this value is set through some mechanism.
|
||||
Format string // Formatting directive, if applicable.
|
||||
Position int // Position (for positional arguments).
|
||||
Passthrough bool // Set to true to stop flag parsing when encountered.
|
||||
Active bool // Denotes the value is part of an active branch in the CLI.
|
||||
Flag *Flag // Nil if positional argument.
|
||||
Name string
|
||||
Help string
|
||||
OrigHelp string // Original help string, without interpolated variables.
|
||||
HasDefault bool
|
||||
Default string
|
||||
DefaultValue reflect.Value
|
||||
Enum string
|
||||
Mapper Mapper
|
||||
Tag *Tag
|
||||
Target reflect.Value
|
||||
Required bool
|
||||
Set bool // Set to true when this value is set through some mechanism.
|
||||
Format string // Formatting directive, if applicable.
|
||||
Position int // Position (for positional arguments).
|
||||
Passthrough bool // Deprecated: Use PassthroughMode instead. Set to true to stop flag parsing when encountered.
|
||||
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.
|
||||
|
||||
@@ -9,37 +9,50 @@ import (
|
||||
"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.
|
||||
type Tag struct {
|
||||
Ignored bool // Field is ignored by Kong. ie. kong:"-"
|
||||
Cmd bool
|
||||
Arg bool
|
||||
Required bool
|
||||
Optional bool
|
||||
Name string
|
||||
Help string
|
||||
Type string
|
||||
TypeName string
|
||||
HasDefault bool
|
||||
Default string
|
||||
Format string
|
||||
PlaceHolder string
|
||||
Envs []string
|
||||
Short rune
|
||||
Hidden bool
|
||||
Sep rune
|
||||
MapSep rune
|
||||
Enum string
|
||||
Group string
|
||||
Xor []string
|
||||
And []string
|
||||
Vars Vars
|
||||
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
||||
EnvPrefix string
|
||||
Embed bool
|
||||
Aliases []string
|
||||
Negatable string
|
||||
Passthrough bool
|
||||
Ignored bool // Field is ignored by Kong. ie. kong:"-"
|
||||
Cmd bool
|
||||
Arg bool
|
||||
Required bool
|
||||
Optional bool
|
||||
Name string
|
||||
Help string
|
||||
Type string
|
||||
TypeName string
|
||||
HasDefault bool
|
||||
Default string
|
||||
Format string
|
||||
PlaceHolder string
|
||||
Envs []string
|
||||
Short rune
|
||||
Hidden bool
|
||||
Sep rune
|
||||
MapSep rune
|
||||
Enum string
|
||||
Group string
|
||||
Xor []string
|
||||
And []string
|
||||
Vars Vars
|
||||
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
||||
EnvPrefix string
|
||||
Embed bool
|
||||
Aliases []string
|
||||
Negatable string
|
||||
Passthrough bool // Deprecated: use PassthroughMode instead.
|
||||
PassthroughMode PassthroughMode
|
||||
|
||||
// Storage for all tag keys for arbitrary lookups.
|
||||
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")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user