From b4e0ec3b8a0f4e604c03d3a7edd74b909d38a3aa Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 23 Mar 2021 14:11:36 -0400 Subject: [PATCH 01/17] Add count option for bool flags --- flag_bool.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++--- flag_test.go | 13 +++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/flag_bool.go b/flag_bool.go index 3e19bde5b2..6dbfaed547 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -1,11 +1,55 @@ package cli import ( + "errors" "flag" "fmt" "strconv" ) +// boolValue needs to implement the boolFlag internal interface in flag +// to be able to capture bool fields and values +// type boolFlag interface { +// Value +// IsBoolFlag() bool +// } +type boolValue struct { + destination *bool + count *int +} + +func newBoolValue(val bool, p *bool, count *int) *boolValue { + *p = val + return &boolValue{ + destination: p, + count: count, + } +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + if err != nil { + err = errors.New("parse error") + return err + } + *b.destination = v + if b.count != nil { + *b.count = *b.count + 1 + } + return err +} + +func (b *boolValue) Get() interface{} { return *b.destination } + +func (b *boolValue) String() string { + if b.destination != nil { + return strconv.FormatBool(*b.destination) + } + return strconv.FormatBool(false) +} + +func (b *boolValue) IsBoolFlag() bool { return true } + // TakesValue returns true of the flag takes a value, otherwise false func (f *BoolFlag) TakesValue() bool { return false @@ -61,11 +105,14 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { } for _, name := range f.Names() { + var value flag.Value if f.Destination != nil { - set.BoolVar(f.Destination, name, f.Value, f.Usage) - continue + value = newBoolValue(f.Value, f.Destination, f.Count) + } else { + t := new(bool) + value = newBoolValue(f.Value, t, f.Count) } - set.Bool(name, f.Value, f.Usage) + set.Var(value, name, f.Usage) } return nil diff --git a/flag_test.go b/flag_test.go index 001bc22d58..167f31ed49 100644 --- a/flag_test.go +++ b/flag_test.go @@ -62,6 +62,19 @@ func TestBoolFlagValueFromContext(t *testing.T) { expect(t, ff.Get(ctx), false) } +func TestBoolFlagApply_SetsCount(t *testing.T) { + v := false + count := 0 + fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v, Count: &count} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--wat", "-W", "--huh"}) + expect(t, err, nil) + expect(t, v, true) + expect(t, count, 3) +} + func TestFlagsFromEnv(t *testing.T) { newSetFloat64Slice := func(defaults ...float64) Float64Slice { s := NewFloat64Slice(defaults...) From 193fd848b1dcbb78e347ab4ce304840c679f7184 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 12 Aug 2022 20:56:10 -0400 Subject: [PATCH 02/17] Merge changes onto latest main --- cmd/urfave-cli-genflags/generated.gotmpl | 2 +- flag-spec.yaml | 4 +- go.mod | 4 + go.sum | 16 ++++ godoc-current.txt | 2 + internal/genflags/spec.go | 109 +++++++++++++++++++++++ zz_generated.flags.go | 2 + 7 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 internal/genflags/spec.go diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 8b82ccfb1d..ed58b4f042 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -23,7 +23,7 @@ type {{.TypeName}} struct { EnvVars []string {{range .StructFields}} - {{.Name}} {{.Type}} + {{.Name}} {{if .Pointer}}*{{end}}{{.Type}} {{end}} } diff --git a/flag-spec.yaml b/flag-spec.yaml index 1c594d7231..280f909a3b 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -3,7 +3,9 @@ # `Spec` type that maps to this file structure. flag_types: - bool: {} + bool: + struct_fields: + - { name: Count, type: int, pointer: true } float64: {} int64: struct_fields: diff --git a/go.mod b/go.mod index 09ef1477d8..cb2de902f7 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,8 @@ require ( require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect golang.org/x/text v0.3.7 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/urfave/gfmrun v1.3.1 // indirect + golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 96058c71de..1f677ddbdf 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,29 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/gfmrun v1.3.1 h1:HH9HkunC0ruk+AGwhog51ELC8RNv+sl7o1oYjfOuqF4= +github.com/urfave/gfmrun v1.3.1/go.mod h1:rxfNw5cpqox+NrjhACpJgTlK+qxJCN8YSpauiFicEno= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 h1:Js08h5hqB5xyWR789+QqueR6sDE8mk+YvpETZ+F6X9Y= +golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/godoc-current.txt b/godoc-current.txt index f3be3c0393..b88a19ad3d 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -450,6 +450,8 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int } BoolFlag is a flag with type bool diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go new file mode 100644 index 0000000000..d2d1c3027d --- /dev/null +++ b/internal/genflags/spec.go @@ -0,0 +1,109 @@ +package genflags + +import ( + "sort" + "strings" +) + +type Spec struct { + FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"` + PackageName string `yaml:"package_name"` + TestPackageName string `yaml:"test_package_name"` + UrfaveCLINamespace string `yaml:"urfave_cli_namespace"` + UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"` +} + +func (gfs *Spec) SortedFlagTypes() []*FlagType { + typeNames := []string{} + + for name := range gfs.FlagTypes { + if strings.HasPrefix(name, "[]") { + name = strings.TrimPrefix(name, "[]") + "Slice" + } + + typeNames = append(typeNames, name) + } + + sort.Strings(typeNames) + + ret := make([]*FlagType, len(typeNames)) + + for i, typeName := range typeNames { + ret[i] = &FlagType{ + GoType: typeName, + Config: gfs.FlagTypes[typeName], + } + } + + return ret +} + +type FlagTypeConfig struct { + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` +} + +type FlagStructField struct { + Name string + Type string + Pointer bool +} + +type FlagType struct { + GoType string + Config *FlagTypeConfig +} + +func (ft *FlagType) StructFields() []*FlagStructField { + if ft.Config == nil || ft.Config.StructFields == nil { + return []*FlagStructField{} + } + + return ft.Config.StructFields +} + +func (ft *FlagType) ValuePointer() bool { + if ft.Config == nil { + return false + } + + return ft.Config.ValuePointer +} + +func (ft *FlagType) TypeName() string { + return TypeName(ft.GoType, ft.Config) +} + +func (ft *FlagType) GenerateFmtStringerInterface() bool { + return ft.skipInterfaceNamed("fmt.Stringer") +} + +func (ft *FlagType) GenerateFlagInterface() bool { + return ft.skipInterfaceNamed("Flag") +} + +func (ft *FlagType) GenerateRequiredFlagInterface() bool { + return ft.skipInterfaceNamed("RequiredFlag") +} + +func (ft *FlagType) GenerateVisibleFlagInterface() bool { + return ft.skipInterfaceNamed("VisibleFlag") +} + +func (ft *FlagType) skipInterfaceNamed(name string) bool { + if ft.Config == nil { + return true + } + + lowName := strings.ToLower(name) + + for _, interfaceName := range ft.Config.SkipInterfaces { + if strings.ToLower(interfaceName) == lowName { + return false + } + } + + return true +} diff --git a/zz_generated.flags.go b/zz_generated.flags.go index f5b73ba245..bd2fc54a8d 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -327,6 +327,8 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int } // String returns a readable representation of this value (for usage defaults) From c8f53f9bdb5af4d03a6c6a59f714375c42a12fdf Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 12 Aug 2022 20:58:36 -0400 Subject: [PATCH 03/17] Remove go changes --- go.mod | 2 +- go.sum | 9 -------- godoc-current.txt | 48 +++++++++++++++++++++---------------------- zz_generated.flags.go | 2 -- 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index cb2de902f7..517ea00a83 100644 --- a/go.mod +++ b/go.mod @@ -16,4 +16,4 @@ require ( github.com/urfave/gfmrun v1.3.1 // indirect golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index 1f677ddbdf..101b095a31 100644 --- a/go.sum +++ b/go.sum @@ -2,15 +2,8 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/urfave/gfmrun v1.3.1 h1:HH9HkunC0ruk+AGwhog51ELC8RNv+sl7o1oYjfOuqF4= -github.com/urfave/gfmrun v1.3.1/go.mod h1:rxfNw5cpqox+NrjhACpJgTlK+qxJCN8YSpauiFicEno= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= @@ -24,7 +17,5 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IK golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/godoc-current.txt b/godoc-current.txt index b88a19ad3d..0d09fcf30f 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -64,8 +64,8 @@ GLOBAL OPTIONS: COPYRIGHT: {{wrap .Copyright 3}}{{end}} ` - AppHelpTemplate is the text template for the Default help topic. cli.go - uses text/template to render templates. You can render custom help text by + AppHelpTemplate is the text template for the Default help topic. cli.go uses + text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: @@ -201,9 +201,9 @@ func DefaultAppComplete(cCtx *Context) func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) func FlagNames(name string, aliases []string) []string func HandleAction(action interface{}, cCtx *Context) (err error) - HandleAction attempts to figure out which Action signature was used. - If it's an ActionFunc or a func with the legacy signature for Action, - the func is run! + HandleAction attempts to figure out which Action signature was used. If it's + an ActionFunc or a func with the legacy signature for Action, the func is + run! func HandleExitCoder(err error) HandleExitCoder handles errors implementing ExitCoder by printing their @@ -360,14 +360,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) to generate command-specific flags func (a *App) RunContext(ctx context.Context, arguments []string) (err error) - RunContext is like Run except it takes a Context that will be passed to - its commands and sub-commands. Through this, you can propagate timeouts and + RunContext is like Run except it takes a Context that will be passed to its + commands and sub-commands. Through this, you can propagate timeouts and cancellation requests func (a *App) Setup() - Setup runs initialization code to ensure all data structures are ready - for `Run` or inspection prior to `Run`. It is internally called by `Run`, - but will return early if setup has already happened. + Setup runs initialization code to ensure all data structures are ready for + `Run` or inspection prior to `Run`. It is internally called by `Run`, but + will return early if setup has already happened. func (a *App) ToFishCompletion() (string, error) ToFishCompletion creates a fish completion string for the `*App` The @@ -450,8 +450,6 @@ type BoolFlag struct { Aliases []string EnvVars []string - - Count *int } BoolFlag is a flag with type bool @@ -801,9 +799,9 @@ func Exit(message interface{}, exitCode int) ExitCoder Exit wraps a message and exit code into an error, which by default is handled with a call to os.Exit during default error handling. - This is the simplest way to trigger a non-zero exit code for an App - without having to call os.Exit manually. During testing, this behavior - can be avoided by overiding the ExitErrHandler function on an App or the + This is the simplest way to trigger a non-zero exit code for an App without + having to call os.Exit manually. During testing, this behavior can be + avoided by overiding the ExitErrHandler function on an App or the package-global OsExiter function. func NewExitError(message interface{}, exitCode int) ExitCoder @@ -1437,8 +1435,8 @@ type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] directly, as Value and/or Destination. See also SliceFlag. type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] - MultiIntFlag extends IntSliceFlag with support for using slices directly, - as Value and/or Destination. See also SliceFlag. + MultiIntFlag extends IntSliceFlag with support for using slices directly, as + Value and/or Destination. See also SliceFlag. type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] MultiStringFlag extends StringSliceFlag with support for using slices @@ -1519,8 +1517,8 @@ type RequiredFlag interface { IsRequired() bool } - RequiredFlag is an interface that allows us to mark flags as required - it allows flags required flags to be backwards compatible with the Flag + RequiredFlag is an interface that allows us to mark flags as required it + allows flags required flags to be backwards compatible with the Flag interface type Serializer interface { @@ -1533,9 +1531,9 @@ type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { Value S Destination *S } - SliceFlag extends implementations like StringSliceFlag and IntSliceFlag - with support for using slices directly, as Value and/or Destination. - See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with + support for using slices directly, as Value and/or Destination. See also + SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error @@ -1996,9 +1994,9 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont that are supported by the input source func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc - InitInputSourceWithContext is used to to setup an InputSourceContext on - a cli.Command Before method. It will create a new input source based on - the func provided with potentially using existing cli.Context values to + InitInputSourceWithContext is used to to setup an InputSourceContext on a + cli.Command Before method. It will create a new input source based on the + func provided with potentially using existing cli.Context values to initialize itself. If there is no error it will then apply the new input source to any flags that are supported by the input source diff --git a/zz_generated.flags.go b/zz_generated.flags.go index bd2fc54a8d..f5b73ba245 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -327,8 +327,6 @@ type BoolFlag struct { Aliases []string EnvVars []string - - Count *int } // String returns a readable representation of this value (for usage defaults) From ff23fe0b4c98189b1788dc028d5ea14ff6b979ad Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 16 Aug 2022 09:50:39 -0400 Subject: [PATCH 04/17] Merge commits --- godoc-current.txt | 48 ++++++++++++++++++++++--------------------- zz_generated.flags.go | 3 +++ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/godoc-current.txt b/godoc-current.txt index 0d09fcf30f..b88a19ad3d 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -64,8 +64,8 @@ GLOBAL OPTIONS: COPYRIGHT: {{wrap .Copyright 3}}{{end}} ` - AppHelpTemplate is the text template for the Default help topic. cli.go uses - text/template to render templates. You can render custom help text by + AppHelpTemplate is the text template for the Default help topic. cli.go + uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: @@ -201,9 +201,9 @@ func DefaultAppComplete(cCtx *Context) func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) func FlagNames(name string, aliases []string) []string func HandleAction(action interface{}, cCtx *Context) (err error) - HandleAction attempts to figure out which Action signature was used. If it's - an ActionFunc or a func with the legacy signature for Action, the func is - run! + HandleAction attempts to figure out which Action signature was used. + If it's an ActionFunc or a func with the legacy signature for Action, + the func is run! func HandleExitCoder(err error) HandleExitCoder handles errors implementing ExitCoder by printing their @@ -360,14 +360,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) to generate command-specific flags func (a *App) RunContext(ctx context.Context, arguments []string) (err error) - RunContext is like Run except it takes a Context that will be passed to its - commands and sub-commands. Through this, you can propagate timeouts and + RunContext is like Run except it takes a Context that will be passed to + its commands and sub-commands. Through this, you can propagate timeouts and cancellation requests func (a *App) Setup() - Setup runs initialization code to ensure all data structures are ready for - `Run` or inspection prior to `Run`. It is internally called by `Run`, but - will return early if setup has already happened. + Setup runs initialization code to ensure all data structures are ready + for `Run` or inspection prior to `Run`. It is internally called by `Run`, + but will return early if setup has already happened. func (a *App) ToFishCompletion() (string, error) ToFishCompletion creates a fish completion string for the `*App` The @@ -450,6 +450,8 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int } BoolFlag is a flag with type bool @@ -799,9 +801,9 @@ func Exit(message interface{}, exitCode int) ExitCoder Exit wraps a message and exit code into an error, which by default is handled with a call to os.Exit during default error handling. - This is the simplest way to trigger a non-zero exit code for an App without - having to call os.Exit manually. During testing, this behavior can be - avoided by overiding the ExitErrHandler function on an App or the + This is the simplest way to trigger a non-zero exit code for an App + without having to call os.Exit manually. During testing, this behavior + can be avoided by overiding the ExitErrHandler function on an App or the package-global OsExiter function. func NewExitError(message interface{}, exitCode int) ExitCoder @@ -1435,8 +1437,8 @@ type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] directly, as Value and/or Destination. See also SliceFlag. type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] - MultiIntFlag extends IntSliceFlag with support for using slices directly, as - Value and/or Destination. See also SliceFlag. + MultiIntFlag extends IntSliceFlag with support for using slices directly, + as Value and/or Destination. See also SliceFlag. type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] MultiStringFlag extends StringSliceFlag with support for using slices @@ -1517,8 +1519,8 @@ type RequiredFlag interface { IsRequired() bool } - RequiredFlag is an interface that allows us to mark flags as required it - allows flags required flags to be backwards compatible with the Flag + RequiredFlag is an interface that allows us to mark flags as required + it allows flags required flags to be backwards compatible with the Flag interface type Serializer interface { @@ -1531,9 +1533,9 @@ type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { Value S Destination *S } - SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with - support for using slices directly, as Value and/or Destination. See also - SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag + with support for using slices directly, as Value and/or Destination. + See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error @@ -1994,9 +1996,9 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont that are supported by the input source func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc - InitInputSourceWithContext is used to to setup an InputSourceContext on a - cli.Command Before method. It will create a new input source based on the - func provided with potentially using existing cli.Context values to + InitInputSourceWithContext is used to to setup an InputSourceContext on + a cli.Command Before method. It will create a new input source based on + the func provided with potentially using existing cli.Context values to initialize itself. If there is no error it will then apply the new input source to any flags that are supported by the input source diff --git a/zz_generated.flags.go b/zz_generated.flags.go index f5b73ba245..ffee504af8 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -22,6 +22,7 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + } // IsSet returns whether or not the flag has been set through env or file @@ -327,6 +328,8 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int } // String returns a readable representation of this value (for usage defaults) From faf75f696092b0221450e87642c41f36b6dbaf57 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 16 Aug 2022 10:00:44 -0400 Subject: [PATCH 05/17] Update for build --- cmd/urfave-cli-genflags/main.go | 5 +- internal/genflags/spec.go | 109 -------------------------------- zz_generated.flags.go | 1 - 3 files changed, 3 insertions(+), 112 deletions(-) delete mode 100644 internal/genflags/spec.go diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index 835216a98a..c754147bea 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -230,8 +230,9 @@ type FlagTypeConfig struct { } type FlagStructField struct { - Name string - Type string + Name string + Type string + Pointer bool } type FlagType struct { diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go deleted file mode 100644 index d2d1c3027d..0000000000 --- a/internal/genflags/spec.go +++ /dev/null @@ -1,109 +0,0 @@ -package genflags - -import ( - "sort" - "strings" -) - -type Spec struct { - FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"` - PackageName string `yaml:"package_name"` - TestPackageName string `yaml:"test_package_name"` - UrfaveCLINamespace string `yaml:"urfave_cli_namespace"` - UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"` -} - -func (gfs *Spec) SortedFlagTypes() []*FlagType { - typeNames := []string{} - - for name := range gfs.FlagTypes { - if strings.HasPrefix(name, "[]") { - name = strings.TrimPrefix(name, "[]") + "Slice" - } - - typeNames = append(typeNames, name) - } - - sort.Strings(typeNames) - - ret := make([]*FlagType, len(typeNames)) - - for i, typeName := range typeNames { - ret[i] = &FlagType{ - GoType: typeName, - Config: gfs.FlagTypes[typeName], - } - } - - return ret -} - -type FlagTypeConfig struct { - SkipInterfaces []string `yaml:"skip_interfaces"` - StructFields []*FlagStructField `yaml:"struct_fields"` - TypeName string `yaml:"type_name"` - ValuePointer bool `yaml:"value_pointer"` -} - -type FlagStructField struct { - Name string - Type string - Pointer bool -} - -type FlagType struct { - GoType string - Config *FlagTypeConfig -} - -func (ft *FlagType) StructFields() []*FlagStructField { - if ft.Config == nil || ft.Config.StructFields == nil { - return []*FlagStructField{} - } - - return ft.Config.StructFields -} - -func (ft *FlagType) ValuePointer() bool { - if ft.Config == nil { - return false - } - - return ft.Config.ValuePointer -} - -func (ft *FlagType) TypeName() string { - return TypeName(ft.GoType, ft.Config) -} - -func (ft *FlagType) GenerateFmtStringerInterface() bool { - return ft.skipInterfaceNamed("fmt.Stringer") -} - -func (ft *FlagType) GenerateFlagInterface() bool { - return ft.skipInterfaceNamed("Flag") -} - -func (ft *FlagType) GenerateRequiredFlagInterface() bool { - return ft.skipInterfaceNamed("RequiredFlag") -} - -func (ft *FlagType) GenerateVisibleFlagInterface() bool { - return ft.skipInterfaceNamed("VisibleFlag") -} - -func (ft *FlagType) skipInterfaceNamed(name string) bool { - if ft.Config == nil { - return true - } - - lowName := strings.ToLower(name) - - for _, interfaceName := range ft.Config.SkipInterfaces { - if strings.ToLower(interfaceName) == lowName { - return false - } - } - - return true -} diff --git a/zz_generated.flags.go b/zz_generated.flags.go index ffee504af8..bd2fc54a8d 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -22,7 +22,6 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string - } // IsSet returns whether or not the flag has been set through env or file From 3d03f6dfd35ac4bd3e64249d53179e4ae1c56b8c Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 28 Aug 2022 13:00:51 -0400 Subject: [PATCH 06/17] Merge main and run tidy --- go.mod | 9 +-------- go.sum | 9 --------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 517ea00a83..7fa4542cbc 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require ( - github.com/russross/blackfriday/v2 v2.1.0 // indirect - golang.org/x/text v0.3.7 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/urfave/gfmrun v1.3.1 // indirect - golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect -) \ No newline at end of file +require github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 101b095a31..0756e415ce 100644 --- a/go.sum +++ b/go.sum @@ -6,15 +6,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 h1:Js08h5hqB5xyWR789+QqueR6sDE8mk+YvpETZ+F6X9Y= -golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 6bfa730c7e843a2c33c2ae449dc7bc043b60de07 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:10:40 -0400 Subject: [PATCH 07/17] Rebase main --- go.mod | 5 ++++- go.sum | 2 ++ godoc-current.txt | 14 ++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7fa4542cbc..09ef1477d8 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require github.com/russross/blackfriday/v2 v2.1.0 // indirect +require ( + github.com/russross/blackfriday/v2 v2.1.0 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum index 0756e415ce..96058c71de 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/godoc-current.txt b/godoc-current.txt index b88a19ad3d..db2b5ac046 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -49,8 +49,8 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -157,8 +157,8 @@ DESCRIPTION: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} @@ -300,6 +300,8 @@ type App struct { CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc // Compilation date Compiled time.Time // List of all authors who contributed @@ -1422,6 +1424,10 @@ func (f *IntSliceFlag) String() string func (f *IntSliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type InvalidFlagAccessFunc func(*Context, string) + InvalidFlagAccessFunc is executed when an invalid flag is accessed from the + context. + type MultiError interface { error Errors() []error From 1047a3303cd43cbc40a9984ef4ff25d8c2cc6a10 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:34:00 -0400 Subject: [PATCH 08/17] Add to docs --- docs/v2/examples/flags.md | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 2a629f782a..ed7dab29f5 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -101,6 +101,45 @@ func main() { See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 +For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + var count int + + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "foo", + Usage: "foo greeting", + Count: &count, + }, + }, + Action: func(cCtx *cli.Context) error { + fmt.Println("Count %d", count) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + #### Placeholder Values Sometimes it's useful to specify a flag's value within the usage string itself. @@ -550,6 +589,7 @@ Will result in help output like: #### Precedence The precedence for flag value sources is as follows (highest to lowest): +eikdcclkkujudfjnhknkgruvlncbgvuckugignuhturk 0. Command line flag value from user 0. Environment variable (if specified) From b75c1aa07dc95388d32f99d32d118c31aea7f06e Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:35:09 -0400 Subject: [PATCH 09/17] Run gofmt --- flag_bool.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flag_bool.go b/flag_bool.go index 6dbfaed547..dc3240240c 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -9,10 +9,11 @@ import ( // boolValue needs to implement the boolFlag internal interface in flag // to be able to capture bool fields and values -// type boolFlag interface { -// Value -// IsBoolFlag() bool -// } +// +// type boolFlag interface { +// Value +// IsBoolFlag() bool +// } type boolValue struct { destination *bool count *int From af60ce5f444e8cf7468cb12732db6317f9b0487f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:36:55 -0400 Subject: [PATCH 10/17] Update docs --- docs/v2/examples/flags.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index ed7dab29f5..805fb82137 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,7 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go package main @@ -129,7 +129,7 @@ func main() { }, }, Action: func(cCtx *cli.Context) error { - fmt.Println("Count %d", count) + fmt.Println("Count ", count) return nil }, } From 9a22ec034e4bf446ed4cee4dd0ab128a78c8f07c Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:43:09 -0400 Subject: [PATCH 11/17] Update godocs --- docs/v2/examples/flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 805fb82137..527e43c6ae 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -129,7 +129,7 @@ func main() { }, }, Action: func(cCtx *cli.Context) error { - fmt.Println("Count ", count) + fmt.Println("count", count) return nil }, } From c22edee8b0583ae57072347b5f4e4e3952e8be36 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 14:28:33 -0400 Subject: [PATCH 12/17] Add args --- docs/v2/examples/flags.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 527e43c6ae..4307b0104e 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,8 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go package main From 5db4e8086ec353c0fd132bd4cd595dfbc35642b2 Mon Sep 17 00:00:00 2001 From: dearchap Date: Mon, 5 Sep 2022 16:45:44 -0400 Subject: [PATCH 13/17] Update docs/v2/examples/flags.md Co-authored-by: Dan Buch --- docs/v2/examples/flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 4307b0104e..990a928548 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,7 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go From 7941e8ccf62802e7ce0148dd8f66bbc2e9874c97 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 17:35:30 -0400 Subject: [PATCH 14/17] Add context.Count --- context.go | 12 ++++++++++++ flag_bool.go | 18 +++++++++++------- flag_test.go | 29 +++++++++++++++++++++++++++-- godoc-current.txt | 3 +++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index dc0d1ef0e4..0c7a07e78d 100644 --- a/context.go +++ b/context.go @@ -105,6 +105,18 @@ func (cCtx *Context) Lineage() []*Context { return lineage } +// NumOccurrences returns the num of occurences of this flag +func (cCtx *Context) Count(name string) int { + if fs := cCtx.lookupFlagSet(name); fs != nil { + if bf, ok := fs.Lookup(name).Value.(*boolValue); ok { + if bf.count != nil { + return *bf.count + } + } + } + return 0 +} + // Value returns the value of the flag corresponding to `name` func (cCtx *Context) Value(name string) interface{} { if fs := cCtx.lookupFlagSet(name); fs != nil { diff --git a/flag_bool.go b/flag_bool.go index dc3240240c..aad26cc556 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -105,14 +105,18 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + count := f.Count + dest := f.Destination + + if count == nil { + count = new(int) + } + if dest == nil { + dest = new(bool) + } + for _, name := range f.Names() { - var value flag.Value - if f.Destination != nil { - value = newBoolValue(f.Value, f.Destination, f.Count) - } else { - t := new(bool) - value = newBoolValue(f.Value, t, f.Count) - } + value := newBoolValue(f.Value, dest, count) set.Var(value, name, f.Usage) } diff --git a/flag_test.go b/flag_test.go index 167f31ed49..8451ccb0bd 100644 --- a/flag_test.go +++ b/flag_test.go @@ -67,14 +67,39 @@ func TestBoolFlagApply_SetsCount(t *testing.T) { count := 0 fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v, Count: &count} set := flag.NewFlagSet("test", 0) - _ = fl.Apply(set) + err := fl.Apply(set) + expect(t, err, nil) - err := set.Parse([]string{"--wat", "-W", "--huh"}) + err = set.Parse([]string{"--wat", "-W", "--huh"}) expect(t, err, nil) expect(t, v, true) expect(t, count, 3) } +func TestBoolFlagCountFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err := tf.Apply(set) + expect(t, err, nil) + + err = set.Parse([]string{"-tf", "-w", "-huh"}) + expect(t, err, nil) + expect(t, tf.Get(ctx), true) + expect(t, ctx.Count("tf"), 3) + + set1 := flag.NewFlagSet("test", 0) + ctx1 := NewContext(nil, set1, nil) + tf1 := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err = tf1.Apply(set1) + expect(t, err, nil) + + err = set1.Parse([]string{}) + expect(t, err, nil) + expect(t, tf1.Get(ctx1), false) + expect(t, ctx1.Count("tf"), 0) +} + func TestFlagsFromEnv(t *testing.T) { newSetFloat64Slice := func(defaults ...float64) Float64Slice { s := NewFloat64Slice(defaults...) diff --git a/godoc-current.txt b/godoc-current.txt index db2b5ac046..382d4f72ea 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -633,6 +633,9 @@ func (cCtx *Context) Args() Args func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found +func (cCtx *Context) Count(name string) int + NumOccurrences returns the num of occurences of this flag + func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found From 15fd35e7b4a52cf771f18d1d00fc4a4b25c68df9 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 17:40:42 -0400 Subject: [PATCH 15/17] Add parametrize tests --- flag_test.go | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/flag_test.go b/flag_test.go index 8451ccb0bd..aee9b834b6 100644 --- a/flag_test.go +++ b/flag_test.go @@ -77,27 +77,36 @@ func TestBoolFlagApply_SetsCount(t *testing.T) { } func TestBoolFlagCountFromContext(t *testing.T) { - set := flag.NewFlagSet("test", 0) - ctx := NewContext(nil, set, nil) - tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} - err := tf.Apply(set) - expect(t, err, nil) - err = set.Parse([]string{"-tf", "-w", "-huh"}) - expect(t, err, nil) - expect(t, tf.Get(ctx), true) - expect(t, ctx.Count("tf"), 3) + boolCountTests := []struct { + input []string + expectedVal bool + expectedCount int + }{ + { + input: []string{"-tf", "-w", "-huh"}, + expectedVal: true, + expectedCount: 3, + }, + { + input: []string{}, + expectedVal: false, + expectedCount: 0, + }, + } - set1 := flag.NewFlagSet("test", 0) - ctx1 := NewContext(nil, set1, nil) - tf1 := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} - err = tf1.Apply(set1) - expect(t, err, nil) + for _, bct := range boolCountTests { + set := flag.NewFlagSet("test", 0) + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err := tf.Apply(set) + expect(t, err, nil) - err = set1.Parse([]string{}) - expect(t, err, nil) - expect(t, tf1.Get(ctx1), false) - expect(t, ctx1.Count("tf"), 0) + err = set.Parse(bct.input) + expect(t, err, nil) + expect(t, tf.Get(ctx), bct.expectedVal) + expect(t, ctx.Count("tf"), bct.expectedCount) + } } func TestFlagsFromEnv(t *testing.T) { From dbdf11974276cc05463c373a0ca78e33837ba46b Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 19:13:26 -0400 Subject: [PATCH 16/17] Add countable interface --- context.go | 8 +++----- flag.go | 6 ++++++ flag_bool.go | 7 +++++++ godoc-current.txt | 8 +++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 0c7a07e78d..0335849a1b 100644 --- a/context.go +++ b/context.go @@ -105,13 +105,11 @@ func (cCtx *Context) Lineage() []*Context { return lineage } -// NumOccurrences returns the num of occurences of this flag +// Count returns the num of occurences of this flag func (cCtx *Context) Count(name string) int { if fs := cCtx.lookupFlagSet(name); fs != nil { - if bf, ok := fs.Lookup(name).Value.(*boolValue); ok { - if bf.count != nil { - return *bf.count - } + if cf, ok := fs.Lookup(name).Value.(Countable); ok { + return cf.Count() } } return 0 diff --git a/flag.go b/flag.go index 37c9fdab21..050bb4b1d1 100644 --- a/flag.go +++ b/flag.go @@ -139,6 +139,12 @@ type CategorizableFlag interface { GetCategory() string } +// Countable is an interface to enable detection of flag values which support +// repetitive flags +type Countable interface { + Count() int +} + func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) diff --git a/flag_bool.go b/flag_bool.go index aad26cc556..cb937ae65d 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -51,6 +51,13 @@ func (b *boolValue) String() string { func (b *boolValue) IsBoolFlag() bool { return true } +func (b *boolValue) Count() int { + if b.count != nil { + return *b.count + } + return 0 +} + // TakesValue returns true of the flag takes a value, otherwise false func (f *BoolFlag) TakesValue() bool { return false diff --git a/godoc-current.txt b/godoc-current.txt index 382d4f72ea..12db90eea3 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -634,7 +634,7 @@ func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found func (cCtx *Context) Count(name string) int - NumOccurrences returns the num of occurences of this flag + Count returns the num of occurences of this flag func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found @@ -708,6 +708,12 @@ func (cCtx *Context) Uint64(name string) uint64 func (cCtx *Context) Value(name string) interface{} Value returns the value of the flag corresponding to `name` +type Countable interface { + Count() int +} + Countable is an interface to enable detection of flag values which support + repetitive flags + type DocGenerationFlag interface { Flag From 869f29af0a63edd5af165d6ee92c1b006872d318 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 20:04:11 -0400 Subject: [PATCH 17/17] Remove keystroke errors --- docs/v2/examples/flags.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 990a928548..dbb41ea936 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -590,7 +590,6 @@ Will result in help output like: #### Precedence The precedence for flag value sources is as follows (highest to lowest): -eikdcclkkujudfjnhknkgruvlncbgvuckugignuhturk 0. Command line flag value from user 0. Environment variable (if specified)