fix #1238: accept multi-value input on sclice flags
This commit is contained in:
parent
92d77844fe
commit
ef9430e77e
34
app_test.go
34
app_test.go
@ -390,6 +390,40 @@ func ExampleApp_Run_zshComplete() {
|
|||||||
// h:Shows a list of commands or help for one command
|
// 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: <nil>
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_Run(t *testing.T) {
|
func TestApp_Run(t *testing.T) {
|
||||||
s := ""
|
s := ""
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ cli v2 manual
|
|||||||
+ [Values from the Environment](#values-from-the-environment)
|
+ [Values from the Environment](#values-from-the-environment)
|
||||||
+ [Values from files](#values-from-files)
|
+ [Values from files](#values-from-files)
|
||||||
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
|
+ [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)
|
+ [Required Flags](#required-flags)
|
||||||
+ [Default Values for help output](#default-values-for-help-output)
|
+ [Default Values for help output](#default-values-for-help-output)
|
||||||
+ [Precedence](#precedence)
|
+ [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:
|
||||||
|
|
||||||
|
<!-- {
|
||||||
|
"output": "0-float64Sclice cli.Float64Slice{slice:[]float64{13.3, 14.4, 15.5, 16.6}, hasBeenSet:true}\n1-int64Sclice cli.Int64Slice{slice:[]int64{13, 14, 15, 16}, hasBeenSet:true}\n2-intSclice cli.IntSlice{slice:[]int{13, 14, 15, 16}, hasBeenSet:true}\n3-stringSclice cli.StringSlice{slice:[]string{"parsed1", "parsed2", "parsed3", "parsed4"}, hasBeenSet:true}",
|
||||||
|
} -->
|
||||||
|
```
|
||||||
|
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
|
#### Required Flags
|
||||||
|
|
||||||
You can make a flag required by setting the `Required` field to `true`. If a user
|
You can make a flag required by setting the `Required` field to `true`. If a user
|
||||||
|
4
flag.go
4
flag.go
@ -390,3 +390,7 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool)
|
|||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flagSplitMultiValues(val string) []string {
|
||||||
|
return strings.Split(val, ",")
|
||||||
|
}
|
||||||
|
@ -33,12 +33,14 @@ func (f *Float64Slice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, err := strconv.ParseFloat(value, 64)
|
for _, s := range flagSplitMultiValues(value) {
|
||||||
if err != nil {
|
tmp, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
f.slice = append(f.slice, tmp)
|
f.slice = append(f.slice, tmp)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +33,14 @@ func (i *Int64Slice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, err := strconv.ParseInt(value, 0, 64)
|
for _, s := range flagSplitMultiValues(value) {
|
||||||
if err != nil {
|
tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
i.slice = append(i.slice, tmp)
|
i.slice = append(i.slice, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -123,7 +125,7 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
f.Value = &Int64Slice{}
|
f.Value = &Int64Slice{}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
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)
|
return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
|
@ -44,12 +44,14 @@ func (i *IntSlice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, err := strconv.ParseInt(value, 0, 64)
|
for _, s := range flagSplitMultiValues(value) {
|
||||||
if err != nil {
|
tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
i.slice = append(i.slice, int(tmp))
|
i.slice = append(i.slice, int(tmp))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -134,7 +136,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
f.Value = &IntSlice{}
|
f.Value = &IntSlice{}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
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)
|
return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,9 @@ func (s *StringSlice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.slice = append(s.slice, value)
|
for _, t := range flagSplitMultiValues(value) {
|
||||||
|
s.slice = append(s.slice, strings.TrimSpace(t))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -132,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
destination = f.Destination
|
destination = f.Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := destination.Set(strings.TrimSpace(s)); err != nil {
|
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)
|
return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
|
48
flag_test.go
48
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"})
|
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\""))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user