From 73519a3538720b2cbca59cde36f755a1e4068eae Mon Sep 17 00:00:00 2001 From: GrĂ©goire DuchĂȘne Date: Sun, 4 Dec 2022 00:00:47 +0000 Subject: Add Feature and FlagFeature{,Var} Feature is essentially a boolean flag that can be safely mutated at run time. FlagFeature{,Var} make Feature objects work with flags. --- flag.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ flag_test.go | 21 ++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/flag.go b/flag.go index 4506683..fd60893 100644 --- a/flag.go +++ b/flag.go @@ -7,7 +7,9 @@ import ( "flag" "fmt" "os" + "strconv" "strings" + "sync/atomic" "time" ) @@ -103,6 +105,41 @@ func InitFlagSet(fs *flag.FlagSet, env []string, cfg map[string]string, args []s return err } +// Feature represent a code feature that can be enabled and disabled. +// +// Feature must not be copied after its first use. +type Feature struct { + Name string + + _ NoCopy + enabled int32 +} + +// FlagFeature creates a feature that, i.e. a boolean flag that can +// potentially be changed at run time. +func FlagFeature(fs *flag.FlagSet, name string, enabled bool, usage string) *Feature { + f := &Feature{Name: name} + if enabled { + f.enabled = 1 + } else { + f.enabled = 0 + } + FlagFeatureVar(fs, f, name, usage) + return f +} + +func FlagFeatureVar(fs *flag.FlagSet, f *Feature, name, usage string) { + fs.Var(flagFeature{f}, name, usage) +} + +func (f *Feature) Disable() { atomic.SwapInt32(&f.enabled, 0) } +func (f *Feature) Enable() { atomic.SwapInt32(&f.enabled, 1) } +func (f *Feature) Enabled() bool { return atomic.LoadInt32(&f.enabled) == 1 } + +func (f *Feature) String() string { + return fmt.Sprintf("%s (enabled: %t)", f.Name, f.Enabled()) +} + // ParseTime parses a string according to the time.RFC3339 format. func ParseTime(s string) (time.Time, error) { return time.Parse(time.RFC3339, s) @@ -112,6 +149,31 @@ func ParseTime(s string) (time.Time, error) { // value or an error. type ParseFunc[T any] func(string) (T, error) +type flagFeature struct{ *Feature } + +func (flagFeature) IsBoolFlag() bool { return true } +func (flagFeature) MutableFlag() {} + +func (f flagFeature) Set(s string) error { + enable, err := strconv.ParseBool(s) + if err != nil { + return err + } + if enable { + f.Enable() + } else { + f.Disable() + } + return nil +} + +func (f flagFeature) String() string { + if f.Enabled() { + return "true" + } + return "false" +} + type flagValue[T any] struct { Parse ParseFunc[T] Value *T diff --git a/flag_test.go b/flag_test.go index 91c6caa..1cc9ba8 100644 --- a/flag_test.go +++ b/flag_test.go @@ -11,6 +11,18 @@ import ( "go.awhk.org/core" ) +func TestFeature_Disable(t *testing.T) { + f := core.Feature{} + f.Disable() + (&core.T{T: t}).AssertEqual(false, f.Enabled()) +} + +func TestFeature_Enable(t *testing.T) { + f := core.Feature{} + f.Enable() + (&core.T{T: t}).AssertEqual(true, f.Enabled()) +} + func TestFlag(s *testing.T) { t := core.T{T: s} @@ -21,6 +33,15 @@ func TestFlag(s *testing.T) { t.AssertEqual(84, *fl) } +func TestFlagFeature(s *testing.T) { + t := core.T{T: s} + + fs := flag.NewFlagSet("", flag.PanicOnError) + ff := core.FlagFeature(fs, "some-feature", false, "") + t.AssertErrorIs(nil, fs.Parse([]string{"-some-feature"})) + t.AssertEqual(true, ff.Enabled()) +} + func TestFlagVar(s *testing.T) { t := core.T{T: s} -- cgit v1.2.3-70-g09d2