diff --git a/bool_slice.go b/bool_slice.go index 5af02f1a..3731370d 100644 --- a/bool_slice.go +++ b/bool_slice.go @@ -71,6 +71,44 @@ func (s *boolSliceValue) String() string { return "[" + out + "]" } +func (s *boolSliceValue) fromString(val string) (bool, error) { + return strconv.ParseBool(val) +} + +func (s *boolSliceValue) toString(val bool) string { + return strconv.FormatBool(val) +} + +func (s *boolSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *boolSliceValue) Replace(val []string) error { + out := make([]bool, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *boolSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func boolSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/bool_slice_test.go b/bool_slice_test.go index b617dd23..3c5a274f 100644 --- a/bool_slice_test.go +++ b/bool_slice_test.go @@ -160,6 +160,29 @@ func TestBSCalledTwice(t *testing.T) { } } +func TestBSAsSliceValue(t *testing.T) { + var bs []bool + f := setUpBSFlagSet(&bs) + + in := []string{"true", "false"} + argfmt := "--bs=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"false"}) + } + }) + if len(bs) != 1 || bs[0] != false { + t.Fatalf("Expected ss to be overwritten with 'false', but got: %v", bs) + } +} + func TestBSBadQuoting(t *testing.T) { tests := []struct { diff --git a/duration_slice.go b/duration_slice.go index 52c6b6dc..badadda5 100644 --- a/duration_slice.go +++ b/duration_slice.go @@ -51,6 +51,44 @@ func (s *durationSliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *durationSliceValue) fromString(val string) (time.Duration, error) { + return time.ParseDuration(val) +} + +func (s *durationSliceValue) toString(val time.Duration) string { + return fmt.Sprintf("%s", val) +} + +func (s *durationSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *durationSliceValue) Replace(val []string) error { + out := make([]time.Duration, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *durationSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func durationSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/duration_slice_test.go b/duration_slice_test.go index 489b012f..651fbd8b 100644 --- a/duration_slice_test.go +++ b/duration_slice_test.go @@ -144,6 +144,29 @@ func TestDSWithDefault(t *testing.T) { } } +func TestDSAsSliceValue(t *testing.T) { + var ds []time.Duration + f := setUpDSFlagSet(&ds) + + in := []string{"1ns", "2ns"} + argfmt := "--ds=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3ns"}) + } + }) + if len(ds) != 1 || ds[0] != time.Duration(3) { + t.Fatalf("Expected ss to be overwritten with '3ns', but got: %v", ds) + } +} + func TestDSCalledTwice(t *testing.T) { var ds []time.Duration f := setUpDSFlagSet(&ds) diff --git a/flag.go b/flag.go index 669c3928..24a5036e 100644 --- a/flag.go +++ b/flag.go @@ -190,6 +190,18 @@ type Value interface { Type() string } +// SliceValue is a secondary interface to all flags which hold a list +// of values. This allows full control over the value of list flags, +// and avoids complicated marshalling and unmarshalling to csv. +type SliceValue interface { + // Append adds the specified value to the end of the flag value list. + Append(string) error + // Replace will fully overwrite any data currently in the flag value list. + Replace([]string) error + // GetSlice returns the flag value list as an array of strings. + GetSlice() []string +} + // sortFlags returns the flags as a slice in lexicographical sorted order. func sortFlags(flags map[NormalizedName]*Flag) []*Flag { list := make(sort.StringSlice, len(flags)) diff --git a/float32_slice.go b/float32_slice.go index a80848a9..caa35274 100644 --- a/float32_slice.go +++ b/float32_slice.go @@ -53,6 +53,48 @@ func (s *float32SliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *float32SliceValue) fromString(val string) (float32, error) { + t64, err := strconv.ParseFloat(val, 32) + if err != nil { + return 0, err + } + return float32(t64), nil +} + +func (s *float32SliceValue) toString(val float32) string { + return fmt.Sprintf("%f", val) +} + +func (s *float32SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *float32SliceValue) Replace(val []string) error { + out := make([]float32, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *float32SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func float32SliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/float32_slice_test.go b/float32_slice_test.go index 2429c8f4..997ce5c6 100644 --- a/float32_slice_test.go +++ b/float32_slice_test.go @@ -156,6 +156,29 @@ func TestF32SWithDefault(t *testing.T) { } } +func TestF32SAsSliceValue(t *testing.T) { + var f32s []float32 + f := setUpF32SFlagSet(&f32s) + + in := []string{"1.0", "2.0"} + argfmt := "--f32s=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3.1"}) + } + }) + if len(f32s) != 1 || f32s[0] != 3.1 { + t.Fatalf("Expected ss to be overwritten with '3.1', but got: %v", f32s) + } +} + func TestF32SCalledTwice(t *testing.T) { var f32s []float32 f := setUpF32SFlagSet(&f32s) diff --git a/float64_slice.go b/float64_slice.go index 45d12b89..85bf3073 100644 --- a/float64_slice.go +++ b/float64_slice.go @@ -51,6 +51,44 @@ func (s *float64SliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *float64SliceValue) fromString(val string) (float64, error) { + return strconv.ParseFloat(val, 64) +} + +func (s *float64SliceValue) toString(val float64) string { + return fmt.Sprintf("%f", val) +} + +func (s *float64SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *float64SliceValue) Replace(val []string) error { + out := make([]float64, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *float64SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func float64SliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/float64_slice_test.go b/float64_slice_test.go index fe9ede4e..43778ef1 100644 --- a/float64_slice_test.go +++ b/float64_slice_test.go @@ -144,6 +144,29 @@ func TestF64SWithDefault(t *testing.T) { } } +func TestF64SAsSliceValue(t *testing.T) { + var f64s []float64 + f := setUpF64SFlagSet(&f64s) + + in := []string{"1.0", "2.0"} + argfmt := "--f64s=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3.1"}) + } + }) + if len(f64s) != 1 || f64s[0] != 3.1 { + t.Fatalf("Expected ss to be overwritten with '3.1', but got: %v", f64s) + } +} + func TestF64SCalledTwice(t *testing.T) { var f64s []float64 f := setUpF64SFlagSet(&f64s) diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..7eb892ee --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/spf13/pflags + +go 1.12 + +require github.com/spf13/pflag v1.0.3 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..edd0bcf7 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= diff --git a/int32_slice.go b/int32_slice.go index 6ccedccb..ff128ff0 100644 --- a/int32_slice.go +++ b/int32_slice.go @@ -53,6 +53,48 @@ func (s *int32SliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *int32SliceValue) fromString(val string) (int32, error) { + t64, err := strconv.ParseInt(val, 0, 32) + if err != nil { + return 0, err + } + return int32(t64), nil +} + +func (s *int32SliceValue) toString(val int32) string { + return fmt.Sprintf("%d", val) +} + +func (s *int32SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *int32SliceValue) Replace(val []string) error { + out := make([]int32, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *int32SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func int32SliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/int32_slice_test.go b/int32_slice_test.go index 69aaf9b1..809c5633 100644 --- a/int32_slice_test.go +++ b/int32_slice_test.go @@ -150,6 +150,29 @@ func TestI32SWithDefault(t *testing.T) { } } +func TestI32SAsSliceValue(t *testing.T) { + var i32s []int32 + f := setUpI32SFlagSet(&i32s) + + in := []string{"1", "2"} + argfmt := "--is=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3"}) + } + }) + if len(i32s) != 1 || i32s[0] != 3 { + t.Fatalf("Expected ss to be overwritten with '3.1', but got: %v", i32s) + } +} + func TestI32SCalledTwice(t *testing.T) { var is []int32 f := setUpI32SFlagSet(&is) diff --git a/int64_slice.go b/int64_slice.go index 86cb6198..25464638 100644 --- a/int64_slice.go +++ b/int64_slice.go @@ -51,6 +51,44 @@ func (s *int64SliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *int64SliceValue) fromString(val string) (int64, error) { + return strconv.ParseInt(val, 0, 64) +} + +func (s *int64SliceValue) toString(val int64) string { + return fmt.Sprintf("%d", val) +} + +func (s *int64SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *int64SliceValue) Replace(val []string) error { + out := make([]int64, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *int64SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func int64SliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/int64_slice_test.go b/int64_slice_test.go index b536943e..09805c76 100644 --- a/int64_slice_test.go +++ b/int64_slice_test.go @@ -144,6 +144,29 @@ func TestI64SWithDefault(t *testing.T) { } } +func TestI64SAsSliceValue(t *testing.T) { + var i64s []int64 + f := setUpI64SFlagSet(&i64s) + + in := []string{"1", "2"} + argfmt := "--is=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3"}) + } + }) + if len(i64s) != 1 || i64s[0] != 3 { + t.Fatalf("Expected ss to be overwritten with '3.1', but got: %v", i64s) + } +} + func TestI64SCalledTwice(t *testing.T) { var is []int64 f := setUpI64SFlagSet(&is) diff --git a/int_slice.go b/int_slice.go index 1e7c9edd..e71c39d9 100644 --- a/int_slice.go +++ b/int_slice.go @@ -51,6 +51,36 @@ func (s *intSliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *intSliceValue) Append(val string) error { + i, err := strconv.Atoi(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *intSliceValue) Replace(val []string) error { + out := make([]int, len(val)) + for i, d := range val { + var err error + out[i], err = strconv.Atoi(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *intSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = strconv.Itoa(d) + } + return out +} + func intSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/ip_slice.go b/ip_slice.go index 6ad2e926..775faae4 100644 --- a/ip_slice.go +++ b/ip_slice.go @@ -72,6 +72,44 @@ func (s *ipSliceValue) String() string { return "[" + out + "]" } +func (s *ipSliceValue) fromString(val string) (net.IP, error) { + return net.ParseIP(strings.TrimSpace(val)), nil +} + +func (s *ipSliceValue) toString(val net.IP) string { + return val.String() +} + +func (s *ipSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *ipSliceValue) Replace(val []string) error { + out := make([]net.IP, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *ipSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func ipSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/ip_slice_test.go b/ip_slice_test.go index b0c681c5..d1892768 100644 --- a/ip_slice_test.go +++ b/ip_slice_test.go @@ -141,6 +141,29 @@ func TestIPSCalledTwice(t *testing.T) { } } +func TestIPSAsSliceValue(t *testing.T) { + var ips []net.IP + f := setUpIPSFlagSet(&ips) + + in := []string{"192.168.1.1", "0:0:0:0:0:0:0:1"} + argfmt := "--ips=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"192.168.1.2"}) + } + }) + if len(ips) != 1 || !ips[0].Equal(net.ParseIP("192.168.1.2")) { + t.Fatalf("Expected ss to be overwritten with '192.168.1.2', but got: %v", ips) + } +} + func TestIPSBadQuoting(t *testing.T) { tests := []struct { diff --git a/string_array.go b/string_array.go index fa7bc601..4894af81 100644 --- a/string_array.go +++ b/string_array.go @@ -23,6 +23,32 @@ func (s *stringArrayValue) Set(val string) error { return nil } +func (s *stringArrayValue) Append(val string) error { + *s.value = append(*s.value, val) + return nil +} + +func (s *stringArrayValue) Replace(val []string) error { + out := make([]string, len(val)) + for i, d := range val { + var err error + out[i] = d + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *stringArrayValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = d + } + return out +} + func (s *stringArrayValue) Type() string { return "stringArray" } diff --git a/string_array_test.go b/string_array_test.go index 1ceac8c6..3c6d5958 100644 --- a/string_array_test.go +++ b/string_array_test.go @@ -193,6 +193,29 @@ func TestSAWithSpecialChar(t *testing.T) { } } +func TestSAAsSliceValue(t *testing.T) { + var sa []string + f := setUpSAFlagSet(&sa) + + in := []string{"1ns", "2ns"} + argfmt := "--sa=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3ns"}) + } + }) + if len(sa) != 1 || sa[0] != "3ns" { + t.Fatalf("Expected ss to be overwritten with '3ns', but got: %v", sa) + } +} + func TestSAWithSquareBrackets(t *testing.T) { var sa []string f := setUpSAFlagSet(&sa) diff --git a/string_slice.go b/string_slice.go index b816ca81..3cb2e69d 100644 --- a/string_slice.go +++ b/string_slice.go @@ -62,6 +62,20 @@ func (s *stringSliceValue) String() string { return "[" + str + "]" } +func (s *stringSliceValue) Append(val string) error { + *s.value = append(*s.value, val) + return nil +} + +func (s *stringSliceValue) Replace(val []string) error { + *s.value = val + return nil +} + +func (s *stringSliceValue) GetSlice() []string { + return *s.value +} + func stringSliceConv(sval string) (interface{}, error) { sval = sval[1 : len(sval)-1] // An empty string would cause a slice with one (empty) string diff --git a/string_slice_test.go b/string_slice_test.go index c41f3bd6..96924617 100644 --- a/string_slice_test.go +++ b/string_slice_test.go @@ -251,3 +251,26 @@ func TestSSWithSquareBrackets(t *testing.T) { } } } + +func TestSSAsSliceValue(t *testing.T) { + var ss []string + f := setUpSSFlagSet(&ss) + + in := []string{"one", "two"} + argfmt := "--ss=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"three"}) + } + }) + if len(ss) != 1 || ss[0] != "three" { + t.Fatalf("Expected ss to be overwritten with 'three', but got: %s", ss) + } +} diff --git a/uint_slice.go b/uint_slice.go index edd94c60..5fa92483 100644 --- a/uint_slice.go +++ b/uint_slice.go @@ -50,6 +50,48 @@ func (s *uintSliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *uintSliceValue) fromString(val string) (uint, error) { + t, err := strconv.ParseUint(val, 10, 0) + if err != nil { + return 0, err + } + return uint(t), nil +} + +func (s *uintSliceValue) toString(val uint) string { + return fmt.Sprintf("%d", val) +} + +func (s *uintSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *uintSliceValue) Replace(val []string) error { + out := make([]uint, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *uintSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func uintSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/uint_slice_test.go b/uint_slice_test.go index db1a19dc..d0da4d07 100644 --- a/uint_slice_test.go +++ b/uint_slice_test.go @@ -140,6 +140,29 @@ func TestUISWithDefault(t *testing.T) { } } +func TestUISAsSliceValue(t *testing.T) { + var uis []uint + f := setUpUISFlagSet(&uis) + + in := []string{"1", "2"} + argfmt := "--uis=%s" + arg1 := fmt.Sprintf(argfmt, in[0]) + arg2 := fmt.Sprintf(argfmt, in[1]) + err := f.Parse([]string{arg1, arg2}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + f.VisitAll(func(f *Flag) { + if val, ok := f.Value.(SliceValue); ok { + _ = val.Replace([]string{"3"}) + } + }) + if len(uis) != 1 || uis[0] != 3 { + t.Fatalf("Expected ss to be overwritten with '3.1', but got: %v", uis) + } +} + func TestUISCalledTwice(t *testing.T) { var uis []uint f := setUpUISFlagSet(&uis)