From ef9430e77eb1e7e05daecdfaa9ec75fa43b79e7e Mon Sep 17 00:00:00 2001 From: Ally Dale Date: Mon, 8 Feb 2021 20:10:28 +0800 Subject: [PATCH 1/2] fix #1238: accept multi-value input on sclice flags --- app_test.go | 34 ++++++++++++++++++++++++++++++ docs/v2/manual.md | 46 +++++++++++++++++++++++++++++++++++++++++ flag.go | 4 ++++ flag_float64_slice.go | 12 ++++++----- flag_int64_slice.go | 14 +++++++------ flag_int_slice.go | 14 +++++++------ flag_string_slice.go | 6 ++++-- flag_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 159 insertions(+), 19 deletions(-) diff --git a/app_test.go b/app_test.go index 7c38f60489..0b8b4bfae0 100644 --- a/app_test.go +++ b/app_test.go @@ -390,6 +390,40 @@ func ExampleApp_Run_zshComplete() { // h:Shows a list of commands or help for one command } +func ExampleApp_Run_sliceValues() { + // set args for examples sake + os.Args = []string{"multi_values", + "--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4", + "--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6", + "--int64Sclice", "13,14", "--int64Sclice", "15,16", + "--intSclice", "13,14", "--intSclice", "15,16", + } + app := NewApp() + app.Name = "multi_values" + app.Flags = []Flag{ + &StringSliceFlag{Name: "stringSclice"}, + &Float64SliceFlag{Name: "float64Sclice"}, + &Int64SliceFlag{Name: "int64Sclice"}, + &IntSliceFlag{Name: "intSclice"}, + } + app.Action = func(ctx *Context) error { + for i, v := range ctx.FlagNames() { + fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v)) + } + err := ctx.Err() + fmt.Println("error:", err) + return err + } + + _ = app.Run(os.Args) + // Output: + // 0-float64Sclice cli.Float64Slice{slice:[]float64{13.3, 14.4, 15.5, 16.6}, hasBeenSet:true} + // 1-int64Sclice cli.Int64Slice{slice:[]int64{13, 14, 15, 16}, hasBeenSet:true} + // 2-intSclice cli.IntSlice{slice:[]int{13, 14, 15, 16}, hasBeenSet:true} + // 3-stringSclice cli.StringSlice{slice:[]string{"parsed1", "parsed2", "parsed3", "parsed4"}, hasBeenSet:true} + // error: +} + func TestApp_Run(t *testing.T) { s := "" diff --git a/docs/v2/manual.md b/docs/v2/manual.md index af09010b9a..8f7cbbb4ca 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -14,6 +14,7 @@ cli v2 manual + [Values from the Environment](#values-from-the-environment) + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Muti values](#multi-values) + [Required Flags](#required-flags) + [Default Values for help output](#default-values-for-help-output) + [Precedence](#precedence) @@ -660,6 +661,51 @@ func main() { } ``` +#### Multi values + +Slice flags(Float64SliceFlag, Int64SliceFlag, IntSliceFlag, StringSliceFlag) are designed to accept multi values. + +Here is a sample of setting multi values for slice flags: + + +``` +package main + +import ( + "fmt" + "os" + "github.com/urfave/cli/v2" +) + +func main() { + os.Args = []string{"multi_values", + "--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4", + "--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6", + "--int64Sclice", "13,14", "--int64Sclice", "15,16", + "--intSclice", "13,14", "--intSclice", "15,16", + } + app := cli.NewApp() + app.Name = "multi_values" + app.Flags = []cli.Flag{ + &cli.StringSliceFlag{Name: "stringSclice"}, + &cli.Float64SliceFlag{Name: "float64Sclice"}, + &cli.Int64SliceFlag{Name: "int64Sclice"}, + &cli.IntSliceFlag{Name: "intSclice"}, + } + app.Action = func(ctx *cli.Context) error { + for i, v := range ctx.FlagNames() { + fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v)) + } + return ctx.Err() + } + + _ = app.Run(os.Args) +} + +``` + #### Required Flags You can make a flag required by setting the `Required` field to `true`. If a user diff --git a/flag.go b/flag.go index aff8d5be63..bca41cc316 100644 --- a/flag.go +++ b/flag.go @@ -390,3 +390,7 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) } return "", false } + +func flagSplitMultiValues(val string) []string { + return strings.Split(val, ",") +} diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 706ee6cd4b..8ee1204ec3 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -33,12 +33,14 @@ func (f *Float64Slice) Set(value string) error { return nil } - tmp, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return err + } - f.slice = append(f.slice, tmp) + f.slice = append(f.slice, tmp) + } return nil } diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 2c9a15af3e..2edc3c6be6 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -33,12 +33,14 @@ func (i *Int64Slice) Set(value string) error { return nil } - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } - i.slice = append(i.slice, tmp) + i.slice = append(i.slice, tmp) + } return nil } @@ -123,7 +125,7 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &Int64Slice{} - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) } diff --git a/flag_int_slice.go b/flag_int_slice.go index a73ca6b81c..8861649337 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -44,12 +44,14 @@ func (i *IntSlice) Set(value string) error { return nil } - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } - i.slice = append(i.slice, int(tmp)) + i.slice = append(i.slice, int(tmp)) + } return nil } @@ -134,7 +136,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &IntSlice{} - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) } diff --git a/flag_string_slice.go b/flag_string_slice.go index 35497032cb..92ded57c7e 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -32,7 +32,9 @@ func (s *StringSlice) Set(value string) error { return nil } - s.slice = append(s.slice, value) + for _, t := range flagSplitMultiValues(value) { + s.slice = append(s.slice, strings.TrimSpace(t)) + } return nil } @@ -132,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { destination = f.Destination } - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := destination.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) } diff --git a/flag_test.go b/flag_test.go index b3b0d7c587..1afc63188c 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1973,3 +1973,51 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) { err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"}) expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\"")) } + +type flagValueTestCase struct { + name string + flag Flag + toParse []string + expect string +} + +func TestFlagValue(t *testing.T) { + cases := []*flagValueTestCase{ + &flagValueTestCase{ + name: "stringSclice", + flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, + toParse: []string{"--flag", "parsed,parsed2", "--flag", "parsed3,parsed4"}, + expect: `[parsed parsed2 parsed3 parsed4]`, + }, + &flagValueTestCase{ + name: "float64Sclice", + flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, + toParse: []string{"--flag", "13.3,14.4", "--flag", "15.5,16.6"}, + expect: `[]float64{13.3, 14.4, 15.5, 16.6}`, + }, + &flagValueTestCase{ + name: "int64Sclice", + flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]int64{13, 14, 15, 16}`, + }, + &flagValueTestCase{ + name: "intSclice", + flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]int{13, 14, 15, 16}`, + }, + } + for i, v := range cases { + set := flag.NewFlagSet("test", 0) + set.SetOutput(ioutil.Discard) + _ = v.flag.Apply(set) + if err := set.Parse(v.toParse); err != nil { + t.Error(err) + } + f := set.Lookup("flag") + if got := f.Value.String(); got != v.expect { + t.Errorf("TestFlagValue %d-%s\nexpect:%s\ngot:%s", i, v.name, v.expect, got) + } + } +} From 06f6815b8d881483b4ce75de2471fa7d2b724903 Mon Sep 17 00:00:00 2001 From: Ally Dale Date: Mon, 8 Feb 2021 22:41:34 +0800 Subject: [PATCH 2/2] revert docs/v2/manual.md --- docs/v2/manual.md | 46 ------------------------------------------- flag_float64_slice.go | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 8f7cbbb4ca..af09010b9a 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -14,7 +14,6 @@ cli v2 manual + [Values from the Environment](#values-from-the-environment) + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) - + [Muti values](#multi-values) + [Required Flags](#required-flags) + [Default Values for help output](#default-values-for-help-output) + [Precedence](#precedence) @@ -661,51 +660,6 @@ func main() { } ``` -#### Multi values - -Slice flags(Float64SliceFlag, Int64SliceFlag, IntSliceFlag, StringSliceFlag) are designed to accept multi values. - -Here is a sample of setting multi values for slice flags: - - -``` -package main - -import ( - "fmt" - "os" - "github.com/urfave/cli/v2" -) - -func main() { - os.Args = []string{"multi_values", - "--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4", - "--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6", - "--int64Sclice", "13,14", "--int64Sclice", "15,16", - "--intSclice", "13,14", "--intSclice", "15,16", - } - app := cli.NewApp() - app.Name = "multi_values" - app.Flags = []cli.Flag{ - &cli.StringSliceFlag{Name: "stringSclice"}, - &cli.Float64SliceFlag{Name: "float64Sclice"}, - &cli.Int64SliceFlag{Name: "int64Sclice"}, - &cli.IntSliceFlag{Name: "intSclice"}, - } - app.Action = func(ctx *cli.Context) error { - for i, v := range ctx.FlagNames() { - fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v)) - } - return ctx.Err() - } - - _ = app.Run(os.Args) -} - -``` - #### Required Flags You can make a flag required by setting the `Required` field to `true`. If a user diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 8ee1204ec3..343c9b0d9a 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -125,7 +125,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { if val != "" { f.Value = &Float64Slice{} - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) }