Merge pull request #1257 from dearchap/bool_count

Add count option for bool flags
This commit is contained in:
dearchap 2022-09-06 09:26:21 -04:00 committed by GitHub
commit 6ccecf2dbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 197 additions and 13 deletions

View File

@ -23,7 +23,7 @@ type {{.TypeName}} struct {
EnvVars []string
{{range .StructFields}}
{{.Name}} {{.Type}}
{{.Name}} {{if .Pointer}}*{{end}}{{.Type}}
{{end}}
}

View File

@ -230,8 +230,9 @@ type FlagTypeConfig struct {
}
type FlagStructField struct {
Name string
Type string
Name string
Type string
Pointer bool
}
type FlagType struct {

View File

@ -105,6 +105,16 @@ func (cCtx *Context) Lineage() []*Context {
return lineage
}
// Count returns the num of occurences of this flag
func (cCtx *Context) Count(name string) int {
if fs := cCtx.lookupFlagSet(name); fs != nil {
if cf, ok := fs.Lookup(name).Value.(Countable); ok {
return cf.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 {

View File

@ -101,6 +101,46 @@ 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)
<!-- {
"args": ["&#45;&#45;foo", "&#45;&#45;foo"],
"output": "count 2"
} -->
```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", 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.

View File

@ -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:

View File

@ -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)

View File

@ -1,11 +1,63 @@
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 }
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
@ -60,12 +112,19 @@ 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() {
if f.Destination != nil {
set.BoolVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Bool(name, f.Value, f.Usage)
value := newBoolValue(f.Value, dest, count)
set.Var(value, name, f.Usage)
}
return nil

View File

@ -62,6 +62,53 @@ 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)
err := fl.Apply(set)
expect(t, err, nil)
err = set.Parse([]string{"--wat", "-W", "--huh"})
expect(t, err, nil)
expect(t, v, true)
expect(t, count, 3)
}
func TestBoolFlagCountFromContext(t *testing.T) {
boolCountTests := []struct {
input []string
expectedVal bool
expectedCount int
}{
{
input: []string{"-tf", "-w", "-huh"},
expectedVal: true,
expectedCount: 3,
},
{
input: []string{},
expectedVal: false,
expectedCount: 0,
},
}
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 = 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) {
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
s := NewFloat64Slice(defaults...)

View File

@ -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
@ -450,6 +452,8 @@ type BoolFlag struct {
Aliases []string
EnvVars []string
Count *int
}
BoolFlag is a flag with type bool
@ -629,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
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
@ -701,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
@ -1420,6 +1433,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

View File

@ -327,6 +327,8 @@ type BoolFlag struct {
Aliases []string
EnvVars []string
Count *int
}
// String returns a readable representation of this value (for usage defaults)