Merge remote-tracking branch 'upstream/master'
This commit is contained in:
+727
-50
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
"git.company.lan/gopkg/kong"
|
||||
)
|
||||
|
||||
func mustNew(t *testing.T, cli interface{}, options ...kong.Option) *kong.Kong {
|
||||
func mustNew(t *testing.T, cli any, options ...kong.Option) *kong.Kong {
|
||||
t.Helper()
|
||||
options = append([]kong.Option{
|
||||
kong.Name("test"),
|
||||
@@ -47,6 +48,25 @@ func TestPositionalArguments(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemainderReturnsUnparsedArgs(t *testing.T) {
|
||||
var cli struct {
|
||||
User struct {
|
||||
Create struct {
|
||||
ID int `kong:"arg"`
|
||||
First string `kong:"arg"`
|
||||
Last string `kong:"arg"`
|
||||
} `kong:"cmd"`
|
||||
} `kong:"cmd"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
args := []string{"user", "create", "10", "Alec", "Thomas"}
|
||||
ctx, err := p.Parse(args)
|
||||
assert.NoError(t, err)
|
||||
for i, x := range ctx.Path {
|
||||
assert.Equal(t, strings.Join(args[i:], " "), strings.Join(x.Remainder(), " "))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBranchingArgument(t *testing.T) {
|
||||
/*
|
||||
app user create <id> <first> <last>
|
||||
@@ -356,8 +376,9 @@ func TestTraceErrorPartiallySucceeds(t *testing.T) {
|
||||
}
|
||||
|
||||
type commandWithNegatableFlag struct {
|
||||
Flag bool `kong:"default='true',negatable"`
|
||||
ran bool
|
||||
Flag bool `kong:"default='true',negatable"`
|
||||
Custom bool `kong:"default='true',negatable='standard'"`
|
||||
ran bool
|
||||
}
|
||||
|
||||
func (c *commandWithNegatableFlag) Run() error {
|
||||
@@ -367,34 +388,64 @@ func (c *commandWithNegatableFlag) Run() error {
|
||||
|
||||
func TestNegatableFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected bool
|
||||
name string
|
||||
args []string
|
||||
expectedFlag bool
|
||||
expectedCustom bool
|
||||
}{
|
||||
{
|
||||
name: "no flag",
|
||||
args: []string{"cmd"},
|
||||
expected: true,
|
||||
name: "no flag",
|
||||
args: []string{"cmd"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: true,
|
||||
},
|
||||
{
|
||||
name: "boolean flag",
|
||||
args: []string{"cmd", "--flag"},
|
||||
expected: true,
|
||||
name: "boolean flag",
|
||||
args: []string{"cmd", "--flag"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: true,
|
||||
},
|
||||
{
|
||||
name: "inverted boolean flag",
|
||||
args: []string{"cmd", "--flag=false"},
|
||||
expected: false,
|
||||
name: "custom boolean flag",
|
||||
args: []string{"cmd", "--custom"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: true,
|
||||
},
|
||||
{
|
||||
name: "negated boolean flag",
|
||||
args: []string{"cmd", "--no-flag"},
|
||||
expected: false,
|
||||
name: "inverted boolean flag",
|
||||
args: []string{"cmd", "--flag=false"},
|
||||
expectedFlag: false,
|
||||
expectedCustom: true,
|
||||
},
|
||||
{
|
||||
name: "inverted negated boolean flag",
|
||||
args: []string{"cmd", "--no-flag=false"},
|
||||
expected: true,
|
||||
name: "custom inverted boolean flag",
|
||||
args: []string{"cmd", "--custom=false"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: false,
|
||||
},
|
||||
{
|
||||
name: "negated boolean flag",
|
||||
args: []string{"cmd", "--no-flag"},
|
||||
expectedFlag: false,
|
||||
expectedCustom: true,
|
||||
},
|
||||
{
|
||||
name: "custom negated boolean flag",
|
||||
args: []string{"cmd", "--standard"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: false,
|
||||
},
|
||||
{
|
||||
name: "inverted negated boolean flag",
|
||||
args: []string{"cmd", "--no-flag=false"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: true,
|
||||
},
|
||||
{
|
||||
name: "inverted custom negated boolean flag",
|
||||
args: []string{"cmd", "--standard=false"},
|
||||
expectedFlag: true,
|
||||
expectedCustom: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -407,16 +458,47 @@ func TestNegatableFlag(t *testing.T) {
|
||||
p := mustNew(t, &cli)
|
||||
kctx, err := p.Parse(tt.args)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, cli.Cmd.Flag)
|
||||
assert.Equal(t, tt.expectedFlag, cli.Cmd.Flag)
|
||||
assert.Equal(t, tt.expectedCustom, cli.Cmd.Custom)
|
||||
|
||||
err = kctx.Run()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, cli.Cmd.Flag)
|
||||
assert.Equal(t, tt.expectedFlag, cli.Cmd.Flag)
|
||||
assert.Equal(t, tt.expectedCustom, cli.Cmd.Custom)
|
||||
assert.True(t, cli.Cmd.ran)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateNegatableLong(t *testing.T) {
|
||||
cli2 := struct {
|
||||
NoFlag bool
|
||||
Flag bool `negatable:""` // negation duplicates NoFlag
|
||||
}{}
|
||||
_, err := kong.New(&cli2)
|
||||
assert.EqualError(t, err, "<anonymous struct>.Flag: duplicate negation flag --no-flag")
|
||||
|
||||
cli3 := struct {
|
||||
One bool
|
||||
Two bool `negatable:"one"` // negation duplicates Flag2
|
||||
}{}
|
||||
_, err = kong.New(&cli3)
|
||||
assert.EqualError(t, err, "<anonymous struct>.Two: duplicate negation flag --one")
|
||||
}
|
||||
|
||||
func TestDuplicateNegatableFlagsInSubcommands(t *testing.T) {
|
||||
cli2 := struct {
|
||||
Sub struct {
|
||||
Negated bool `negatable:"nope-"`
|
||||
} `cmd:""`
|
||||
Sub2 struct {
|
||||
Negated bool `negatable:"nope-"`
|
||||
} `cmd:""`
|
||||
}{}
|
||||
_, err := kong.New(&cli2)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestExistingNoFlag(t *testing.T) {
|
||||
var cli struct {
|
||||
Cmd struct {
|
||||
@@ -506,6 +588,65 @@ func TestHooks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobalHooks(t *testing.T) {
|
||||
var cli struct {
|
||||
One struct {
|
||||
Two string `kong:"arg,optional"`
|
||||
Three string
|
||||
} `cmd:""`
|
||||
}
|
||||
|
||||
called := []string{}
|
||||
log := func(name string) any {
|
||||
return func(value *kong.Path) error {
|
||||
switch {
|
||||
case value.App != nil:
|
||||
called = append(called, fmt.Sprintf("%s (app)", name))
|
||||
|
||||
case value.Positional != nil:
|
||||
called = append(called, fmt.Sprintf("%s (arg) %s", name, value.Positional.Name))
|
||||
|
||||
case value.Flag != nil:
|
||||
called = append(called, fmt.Sprintf("%s (flag) %s", name, value.Flag.Name))
|
||||
|
||||
case value.Argument != nil:
|
||||
called = append(called, fmt.Sprintf("%s (arg) %s", name, value.Argument.Name))
|
||||
|
||||
case value.Command != nil:
|
||||
called = append(called, fmt.Sprintf("%s (cmd) %s", name, value.Command.Name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
p := mustNew(t, &cli,
|
||||
kong.WithBeforeReset(log("BeforeReset")),
|
||||
kong.WithBeforeResolve(log("BeforeResolve")),
|
||||
kong.WithBeforeApply(log("BeforeApply")),
|
||||
kong.WithAfterApply(log("AfterApply")),
|
||||
)
|
||||
|
||||
_, err := p.Parse([]string{"one", "two", "--three=THREE"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{
|
||||
"BeforeReset (app)",
|
||||
"BeforeReset (cmd) one",
|
||||
"BeforeReset (arg) two",
|
||||
"BeforeReset (flag) three",
|
||||
"BeforeResolve (app)",
|
||||
"BeforeResolve (cmd) one",
|
||||
"BeforeResolve (arg) two",
|
||||
"BeforeResolve (flag) three",
|
||||
"BeforeApply (app)",
|
||||
"BeforeApply (cmd) one",
|
||||
"BeforeApply (arg) two",
|
||||
"BeforeApply (flag) three",
|
||||
"AfterApply (app)",
|
||||
"AfterApply (cmd) one",
|
||||
"AfterApply (arg) two",
|
||||
"AfterApply (flag) three",
|
||||
}, called)
|
||||
}
|
||||
|
||||
func TestShort(t *testing.T) {
|
||||
var cli struct {
|
||||
Bool bool `short:"b"`
|
||||
@@ -593,11 +734,24 @@ func TestSliceWithDisabledSeparator(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMultilineMessage(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
var cli struct{}
|
||||
p := mustNew(t, &cli, kong.Writers(w, w))
|
||||
p.Printf("hello\nworld")
|
||||
assert.Equal(t, "test: hello\n world\n", w.String())
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{"Simple", "hello\nworld", "test: hello\n world\n"},
|
||||
{"WithNewline", "hello\nworld\n", "test: hello\n world\n"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
var cli struct{}
|
||||
p := mustNew(t, &cli, kong.Writers(w, w))
|
||||
p.Printf("%s", test.text)
|
||||
assert.Equal(t, test.want, w.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type cmdWithRun struct {
|
||||
@@ -671,8 +825,8 @@ func TestPassesThroughOriginalCommandError(t *testing.T) {
|
||||
|
||||
func TestInterpolationIntoModel(t *testing.T) {
|
||||
var cli struct {
|
||||
Flag string `default:"${default_value}" help:"Help, I need ${somebody}" enum:"${enum}"`
|
||||
EnumRef string `enum:"a,b" required:"" help:"One of ${enum}"`
|
||||
Flag string `default:"${default_value}" help:"Help, I need ${somebody}" enum:"${enum}" placeholder:"${enum}"`
|
||||
EnumRef string `enum:"a,b" required:"" help:"One of ${enum}" placeholder:"${enum}"`
|
||||
EnvRef string `env:"${env}" help:"God ${env}"`
|
||||
}
|
||||
_, err := kong.New(&cli)
|
||||
@@ -692,7 +846,9 @@ func TestInterpolationIntoModel(t *testing.T) {
|
||||
assert.Equal(t, "Help, I need chickens!", flag.Help)
|
||||
assert.Equal(t, map[string]bool{"a": true, "b": true, "c": true, "d": true}, flag.EnumMap())
|
||||
assert.Equal(t, []string{"a", "b", "c", "d"}, flag.EnumSlice())
|
||||
assert.Equal(t, "a,b,c,d", flag.PlaceHolder)
|
||||
assert.Equal(t, "One of a,b", flag2.Help)
|
||||
assert.Equal(t, "a,b", flag2.PlaceHolder)
|
||||
assert.Equal(t, []string{"SAVE_THE_QUEEN"}, flag3.Envs)
|
||||
assert.Equal(t, "God SAVE_THE_QUEEN", flag3.Help)
|
||||
}
|
||||
@@ -780,6 +936,43 @@ func TestExcludedField(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestExcludeEmbeddedField(t *testing.T) {
|
||||
type Embedded struct {
|
||||
Flag string
|
||||
Excluded string
|
||||
}
|
||||
type Embedded2 struct {
|
||||
Flag2 string
|
||||
Excluded string
|
||||
}
|
||||
var cli struct {
|
||||
Embedded
|
||||
Excluded string `kong:"-"`
|
||||
Embedded2
|
||||
}
|
||||
var cli2 struct {
|
||||
Embedded Embedded `kong:"embed"`
|
||||
Excluded string `kong:"-"`
|
||||
Embedded2 Embedded2 `kong:"embed"`
|
||||
}
|
||||
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--flag=foo"})
|
||||
assert.NoError(t, err)
|
||||
_, err = p.Parse([]string{"--flag-2=foo"})
|
||||
assert.NoError(t, err)
|
||||
_, err = p.Parse([]string{"--excluded=foo"})
|
||||
assert.Error(t, err)
|
||||
|
||||
p = mustNew(t, &cli2)
|
||||
_, err = p.Parse([]string{"--flag=foo"})
|
||||
assert.NoError(t, err)
|
||||
_, err = p.Parse([]string{"--flag-2=foo"})
|
||||
assert.NoError(t, err)
|
||||
_, err = p.Parse([]string{"--excluded=foo"})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUnnamedFieldEmbeds(t *testing.T) {
|
||||
type Embed struct {
|
||||
Flag string
|
||||
@@ -850,15 +1043,6 @@ func TestParentBindings(t *testing.T) {
|
||||
assert.Equal(t, "foo", cli.Command.value)
|
||||
}
|
||||
|
||||
func TestNumericParamErrors(t *testing.T) {
|
||||
var cli struct {
|
||||
Name string
|
||||
}
|
||||
parser := mustNew(t, &cli)
|
||||
_, err := parser.Parse([]string{"--name", "-10"})
|
||||
assert.EqualError(t, err, `--name: expected string value but got "-10" (short flag); perhaps try --name="-10"?`)
|
||||
}
|
||||
|
||||
func TestDefaultValueIsHyphen(t *testing.T) {
|
||||
var cli struct {
|
||||
Flag string `default:"-"`
|
||||
@@ -879,14 +1063,12 @@ func TestDefaultEnumValidated(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvarEnumValidated(t *testing.T) {
|
||||
restore := tempEnv(map[string]string{
|
||||
"FLAG": "invalid",
|
||||
})
|
||||
defer restore()
|
||||
var cli struct {
|
||||
Flag string `env:"FLAG" required:"" enum:"valid"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
p := newEnvParser(t, &cli, envMap{
|
||||
"FLAG": "invalid",
|
||||
})
|
||||
_, err := p.Parse(nil)
|
||||
assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"")
|
||||
}
|
||||
@@ -906,6 +1088,21 @@ func TestXor(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAnd(t *testing.T) {
|
||||
var cli struct {
|
||||
Hello bool `and:"another"`
|
||||
One bool `and:"group"`
|
||||
Two string `and:"group"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--hello", "--one"})
|
||||
assert.EqualError(t, err, "--one and --two must be used together")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--one", "--two=hi", "--hello"})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestXorChild(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `xor:"group"`
|
||||
@@ -923,6 +1120,23 @@ func TestXorChild(t *testing.T) {
|
||||
assert.Error(t, err, "--two and --three can't be used together")
|
||||
}
|
||||
|
||||
func TestAndChild(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `and:"group"`
|
||||
Cmd struct {
|
||||
Two string `and:"group"`
|
||||
Three string `and:"group"`
|
||||
} `cmd`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--one", "cmd", "--two=hi", "--three=hello"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--two=hi", "cmd"})
|
||||
assert.Error(t, err, "--two and --three must be used together")
|
||||
}
|
||||
|
||||
func TestMultiXor(t *testing.T) {
|
||||
var cli struct {
|
||||
Hello bool `xor:"one,two"`
|
||||
@@ -939,6 +1153,57 @@ func TestMultiXor(t *testing.T) {
|
||||
assert.EqualError(t, err, "--hello and --two can't be used together")
|
||||
}
|
||||
|
||||
func TestMultiAnd(t *testing.T) {
|
||||
var cli struct {
|
||||
Hello bool `and:"one,two"`
|
||||
One bool `and:"one"`
|
||||
Two string `and:"two"`
|
||||
}
|
||||
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--hello"})
|
||||
// Split and combine error so messages always will be in the same order
|
||||
// when testing
|
||||
missingMsgs := strings.Split(err.Error(), ", ")
|
||||
sort.Strings(missingMsgs)
|
||||
err = fmt.Errorf("%s", strings.Join(missingMsgs, ", "))
|
||||
assert.EqualError(t, err, "--hello and --one must be used together, --hello and --two must be used together")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--two=foo"})
|
||||
assert.EqualError(t, err, "--hello and --two must be used together")
|
||||
}
|
||||
|
||||
func TestXorAnd(t *testing.T) {
|
||||
var cli struct {
|
||||
Hello bool `xor:"one" and:"two"`
|
||||
One bool `xor:"one"`
|
||||
Two string `and:"two"`
|
||||
}
|
||||
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--hello"})
|
||||
assert.EqualError(t, err, "--hello and --two must be used together")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--one"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--hello", "--one"})
|
||||
assert.EqualError(t, err, "--hello and --one can't be used together, --hello and --two must be used together")
|
||||
}
|
||||
|
||||
func TestOverLappingXorAnd(t *testing.T) {
|
||||
var cli struct {
|
||||
Hello bool `xor:"one" and:"two"`
|
||||
One bool `xor:"one" and:"two"`
|
||||
Two string `xor:"one" and:"two"`
|
||||
}
|
||||
_, err := kong.New(&cli)
|
||||
assert.EqualError(t, err, "invalid xor and combination, one and two overlap with more than one: [hello one two]")
|
||||
}
|
||||
|
||||
func TestXorRequired(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `xor:"one,two" required:""`
|
||||
@@ -959,6 +1224,26 @@ func TestXorRequired(t *testing.T) {
|
||||
assert.EqualError(t, err, "missing flags: --four, --one or --three, --one or --two")
|
||||
}
|
||||
|
||||
func TestAndRequired(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `and:"one,two" required:""`
|
||||
Two bool `and:"one" required:""`
|
||||
Three bool `and:"two"`
|
||||
Four bool `required:""`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--one", "--two", "--three"})
|
||||
assert.EqualError(t, err, "missing flags: --four")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--four"})
|
||||
assert.EqualError(t, err, "missing flags: --one and --three, --one and --two")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{})
|
||||
assert.EqualError(t, err, "missing flags: --four, --one and --three, --one and --two")
|
||||
}
|
||||
|
||||
func TestXorRequiredMany(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `xor:"one" required:""`
|
||||
@@ -978,6 +1263,21 @@ func TestXorRequiredMany(t *testing.T) {
|
||||
assert.EqualError(t, err, "missing flags: --one or --two or --three")
|
||||
}
|
||||
|
||||
func TestAndRequiredMany(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `and:"one" required:""`
|
||||
Two bool `and:"one" required:""`
|
||||
Three bool `and:"one" required:""`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{})
|
||||
assert.EqualError(t, err, "missing flags: --one and --two and --three")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--three"})
|
||||
assert.EqualError(t, err, "missing flags: --one and --two")
|
||||
}
|
||||
|
||||
func TestEnumSequence(t *testing.T) {
|
||||
var cli struct {
|
||||
State []string `enum:"a,b,c" default:"a"`
|
||||
@@ -1040,10 +1340,9 @@ func TestIssue153(t *testing.T) {
|
||||
Ls LsCmd `cmd help:"List paths."`
|
||||
}
|
||||
|
||||
p, revert := newEnvParser(t, &cli, envMap{
|
||||
p := newEnvParser(t, &cli, envMap{
|
||||
"CMD_PATHS": "hello",
|
||||
})
|
||||
defer revert()
|
||||
_, err := p.Parse([]string{"ls"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"hello"}, cli.Ls.Paths)
|
||||
@@ -1272,6 +1571,19 @@ func TestValidateArg(t *testing.T) {
|
||||
assert.EqualError(t, err, "<arg>: flag error")
|
||||
}
|
||||
|
||||
type extendedValidateFlag string
|
||||
|
||||
func (v *extendedValidateFlag) Validate(kctx *kong.Context) error { return errors.New("flag error") }
|
||||
|
||||
func TestExtendedValidateFlag(t *testing.T) {
|
||||
cli := struct {
|
||||
Flag extendedValidateFlag
|
||||
}{}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--flag=one"})
|
||||
assert.EqualError(t, err, "--flag: flag error")
|
||||
}
|
||||
|
||||
func TestPointers(t *testing.T) {
|
||||
cli := struct {
|
||||
Mapped *mappedValue
|
||||
@@ -1297,6 +1609,12 @@ func (d *dynamicCommand) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type commandFunc func() error
|
||||
|
||||
func (cf commandFunc) Run() error {
|
||||
return cf()
|
||||
}
|
||||
|
||||
func TestDynamicCommands(t *testing.T) {
|
||||
cli := struct {
|
||||
One struct{} `cmd:"one"`
|
||||
@@ -1304,9 +1622,12 @@ func TestDynamicCommands(t *testing.T) {
|
||||
help := &strings.Builder{}
|
||||
two := &dynamicCommand{}
|
||||
three := &dynamicCommand{}
|
||||
fourRan := false
|
||||
four := commandFunc(func() error { fourRan = true; return nil })
|
||||
p := mustNew(t, &cli,
|
||||
kong.DynamicCommand("two", "", "", &two),
|
||||
kong.DynamicCommand("three", "", "", three, "hidden"),
|
||||
kong.DynamicCommand("four", "", "", &four),
|
||||
kong.Writers(help, help),
|
||||
kong.Exit(func(int) {}))
|
||||
kctx, err := p.Parse([]string{"two", "--flag=flag"})
|
||||
@@ -1317,8 +1638,15 @@ func TestDynamicCommands(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, two.ran)
|
||||
|
||||
kctx, err = p.Parse([]string{"four"})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, fourRan)
|
||||
err = kctx.Run()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, fourRan)
|
||||
|
||||
_, err = p.Parse([]string{"--help"})
|
||||
assert.EqualError(t, err, `expected one of "one", "two"`)
|
||||
assert.EqualError(t, err, `expected one of "one", "two", "four"`)
|
||||
assert.NotContains(t, help.String(), "three", help.String())
|
||||
}
|
||||
|
||||
@@ -1460,7 +1788,7 @@ func TestOptionReturnsErr(t *testing.T) {
|
||||
func TestEnumValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli interface{}
|
||||
cli any
|
||||
fail bool
|
||||
}{
|
||||
{
|
||||
@@ -1526,6 +1854,89 @@ func TestEnumValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
flag string
|
||||
cmdArgs []string
|
||||
}{
|
||||
{
|
||||
"NoArgs",
|
||||
[]string{},
|
||||
"",
|
||||
[]string(nil),
|
||||
},
|
||||
{
|
||||
"RecognizedFlagAndArgs",
|
||||
[]string{"--flag", "foobar", "something"},
|
||||
"foobar",
|
||||
[]string{"something"},
|
||||
},
|
||||
{
|
||||
"DashDashBetweenArgs",
|
||||
[]string{"foo", "--", "bar"},
|
||||
"",
|
||||
[]string{"foo", "--", "bar"},
|
||||
},
|
||||
{
|
||||
"DashDash",
|
||||
[]string{"--", "--flag", "foobar"},
|
||||
"",
|
||||
[]string{"--", "--flag", "foobar"},
|
||||
},
|
||||
{
|
||||
"UnrecognizedFlagAndArgs",
|
||||
[]string{"--unrecognized-flag", "something"},
|
||||
"",
|
||||
[]string{"--unrecognized-flag", "something"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var cli struct {
|
||||
Flag string
|
||||
Args []string `arg:"" optional:"" passthrough:""`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse(test.args)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.flag, cli.Flag)
|
||||
assert.Equal(t, test.cmdArgs, cli.Args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1651,7 +2062,7 @@ func TestVersionFlagShouldStillWork(t *testing.T) {
|
||||
func TestSliceDecoderHelpfulErrorMsg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli interface{}
|
||||
cli any
|
||||
args []string
|
||||
err string
|
||||
}{
|
||||
@@ -1701,7 +2112,7 @@ func TestSliceDecoderHelpfulErrorMsg(t *testing.T) {
|
||||
func TestMapDecoderHelpfulErrorMsg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli interface{}
|
||||
cli any
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
@@ -2027,3 +2438,269 @@ func TestEnumPtrOmittedNoDefault(t *testing.T) {
|
||||
assert.NotZero(t, ctx)
|
||||
assert.Zero(t, cli.X)
|
||||
}
|
||||
|
||||
func TestIntEnum(t *testing.T) {
|
||||
var cli struct {
|
||||
Enum int `enum:"1,2,3" default:"1"`
|
||||
}
|
||||
k, err := kong.New(&cli)
|
||||
assert.NoError(t, err)
|
||||
_, err = k.Parse([]string{"--enum=123"})
|
||||
assert.EqualError(t, err, `--enum must be one of "1","2","3" but got "123"`)
|
||||
}
|
||||
|
||||
func TestRecursiveVariableExpansion(t *testing.T) {
|
||||
var cli struct {
|
||||
Config string `type:"path" default:"${config_file}" help:"Default: ${default}"`
|
||||
}
|
||||
k := mustNew(t, &cli, kong.Vars{"config_file": "/etc/config"}, kong.Exit(func(int) {}))
|
||||
w := &strings.Builder{}
|
||||
k.Stderr = w
|
||||
k.Stdout = w
|
||||
_, err := k.Parse([]string{"--help"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, w.String(), "Default: /etc/config")
|
||||
}
|
||||
|
||||
type afterRunCLI struct {
|
||||
runCalled bool `kong:"-"`
|
||||
afterRunCalled bool `kong:"-"`
|
||||
}
|
||||
|
||||
func (c *afterRunCLI) Run() error {
|
||||
c.runCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *afterRunCLI) AfterRun() error {
|
||||
c.afterRunCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAfterRun(t *testing.T) {
|
||||
var cli afterRunCLI
|
||||
k := mustNew(t, &cli)
|
||||
kctx, err := k.Parse([]string{})
|
||||
assert.NoError(t, err)
|
||||
err = kctx.Run()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, afterRunCLI{runCalled: true, afterRunCalled: true}, cli)
|
||||
}
|
||||
|
||||
type ProvidedString string
|
||||
|
||||
type providerCLI struct {
|
||||
Sub providerSubCommand `cmd:""`
|
||||
}
|
||||
|
||||
type providerSubCommand struct{}
|
||||
|
||||
func (p *providerCLI) ProvideFoo() (ProvidedString, error) {
|
||||
return ProvidedString("foo"), nil
|
||||
}
|
||||
|
||||
func (p *providerSubCommand) Run(t *testing.T, ps ProvidedString) error {
|
||||
assert.Equal(t, ProvidedString("foo"), ps)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProviderMethods(t *testing.T) {
|
||||
k := mustNew(t, &providerCLI{})
|
||||
kctx, err := k.Parse([]string{"sub"})
|
||||
assert.NoError(t, err)
|
||||
err = kctx.Run(t)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
type EmbeddedCallback struct {
|
||||
Nested NestedCallback `embed:""`
|
||||
|
||||
Embedded bool
|
||||
}
|
||||
|
||||
func (e *EmbeddedCallback) AfterApply() error {
|
||||
e.Embedded = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type taggedEmbeddedCallback struct {
|
||||
NestedCallback
|
||||
|
||||
Tagged bool
|
||||
}
|
||||
|
||||
func (e *taggedEmbeddedCallback) AfterApply() error {
|
||||
e.Tagged = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type NestedCallback struct {
|
||||
nested bool
|
||||
}
|
||||
|
||||
func (n *NestedCallback) AfterApply() error {
|
||||
n.nested = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type EmbeddedRoot struct {
|
||||
EmbeddedCallback
|
||||
Tagged taggedEmbeddedCallback `embed:""`
|
||||
Root bool
|
||||
}
|
||||
|
||||
func (e *EmbeddedRoot) AfterApply() error {
|
||||
e.Root = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestEmbeddedCallbacks(t *testing.T) {
|
||||
actual := &EmbeddedRoot{}
|
||||
k := mustNew(t, actual)
|
||||
_, err := k.Parse(nil)
|
||||
assert.NoError(t, err)
|
||||
expected := &EmbeddedRoot{
|
||||
EmbeddedCallback: EmbeddedCallback{
|
||||
Embedded: true,
|
||||
Nested: NestedCallback{
|
||||
nested: true,
|
||||
},
|
||||
},
|
||||
Tagged: taggedEmbeddedCallback{
|
||||
Tagged: true,
|
||||
NestedCallback: NestedCallback{
|
||||
nested: true,
|
||||
},
|
||||
},
|
||||
Root: true,
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
type applyCalledOnce struct {
|
||||
Dev bool
|
||||
}
|
||||
|
||||
func (c *applyCalledOnce) AfterApply() error {
|
||||
c.Dev = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c applyCalledOnce) Run() error {
|
||||
if c.Dev {
|
||||
return fmt.Errorf("--dev should not be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestApplyCalledOnce(t *testing.T) {
|
||||
cli := &applyCalledOnce{}
|
||||
kctx, err := mustNew(t, cli).Parse([]string{"--dev"})
|
||||
assert.NoError(t, err)
|
||||
err = kctx.Run()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCustomTypeNoEllipsis(t *testing.T) {
|
||||
type CLI struct {
|
||||
Flag []byte `type:"existingfile"`
|
||||
}
|
||||
var cli CLI
|
||||
p := mustNew(t, &cli, kong.Exit(func(int) {}))
|
||||
w := &strings.Builder{}
|
||||
p.Stderr = w
|
||||
p.Stdout = w
|
||||
_, err := p.Parse([]string{"--help"})
|
||||
assert.NoError(t, err)
|
||||
help := w.String()
|
||||
assert.NotContains(t, help, "...")
|
||||
}
|
||||
|
||||
func TestPrefixXorIssue343(t *testing.T) {
|
||||
type DBConfig struct {
|
||||
Password string `help:"Password" xor:"password" optional:""`
|
||||
PasswordFile string `help:"File which content will be used for a password" xor:"password" optional:""`
|
||||
PasswordCommand string `help:"Command to run to retrieve password" xor:"password" optional:""`
|
||||
}
|
||||
|
||||
type SourceTargetConfig struct {
|
||||
Source DBConfig `help:"Database config of source to be copied from" prefix:"source-" xorprefix:"source-" embed:""`
|
||||
Target DBConfig `help:"Database config of source to be copied from" prefix:"target-" xorprefix:"target-" embed:""`
|
||||
}
|
||||
|
||||
cli := SourceTargetConfig{}
|
||||
kctx := mustNew(t, &cli)
|
||||
_, err := kctx.Parse([]string{"--source-password=foo", "--target-password=bar"})
|
||||
assert.NoError(t, err)
|
||||
_, err = kctx.Parse([]string{"--source-password-file=foo", "--source-password=bar"})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIssue483EmptyRootNodeNoRun(t *testing.T) {
|
||||
var emptyCLI struct{}
|
||||
parser, err := kong.New(&emptyCLI)
|
||||
assert.NoError(t, err)
|
||||
|
||||
kctx, err := parser.Parse([]string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = kctx.Run()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no command selected")
|
||||
}
|
||||
|
||||
type providerWithoutErrorCLI struct {
|
||||
}
|
||||
|
||||
func (p *providerWithoutErrorCLI) Run(name string) error {
|
||||
if name == "Bob" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("name %s is not Bob", name)
|
||||
}
|
||||
|
||||
func TestProviderWithoutError(t *testing.T) {
|
||||
k := mustNew(t, &providerWithoutErrorCLI{})
|
||||
kctx, err := k.Parse(nil)
|
||||
assert.NoError(t, err)
|
||||
err = kctx.BindToProvider(func() string { return "Bob" })
|
||||
assert.NoError(t, err)
|
||||
err = kctx.Run()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestParseHyphenParameter(t *testing.T) {
|
||||
type shortFlag struct {
|
||||
Flag string `short:"f"`
|
||||
Other string `short:"o"`
|
||||
Numeric int `short:"n"`
|
||||
}
|
||||
|
||||
t.Run("ShortFlag", func(t *testing.T) {
|
||||
actual := &shortFlag{}
|
||||
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"-f", "-foo"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &shortFlag{Flag: "-foo"}, actual)
|
||||
})
|
||||
|
||||
t.Run("LongFlag", func(t *testing.T) {
|
||||
actual := &shortFlag{}
|
||||
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--flag", "-foo"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &shortFlag{Flag: "-foo"}, actual)
|
||||
})
|
||||
|
||||
t.Run("ParamMatchesFlag", func(t *testing.T) {
|
||||
actual := &shortFlag{}
|
||||
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--flag", "-oo"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &shortFlag{Flag: "-oo"}, actual)
|
||||
})
|
||||
|
||||
t.Run("NegativeNumber", func(t *testing.T) {
|
||||
actual := &shortFlag{}
|
||||
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--numeric", "-10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &shortFlag{Numeric: -10}, actual)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user