Upstream urfave/cli state of the touched files before 9593c3306f
This commit is contained in:
1 parent
d03cd5d09c
commit
ba1266f7c4
2 files changed
+911
No files matched your search
@@ -0,0 +1,306 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DefaultInverseBoolPrefix = "no-"
|
||||
|
||||
type BoolWithInverseFlag struct {
|
||||
Name string `json:"name"` // name of the flag
|
||||
Category string `json:"category"` // category of the flag, if any
|
||||
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
|
||||
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
|
||||
Usage string `json:"usage"` // usage string for help output
|
||||
Sources ValueSourceChain `json:"-"` // sources to load flag value from
|
||||
Required bool `json:"required"` // whether the flag is required or not
|
||||
Hidden bool `json:"hidden"` // whether to hide the flag in help output
|
||||
Deprecated string `json:"deprecated"` // deprecation message, if set a warning is printed when the flag is set
|
||||
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
|
||||
Value bool `json:"defaultValue"` // default value for this flag if not set by from any source
|
||||
Destination *bool `json:"-"` // destination pointer for value when set
|
||||
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
|
||||
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
|
||||
Action func(context.Context, *Command, bool) error `json:"-"` // Action callback to be called when flag is set
|
||||
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
|
||||
Validator func(bool) error `json:"-"` // custom function to validate this flag value
|
||||
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
|
||||
Config BoolConfig `json:"config"` // Additional/Custom configuration associated with this flag type
|
||||
InversePrefix string `json:"invPrefix"` // The prefix used to indicate a negative value. Default: `env` becomes `no-env`
|
||||
|
||||
// unexported fields for internal use
|
||||
count int // number of times the flag has been set
|
||||
hasBeenSet bool // whether the flag has been set from env or file
|
||||
fromSource bool // whether the current value came from Sources
|
||||
applied bool // whether the flag has been applied to a flag set already
|
||||
value Value // value representing this flag's value
|
||||
pset bool
|
||||
nset bool
|
||||
stringer FlagStringFunc // optional per-flag override of FlagStringer
|
||||
}
|
||||
|
||||
// SetStringer overrides the [FlagStringFunc] used by this flag's String
|
||||
// method. Passing nil restores the default behavior of using the
|
||||
// package-level [FlagStringer]. This is used e.g. by
|
||||
// [MutuallyExclusiveFlags.Stringer].
|
||||
//
|
||||
// Note: unlike [FlagBase], BoolWithInverseFlag.String only honors the
|
||||
// stringer partially. The names segment (the part before the first tab,
|
||||
// e.g. "--[no-]env, -e") is always recomputed from Name/Aliases/InversePrefix
|
||||
// and cannot be overridden; only the tab-delimited details after it come
|
||||
// from the stringer's output.
|
||||
func (bif *BoolWithInverseFlag) SetStringer(s FlagStringFunc) {
|
||||
bif.stringer = s
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsSet() bool {
|
||||
return bif.hasBeenSet
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Get() any {
|
||||
return bif.value.Get()
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error {
|
||||
if bif.Action != nil {
|
||||
return bif.Action(ctx, cmd, bif.Get().(bool))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsLocal() bool {
|
||||
return bif.Local
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) inversePrefix() string {
|
||||
if bif.InversePrefix == "" {
|
||||
bif.InversePrefix = DefaultInverseBoolPrefix
|
||||
}
|
||||
|
||||
return bif.InversePrefix
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) PreParse() error {
|
||||
dest := bif.Destination
|
||||
if dest == nil {
|
||||
dest = new(bool)
|
||||
}
|
||||
*dest = bif.Value
|
||||
bif.value = &boolValue{
|
||||
destination: dest,
|
||||
count: bif.Config.Count,
|
||||
}
|
||||
|
||||
// Validate the given default or values set from external sources as well
|
||||
if bif.Validator != nil && bif.ValidateDefaults {
|
||||
if err := bif.Validator(bif.value.Get().(bool)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bif.applied = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) PostParse() error {
|
||||
tracef("postparse (flag=%[1]q)", bif.Name)
|
||||
|
||||
if !bif.hasBeenSet {
|
||||
if val, source, found := bif.Sources.LookupWithSource(); found {
|
||||
if val == "" {
|
||||
val = "false"
|
||||
}
|
||||
if err := bif.Set(bif.Name, val); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
|
||||
val, bif.Value, source, bif.Name, err,
|
||||
)
|
||||
}
|
||||
|
||||
bif.hasBeenSet = true
|
||||
bif.fromSource = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Set(name, val string) error {
|
||||
// A value from Sources is only a fallback, so a value set afterwards
|
||||
// replaces it (see FlagBase.Set).
|
||||
if bif.fromSource {
|
||||
bif.fromSource = false
|
||||
bif.count = 0
|
||||
bif.pset = false
|
||||
bif.nset = false
|
||||
}
|
||||
|
||||
if bif.count > 0 && bif.OnlyOnce {
|
||||
return fmt.Errorf("can't duplicate this flag")
|
||||
}
|
||||
|
||||
bif.hasBeenSet = true
|
||||
|
||||
if slices.Contains(append([]string{bif.Name}, bif.Aliases...), name) {
|
||||
if bif.nset {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
|
||||
}
|
||||
if err := bif.value.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
bif.pset = true
|
||||
} else {
|
||||
if bif.pset {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
|
||||
}
|
||||
if err := bif.value.Set("false"); err != nil {
|
||||
return err
|
||||
}
|
||||
bif.nset = true
|
||||
}
|
||||
bif.count++
|
||||
|
||||
if bif.Validator != nil {
|
||||
return bif.Validator(bif.value.Get().(bool))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Names() []string {
|
||||
names := append([]string{bif.Name}, bif.Aliases...)
|
||||
|
||||
for _, name := range names {
|
||||
names = append(names, bif.inversePrefix()+name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsRequired() bool {
|
||||
return bif.Required
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsVisible() bool {
|
||||
return !bif.Hidden
|
||||
}
|
||||
|
||||
// String implements the standard Stringer interface.
|
||||
//
|
||||
// Example for BoolFlag{Name: "env"}
|
||||
// --[no-]env (default: false)
|
||||
//
|
||||
// Example for BoolFlag{Name: "env", Aliases: []string{"e"}}
|
||||
// --[no-]env, -e (default: false)
|
||||
func (bif *BoolWithInverseFlag) String() string {
|
||||
fs := FlagStringer
|
||||
if bif.stringer != nil {
|
||||
fs = bif.stringer
|
||||
}
|
||||
out := fs(bif)
|
||||
|
||||
i := strings.Index(out, "\t")
|
||||
|
||||
prefix := "--"
|
||||
|
||||
// single character flags are prefixed with `-` instead of `--`
|
||||
if len(bif.Name) == 1 {
|
||||
prefix = "-"
|
||||
}
|
||||
|
||||
// Guard against a FlagStringer that returns a string without a tab (e.g.
|
||||
// a custom stringer or the default stringer when the flag does not
|
||||
// implement DocGenerationFlag). In that case treat the entire output as
|
||||
// the tab-delimited suffix so the slice never goes out of bounds.
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
|
||||
var aliasParts []string
|
||||
for _, alias := range bif.Aliases {
|
||||
aPrefix := "--"
|
||||
if len(alias) == 1 {
|
||||
aPrefix = "-"
|
||||
}
|
||||
aliasParts = append(aliasParts, aPrefix+alias)
|
||||
}
|
||||
|
||||
names := fmt.Sprintf("%s[%s]%s", prefix, bif.inversePrefix(), bif.Name)
|
||||
if len(aliasParts) > 0 {
|
||||
names = names + ", " + strings.Join(aliasParts, ", ")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s", names, out[i:])
|
||||
}
|
||||
|
||||
// IsBoolFlag returns whether the flag doesn't need to accept args
|
||||
func (bif *BoolWithInverseFlag) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Count returns the number of times this flag has been invoked
|
||||
func (bif *BoolWithInverseFlag) Count() int {
|
||||
return bif.count
|
||||
}
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (bif *BoolWithInverseFlag) GetDefaultText() string {
|
||||
if bif.DefaultText != "" {
|
||||
return bif.DefaultText
|
||||
}
|
||||
return boolValue{}.ToString(bif.Value)
|
||||
}
|
||||
|
||||
// GetDeprecated returns the deprecation message of the flag
|
||||
func (bif *BoolWithInverseFlag) GetDeprecated() string {
|
||||
return bif.Deprecated
|
||||
}
|
||||
|
||||
// GetCategory returns the category of the flag
|
||||
func (bif *BoolWithInverseFlag) GetCategory() string {
|
||||
return bif.Category
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) SetCategory(c string) {
|
||||
bif.Category = c
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (bif *BoolWithInverseFlag) GetUsage() string {
|
||||
return bif.Usage
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (bif *BoolWithInverseFlag) GetEnvVars() []string {
|
||||
return bif.Sources.EnvKeys()
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (bif *BoolWithInverseFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
|
||||
func (bif *BoolWithInverseFlag) IsDefaultVisible() bool {
|
||||
return !bif.HideDefault
|
||||
}
|
||||
|
||||
// TypeName is used for stringify/docs. For bool its a no-op
|
||||
func (bif *BoolWithInverseFlag) TypeName() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) SchemaType() string {
|
||||
return "boolean"
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) SchemaItemsType() string {
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var errBothEnvFlagsAreSet = 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 (tc *boolWithInverseTestCase) Run(t *testing.T, flagWithInverse *BoolWithInverseFlag) error {
|
||||
cmd := &Command{
|
||||
Flags: []Flag{flagWithInverse},
|
||||
Action: func(context.Context, *Command) error { return nil },
|
||||
}
|
||||
|
||||
for key, val := range tc.envVars {
|
||||
t.Setenv(key, val)
|
||||
}
|
||||
|
||||
err := cmd.Run(buildTestContext(t), append([]string{"prog"}, tc.args...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flagWithInverse.IsSet() != tc.toBeSet {
|
||||
return fmt.Errorf("flag should be set %t, but got %t", tc.toBeSet, flagWithInverse.IsSet())
|
||||
}
|
||||
|
||||
if flagWithInverse.Get() != tc.value {
|
||||
return fmt.Errorf("flag value should be %t, but got %t", tc.value, flagWithInverse.Get())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runBoolWithInverseFlagTests(t *testing.T, newFlagMethod func() *BoolWithInverseFlag, cases []*boolWithInverseTestCase) error {
|
||||
for _, tc := range cases {
|
||||
t.Run(strings.Join(tc.args, " ")+fmt.Sprintf("%[1]v %[2]v %[3]v", tc.value, tc.toBeSet, tc.err), func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
fl := newFlagMethod()
|
||||
|
||||
err := tc.Run(t, fl)
|
||||
if err != nil && tc.err == nil {
|
||||
r.NoError(err)
|
||||
}
|
||||
|
||||
if err == nil && tc.err != nil {
|
||||
r.Error(err)
|
||||
}
|
||||
|
||||
if err != nil && tc.err != nil {
|
||||
r.ErrorContains(err, tc.err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBoolWithInverseBasic(t *testing.T) {
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
return &BoolWithInverseFlag{
|
||||
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: errBothEnvFlagsAreSet,
|
||||
},
|
||||
}
|
||||
|
||||
err := runBoolWithInverseFlagTests(t, flagMethod, testCases)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseAction(t *testing.T) {
|
||||
err := fmt.Errorf("action called")
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
bif := &BoolWithInverseFlag{
|
||||
Name: "env",
|
||||
|
||||
// Setting env to the opposite to test flag Action is working as intended
|
||||
Action: func(_ context.Context, cmd *Command, value bool) error {
|
||||
return err
|
||||
},
|
||||
}
|
||||
return bif
|
||||
}
|
||||
|
||||
testCases := []*boolWithInverseTestCase{
|
||||
{
|
||||
args: []string{"--no-env"},
|
||||
toBeSet: true,
|
||||
value: false,
|
||||
err: err,
|
||||
},
|
||||
{
|
||||
args: []string{"--env"},
|
||||
toBeSet: true,
|
||||
value: true,
|
||||
err: err,
|
||||
},
|
||||
|
||||
// This test is not inverse because the flag action is never called
|
||||
{
|
||||
toBeSet: false,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
args: []string{"--env", "--no-env"},
|
||||
err: errBothEnvFlagsAreSet,
|
||||
},
|
||||
}
|
||||
|
||||
errr := runBoolWithInverseFlagTests(t, flagMethod, testCases)
|
||||
if errr != nil {
|
||||
t.Error(errr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseAlias(t *testing.T) {
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
return &BoolWithInverseFlag{
|
||||
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: errBothEnvFlagsAreSet,
|
||||
},
|
||||
}
|
||||
|
||||
err := runBoolWithInverseFlagTests(t, flagMethod, testCases)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseEnvVars(t *testing.T) {
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
return &BoolWithInverseFlag{
|
||||
Name: "env",
|
||||
Sources: EnvVars("ENV", "NO-ENV"),
|
||||
Local: true,
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []*boolWithInverseTestCase{
|
||||
{
|
||||
toBeSet: true,
|
||||
value: false,
|
||||
envVars: map[string]string{
|
||||
"NO-ENV": "false",
|
||||
},
|
||||
},
|
||||
{
|
||||
toBeSet: true,
|
||||
value: true,
|
||||
envVars: map[string]string{
|
||||
"ENV": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
toBeSet: true,
|
||||
value: false,
|
||||
envVars: map[string]string{
|
||||
"ENV": "false",
|
||||
},
|
||||
},
|
||||
{
|
||||
toBeSet: false,
|
||||
value: false,
|
||||
},
|
||||
// TODO
|
||||
/*{
|
||||
err: errBothEnvFlagsAreSet,
|
||||
envVars: map[string]string{
|
||||
"ENV": "true",
|
||||
"NO-ENV": "true",
|
||||
},
|
||||
},*/
|
||||
{
|
||||
err: fmt.Errorf("could not parse \"true_env\" as bool value from environment variable \"ENV\" for flag env: parse error"),
|
||||
envVars: map[string]string{
|
||||
"ENV": "true_env",
|
||||
},
|
||||
},
|
||||
{
|
||||
err: fmt.Errorf("could not parse \"false_env\" as bool value from environment variable \"NO-ENV\" for flag env: parse error"),
|
||||
envVars: map[string]string{
|
||||
"NO-ENV": "false_env",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := runBoolWithInverseFlagTests(t, flagMethod, testCases)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseWithPrefix(t *testing.T) {
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
return &BoolWithInverseFlag{
|
||||
Name: "env",
|
||||
InversePrefix: "without-",
|
||||
}
|
||||
}
|
||||
|
||||
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`"),
|
||||
},
|
||||
{
|
||||
args: []string{"--without-env", "--env"},
|
||||
err: fmt.Errorf("cannot set both flags `--env` and `--without-env`"),
|
||||
},
|
||||
}
|
||||
|
||||
err := runBoolWithInverseFlagTests(t, flagMethod, testCases)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseRequired(t *testing.T) {
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
return &BoolWithInverseFlag{
|
||||
Name: "env",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []*boolWithInverseTestCase{
|
||||
{
|
||||
args: []string{"--no-env"},
|
||||
toBeSet: true,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
args: []string{"--env"},
|
||||
toBeSet: true,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
args: []string{"--env", "--no-env"},
|
||||
err: errBothEnvFlagsAreSet,
|
||||
},
|
||||
}
|
||||
|
||||
err := runBoolWithInverseFlagTests(t, flagMethod, testCases)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseNames(t *testing.T) {
|
||||
flag := &BoolWithInverseFlag{
|
||||
Name: "env",
|
||||
Required: true,
|
||||
}
|
||||
names := flag.Names()
|
||||
|
||||
require.Len(t, names, 2)
|
||||
require.Equal(t, "env", names[0], "expected first name to be `env`")
|
||||
require.Equal(t, "no-env", names[1], "expected first name to be `no-env`")
|
||||
|
||||
var d DocGenerationFlag = flag
|
||||
require.Equal(t, "bool", d.TypeName())
|
||||
}
|
||||
|
||||
func TestBoolWithInverseDefaultText(t *testing.T) {
|
||||
require.Equal(t, "false", (&BoolWithInverseFlag{}).GetDefaultText())
|
||||
require.Equal(t, "true", (&BoolWithInverseFlag{Value: true}).GetDefaultText())
|
||||
require.Equal(t, "auto", (&BoolWithInverseFlag{DefaultText: "auto"}).GetDefaultText())
|
||||
}
|
||||
|
||||
func TestBoolWithInverseString(t *testing.T) {
|
||||
tcs := []struct {
|
||||
testName string
|
||||
flagName string
|
||||
required bool
|
||||
usage string
|
||||
inversePrefix string
|
||||
aliases []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
testName: "empty inverse prefix no flag",
|
||||
flagName: "",
|
||||
required: true,
|
||||
expected: "--[no-]\t",
|
||||
},
|
||||
{
|
||||
testName: "single-char flag name",
|
||||
flagName: "e",
|
||||
required: true,
|
||||
expected: "-[no-]e\t",
|
||||
},
|
||||
{
|
||||
testName: "multi-char flag name",
|
||||
flagName: "env",
|
||||
required: true,
|
||||
expected: "--[no-]env\t",
|
||||
},
|
||||
{
|
||||
testName: "required with usage",
|
||||
flagName: "env",
|
||||
required: true,
|
||||
usage: "env usage",
|
||||
expected: "--[no-]env\tenv usage",
|
||||
},
|
||||
{
|
||||
testName: "required without usage",
|
||||
flagName: "env",
|
||||
required: true,
|
||||
expected: "--[no-]env\t",
|
||||
},
|
||||
{
|
||||
testName: "not required with default usage",
|
||||
flagName: "env",
|
||||
required: false,
|
||||
expected: "--[no-]env\t(default: false)",
|
||||
},
|
||||
{
|
||||
testName: "custom inverse prefix",
|
||||
flagName: "env",
|
||||
required: true,
|
||||
inversePrefix: "nope-",
|
||||
expected: "--[nope-]env\t",
|
||||
},
|
||||
{
|
||||
testName: "empty inverse prefix",
|
||||
flagName: "env",
|
||||
required: true,
|
||||
expected: "--[no-]env\t",
|
||||
},
|
||||
{
|
||||
testName: "short alias",
|
||||
flagName: "color",
|
||||
required: false,
|
||||
aliases: []string{"c"},
|
||||
expected: "--[no-]color, -c\t(default: false)",
|
||||
},
|
||||
{
|
||||
testName: "long alias",
|
||||
flagName: "color",
|
||||
required: false,
|
||||
aliases: []string{"colour"},
|
||||
expected: "--[no-]color, --colour\t(default: false)",
|
||||
},
|
||||
{
|
||||
testName: "multiple aliases",
|
||||
flagName: "color",
|
||||
required: false,
|
||||
aliases: []string{"c", "colour"},
|
||||
expected: "--[no-]color, -c, --colour\t(default: false)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
flag := &BoolWithInverseFlag{
|
||||
Name: tc.flagName,
|
||||
Usage: tc.usage,
|
||||
Required: tc.required,
|
||||
InversePrefix: tc.inversePrefix,
|
||||
Aliases: tc.aliases,
|
||||
}
|
||||
|
||||
require.Equal(t, tc.expected, flag.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseDestination(t *testing.T) {
|
||||
destination := new(bool)
|
||||
count := new(int)
|
||||
|
||||
flagMethod := func() *BoolWithInverseFlag {
|
||||
return &BoolWithInverseFlag{
|
||||
Name: "env",
|
||||
Destination: destination,
|
||||
Config: 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(t, 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(t, 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(t, flagMethod())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkAndReset(0, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
f := flagMethod()
|
||||
f.Value = true
|
||||
err = (&boolWithInverseTestCase{
|
||||
args: []string{},
|
||||
toBeSet: false,
|
||||
value: true,
|
||||
}).Run(t, f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkAndReset(0, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||
var f RequiredFlag = &BoolWithInverseFlag{}
|
||||
|
||||
_ = f.IsRequired()
|
||||
}
|
||||
|
||||
func TestBoolWithInverseFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||
var f VisibleFlag = &BoolWithInverseFlag{}
|
||||
|
||||
_ = f.IsVisible()
|
||||
}
|
||||
|
||||
// TestBoolWithInverseFlagStringNoPanicWithNoTabStringer is a regression test for
|
||||
// https://github.com/urfave/cli/issues/2303.
|
||||
// BoolWithInverseFlag.String() panicked with "slice bounds out of range [-1:]"
|
||||
// when the FlagStringer returned a string without a tab character.
|
||||
func TestBoolWithInverseFlagStringNoPanicWithNoTabStringer(t *testing.T) {
|
||||
orig := FlagStringer
|
||||
defer func() { FlagStringer = orig }()
|
||||
|
||||
FlagStringer = func(f Flag) string {
|
||||
return "no tab here"
|
||||
}
|
||||
|
||||
flag := &BoolWithInverseFlag{
|
||||
Name: "verbose",
|
||||
}
|
||||
|
||||
// Must not panic.
|
||||
got := flag.String()
|
||||
if !strings.Contains(got, "verbose") {
|
||||
t.Errorf("expected String() to contain the flag name, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolWithInverseFlagCount(t *testing.T) {
|
||||
for _, externalCount := range []bool{false, true} {
|
||||
for _, onlyOnce := range []bool{false, true} {
|
||||
for _, name := range []string{"--env", "--no-env", "-e", "--no-e"} {
|
||||
t.Run(fmt.Sprintf("external=%v/onlyOnce=%v/%s", externalCount, onlyOnce, name), func(t *testing.T) {
|
||||
const initialCount = 7
|
||||
count := initialCount
|
||||
fl := &BoolWithInverseFlag{Name: "env", Aliases: []string{"e"}, OnlyOnce: onlyOnce}
|
||||
if externalCount {
|
||||
fl.Config.Count = &count
|
||||
}
|
||||
cmd := &Command{Flags: []Flag{fl}, Writer: io.Discard, ErrWriter: io.Discard}
|
||||
err := cmd.Run(buildTestContext(t), []string{"prog", name, name})
|
||||
wantCount := 2
|
||||
if onlyOnce {
|
||||
require.ErrorContains(t, err, "can't duplicate this flag")
|
||||
wantCount = 1
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, wantCount, fl.Count())
|
||||
if externalCount {
|
||||
require.Equal(t, initialCount+wantCount, count)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user