From a7514bab625533de40c6492abf56bf8444a4d4cc Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Thu, 12 Jan 2023 13:50:57 -0600 Subject: [PATCH 01/11] init inverse falg --- flag_bool_with_inverse.go | 153 +++++++++++++++++++++++++++++++++ flag_bool_with_inverse_test.go | 114 ++++++++++++++++++++++++ godoc-current.txt | 26 ++++++ 3 files changed, 293 insertions(+) create mode 100644 flag_bool_with_inverse.go create mode 100644 flag_bool_with_inverse_test.go diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go new file mode 100644 index 0000000000..d7acc231c0 --- /dev/null +++ b/flag_bool_with_inverse.go @@ -0,0 +1,153 @@ +package cli + +import ( + "flag" + "fmt" +) + +type BoolWithInverseFlag interface { + fmt.Stringer + + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) error + + // All possible names for this flag + Names() []string + + // Whether the flag has been set or not + IsSet() bool + + // Will return the original flag and the inverse flag + Flags() []Flag + + // Will return the value as it relates to the boolFlag provided + Value() bool +} + +type boolWithInverse struct { + fmt.Stringer + positiveFlag *BoolFlag + negativeFlag *BoolFlag + + action func(*Context, bool) error + + posDest *bool + negDest *bool + + posCount *int + negCount *int +} + +func (s *boolWithInverse) Flags() []Flag { + return []Flag{s.positiveFlag, s.negativeFlag} +} + +func (s *boolWithInverse) IsSet() bool { + return *s.negDest || *s.posDest +} + +func (s *boolWithInverse) Value() bool { + return *s.posDest +} + +func (s *boolWithInverse) RunAction(ctx *Context) error { + if *s.negDest && *s.posDest { + return fmt.Errorf("cannot set both flags `--%s` and `--%s`", s.positiveFlag.Name, s.negativeFlag.Name) + } + + if s.positiveFlag.Action != nil { + return s.positiveFlag.Action(ctx, *s.posDest) + } + + return nil +} + +/* +NewBoolWithInverse creates a new BoolFlag that has an inverse flag + +consider a bool flag `--env`, there is no way to set it to false +this function allows you to set `--env` or `--no-env` and in the command action +it can be determined that BoolWithInverseFlag.IsSet() +*/ +func NewBoolWithInverse(flag BoolFlag) BoolWithInverseFlag { + special := &boolWithInverse{ + negDest: new(bool), + negCount: new(int), + } + + if flag.Destination != nil { + special.posDest = flag.Destination + } else { + special.posDest = new(bool) + } + + if flag.Count != nil { + special.posCount = flag.Count + } else { + special.posCount = new(int) + } + + special.positiveFlag = &flag + special.positiveFlag.Destination = special.posDest + special.positiveFlag.Count = special.posCount + + // Append `no-` to each alias + var inverseAliases []string + if len(flag.Aliases) > 0 { + inverseAliases = make([]string, len(flag.Aliases)) + for idx, alias := range flag.Aliases { + inverseAliases[idx] = "no-" + alias + } + } + + special.negativeFlag = &BoolFlag{ + Name: "no-" + flag.Name, + Category: flag.Category, + DefaultText: flag.DefaultText, + FilePath: flag.FilePath, + Usage: flag.Usage, + Required: flag.Required, + Hidden: flag.Hidden, + HasBeenSet: flag.HasBeenSet, + Value: flag.Value, + Aliases: inverseAliases, + + Destination: special.negDest, + Count: special.negCount, + } + + if len(flag.EnvVars) > 0 { + // TODO we need to append to the action to reverse the value of the env vars + special.negativeFlag.EnvVars = append([]string{}, flag.EnvVars...) + } + + return special +} + +func (s *boolWithInverse) Apply(set *flag.FlagSet) error { + if err := s.positiveFlag.Apply(set); err != nil { + return err + } + + if err := s.negativeFlag.Apply(set); err != nil { + return err + } + + return nil +} + +func (s *boolWithInverse) Names() []string { + if *s.negDest { + return s.negativeFlag.Names() + } + + if *s.posDest { + return s.positiveFlag.Names() + } + + return append(s.negativeFlag.Names(), s.positiveFlag.Names()...) +} + +func (s *boolWithInverse) String() string { + return fmt.Sprintf("%s || %s", s.positiveFlag.String(), s.negativeFlag.String()) +} diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go new file mode 100644 index 0000000000..6341e24652 --- /dev/null +++ b/flag_bool_with_inverse_test.go @@ -0,0 +1,114 @@ +package cli_test + +import ( + "fmt" + "testing" + + "github.com/urfave/cli/v3" +) + +type boolWithInverseTestCase struct { + args []string + toBeSet bool + value bool +} + +func (test boolWithInverseTestCase) Run() error { + flagWithInverse := cli.NewBoolWithInverse(cli.BoolFlag{ + Name: "env", + }) + + app := cli.App{ + Flags: []cli.Flag{ + flagWithInverse, + }, + Action: func(ctx *cli.Context) error { + return nil + }, + } + + err := app.Run(append([]string{"prog"}, test.args...)) + if err != nil { + return err + } + + if flagWithInverse.IsSet() != test.toBeSet { + return fmt.Errorf("flag should be set %t, but got %t", test.toBeSet, flagWithInverse.IsSet()) + } + + if flagWithInverse.Value() != test.value { + return fmt.Errorf("flag value should be %t, but got %t", test.value, flagWithInverse.Value()) + } + + return nil +} + +func TestBoolWithInverse(t *testing.T) { + err := boolWithInverseTestCase{ + args: []string{"--no-env"}, + toBeSet: true, + value: false, + }.Run() + if err != nil { + t.Error(err) + return + } + + err = boolWithInverseTestCase{ + args: []string{"--env"}, + toBeSet: true, + value: true, + }.Run() + if err != nil { + t.Error(err) + return + } + + err = boolWithInverseTestCase{ + toBeSet: false, + value: false, + }.Run() + if err != nil { + t.Error(err) + return + } + + expectedError := fmt.Errorf("cannot set both flags `--env` and `--no-env`") + err = boolWithInverseTestCase{ + args: []string{"--env", "--no-env"}, + }.Run() + if err == nil || err.Error() != expectedError.Error() { + t.Errorf("expected error %q, but got %q", expectedError, err) + return + } +} + +func ExampleNewBoolWithInverse() { + flagWithInverse := cli.NewBoolWithInverse(cli.BoolFlag{ + Name: "env", + }) + + app := cli.App{ + Flags: []cli.Flag{ + flagWithInverse, + }, + Action: func(ctx *cli.Context) error { + if flagWithInverse.IsSet() { + if flagWithInverse.Value() { + fmt.Println("env is set") + } else { + fmt.Println("no-env is set") + } + } + + return nil + }, + } + + _ = app.Run([]string{"prog", "--no-env"}) + _ = app.Run([]string{"prog", "--env"}) + + // Output: + // no-env is set + // env is set +} diff --git a/godoc-current.txt b/godoc-current.txt index e5d2116983..b868f8e2cb 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -490,6 +490,32 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type BoolWithInverseFlag interface { + fmt.Stringer + + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) error + + // All possible names for this flag + Names() []string + + // Whether the flag has been set or not + IsSet() bool + + // Will return the original flag and the inverse flag + Flags() []Flag + + // Will return the value as it relates to the boolFlag provided + Value() bool +} + +func NewBoolWithInverse(flag BoolFlag) BoolWithInverseFlag + NewBoolWithInverse creates a new BoolFlag that has an inverse flag + + consider a bool flag `--env`, there is no way to set it to false this + function allows you to set `--env` or `--no-env` and in the command action + it can be determined that BoolWithInverseFlag.IsSet() + type CategorizableFlag interface { VisibleFlag From 01839eba254925c96dcfecb2cc58956619382462 Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Thu, 12 Jan 2023 14:17:36 -0600 Subject: [PATCH 02/11] Implement ctx.IsSet("val") --- flag_bool_with_inverse.go | 12 ++++++++++++ flag_bool_with_inverse_test.go | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index d7acc231c0..3ee5d9cba6 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -55,6 +55,18 @@ func (s *boolWithInverse) RunAction(ctx *Context) error { return fmt.Errorf("cannot set both flags `--%s` and `--%s`", s.positiveFlag.Name, s.negativeFlag.Name) } + if *s.negDest { + err := ctx.Set(s.positiveFlag.Name, "false") + if err != nil { + return err + } + } else if *s.posDest { + err := ctx.Set(s.negativeFlag.Name, "false") + if err != nil { + return err + } + } + if s.positiveFlag.Action != nil { return s.positiveFlag.Action(ctx, *s.posDest) } diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go index 6341e24652..b071c3dd0d 100644 --- a/flag_bool_with_inverse_test.go +++ b/flag_bool_with_inverse_test.go @@ -18,11 +18,13 @@ func (test boolWithInverseTestCase) Run() error { Name: "env", }) + var isSet bool app := cli.App{ Flags: []cli.Flag{ flagWithInverse, }, Action: func(ctx *cli.Context) error { + isSet = ctx.IsSet("env") return nil }, } @@ -32,7 +34,7 @@ func (test boolWithInverseTestCase) Run() error { return err } - if flagWithInverse.IsSet() != test.toBeSet { + if flagWithInverse.IsSet() != test.toBeSet || isSet != test.toBeSet { return fmt.Errorf("flag should be set %t, but got %t", test.toBeSet, flagWithInverse.IsSet()) } From b7e4a0dc4f4738e634fc583c876cab19b4f2bbde Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Thu, 12 Jan 2023 23:17:00 -0600 Subject: [PATCH 03/11] Remove interface, set up the flag to act exactly as all other flags act. Added more tests and solved logical issues. --- .air.toml | 37 +++++ flag_bool_with_inverse.go | 142 +++++++++-------- flag_bool_with_inverse_test.go | 280 ++++++++++++++++++++++++++++----- godoc-current.txt | 40 ++--- tmp/build-errors.log | 1 + 5 files changed, 375 insertions(+), 125 deletions(-) create mode 100644 .air.toml create mode 100644 tmp/build-errors.log diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000000..bf2687fabf --- /dev/null +++ b/.air.toml @@ -0,0 +1,37 @@ +root = "." +testdata_dir = "" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/air-main" + cmd = "go test -v --run Inverse" + delay = 1 + exclude_dir = ["assets", "tmp", "vendor"] + exclude_file = [] + exclude_regex = [] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + kill_delay = "0s" + log = "build-errors.log" + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index 3ee5d9cba6..00ed0a9cb5 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -3,140 +3,140 @@ package cli import ( "flag" "fmt" + "strings" ) -type BoolWithInverseFlag interface { - fmt.Stringer - - // Apply Flag settings to the given flag set - Apply(*flag.FlagSet) error - - // All possible names for this flag - Names() []string +var ( + DefaultInverseBoolPrefix = "no-" +) - // Whether the flag has been set or not - IsSet() bool +type BoolWithInverseFlag struct { + // The BoolFlag which the positive and negative flags are generated from + *BoolFlag - // Will return the original flag and the inverse flag - Flags() []Flag + // The prefix used to indicate a negative value + // Default: `env` becomes `no-env` + InversePrefix string - // Will return the value as it relates to the boolFlag provided - Value() bool -} - -type boolWithInverse struct { - fmt.Stringer positiveFlag *BoolFlag negativeFlag *BoolFlag action func(*Context, bool) error - posDest *bool - negDest *bool - + // pointers obtained from the embedded bool flag + posDest *bool posCount *int + + negDest *bool negCount *int } -func (s *boolWithInverse) Flags() []Flag { +func (s *BoolWithInverseFlag) Flags() []Flag { return []Flag{s.positiveFlag, s.negativeFlag} } -func (s *boolWithInverse) IsSet() bool { - return *s.negDest || *s.posDest +func (s *BoolWithInverseFlag) IsSet() bool { + return (*s.posCount > 0 || *s.negCount > 0) || (s.positiveFlag.IsSet() || s.negativeFlag.IsSet()) } -func (s *boolWithInverse) Value() bool { +func (s *BoolWithInverseFlag) Value() bool { return *s.posDest } -func (s *boolWithInverse) RunAction(ctx *Context) error { +func (s *BoolWithInverseFlag) RunAction(ctx *Context) error { if *s.negDest && *s.posDest { return fmt.Errorf("cannot set both flags `--%s` and `--%s`", s.positiveFlag.Name, s.negativeFlag.Name) } if *s.negDest { - err := ctx.Set(s.positiveFlag.Name, "false") + err := ctx.Set(s.negativeFlag.Name, "true") if err != nil { return err } } else if *s.posDest { - err := ctx.Set(s.negativeFlag.Name, "false") + err := ctx.Set(s.positiveFlag.Name, "true") if err != nil { return err } } - if s.positiveFlag.Action != nil { - return s.positiveFlag.Action(ctx, *s.posDest) + if s.BoolFlag.Action != nil { + return s.BoolFlag.Action(ctx, s.Value()) } return nil } /* -NewBoolWithInverse creates a new BoolFlag that has an inverse flag +initialize creates a new BoolFlag that has an inverse flag consider a bool flag `--env`, there is no way to set it to false this function allows you to set `--env` or `--no-env` and in the command action it can be determined that BoolWithInverseFlag.IsSet() */ -func NewBoolWithInverse(flag BoolFlag) BoolWithInverseFlag { - special := &boolWithInverse{ - negDest: new(bool), - negCount: new(int), - } +func (parent *BoolWithInverseFlag) initialize() { + child := parent.BoolFlag - if flag.Destination != nil { - special.posDest = flag.Destination + parent.negDest = new(bool) + parent.negCount = new(int) + if child.Destination != nil { + parent.posDest = child.Destination } else { - special.posDest = new(bool) + parent.posDest = new(bool) } - if flag.Count != nil { - special.posCount = flag.Count + if child.Count != nil { + parent.posCount = child.Count } else { - special.posCount = new(int) + parent.posCount = new(int) } - special.positiveFlag = &flag - special.positiveFlag.Destination = special.posDest - special.positiveFlag.Count = special.posCount + parent.positiveFlag = child + parent.positiveFlag.Destination = parent.posDest + parent.positiveFlag.Count = parent.posCount + + if parent.InversePrefix == "" { + parent.InversePrefix = DefaultInverseBoolPrefix + } // Append `no-` to each alias var inverseAliases []string - if len(flag.Aliases) > 0 { - inverseAliases = make([]string, len(flag.Aliases)) - for idx, alias := range flag.Aliases { - inverseAliases[idx] = "no-" + alias + if len(child.Aliases) > 0 { + inverseAliases = make([]string, len(child.Aliases)) + for idx, alias := range child.Aliases { + inverseAliases[idx] = parent.InversePrefix + alias } } - special.negativeFlag = &BoolFlag{ - Name: "no-" + flag.Name, - Category: flag.Category, - DefaultText: flag.DefaultText, - FilePath: flag.FilePath, - Usage: flag.Usage, - Required: flag.Required, - Hidden: flag.Hidden, - HasBeenSet: flag.HasBeenSet, - Value: flag.Value, + parent.negativeFlag = &BoolFlag{ + Name: parent.InversePrefix + child.Name, + Category: child.Category, + DefaultText: child.DefaultText, + FilePath: child.FilePath, + Usage: child.Usage, + Required: child.Required, + Hidden: child.Hidden, + HasBeenSet: child.HasBeenSet, + Value: child.Value, Aliases: inverseAliases, - Destination: special.negDest, - Count: special.negCount, + Destination: parent.negDest, + Count: parent.negCount, } - if len(flag.EnvVars) > 0 { - // TODO we need to append to the action to reverse the value of the env vars - special.negativeFlag.EnvVars = append([]string{}, flag.EnvVars...) + if len(child.EnvVars) > 0 { + parent.negativeFlag.EnvVars = make([]string, len(child.EnvVars)) + for idx, envVar := range child.EnvVars { + parent.negativeFlag.EnvVars[idx] = strings.ToUpper(parent.InversePrefix) + envVar + } } - return special + return } -func (s *boolWithInverse) Apply(set *flag.FlagSet) error { +func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error { + s.initialize() + if err := s.positiveFlag.Apply(set); err != nil { return err } @@ -148,7 +148,7 @@ func (s *boolWithInverse) Apply(set *flag.FlagSet) error { return nil } -func (s *boolWithInverse) Names() []string { +func (s *BoolWithInverseFlag) Names() []string { if *s.negDest { return s.negativeFlag.Names() } @@ -160,6 +160,12 @@ func (s *boolWithInverse) Names() []string { return append(s.negativeFlag.Names(), s.positiveFlag.Names()...) } -func (s *boolWithInverse) String() string { +// Example for BoolFlag{Name: "env"} +// --env (default: false) || --no-env (default: false) +func (s *BoolWithInverseFlag) String() string { + if s.positiveFlag == nil { + return s.BoolFlag.String() + } + return fmt.Sprintf("%s || %s", s.positiveFlag.String(), s.negativeFlag.String()) } diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go index b071c3dd0d..ed6548e97f 100644 --- a/flag_bool_with_inverse_test.go +++ b/flag_bool_with_inverse_test.go @@ -2,31 +2,33 @@ package cli_test import ( "fmt" + "os" "testing" "github.com/urfave/cli/v3" ) +var ( + bothEnvFlagsAreSetError = fmt.Errorf("cannot set both flags `--env` and `--no-env`") +) + type boolWithInverseTestCase struct { args []string toBeSet bool value bool + err error + envVars map[string]string } -func (test boolWithInverseTestCase) Run() error { - flagWithInverse := cli.NewBoolWithInverse(cli.BoolFlag{ - Name: "env", - }) - - var isSet bool +func (test boolWithInverseTestCase) Run(flagWithInverse *cli.BoolWithInverseFlag) error { app := cli.App{ - Flags: []cli.Flag{ - flagWithInverse, - }, - Action: func(ctx *cli.Context) error { - isSet = ctx.IsSet("env") - return nil - }, + Flags: []cli.Flag{flagWithInverse}, + Action: func(ctx *cli.Context) error { return nil }, + } + + for key, val := range test.envVars { + os.Setenv(key, val) + defer os.Unsetenv(key) } err := app.Run(append([]string{"prog"}, test.args...)) @@ -34,7 +36,7 @@ func (test boolWithInverseTestCase) Run() error { return err } - if flagWithInverse.IsSet() != test.toBeSet || isSet != test.toBeSet { + if flagWithInverse.IsSet() != test.toBeSet { return fmt.Errorf("flag should be set %t, but got %t", test.toBeSet, flagWithInverse.IsSet()) } @@ -45,50 +47,247 @@ func (test boolWithInverseTestCase) Run() error { return nil } -func TestBoolWithInverse(t *testing.T) { - err := boolWithInverseTestCase{ - args: []string{"--no-env"}, - toBeSet: true, - value: false, - }.Run() +func runTests(newFlagMethod func() *cli.BoolWithInverseFlag, cases []boolWithInverseTestCase) error { + for _, test := range cases { + flag := newFlagMethod() + + err := test.Run(flag) + if err != nil && test.err == nil { + return err + } + + if err == nil && test.err != nil { + return fmt.Errorf("expected error %q, but got nil", test.err) + } + + if err != nil && test.err != nil && err.Error() != test.err.Error() { + return fmt.Errorf("expected error %q, but got %q", test.err, err) + } + + } + + return nil +} + +func TestBoolWithInverseBasic(t *testing.T) { + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + }, + } + } + + testCases := []boolWithInverseTestCase{ + { + args: []string{"--no-env"}, + toBeSet: true, + value: false, + }, + { + args: []string{"--env"}, + toBeSet: true, + value: true, + }, + { + toBeSet: false, + value: false, + }, + { + args: []string{"--env", "--no-env"}, + err: bothEnvFlagsAreSetError, + }, + } + + err := runTests(flagMethod, testCases) + if err != nil { + t.Error(err) + return + } +} + +func TestBoolWithInverseAction(t *testing.T) { + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + + // Setting env to the opposite to test flag Action is working as intended + Action: func(ctx *cli.Context, value bool) error { + if value { + return ctx.Set("env", "false") + } + + return ctx.Set("env", "true") + }, + }, + } + } + + testCases := []boolWithInverseTestCase{ + { + args: []string{"--no-env"}, + toBeSet: true, + value: true, + }, + { + args: []string{"--env"}, + toBeSet: true, + value: false, + }, + + // This test is not inverse because the flag action is never called + { + toBeSet: false, + value: false, + }, + { + args: []string{"--env", "--no-env"}, + err: bothEnvFlagsAreSetError, + }, + } + + err := runTests(flagMethod, testCases) if err != nil { t.Error(err) return } +} + +func TestBoolWithInverseAlias(t *testing.T) { + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + Aliases: []string{"e", "do-env"}, + }, + } + } + + testCases := []boolWithInverseTestCase{ + { + args: []string{"--no-e"}, + toBeSet: true, + value: false, + }, + { + args: []string{"--e"}, + toBeSet: true, + value: true, + }, + { + toBeSet: false, + value: false, + }, + { + args: []string{"--do-env", "--no-do-env"}, + err: bothEnvFlagsAreSetError, + }, + } - err = boolWithInverseTestCase{ - args: []string{"--env"}, - toBeSet: true, - value: true, - }.Run() + err := runTests(flagMethod, testCases) if err != nil { t.Error(err) return } +} + +func TestBoolWithInverseEnvVars(t *testing.T) { + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + EnvVars: []string{"ENV"}, + }, + } + } - err = boolWithInverseTestCase{ - toBeSet: false, - value: false, - }.Run() + testCases := []boolWithInverseTestCase{ + { + toBeSet: true, + value: false, + envVars: map[string]string{ + "NO-ENV": "true", + }, + }, + { + toBeSet: true, + value: true, + envVars: map[string]string{ + "ENV": "true", + }, + }, + { + toBeSet: true, + value: false, + envVars: map[string]string{ + "ENV": "false", + }, + }, + { + toBeSet: false, + value: false, + }, + { + err: bothEnvFlagsAreSetError, + envVars: map[string]string{ + "ENV": "true", + "NO-ENV": "true", + }, + }, + } + + err := runTests(flagMethod, testCases) if err != nil { t.Error(err) return } +} + +func TestBoolWithInverseWithPrefix(t *testing.T) { + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + }, + InversePrefix: "without-", + } + } - expectedError := fmt.Errorf("cannot set both flags `--env` and `--no-env`") - err = boolWithInverseTestCase{ - args: []string{"--env", "--no-env"}, - }.Run() - if err == nil || err.Error() != expectedError.Error() { - t.Errorf("expected error %q, but got %q", expectedError, err) + testCases := []boolWithInverseTestCase{ + { + args: []string{"--without-env"}, + toBeSet: true, + value: false, + }, + { + args: []string{"--env"}, + toBeSet: true, + value: true, + }, + { + toBeSet: false, + value: false, + }, + { + args: []string{"--env", "--without-env"}, + err: fmt.Errorf("cannot set both flags `--env` and `--without-env`"), + }, + } + + err := runTests(flagMethod, testCases) + if err != nil { + t.Error(err) return } } -func ExampleNewBoolWithInverse() { - flagWithInverse := cli.NewBoolWithInverse(cli.BoolFlag{ - Name: "env", - }) +func ExampleBoolWithInverseFlag() { + flagWithInverse := &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + }, + } app := cli.App{ Flags: []cli.Flag{ @@ -110,7 +309,10 @@ func ExampleNewBoolWithInverse() { _ = app.Run([]string{"prog", "--no-env"}) _ = app.Run([]string{"prog", "--env"}) + fmt.Println("flags:", len(flagWithInverse.Flags())) + // Output: // no-env is set // env is set + // flags: 2 } diff --git a/godoc-current.txt b/godoc-current.txt index b868f8e2cb..f6f0d5d34a 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -78,6 +78,9 @@ OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` uses text/template to render templates. You can render custom help text by setting this variable. +var ( + DefaultInverseBoolPrefix = "no-" +) var ErrWriter io.Writer = os.Stderr ErrWriter is used to write errors to the user. This can be anything implementing the io.Writer interface and defaults to os.Stderr. @@ -490,31 +493,32 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false -type BoolWithInverseFlag interface { - fmt.Stringer +type BoolWithInverseFlag struct { + // The BoolFlag which the positive and negative flags are generated from + *BoolFlag - // Apply Flag settings to the given flag set - Apply(*flag.FlagSet) error + // The prefix used to indicate a negative value + // Default: `env` becomes `no-env` + InversePrefix string - // All possible names for this flag - Names() []string + // Has unexported fields. +} - // Whether the flag has been set or not - IsSet() bool +func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error - // Will return the original flag and the inverse flag - Flags() []Flag +func (s *BoolWithInverseFlag) Flags() []Flag - // Will return the value as it relates to the boolFlag provided - Value() bool -} +func (s *BoolWithInverseFlag) IsSet() bool + +func (s *BoolWithInverseFlag) Names() []string + +func (s *BoolWithInverseFlag) RunAction(ctx *Context) error -func NewBoolWithInverse(flag BoolFlag) BoolWithInverseFlag - NewBoolWithInverse creates a new BoolFlag that has an inverse flag +func (s *BoolWithInverseFlag) String() string + Example for BoolFlag{Name: "env"} --env (default: false) || --no-env + (default: false) - consider a bool flag `--env`, there is no way to set it to false this - function allows you to set `--env` or `--no-env` and in the command action - it can be determined that BoolWithInverseFlag.IsSet() +func (s *BoolWithInverseFlag) Value() bool type CategorizableFlag interface { VisibleFlag diff --git a/tmp/build-errors.log b/tmp/build-errors.log new file mode 100644 index 0000000000..571fc8e53f --- /dev/null +++ b/tmp/build-errors.log @@ -0,0 +1 @@ +exit status 1exit status 1exit status 1 \ No newline at end of file From a04b84430fec2329e1ba07453d4a0e13f9368efe Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Thu, 12 Jan 2023 23:21:25 -0600 Subject: [PATCH 04/11] Remove air artifact --- tmp/build-errors.log | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tmp/build-errors.log diff --git a/tmp/build-errors.log b/tmp/build-errors.log deleted file mode 100644 index 571fc8e53f..0000000000 --- a/tmp/build-errors.log +++ /dev/null @@ -1 +0,0 @@ -exit status 1exit status 1exit status 1 \ No newline at end of file From 51a01b5b7b12b27acbd926fc90d3ef9f35a13575 Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Thu, 12 Jan 2023 23:22:30 -0600 Subject: [PATCH 05/11] Remove other air artifact --- .air.toml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .air.toml diff --git a/.air.toml b/.air.toml deleted file mode 100644 index bf2687fabf..0000000000 --- a/.air.toml +++ /dev/null @@ -1,37 +0,0 @@ -root = "." -testdata_dir = "" -tmp_dir = "tmp" - -[build] - args_bin = [] - bin = "./tmp/air-main" - cmd = "go test -v --run Inverse" - delay = 1 - exclude_dir = ["assets", "tmp", "vendor"] - exclude_file = [] - exclude_regex = [] - exclude_unchanged = false - follow_symlink = false - full_bin = "" - include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html"] - kill_delay = "0s" - log = "build-errors.log" - send_interrupt = false - stop_on_error = true - -[color] - app = "" - build = "yellow" - main = "magenta" - runner = "green" - watcher = "cyan" - -[log] - time = false - -[misc] - clean_on_exit = false - -[screen] - clear_on_rebuild = false From 77ef5bebe33c1a82ade6ba3c8ee8a087de51f2be Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Mon, 16 Jan 2023 19:09:06 -0600 Subject: [PATCH 06/11] Clean up from merge, only initialize once, and add Required test --- flag_bool_with_inverse.go | 30 +++++++++++++++----------- flag_bool_with_inverse_test.go | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index 00ed0a9cb5..da3f8d670a 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -21,8 +21,6 @@ type BoolWithInverseFlag struct { positiveFlag *BoolFlag negativeFlag *BoolFlag - action func(*Context, bool) error - // pointers obtained from the embedded bool flag posDest *bool posCount *int @@ -85,15 +83,15 @@ func (parent *BoolWithInverseFlag) initialize() { parent.posDest = new(bool) } - if child.Count != nil { - parent.posCount = child.Count + if child.Config.Count != nil { + parent.posCount = child.Config.Count } else { parent.posCount = new(int) } parent.positiveFlag = child parent.positiveFlag.Destination = parent.posDest - parent.positiveFlag.Count = parent.posCount + parent.positiveFlag.Config.Count = parent.posCount if parent.InversePrefix == "" { parent.InversePrefix = DefaultInverseBoolPrefix @@ -107,21 +105,27 @@ func (parent *BoolWithInverseFlag) initialize() { inverseAliases[idx] = parent.InversePrefix + alias } } - parent.negativeFlag = &BoolFlag{ Name: parent.InversePrefix + child.Name, Category: child.Category, DefaultText: child.DefaultText, - FilePath: child.FilePath, + FilePaths: append([]string{}, child.FilePaths...), Usage: child.Usage, Required: child.Required, Hidden: child.Hidden, - HasBeenSet: child.HasBeenSet, + Persistent: child.Persistent, Value: child.Value, - Aliases: inverseAliases, - Destination: parent.negDest, - Count: parent.negCount, + Aliases: inverseAliases, + TakesFile: child.TakesFile, + Config: BoolConfig{ + Count: parent.negCount, + }, + OnlyOnce: child.OnlyOnce, + hasBeenSet: child.hasBeenSet, + applied: child.applied, + creator: boolValue{}, + value: child.value, } if len(child.EnvVars) > 0 { @@ -135,7 +139,9 @@ func (parent *BoolWithInverseFlag) initialize() { } func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error { - s.initialize() + if s.positiveFlag == nil { + s.initialize() + } if err := s.positiveFlag.Apply(set); err != nil { return err diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go index ed6548e97f..905664262a 100644 --- a/flag_bool_with_inverse_test.go +++ b/flag_bool_with_inverse_test.go @@ -282,6 +282,45 @@ func TestBoolWithInverseWithPrefix(t *testing.T) { } } +func TestBoolWithInverseRequired(t *testing.T) { + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + Required: true, + }, + } + } + + testCases := []boolWithInverseTestCase{ + { + args: []string{"--no-env"}, + toBeSet: true, + value: false, + }, + { + args: []string{"--env"}, + toBeSet: true, + value: true, + }, + { + toBeSet: false, + value: false, + err: fmt.Errorf(`Required flag "env" not set`), + }, + { + args: []string{"--env", "--no-env"}, + err: bothEnvFlagsAreSetError, + }, + } + + err := runTests(flagMethod, testCases) + if err != nil { + t.Error(err) + return + } +} + func ExampleBoolWithInverseFlag() { flagWithInverse := &cli.BoolWithInverseFlag{ BoolFlag: &cli.BoolFlag{ From b7d423eae15e9e77e50c50ac590d9e9b38cf596d Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Mon, 16 Jan 2023 19:20:12 -0600 Subject: [PATCH 07/11] v3 diff allowance --- testdata/godoc-v3.x.txt | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 9048b9a05a..f3308c9d4b 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -79,6 +79,9 @@ OPTIONS:{{template "visibleFlagTemplate" .}}{{end}} uses text/template to render templates. You can render custom help text by setting this variable. +var ( + DefaultInverseBoolPrefix = "no-" +) var ErrWriter io.Writer = os.Stderr ErrWriter is used to write errors to the user. This can be anything implementing the io.Writer interface and defaults to os.Stderr. @@ -416,6 +419,33 @@ type BoolConfig struct { type BoolFlag = FlagBase[bool, BoolConfig, boolValue] +type BoolWithInverseFlag struct { + // The BoolFlag which the positive and negative flags are generated from + *BoolFlag + + // The prefix used to indicate a negative value + // Default: `env` becomes `no-env` + InversePrefix string + + // Has unexported fields. +} + +func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error + +func (s *BoolWithInverseFlag) Flags() []Flag + +func (s *BoolWithInverseFlag) IsSet() bool + +func (s *BoolWithInverseFlag) Names() []string + +func (s *BoolWithInverseFlag) RunAction(ctx *Context) error + +func (s *BoolWithInverseFlag) String() string + Example for BoolFlag{Name: "env"} --env (default: false) || --no-env + (default: false) + +func (s *BoolWithInverseFlag) Value() bool + type CategorizableFlag interface { VisibleFlag From 74e69a50480be735e2702637041c44d9938dcce4 Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Mon, 16 Jan 2023 21:15:01 -0600 Subject: [PATCH 08/11] test commit From af867aab510b54511397fde06dd2aa7dc2465b7b Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Mon, 16 Jan 2023 21:28:04 -0600 Subject: [PATCH 09/11] Add pre-initialization Names() handling and test --- flag_bool_with_inverse.go | 41 ++++++++++++++++++++++------------ flag_bool_with_inverse_test.go | 25 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index da3f8d670a..674f63d4a4 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -93,20 +93,7 @@ func (parent *BoolWithInverseFlag) initialize() { parent.positiveFlag.Destination = parent.posDest parent.positiveFlag.Config.Count = parent.posCount - if parent.InversePrefix == "" { - parent.InversePrefix = DefaultInverseBoolPrefix - } - - // Append `no-` to each alias - var inverseAliases []string - if len(child.Aliases) > 0 { - inverseAliases = make([]string, len(child.Aliases)) - for idx, alias := range child.Aliases { - inverseAliases[idx] = parent.InversePrefix + alias - } - } parent.negativeFlag = &BoolFlag{ - Name: parent.InversePrefix + child.Name, Category: child.Category, DefaultText: child.DefaultText, FilePaths: append([]string{}, child.FilePaths...), @@ -116,7 +103,6 @@ func (parent *BoolWithInverseFlag) initialize() { Persistent: child.Persistent, Value: child.Value, Destination: parent.negDest, - Aliases: inverseAliases, TakesFile: child.TakesFile, Config: BoolConfig{ Count: parent.negCount, @@ -128,6 +114,9 @@ func (parent *BoolWithInverseFlag) initialize() { value: child.value, } + // Set inverse names ex: --env => --no-env + parent.negativeFlag.Name, parent.negativeFlag.Aliases = parent.inverseNames() + if len(child.EnvVars) > 0 { parent.negativeFlag.EnvVars = make([]string, len(child.EnvVars)) for idx, envVar := range child.EnvVars { @@ -138,6 +127,23 @@ func (parent *BoolWithInverseFlag) initialize() { return } +func (parent *BoolWithInverseFlag) inverseNames() (name string, aliases []string) { + if parent.InversePrefix == "" { + parent.InversePrefix = DefaultInverseBoolPrefix + } + + name = parent.InversePrefix + parent.BoolFlag.Name + + if len(parent.BoolFlag.Aliases) > 0 { + aliases = make([]string, len(parent.BoolFlag.Aliases)) + for idx, alias := range parent.BoolFlag.Aliases { + aliases[idx] = parent.InversePrefix + alias + } + } + + return +} + func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error { if s.positiveFlag == nil { s.initialize() @@ -155,6 +161,13 @@ func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error { } func (s *BoolWithInverseFlag) Names() []string { + // Get Names when flag has not been initialized + if s.positiveFlag == nil { + inverseName, inverseAliases := s.inverseNames() + + return append(s.BoolFlag.Names(), FlagNames(inverseName, inverseAliases)...) + } + if *s.negDest { return s.negativeFlag.Names() } diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go index 905664262a..750f65961a 100644 --- a/flag_bool_with_inverse_test.go +++ b/flag_bool_with_inverse_test.go @@ -321,6 +321,31 @@ func TestBoolWithInverseRequired(t *testing.T) { } } +func TestBoolWithInverseNames(t *testing.T) { + flag := &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + Required: true, + }, + } + names := flag.Names() + + if len(names) != 2 { + t.Errorf("expected 2 names, got %d", len(names)) + return + } + + if names[0] != "env" { + t.Errorf("expected first name to be `env`, got `%s`", names[0]) + return + } + + if names[1] != "no-env" { + t.Errorf("expected first name to be `no-env`, got `%s`", names[1]) + return + } +} + func ExampleBoolWithInverseFlag() { flagWithInverse := &cli.BoolWithInverseFlag{ BoolFlag: &cli.BoolFlag{ From 4b0811d94adc4bf899db17b03fd05a4370713d1b Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Mon, 16 Jan 2023 21:49:30 -0600 Subject: [PATCH 10/11] Update string call, and split inverseNames() into inverseName && inverseAliases --- flag_bool_with_inverse.go | 15 ++++++++------- flag_bool_with_inverse_test.go | 12 ++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index 674f63d4a4..a69674f815 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -115,7 +115,8 @@ func (parent *BoolWithInverseFlag) initialize() { } // Set inverse names ex: --env => --no-env - parent.negativeFlag.Name, parent.negativeFlag.Aliases = parent.inverseNames() + parent.negativeFlag.Name = parent.inverseName() + parent.negativeFlag.Aliases = parent.inverseAliases() if len(child.EnvVars) > 0 { parent.negativeFlag.EnvVars = make([]string, len(child.EnvVars)) @@ -127,13 +128,15 @@ func (parent *BoolWithInverseFlag) initialize() { return } -func (parent *BoolWithInverseFlag) inverseNames() (name string, aliases []string) { +func (parent *BoolWithInverseFlag) inverseName() string { if parent.InversePrefix == "" { parent.InversePrefix = DefaultInverseBoolPrefix } - name = parent.InversePrefix + parent.BoolFlag.Name + return parent.InversePrefix + parent.BoolFlag.Name +} +func (parent *BoolWithInverseFlag) inverseAliases() (aliases []string) { if len(parent.BoolFlag.Aliases) > 0 { aliases = make([]string, len(parent.BoolFlag.Aliases)) for idx, alias := range parent.BoolFlag.Aliases { @@ -163,9 +166,7 @@ func (s *BoolWithInverseFlag) Apply(set *flag.FlagSet) error { func (s *BoolWithInverseFlag) Names() []string { // Get Names when flag has not been initialized if s.positiveFlag == nil { - inverseName, inverseAliases := s.inverseNames() - - return append(s.BoolFlag.Names(), FlagNames(inverseName, inverseAliases)...) + return append(s.BoolFlag.Names(), FlagNames(s.inverseName(), s.inverseAliases())...) } if *s.negDest { @@ -183,7 +184,7 @@ func (s *BoolWithInverseFlag) Names() []string { // --env (default: false) || --no-env (default: false) func (s *BoolWithInverseFlag) String() string { if s.positiveFlag == nil { - return s.BoolFlag.String() + return fmt.Sprintf("%s || --%s", s.BoolFlag.String(), s.inverseName()) } return fmt.Sprintf("%s || %s", s.positiveFlag.String(), s.negativeFlag.String()) diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go index 750f65961a..a1a0897b43 100644 --- a/flag_bool_with_inverse_test.go +++ b/flag_bool_with_inverse_test.go @@ -3,6 +3,7 @@ package cli_test import ( "fmt" "os" + "strings" "testing" "github.com/urfave/cli/v3" @@ -344,6 +345,17 @@ func TestBoolWithInverseNames(t *testing.T) { t.Errorf("expected first name to be `no-env`, got `%s`", names[1]) return } + + flagString := flag.String() + if strings.Contains(flagString, "--env") == false { + t.Errorf("expected `%s` to contain `--env`", flagString) + return + } + + if strings.Contains(flagString, "--no-env") == false { + t.Errorf("expected `%s` to contain `--no-env`", flagString) + return + } } func ExampleBoolWithInverseFlag() { From ea59906eba30f4516312e352e763fafa57bd3b95 Mon Sep 17 00:00:00 2001 From: Samuel Stoltenberg Date: Mon, 16 Jan 2023 22:10:37 -0600 Subject: [PATCH 11/11] Remove unused negative count, simplify RunAction, and simplify isSet --- flag_bool_with_inverse.go | 26 ++++------- flag_bool_with_inverse_test.go | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index a69674f815..cb892b438c 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -25,8 +25,7 @@ type BoolWithInverseFlag struct { posDest *bool posCount *int - negDest *bool - negCount *int + negDest *bool } func (s *BoolWithInverseFlag) Flags() []Flag { @@ -34,7 +33,7 @@ func (s *BoolWithInverseFlag) Flags() []Flag { } func (s *BoolWithInverseFlag) IsSet() bool { - return (*s.posCount > 0 || *s.negCount > 0) || (s.positiveFlag.IsSet() || s.negativeFlag.IsSet()) + return (*s.posCount > 0) || (s.positiveFlag.IsSet() || s.negativeFlag.IsSet()) } func (s *BoolWithInverseFlag) Value() bool { @@ -47,12 +46,7 @@ func (s *BoolWithInverseFlag) RunAction(ctx *Context) error { } if *s.negDest { - err := ctx.Set(s.negativeFlag.Name, "true") - if err != nil { - return err - } - } else if *s.posDest { - err := ctx.Set(s.positiveFlag.Name, "true") + err := ctx.Set(s.positiveFlag.Name, "false") if err != nil { return err } @@ -76,7 +70,6 @@ func (parent *BoolWithInverseFlag) initialize() { child := parent.BoolFlag parent.negDest = new(bool) - parent.negCount = new(int) if child.Destination != nil { parent.posDest = child.Destination } else { @@ -104,14 +97,11 @@ func (parent *BoolWithInverseFlag) initialize() { Value: child.Value, Destination: parent.negDest, TakesFile: child.TakesFile, - Config: BoolConfig{ - Count: parent.negCount, - }, - OnlyOnce: child.OnlyOnce, - hasBeenSet: child.hasBeenSet, - applied: child.applied, - creator: boolValue{}, - value: child.value, + OnlyOnce: child.OnlyOnce, + hasBeenSet: child.hasBeenSet, + applied: child.applied, + creator: boolValue{}, + value: child.value, } // Set inverse names ex: --env => --no-env diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go index a1a0897b43..8ef0b0bd99 100644 --- a/flag_bool_with_inverse_test.go +++ b/flag_bool_with_inverse_test.go @@ -358,6 +358,87 @@ func TestBoolWithInverseNames(t *testing.T) { } } +func TestBoolWithInverseDestination(t *testing.T) { + destination := new(bool) + count := new(int) + + flagMethod := func() *cli.BoolWithInverseFlag { + return &cli.BoolWithInverseFlag{ + BoolFlag: &cli.BoolFlag{ + Name: "env", + Destination: destination, + Config: cli.BoolConfig{ + Count: count, + }, + }, + } + } + + checkAndReset := func(expectedCount int, expectedValue bool) error { + if *count != expectedCount { + return fmt.Errorf("expected count to be %d, got %d", expectedCount, *count) + + } + + if *destination != expectedValue { + return fmt.Errorf("expected destination to be %t, got %t", expectedValue, *destination) + } + + *count = 0 + *destination = false + + return nil + } + + err := boolWithInverseTestCase{ + args: []string{"--env"}, + toBeSet: true, + value: true, + }.Run(flagMethod()) + if err != nil { + t.Error(err) + return + } + + err = checkAndReset(1, true) + if err != nil { + t.Error(err) + return + } + + err = boolWithInverseTestCase{ + args: []string{"--no-env"}, + toBeSet: true, + value: false, + }.Run(flagMethod()) + if err != nil { + t.Error(err) + return + } + + err = checkAndReset(1, false) + if err != nil { + t.Error(err) + return + } + + err = boolWithInverseTestCase{ + args: []string{}, + toBeSet: false, + value: false, + }.Run(flagMethod()) + if err != nil { + t.Error(err) + return + } + + err = checkAndReset(0, false) + if err != nil { + t.Error(err) + return + } +} + func ExampleBoolWithInverseFlag() { flagWithInverse := &cli.BoolWithInverseFlag{ BoolFlag: &cli.BoolFlag{