Skip to content

Commit ac3ba1f

Browse files
committed
introduce New() and Flag()/FlagSet() to split definition of dynamic var from assignent to a flag
1 parent f6b0106 commit ac3ba1f

File tree

7 files changed

+79
-19
lines changed

7 files changed

+79
-19
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ All of this can be done simultaneously across a whole shard of your services.
3232
## Features
3333

3434
* compatible with standard go `flag` package
35-
* dynamic `flag` that are thread-safe and efficient:
35+
* dynamic `flag` that are thread-safe and efficient, now also `Dyn[T]` generic:
3636
- `DynBool`
3737
- `DynInt64`
3838
- `DynFloat64`
@@ -88,6 +88,14 @@ func MyHandler(resp http.ResponseWriter, req *http.Request) {
8888
```
8989

9090
All access to `featuresFlag`, which is a `[]string` flag, is synchronized across go-routines using `atomic` pointer swaps.
91+
## Library versus caller style
92+
93+
```golang
94+
// In the library "libfoo" package
95+
var MyConfig = dflag.New("default value", "explanation of what that is for").WithValidator(MyValidator)
96+
// In the caller/users, bind to an actual flag:
97+
dflag.Flag("foocfg", libfoo.MyConfig) // defines -foocfg flag
98+
```
9199

92100
## Complete example
93101

dynbool.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,34 @@ import (
1212
// type DynBoolValue = DynValue[bool]
1313
// but that doesn't work with IsBoolFlag
1414
// https://github.com/golang/go/issues/53473
15+
// only fixed in go 1.20
1516
// so we extend this type as special, the only one with that method.
1617

17-
// DynBool creates a `Flag` that represents `bool` which is safe to change dynamically at runtime.
18-
func DynBool(flagSet *flag.FlagSet, name string, value bool, usage string) *DynBoolValue {
18+
func NewBool(value bool, usage string) *DynBoolValue {
1919
dynValue := DynBoolValue{}
20-
dynInit(&dynValue.DynValue, flagSet, name, value, usage)
21-
flagSet.Var(&dynValue, name, usage)
22-
flagSet.Lookup(name).DefValue = fmt.Sprintf("%v", value)
20+
dynInit(&dynValue.DynValue, value, usage)
2321
return &dynValue
2422
}
2523

24+
func FlagBool(name string, o *DynBoolValue) *DynBoolValue {
25+
return FlagSetBool(flag.CommandLine, name, o)
26+
}
27+
28+
func FlagSetBool(flagSet *flag.FlagSet, name string, dynValue *DynBoolValue) *DynBoolValue {
29+
// we inline/repeat code of FlagSet(flagSet, name, &dynValue.DynValue)
30+
// because we need to set this specific type for the IsBoolFlag to work
31+
dynValue.flagSet = flagSet
32+
dynValue.flagName = name
33+
flagSet.Var(dynValue, name, dynValue.usage)
34+
flagSet.Lookup(name).DefValue = fmt.Sprintf("%v", dynValue.av.Load())
35+
return dynValue
36+
}
37+
38+
// DynBool creates a `Flag` that represents `bool` which is safe to change dynamically at runtime.
39+
func DynBool(flagSet *flag.FlagSet, name string, value bool, usage string) *DynBoolValue {
40+
return FlagSetBool(flagSet, name, NewBool(value, usage))
41+
}
42+
2643
// DynStringSetValue implements a dynamic set of strings.
2744
type DynBoolValue struct {
2845
DynamicBoolValueTag

dyngeneric.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,44 @@ type DynValue[T any] struct {
9292
usage string
9393
}
9494

95-
func Dyn[T DynValueTypes](flagSet *flag.FlagSet, name string, value T, usage string) *DynValue[T] {
95+
// New allows to define a dynamic flag in 2 steps. With the default value and other
96+
// options like validation in the first step (in a library code). And later
97+
// re-assigning using Flag()/FlagSet() to bind to an actual flag name and value.
98+
func New[T DynValueTypes](value T, usage string) *DynValue[T] {
9699
dynValue := DynValue[T]{}
97-
dynInit(&dynValue, flagSet, name, value, usage)
98-
flagSet.Var(&dynValue, name, usage)
99-
flagSet.Lookup(name).DefValue = fmt.Sprintf("%v", value)
100+
dynInit(&dynValue, value, usage)
100101
return &dynValue
101102
}
102103

103-
func dynInit[T any](dynValue *DynValue[T], flagSet *flag.FlagSet, name string, value T, usage string) {
104-
dynValue.flagName = name
104+
// Flag assigns a dynamic value to a command line flag.
105+
// e.g. in a library:
106+
//
107+
// var Value1 = dflag.New("default value", "explanation for value1's usage")
108+
//
109+
// in a main/where the library is used:
110+
//
111+
// dflag.Flag("flag1", library.Value1) // assigns to -flag1
112+
func Flag[T DynValueTypes](name string, o *DynValue[T]) *DynValue[T] {
113+
return FlagSet(flag.CommandLine, name, o)
114+
}
115+
116+
// FlagSet is like Flag but allows to specify the flagset to use.
117+
// also backward compatible with earlier versions of dflag.
118+
func FlagSet[T DynValueTypes](flagSet *flag.FlagSet, name string, dynValue *DynValue[T]) *DynValue[T] {
105119
dynValue.flagSet = flagSet
120+
dynValue.flagName = name
121+
flagSet.Var(dynValue, name, dynValue.usage)
122+
flagSet.Lookup(name).DefValue = fmt.Sprintf("%v", dynValue.av.Load())
123+
return dynValue
124+
}
125+
126+
// Dyn[type] is the all in one function to create a dynamic flag for a flagset.
127+
// For library prefer splitting into New() in library and Flag() in callers.
128+
func Dyn[T DynValueTypes](flagSet *flag.FlagSet, name string, value T, usage string) *DynValue[T] {
129+
return FlagSet(flagSet, name, New(value, usage))
130+
}
131+
132+
func dynInit[T any](dynValue *DynValue[T], value T, usage string) {
106133
dynValue.av.Store(value)
107134
dynValue.inpMutator = strings.TrimSpace // default so parsing of numbers etc works well
108135
dynValue.usage = usage
@@ -111,6 +138,7 @@ func dynInit[T any](dynValue *DynValue[T], flagSet *flag.FlagSet, name string, v
111138

112139
// Unfortunately IsBoolFlag isn't called, just presence is needed
113140
// https://github.com/golang/go/issues/53473
141+
// fixed in 1.20 only
114142

115143
/*
116144
// lets the flag parsing know that -flagname is enough to turn to true.

dynjson.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ func DynJSON(flagSet *flag.FlagSet, name string, value interface{}, usage string
2424
panic("DynJSON value must be a pointer to a struct or to a slice")
2525
}
2626
dynValue := DynJSONValue{}
27-
dynInit(&dynValue.DynValue, flagSet, name, value, usage)
27+
dynInit(&dynValue.DynValue, value, usage)
28+
dynValue.flagSet = flagSet
29+
dynValue.flagName = name
2830
dynValue.structType = reflectVal.Type().Elem()
2931
flagSet.Var(&dynValue, name, usage) // use our Set()
3032
flagSet.Lookup(name).DefValue = dynValue.usageString()

examples/server_kube/http.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ var (
2525
staticInt = flag.Int("example_my_static_int", 1337, "Something integery here.")
2626

2727
// With generics typing
28-
dynStr = dflag.Dyn(flag.CommandLine, "example_my_dynamic_string", "initial_value", "Something interesting here.")
29-
dynInt = dflag.Dyn(flag.CommandLine, "example_my_dynamic_int", int64(1337), "Something integery here.")
28+
dynStr = dflag.Dyn(flag.CommandLine, "example_my_dynamic_string", "initial_value", "Something interesting here.")
29+
dynInt = dflag.Dyn(flag.CommandLine, "example_my_dynamic_int", int64(1337), "Something `int`egery here.")
30+
// This shows the wrong help until 1.20 and https://github.com/golang/go/issues/53473 workaround is removed
3031
dynBool1 = dflag.Dyn(flag.CommandLine, "example_bool1", false, "Something true... or false. Starting false.")
3132
// Or explicit:
3233
dynBool2 = dflag.DynBool(flag.CommandLine, "example_bool2", true, "Something true... or false. Starting true.")
33-
34+
// Or new style:
35+
dynBool3 = dflag.NewBool(true, "defined in 2 steps... Starting true.")
36+
dynStr2 = dflag.New("starting string value", "explanation of the config variable")
3437
// This is an example of a dynamically-modifiable JSON flag of an arbitrary type.
3538
dynJSON = dflag.DynJSON(
3639
flag.CommandLine,
@@ -46,6 +49,8 @@ var (
4649
)
4750

4851
func main() {
52+
dynBool3 = dflag.FlagBool("example_bool3", dynBool3)
53+
dynStr2 = dflag.Flag("example_str2", dynStr2)
4954
flag.Parse()
5055
u, err := configmap.Setup(flag.CommandLine, *dirPathWatch)
5156
if err != nil {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module fortio.org/dflag
33
go 1.19
44

55
require (
6-
fortio.org/assert v1.1.2
6+
fortio.org/assert v1.1.3
77
fortio.org/log v1.1.0
88
github.com/fsnotify/fsnotify v1.6.0
99
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
fortio.org/assert v1.1.2 h1:t6WGDqPD5VFrUvx30U0+3mgXXcoPonrdKqt0vfJHn8E=
2-
fortio.org/assert v1.1.2/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls=
1+
fortio.org/assert v1.1.3 h1:zXm8xiNiKvq2xG/YQ3sONAg3287XUuklKIDdjyD9pyg=
2+
fortio.org/assert v1.1.3/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls=
33
fortio.org/log v1.1.0 h1:tCSTZwyLYXS2+1LD2edUoFzEzIFa1Fi75mpHwGoMU4Y=
44
fortio.org/log v1.1.0/go.mod h1:u/8/2lyczXq52aT5Nw6reD+3cR6m/EbS2jBiIYhgiTU=
55
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=

0 commit comments

Comments
 (0)