Make negatable flag name customisable (#439)

* fix: Check if negatable duplicates another flag

Add a check for flags with the `negatable` option if the negative flag
conflicts with another tag, such as:

    Flag   bool `negatable:""`
    NoFlag bool

The flag `--no-flag` is ambiguous in this scenario.

* feat: Make negatable flag name customisable

Allow a value on the `negatable` tag to specify a flag name to use for
negation instead of using `--no-<flag-name>` as the flag.

e.g.

    Approve bool `default:"true",negatable:"deny"`

This example will allow `--deny` to set the `Approve` field to false.
This commit is contained in:
Cam Hutchison
2024-09-10 21:02:37 +10:00
committed by GitHub
parent 7d84b95294
commit 4ecb53599b
8 changed files with 128 additions and 49 deletions
+71 -22
View File
@@ -357,8 +357,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 {
@@ -368,34 +369,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 {
@@ -408,16 +439,34 @@ 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 TestExistingNoFlag(t *testing.T) {
var cli struct {
Cmd struct {