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:
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
|
|||||||
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
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ type Value struct {
|
|||||||
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.
|
||||||
|
PassthroughMode PassthroughMode //
|
||||||
Active bool // Denotes the value is part of an active branch in the CLI.
|
Active bool // Denotes the value is part of an active branch in the CLI.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,18 @@ 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:"-"
|
||||||
@@ -39,7 +51,8 @@ type Tag struct {
|
|||||||
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