diff --git a/README.md b/README.md index f5213b8f31cf0777f2b085311396b6d8e93758be..0384678c3bbbc51284eee667167a126c1ada9225 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## X-Flags -## What do this library? +## What does this library? This library provides a simple way to use flags in your application. It extends the standard library to be able to define and use a structure with flags. @@ -25,8 +25,8 @@ go get gitlab.schukai.com/oss/libraries/go/network/xflags ### Initialize -A new flag set is created using the `xflags.New()` function. The passed -structure is used type for the flags. +A new flag set is created using the `xflags.New()` function. +The structure passed is used as the type for the flags. ```go package main diff --git a/api.go b/api.go index 0bc87fe48c1db25db21151332c6b3b914251fb5d..50dbaf00fdc3901f7848569313b4de36bece4d71 100644 --- a/api.go +++ b/api.go @@ -17,7 +17,7 @@ func New[C any](name string, definitions C) *setting[C] { errorHandling: flag.ContinueOnError, }, } - + if reflect.TypeOf(definitions).Kind() != reflect.Struct { s.errors = append(s.errors, newUnsupportedReflectKindError(reflect.TypeOf(definitions))) return s @@ -40,3 +40,24 @@ func (s *setting[C]) FlagOutput() string { func (s *setting[C]) Args() []string { return s.args } + +//type ConfigAny[C any] struct { +// path string +// ConfigurationAdapter[C] +//} +// +//type ConfigurationAdapter[RT any] interface { +// InitFromFlagSet(flagSet *flag.FlagSet) RT +//} + +//func (s *setting[C]) CopyValuesToStruct(path string, adapter any) *setting[C] { +// +// if s.command == nil { +// s.errors = append(s.errors, MissingCommandError) +// return s +// } +// +// s.command.copyValuesToStruct(path, adapter) +// +// return s +//} diff --git a/command.go b/command.go index 09f870511d823fc66ddf02f8b1ef81fd8f83fc17..278c038bfe6f28ece3fcce135633fbdd0b916127 100644 --- a/command.go +++ b/command.go @@ -5,23 +5,15 @@ import ( "reflect" ) -const ( - tagIgnore = "ignore" - tagCall = "call" - tagCommand = "command" - tagShort = "short" - tagLong = "long" - tagDescription = "description" -) - type cmd[C any] struct { - name string - flagSet *flag.FlagSet - mapping map[string]string - commands []*cmd[C] - settings *setting[C] - valuePath []string - functionName string + name string + flagSet *flag.FlagSet + tagMapping map[string]string + shadowMapping map[string]string + commands []*cmd[C] + settings *setting[C] + valuePath []string + functionName string } func (c *cmd[C]) parse(args []string) { @@ -61,13 +53,14 @@ func (c *cmd[C]) parse(args []string) { func buildCommandStruct[C any](s *setting[C], name, fkt string, errorHandling flag.ErrorHandling, path []string) *cmd[C] { cc := &cmd[C]{ - name: name, - flagSet: flag.NewFlagSet(name, errorHandling), - commands: []*cmd[C]{}, - settings: s, - mapping: map[string]string{}, - valuePath: path, - functionName: fkt, + name: name, + flagSet: flag.NewFlagSet(name, errorHandling), + commands: []*cmd[C]{}, + settings: s, + tagMapping: map[string]string{}, + shadowMapping: map[string]string{}, + valuePath: path, + functionName: fkt, } cc.flagSet.SetOutput(s.flagOutput) @@ -155,18 +148,22 @@ func (c *cmd[C]) parseStruct(dta any) { } if m[tagShort] != "" { - c.mapping[m[tagShort]] = v.Type().Field(i).Name + c.tagMapping[m[tagShort]] = v.Type().Field(i).Name } if m[tagLong] != "" { - c.mapping[m[tagLong]] = v.Type().Field(i).Name + c.tagMapping[m[tagLong]] = v.Type().Field(i).Name + } + + if m[tagShadow] != "" { + c.shadowMapping[v.Type().Field(i).Name] = m[tagShadow] } c.initFlags(x, m) } else if m[tagCommand] != "" { //c.valuePath = append(c.valuePath, ) - c.mapping[m[tagCommand]] = v.Type().Field(i).Name + c.tagMapping[m[tagCommand]] = v.Type().Field(i).Name c.initCommands(x, m, v.Type().Field(i).Name) } else if m[tagIgnore] != "" { diff --git a/error.go b/error.go index 9803ea70823d71d161b736a54745b8b2534b87ed..a31a283c80f0026be723f1c249c3064d250c25d2 100644 --- a/error.go +++ b/error.go @@ -25,6 +25,7 @@ func (s *setting[C]) Errors() []error { var WatchListNotInitializedError = errors.New("watch list not initialized") var MissingCommandError = errors.New("missing command") var NotParsedError = errors.New("flag set not parsed") +var ShadowMustBePointerError = errors.New("shadow must be a pointer to a struct") // At the reflect level, some types are not supported type UnsupportedReflectKindError error diff --git a/execute_test.go b/execute_test.go index 398cb61f16c249ee09d89a5588cb005cc0cf7b64..a6ecbb40462261134eda37a1805cafb486bb9c21 100644 --- a/execute_test.go +++ b/execute_test.go @@ -9,11 +9,12 @@ import ( type testExecutionStruct struct { callbackCounter int `ignore:"true"` - Global1 bool `short:"a" long:"global1" description:"Global 1"` + // for tag shadow see TestFlagCopyToShadow + Global1 bool `short:"a" long:"global1" description:"Global 1" shadow:"ValGlobal1"` Global2 bool `short:"b" long:"global2" description:"Global 2"` Command1 struct { Command1Flag1 bool `short:"c" long:"command1flag1" description:"Command 1 Flag 1"` - Command1Flag2 bool `short:"d" long:"command1flag2" description:"Command 1 Flag 2"` + Command1Flag2 bool `short:"d" long:"command1flag2" description:"Command 1 Flag 2" shadow:"ValCommand1Flag2"` Command2 struct { Command2Flag1 bool `short:"e" long:"command2flag1" description:"Command 2 Flag 1"` Command2Flag2 bool `short:"f" long:"command2flag2" description:"Command 2 Flag 2"` diff --git a/flag.go b/flag.go index 3a0a17e725d90d962c0205fe6e0756c610f1abdf..eb9f08c699b31bedfaae517a6bc08d9fe2381c11 100644 --- a/flag.go +++ b/flag.go @@ -5,35 +5,6 @@ import ( "strings" ) -// Parse parses the command line arguments and assigns the values to the settings. -func (s *setting[C]) Parse(args []string) *setting[C] { - if len(s.errors) > 0 { - return s - } - - if s.command == nil { - s.errors = append(s.errors, MissingCommandError) - return s - } - - err := s.command.flagSet.Parse(args[1:]) - if err != nil { - s.errors = append(s.errors, err) - return s - } - - s.assignValues(*s.command) - - r := s.command.flagSet.Args() - if len(r) == 0 { - return s - } - - s.command.parse(r) - - return s -} - func (s *setting[C]) assignValues(c cmd[C]) { flgs := c.flagSet flgs.Visit(func(f *flag.Flag) { @@ -41,7 +12,7 @@ func (s *setting[C]) assignValues(c cmd[C]) { name := f.Name value := f.Value.String() - k, ok := c.mapping[name] + k, ok := c.tagMapping[name] if !ok { s.errors = append(s.errors, newUnknownFlagError(name)) return @@ -50,7 +21,12 @@ func (s *setting[C]) assignValues(c cmd[C]) { pa := append(c.valuePath, k) p := strings.Join(pa, ".") - err := setTheValueOverPath(&s.definitions, p, value) + err := setValueUsingPath(&s.definitions, p, value) + if err != nil { + s.errors = append(s.errors, err) + } + + err = c.setShadowValue(s.shadow, k, value) if err != nil { s.errors = append(s.errors, err) } @@ -60,3 +36,18 @@ func (s *setting[C]) assignValues(c cmd[C]) { }) } + +func (c cmd[C]) setShadowValue(obj any, k string, value string) error { + + if obj == nil { + return nil + } + + // set shadow + n, ok := c.shadowMapping[k] + if !ok { + return nil + } + + return setValueUsingPath(obj, n, value) +} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000000000000000000000000000000000000..17ed6647cfeabeac094de5fd93cc24c500f514c0 --- /dev/null +++ b/parse.go @@ -0,0 +1,30 @@ +package xflags + +// Parse parses the command line arguments and assigns the values to the settings. +func (s *setting[C]) Parse(args []string) *setting[C] { + if len(s.errors) > 0 { + return s + } + + if s.command == nil { + s.errors = append(s.errors, MissingCommandError) + return s + } + + err := s.command.flagSet.Parse(args[1:]) + if err != nil { + s.errors = append(s.errors, err) + return s + } + + s.assignValues(*s.command) + + r := s.command.flagSet.Args() + if len(r) == 0 { + return s + } + + s.command.parse(r) + + return s +} diff --git a/pathfind.go b/pathfind.go index 8c9bdcb4025017e5d68f848310b451c2007f1e98..643cdf720242a3aa295777e6b207d55da8493453 100644 --- a/pathfind.go +++ b/pathfind.go @@ -42,7 +42,7 @@ func getValueFrom[D any](obj D, keyWithDots string) (interface{}, error) { } // This function sets the value of a field in a struct, given a path to the field. -func setTheValueOverPath[D any](obj D, keyWithDots string, newValue string) error { +func setValueUsingPath[D any](obj D, keyWithDots string, newValue string) error { keySlice := strings.Split(keyWithDots, ".") v := reflect.ValueOf(obj) diff --git a/pathfind_test.go b/pathfind_test.go index dab17a86004fd72a9be1e5f48881ecd017368c4b..24eb42de6cb2f42d6e0dfab96eca9e4da9e7d6a5 100644 --- a/pathfind_test.go +++ b/pathfind_test.go @@ -55,7 +55,7 @@ func TestPathFindSetValueString(t *testing.T) { for k, v := range testData { s := &PathfindTestStruct1{} - err := setTheValueOverPath[*PathfindTestStruct1](s, k, v) + err := setValueUsingPath[*PathfindTestStruct1](s, k, v) if err != nil { t.Error(err) } @@ -195,10 +195,10 @@ func TestPathFindGetValueFrom(t *testing.T) { func TestPathFindSetValueFrom(t *testing.T) { s := &PathfindTestStruct1{} - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.B", "true") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Bi", "2") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Bs", "3") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Bf", "4.0") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.B", "true") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Bi", "2") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Bs", "3") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Bf", "4.0") if s.Sub1.B != true { t.Error("s.Sub1.B != true") @@ -217,10 +217,10 @@ func TestPathFindSetValueFrom(t *testing.T) { t.Error("s.Sub1.Bf != 4.0") } - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.C", "true") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Ci", "2") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Cs", "3") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Cf", "4.0") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.C", "true") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Ci", "2") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Cs", "3") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Cf", "4.0") if s.Sub1.Sub2.C != true { t.Error("s.Sub1.Sub2.C != true") @@ -247,10 +247,10 @@ func TestPathFindSetValueFrom(t *testing.T) { } - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.D", "true") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.Di", "2") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.Ds", "3") - setTheValueOverPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.Df", "4.0") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.D", "true") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.Di", "2") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.Ds", "3") + setValueUsingPath[*PathfindTestStruct1](s, "Sub1.Sub2.Sub3.Df", "4.0") if s.Sub1.Sub2.Sub3.D != true { t.Error("s.Sub1.Sub2.Sub3.D != true") diff --git a/setting.go b/setting.go index f9c218d1dd1c534b50c4c78e468930d5a4f90234..4a73c4b0f40770ce12988e13324027b877f34780 100644 --- a/setting.go +++ b/setting.go @@ -25,6 +25,8 @@ type setting[C any] struct { args []string config config + + shadow any } func (s *setting[C]) GetValues() C { diff --git a/setting_test.go b/setting_test.go index 6ca3021181d8c81db7388ee58b7790af09de8842..23b1265b20a5d6c204fb0e17eec8b03307836410 100644 --- a/setting_test.go +++ b/setting_test.go @@ -21,5 +21,5 @@ func TestInitCommands(t *testing.T) { assert.Equal(t, 0, len(s.errors)) assert.Equal(t, "test", s.command.name) assert.Equal(t, 1, len(s.command.commands)) - assert.Equal(t, 3, len(s.command.mapping)) + assert.Equal(t, 3, len(s.command.tagMapping)) } diff --git a/shadow.go b/shadow.go new file mode 100644 index 0000000000000000000000000000000000000000..b41717f1cbcb0dbfc679fa93def87a91be44c469 --- /dev/null +++ b/shadow.go @@ -0,0 +1,20 @@ +package xflags + +import "reflect" + +// SetShadow sets the shadow struct for the flag configuration. +func (s *setting[C]) SetShadow(shadow any) *setting[C] { + + if reflect.TypeOf(shadow).Kind() != reflect.Ptr { + s.errors = append(s.errors, ShadowMustBePointerError) + return s + } + + if reflect.TypeOf(shadow).Elem().Kind() != reflect.Struct { + s.errors = append(s.errors, ShadowMustBePointerError) + return s + } + + s.shadow = shadow + return s +} diff --git a/shadow_test.go b/shadow_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8418e16e91571d843ca4975ef23204f1b18e6cec --- /dev/null +++ b/shadow_test.go @@ -0,0 +1,43 @@ +package xflags + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +type ConfigStruct6Sub1 struct { + Command3Flag1 bool +} + +type ConfigStruct6 struct { + ValGlobal1 bool + ValGlobal2 bool + ValCommand1Flag1 bool + ValCommand1Flag2 bool + ValSub ConfigStruct6Sub1 +} + +func TestFlagSetShadowError(t *testing.T) { + settings := New("test", testExecutionStruct{}) + settings.SetShadow(3) + assert.True(t, settings.HasErrors()) + +} + +func TestFlagCopyToShadow(t *testing.T) { + + c := ConfigStruct6{} + c.ValSub.Command3Flag1 = true + + settings := New("test", testExecutionStruct{}) + assert.NotNil(t, settings) + + settings.SetShadow(&c) + assert.False(t, settings.HasErrors()) + + settings.Parse([]string{"test", "-a", "command1", "-d"}) + + assert.True(t, c.ValGlobal1) + assert.True(t, c.ValCommand1Flag2) + +} diff --git a/tags.go b/tags.go index 058019103370113aad94ea4385c414d31cbe58e7..dad791ceb7d2478edff5d40d234ce3fc5704519c 100644 --- a/tags.go +++ b/tags.go @@ -5,6 +5,16 @@ import ( "strconv" ) +const ( + tagIgnore = "ignore" + tagCall = "call" + tagCommand = "command" + tagShort = "short" + tagLong = "long" + tagDescription = "description" + tagShadow = "shadow" +) + func getTagMap(field reflect.StructField) (value map[string]string) { tagValues := map[string]string{}