Skip to content

Commit f0851c9

Browse files
authored
Add a String() to Set and Array so it looks better on UI and symetrical with coma input (#4)
* Add a String() to Set so it looks better on UI and symetrical with coma separated input * fix array case too and show in example * fix test (or... made it pass) * actually fix (and add a clearer) test for default value of arrays * add a bunch of set functions * switch for fortio.org/sets
1 parent 867970e commit f0851c9

File tree

9 files changed

+83
-35
lines changed

9 files changed

+83
-35
lines changed

dyngeneric.go

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sync/atomic"
1111
"time"
1212

13+
"fortio.org/sets"
1314
"golang.org/x/exp/constraints"
1415
)
1516

@@ -50,19 +51,17 @@ func (*DynamicBoolValueTag) IsBoolFlag() bool {
5051

5152
// ---- Generics section ---
5253

53-
type Set[T comparable] map[T]struct{}
54-
5554
// ValidateDynSetMinElements validates that the given Set has at least x elements.
56-
func ValidateDynSetMinElements[T comparable](count int) func(Set[T]) error {
57-
return func(value Set[T]) error {
55+
func ValidateDynSetMinElements[T comparable](count int) func(sets.Set[T]) error {
56+
return func(value sets.Set[T]) error {
5857
if len(value) < count {
5958
return fmt.Errorf("value set %+v must have at least %v elements", value, count)
6059
}
6160
return nil
6261
}
6362
}
6463

65-
// ValidateDynSliceMinElements validates that the given Set has at least x elements.
64+
// ValidateDynSliceMinElements validates that the given array has at least x elements.
6665
func ValidateDynSliceMinElements[T any](count int) func([]T) error {
6766
return func(value []T) error {
6867
if len(value) < count {
@@ -75,7 +74,7 @@ func ValidateDynSliceMinElements[T any](count int) func([]T) error {
7574
// DynValueTypes are the types currently supported by Parse[T] and thus by Dyn[T].
7675
// DynJSON is special.
7776
type DynValueTypes interface {
78-
bool | time.Duration | float64 | int64 | string | []string | Set[string]
77+
bool | time.Duration | float64 | int64 | string | []string | sets.Set[string]
7978
}
8079

8180
type DynValue[T any] struct {
@@ -119,7 +118,7 @@ func FlagSet[T DynValueTypes](flagSet *flag.FlagSet, name string, dynValue *DynV
119118
dynValue.flagSet = flagSet
120119
dynValue.flagName = name
121120
flagSet.Var(dynValue, name, dynValue.usage)
122-
flagSet.Lookup(name).DefValue = fmt.Sprintf("%v", dynValue.av.Load())
121+
flagSet.Lookup(name).DefValue = dynValue.String()
123122
return dynValue
124123
}
125124

@@ -194,24 +193,15 @@ func parse[T any](input string) (val T, err error) {
194193
*v = input
195194
case *[]string:
196195
*v = CommaStringToSlice(input)
197-
case *Set[string]:
198-
*v = SetFromSlice(CommaStringToSlice(input))
196+
case *sets.Set[string]:
197+
*v = sets.FromSlice(CommaStringToSlice(input))
199198
default:
200199
// JSON Set() and thus Parse() is handled in dynjson.go
201200
err = fmt.Errorf("unexpected type %T", val)
202201
}
203202
return
204203
}
205204

206-
// SetFromSlice constructs a Set from a slice.
207-
func SetFromSlice[T comparable](items []T) Set[T] {
208-
res := map[T]struct{}{}
209-
for _, item := range items {
210-
res[item] = struct{}{}
211-
}
212-
return res
213-
}
214-
215205
// Set updates the value from a string representation in a thread-safe manner.
216206
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
217207
// optional validator.
@@ -282,7 +272,12 @@ func (d *DynValue[T]) Type() string {
282272

283273
// String returns the canonical string representation of the type.
284274
func (d *DynValue[T]) String() string {
285-
return fmt.Sprintf("%v", d.Get())
275+
switch v := any(d.Get()).(type) {
276+
case []string:
277+
return strings.Join(v, ",")
278+
default:
279+
return fmt.Sprintf("%v", d.Get())
280+
}
286281
}
287282

288283
// WithValueMutator adds a function that changes the value of a flag as needed.

dyngeneric_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"fortio.org/assert"
11+
"fortio.org/sets"
1112
)
1213

1314
// Additional generic tests, most tests are covered by the old per type tests.
@@ -31,3 +32,41 @@ func TestDflag_NonDynamic(t *testing.T) {
3132
assert.True(t, static != nil)
3233
assert.False(t, IsFlagDynamic(static))
3334
}
35+
36+
func TestSetToString(t *testing.T) {
37+
s := sets.Set[string]{"z": {}, "a": {}, "c": {}, "b": {}}
38+
f := New(s, "test set")
39+
assert.Equal(t, "a,b,c,z", s.String())
40+
assert.Equal(t, "a,b,c,z", f.Get().String())
41+
}
42+
43+
func TestArrayToString(t *testing.T) {
44+
s := []string{"z", "a", "c", "b"}
45+
f := New(s, "test array")
46+
Flag("testing123", f)
47+
defValue := flag.CommandLine.Lookup("testing123").DefValue
48+
// order preserved unlike for sets.Set where we sort
49+
str := f.String()
50+
assert.Equal(t, "z,a,c,b", str)
51+
assert.Equal(t, "z,a,c,b", defValue)
52+
}
53+
54+
func TestRemoveCommon(t *testing.T) {
55+
setA := sets.New("a", "b", "c", "d")
56+
setB := sets.New("b", "d", "e", "f", "g")
57+
setAA := setA.Clone()
58+
setBB := setB.Clone()
59+
sets.RemoveCommon(setAA, setBB)
60+
assert.Equal(t, "a,c", setAA.String()) // removed
61+
assert.Equal(t, "e,f,g", setBB.String()) // added
62+
// Swap order to exercise the optimization on length of iteration
63+
// also check clone is not modifying the original etc
64+
setAA = setB.Clone() // putting B in AA on purpose and vice versa
65+
setBB = setA.Clone()
66+
sets.RemoveCommon(setAA, setBB)
67+
assert.Equal(t, "a,c", setBB.String())
68+
assert.Equal(t, "e,f,g", setAA.String())
69+
assert.True(t, setBB.Has("c"))
70+
setBB.Remove("c")
71+
assert.False(t, setBB.Has("c"))
72+
}

dynstringset.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@ package dflag
66
import (
77
"flag"
88
"fmt"
9+
10+
"fortio.org/sets"
911
)
1012

1113
// DynStringSet creates a `Flag` that represents `map[string]struct{}` which is safe to change dynamically at runtime.
1214
// Unlike `pflag.StringSlice`, consecutive sets don't append to the slice, but override it.
1315
func DynStringSet(flagSet *flag.FlagSet, name string, value []string, usage string) *DynStringSetValue {
14-
d := Dyn(flagSet, name, SetFromSlice(value), usage)
16+
d := Dyn(flagSet, name, sets.FromSlice(value), usage)
1517
return &DynStringSetValue{d}
1618
}
1719

1820
// In order to have methods unique to this subtype... we extend/have the generic instantiated type:
1921

2022
// DynStringSetValue implements a dynamic set of strings.
2123
type DynStringSetValue struct {
22-
*DynValue[Set[string]]
24+
*DynValue[sets.Set[string]]
2325
}
2426

2527
// Contains returns whether the specified string is in the flag.
@@ -39,6 +41,6 @@ func (d *DynStringSetValue) String() string {
3941
return fmt.Sprintf("%v", arr)
4042
}
4143

42-
func ValidateDynStringSetMinElements(count int) func(Set[string]) error {
44+
func ValidateDynStringSetMinElements(count int) func(sets.Set[string]) error {
4345
return ValidateDynSetMinElements[string](count)
4446
}

dynstringset_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2+
// Copyright 2020-2023 Fortio Authors. All Rights Reserved.
23
// See LICENSE for licensing terms.
34

45
package dflag
@@ -9,15 +10,16 @@ import (
910
"time"
1011

1112
"fortio.org/assert"
13+
"fortio.org/sets"
1214
)
1315

1416
func TestDynStringSet_SetAndGet(t *testing.T) {
1517
set := flag.NewFlagSet("foobar", flag.ContinueOnError)
1618
dynFlag := DynStringSet(set, "some_stringslice_1", []string{"foo", "bar"}, "Use it or lose it")
17-
assert.Equal(t, Set[string]{"foo": {}, "bar": {}}, dynFlag.Get(), "value must be default after create")
19+
assert.Equal(t, sets.New("foo", "bar"), dynFlag.Get(), "value must be default after create")
1820
err := set.Set("some_stringslice_1", "car,bar")
1921
assert.NoError(t, err, "setting value must succeed")
20-
assert.Equal(t, Set[string]{"car": {}, "bar": {}}, dynFlag.Get(), "value must be set after update")
22+
assert.Equal(t, sets.New("car", "bar"), dynFlag.Get(), "value must be set after update")
2123
}
2224

2325
func TestDynStringSet_Contains(t *testing.T) {
@@ -45,9 +47,9 @@ func TestDynStringSet_FiresValidators(t *testing.T) {
4547

4648
func TestDynStringSet_FiresNotifier(t *testing.T) {
4749
waitCh := make(chan struct{}, 1)
48-
notifier := func(oldVal Set[string], newVal Set[string]) {
49-
assert.EqualValues(t, Set[string]{"foo": {}, "bar": {}}, oldVal, "old value in notify must match previous value")
50-
assert.EqualValues(t, Set[string]{"car": {}, "far": {}}, newVal, "new value in notify must match set value")
50+
notifier := func(oldVal sets.Set[string], newVal sets.Set[string]) {
51+
assert.EqualValues(t, sets.Set[string]{"foo": {}, "bar": {}}, oldVal, "old value in notify must match previous value")
52+
assert.EqualValues(t, sets.Set[string]{"car": {}, "far": {}}, newVal, "new value in notify must match set value")
5153
waitCh <- struct{}{}
5254
}
5355

endpoint/endpoint.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2+
// Copyright 2020-2023 Fortio Authors. All Rights Reserved.
23
// See LICENSE for licensing terms.
34

45
package endpoint

endpoint/endpoint_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ func (s *endpointTestSuite) TestCorrectlyRepresentsResources() {
9797
&flagJSON{
9898
Name: "some_dyn_stringslice",
9999
Description: "Some dynamic slice text",
100-
CurrentValue: "[car star]",
101-
DefaultValue: "[foo bar]",
100+
CurrentValue: "car,star",
101+
DefaultValue: "foo,bar",
102102
IsChanged: true,
103103
IsDynamic: true,
104104
},

examples/server_kube/http.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2016 Michal Witkowski. All Rights Reserved.
2+
// Copyright 2020-2023 Fortio Authors. All Rights Reserved.
23
// See LICENSE for licensing terms.
34

45
package main
@@ -14,6 +15,7 @@ import (
1415
"fortio.org/dflag/dynloglevel"
1516
"fortio.org/dflag/endpoint"
1617
"fortio.org/log"
18+
"fortio.org/sets"
1719
)
1820

1921
var (
@@ -47,11 +49,15 @@ var (
4749
},
4850
},
4951
"An arbitrary JSON struct.")
52+
dynArray = dflag.New([]string{"z", "b", "a"}, "An array of strings (comma separated)")
53+
dynSet = dflag.New(sets.New("z", "b", "a"), "An set of strings (comma separated)")
5054
)
5155

5256
func main() {
5357
dflag.FlagBool("example_bool3", dynBool3)
5458
dflag.Flag("example_str2", dynStr2)
59+
dflag.Flag("example_array", dynArray)
60+
dflag.Flag("example_set", dynSet)
5561
dynloglevel.LoggerFlagSetup()
5662
flag.Parse()
5763
u, err := configmap.Setup(flag.CommandLine, *dirPathWatch)

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ module fortio.org/dflag
33
go 1.19
44

55
require (
6-
fortio.org/assert v1.1.3
6+
fortio.org/assert v1.1.4
77
fortio.org/log v1.2.2
8+
fortio.org/sets v0.3.0
89
github.com/fsnotify/fsnotify v1.6.0
9-
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
10+
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
1011
)
1112

1213
require golang.org/x/sys v0.4.0 // indirect

go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
fortio.org/assert v1.1.3 h1:zXm8xiNiKvq2xG/YQ3sONAg3287XUuklKIDdjyD9pyg=
2-
fortio.org/assert v1.1.3/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls=
1+
fortio.org/assert v1.1.4 h1:Za1RaG+OjsTMpQS3J3UCvTF6wc4+IOHCz+jAOU37Y4o=
2+
fortio.org/assert v1.1.4/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls=
33
fortio.org/log v1.2.2 h1:vs42JjNwiqbMbacittZjJE9+oi72Za6aekML9gKmILg=
44
fortio.org/log v1.2.2/go.mod h1:u/8/2lyczXq52aT5Nw6reD+3cR6m/EbS2jBiIYhgiTU=
5+
fortio.org/sets v0.3.0 h1:StetVwMKYsatjzWuglrlElPaCA054zwiVUhDWTSg6wU=
6+
fortio.org/sets v0.3.0/go.mod h1:xVjulHr0FhlmReSymI+AhDtQ4FgjiazQ3JmuNpYFMs8=
57
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
68
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
7-
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab h1:628ME69lBm9C6JY2wXhAph/yjN3jezx1z7BIDLUwxjo=
8-
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
9+
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
10+
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
911
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1012
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
1113
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

0 commit comments

Comments
 (0)