From bc9dc5bfce5478d25f5349aa40f842e2e71ccd1f Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Sat, 25 Oct 2025 15:39:58 +0200 Subject: [PATCH 1/7] feat: test parallel execution of commands `go test -run 'TestCommand_ParallelRun' -count 100000 -failfast -race` --- command_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/command_test.go b/command_test.go index 4b04706568..436e193161 100644 --- a/command_test.go +++ b/command_test.go @@ -5284,3 +5284,31 @@ func TestCommand_ExclusiveFlagsWithAfter(t *testing.T) { })) require.True(t, called) } + +func TestCommand_ParallelRun(t *testing.T) { + t.Parallel() + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("run_%d", i), func(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r != nil { + t.Errorf("unexpected panic - '%s'", r) + } + }() + + cmd := &Command{ + Name: "debug", + Usage: "make an explosive entrance", + Action: func(_ context.Context, cmd *Command) error { + return nil + }, + } + + if err := cmd.Run(context.Background(), nil); err != nil { + fmt.Printf("%s\n", err) + } + }) + } +} From 55c8fa46e30ce741a43daf8dbf10c55421ccdce1 Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Sat, 25 Oct 2025 15:43:06 +0200 Subject: [PATCH 2/7] fix: parallel running - avoid global HelpFlag reuse in different commands --- command.go | 2 ++ command_setup.go | 28 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/command.go b/command.go index 3eebbaff76..1bd56a8df1 100644 --- a/command.go +++ b/command.go @@ -155,6 +155,8 @@ type Command struct { didSetupDefaults bool // whether in shell completion mode shellCompletion bool + // whether global help flag was added + globaHelpFlagAdded bool } // FullName returns the full name of the command. diff --git a/command_setup.go b/command_setup.go index 09df4a3084..cd0fe70c04 100644 --- a/command_setup.go +++ b/command_setup.go @@ -200,15 +200,27 @@ func (cmd *Command) ensureHelp() { } if HelpFlag != nil { - // TODO need to remove hack - if hf, ok := HelpFlag.(*BoolFlag); ok { - hf.applied = false - hf.hasBeenSet = false - hf.Value = false - hf.value = nil + if !cmd.globaHelpFlagAdded { + var localHelpFlag Flag + if globalHelpFlag, ok := HelpFlag.(*BoolFlag); ok { + // clone HelpFlag + localHelpFlag = &BoolFlag{ + Name: globalHelpFlag.Name, + Aliases: globalHelpFlag.Aliases, + Usage: globalHelpFlag.Usage, + HideDefault: globalHelpFlag.HideDefault, + Local: globalHelpFlag.Local, + } + } else { + localHelpFlag = HelpFlag + } + + tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name) + cmd.appendFlag(localHelpFlag) + cmd.globaHelpFlagAdded = true + } else { + tracef("HelpFlag already added, skip (cmd=%[1]q)", cmd.Name) } - tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name) - cmd.appendFlag(HelpFlag) } } } From 57bfb1c91f9087a60c6ffa8eef3a7d54e7983c0b Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Sat, 25 Oct 2025 16:13:34 +0200 Subject: [PATCH 3/7] fix: parallel running - remove globals used for splicing slices and maps - defaultSliceFlagSeparator, disableSliceFlagSeparator, defaultMapFlagKeyValueSeparator converted to consts - command - new MapFlagKeyValueSeparator field --- command.go | 15 +++++++++++++++ command_setup.go | 8 -------- command_test.go | 33 ++++++++++++++++++++++++++++----- flag.go | 12 ++++++++---- flag_impl.go | 22 ++++++++++++++++++++++ flag_map_impl.go | 29 +++++++++++++++++++++++------ flag_slice_base.go | 18 ++++++++++++++---- flag_test.go | 14 ++------------ 8 files changed, 112 insertions(+), 39 deletions(-) diff --git a/command.go b/command.go index 1bd56a8df1..9d890dd215 100644 --- a/command.go +++ b/command.go @@ -101,6 +101,8 @@ type Command struct { SliceFlagSeparator string `json:"sliceFlagSeparator"` // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"` + // MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "=" + MapFlagKeyValueSeparator string `json:"mapFlagKeyValueSeparator"` // Boolean to enable short-option handling so user can combine several // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov @@ -351,6 +353,7 @@ func (cmd *Command) Root() *Command { func (cmd *Command) set(fName string, f Flag, val string) error { cmd.setFlags[f] = struct{}{} + cmd.setMultiValueParsingConfig(f) if err := f.Set(fName, val); err != nil { return fmt.Errorf("invalid value %q for flag -%s: %v", val, fName, err) } @@ -442,9 +445,21 @@ func (cmd *Command) NumFlags() int { return count // cmd.flagSet.NFlag() } +func (cmd *Command) setMultiValueParsingConfig(f Flag) { + tracef("setMultiValueParsingConfig %T, %+v", f, f) + if cf, ok := f.(multiValueParsingConfigSetter); ok { + cf.setMultiValueParsingConfig(multiValueParsingConfig{ + SliceFlagSeparator: cmd.SliceFlagSeparator, + DisableSliceFlagSeparator: cmd.DisableSliceFlagSeparator, + MapFlagKeyValueSeparator: cmd.MapFlagKeyValueSeparator, + }) + } +} + // Set sets a context flag to a value. func (cmd *Command) Set(name, value string) error { if f := cmd.lookupFlag(name); f != nil { + cmd.setMultiValueParsingConfig(f) return f.Set(name, value) } diff --git a/command_setup.go b/command_setup.go index cd0fe70c04..bed6cbe44f 100644 --- a/command_setup.go +++ b/command_setup.go @@ -130,14 +130,6 @@ func (cmd *Command) setupDefaults(osArgs []string) { cmd.Metadata = map[string]any{} } - if len(cmd.SliceFlagSeparator) != 0 { - tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator (cmd=%[1]q)", cmd.Name) - defaultSliceFlagSeparator = cmd.SliceFlagSeparator - } - - tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator (cmd=%[1]q)", cmd.Name) - disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator - cmd.setFlags = map[Flag]struct{}{} } diff --git a/command_test.go b/command_test.go index 436e193161..4e0d3bccfe 100644 --- a/command_test.go +++ b/command_test.go @@ -4386,11 +4386,6 @@ func TestCommandCategories(t *testing.T) { } func TestCommandSliceFlagSeparator(t *testing.T) { - oldSep := defaultSliceFlagSeparator - defer func() { - defaultSliceFlagSeparator = oldSep - }() - cmd := &Command{ SliceFlagSeparator: ";", Flags: []Flag{ @@ -4405,6 +4400,26 @@ func TestCommandSliceFlagSeparator(t *testing.T) { r.Equal([]string{"ff", "dd", "gg", "t,u"}, cmd.Value("foo")) } +func TestCommandMapKeyValueFlagSeparator(t *testing.T) { + cmd := &Command{ + MapFlagKeyValueSeparator: ":", + Flags: []Flag{ + &StringMapFlag{ + Name: "f_string_map", + }, + }, + } + + r := require.New(t) + r.NoError(cmd.Run(buildTestContext(t), []string{"app", "--f_string_map", "s1:s2,s3:", "--f_string_map", "s4:s5"})) + exp := map[string]string{ + "s1": "s2", + "s3": "", + "s4": "s5", + } + r.Equal(exp, cmd.Value("f_string_map")) +} + // TestStringFlagTerminator tests the string flag "--flag" with "--" terminator. func TestStringFlagTerminator(t *testing.T) { tests := []struct { @@ -4754,6 +4769,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -4817,6 +4833,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -4851,6 +4868,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -4882,6 +4900,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -4932,6 +4951,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -4999,6 +5019,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -5062,6 +5083,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, @@ -5169,6 +5191,7 @@ func TestJSONExportCommand(t *testing.T) { "metadata": null, "sliceFlagSeparator": "", "disableSliceFlagSeparator": false, + "mapFlagKeyValueSeparator": "", "useShortOptionHandling": false, "suggest": false, "allowExtFlags": false, diff --git a/flag.go b/flag.go index a5bd547483..7b9383b1b6 100644 --- a/flag.go +++ b/flag.go @@ -10,7 +10,7 @@ import ( const defaultPlaceholder = "value" -var ( +const ( defaultSliceFlagSeparator = "," defaultMapFlagKeyValueSeparator = "=" disableSliceFlagSeparator = false @@ -222,10 +222,14 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagSplitMultiValues(val string) []string { - if disableSliceFlagSeparator { +func flagSplitMultiValues(val string, sliceSeparator string, disableSliceSeparator bool) []string { + if disableSliceSeparator { return []string{val} } - return strings.Split(val, defaultSliceFlagSeparator) + if len(sliceSeparator) != 0 { + return strings.Split(val, sliceSeparator) + } else { + return strings.Split(val, defaultSliceFlagSeparator) + } } diff --git a/flag_impl.go b/flag_impl.go index d4390ee2d0..c7cc8dffc5 100644 --- a/flag_impl.go +++ b/flag_impl.go @@ -19,6 +19,20 @@ type boolFlag interface { IsBoolFlag() bool } +type multiValueParsingConfig struct { + // SliceFlagSeparator is used to customize the separator for SliceFlag, the default is "," + SliceFlagSeparator string + // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false + DisableSliceFlagSeparator bool + // MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "=" + MapFlagKeyValueSeparator string +} + +type multiValueParsingConfigSetter interface { + // configuration of parsing + setMultiValueParsingConfig(c multiValueParsingConfig) +} + // ValueCreator is responsible for creating a flag.Value emulation // as well as custom formatting // @@ -134,6 +148,14 @@ func (f *FlagBase[T, C, V]) PostParse() error { return nil } +// pass configuration of parsing to value +func (f *FlagBase[T, C, V]) setMultiValueParsingConfig(c multiValueParsingConfig) { + tracef("setMultiValueParsingConfig %T, %+v", f.value, f.value) + if cf, ok := f.value.(multiValueParsingConfigSetter); ok { + cf.setMultiValueParsingConfig(c) + } +} + func (f *FlagBase[T, C, V]) PreParse() error { newVal := f.Value diff --git a/flag_map_impl.go b/flag_map_impl.go index b03514b7d1..a793356cae 100644 --- a/flag_map_impl.go +++ b/flag_map_impl.go @@ -10,9 +10,12 @@ import ( // MapBase wraps map[string]T to satisfy flag.Value type MapBase[T any, C any, VC ValueCreator[T, C]] struct { - dict *map[string]T - hasBeenSet bool - value Value + dict *map[string]T + hasBeenSet bool + value Value + sliceSeparator string + disableSliceSeparator bool + keyValueSeparator string } func (i MapBase[T, C, VC]) Create(val map[string]T, p *map[string]T, c C) Value { @@ -36,6 +39,14 @@ func NewMapBase[T any, C any, VC ValueCreator[T, C]](defaults map[string]T) *Map } } +// configuration of slicing +func (i *MapBase[T, C, VC]) setMultiValueParsingConfig(c multiValueParsingConfig) { + i.disableSliceSeparator = c.DisableSliceFlagSeparator + i.sliceSeparator = c.SliceFlagSeparator + i.keyValueSeparator = c.MapFlagKeyValueSeparator + tracef("set map parsing config - keyValueSeparator '%s', slice separator '%s', disable separator:%v", i.keyValueSeparator, i.sliceSeparator, i.disableSliceSeparator) +} + // Set parses the value and appends it to the list of values func (i *MapBase[T, C, VC]) Set(value string) error { if !i.hasBeenSet { @@ -50,10 +61,16 @@ func (i *MapBase[T, C, VC]) Set(value string) error { return nil } - for _, item := range flagSplitMultiValues(value) { - key, value, ok := strings.Cut(item, defaultMapFlagKeyValueSeparator) + keyValueSeparator := i.keyValueSeparator + if len(keyValueSeparator) == 0 { + keyValueSeparator = defaultMapFlagKeyValueSeparator + } + + tracef("splitting map value '%s', keyValueSeparator '%s', slice separator '%s', disable separator:%v", value, keyValueSeparator, i.sliceSeparator, i.disableSliceSeparator) + for _, item := range flagSplitMultiValues(value, i.sliceSeparator, i.disableSliceSeparator) { + key, value, ok := strings.Cut(item, keyValueSeparator) if !ok { - return fmt.Errorf("item %q is missing separator %q", item, defaultMapFlagKeyValueSeparator) + return fmt.Errorf("item %q is missing separator %q", item, keyValueSeparator) } if err := i.value.Set(value); err != nil { return err diff --git a/flag_slice_base.go b/flag_slice_base.go index 3e7b049ea7..f1c9d0ba88 100644 --- a/flag_slice_base.go +++ b/flag_slice_base.go @@ -9,9 +9,11 @@ import ( // SliceBase wraps []T to satisfy flag.Value type SliceBase[T any, C any, VC ValueCreator[T, C]] struct { - slice *[]T - hasBeenSet bool - value Value + slice *[]T + hasBeenSet bool + value Value + sliceSeparator string + disableSliceSeparator bool } func (i SliceBase[T, C, VC]) Create(val []T, p *[]T, c C) Value { @@ -33,6 +35,13 @@ func NewSliceBase[T any, C any, VC ValueCreator[T, C]](defaults ...T) *SliceBase } } +// configuration of slicing +func (i *SliceBase[T, C, VC]) setMultiValueParsingConfig(c multiValueParsingConfig) { + i.disableSliceSeparator = c.DisableSliceFlagSeparator + i.sliceSeparator = c.SliceFlagSeparator + tracef("set slice parsing config - slice separator '%s', disable separator:%v", i.sliceSeparator, i.disableSliceSeparator) +} + // Set parses the value and appends it to the list of values func (i *SliceBase[T, C, VC]) Set(value string) error { if !i.hasBeenSet { @@ -57,7 +66,8 @@ func (i *SliceBase[T, C, VC]) Set(value string) error { trimSpace = false } - for _, s := range flagSplitMultiValues(value) { + tracef("splitting slice value '%s', separator '%s', disable separator:%v", value, i.sliceSeparator, i.disableSliceSeparator) + for _, s := range flagSplitMultiValues(value, i.sliceSeparator, i.disableSliceSeparator) { if trimSpace { s = strings.TrimSpace(s) } diff --git a/flag_test.go b/flag_test.go index 458c206362..a509987211 100644 --- a/flag_test.go +++ b/flag_test.go @@ -3090,13 +3090,8 @@ func TestSliceShortOptionHandle(t *testing.T) { // Test issue #1541 func TestCustomizedSliceFlagSeparator(t *testing.T) { - oldSep := defaultSliceFlagSeparator - defer func() { - defaultSliceFlagSeparator = oldSep - }() - defaultSliceFlagSeparator = ";" opts := []string{"opt1", "opt2", "opt3,op", "opt4"} - ret := flagSplitMultiValues(strings.Join(opts, ";")) + ret := flagSplitMultiValues(strings.Join(opts, ";"), ";", disableSliceFlagSeparator) require.Equal(t, 4, len(ret), "split slice flag failed") for idx, r := range ret { require.Equal(t, opts[idx], r, "get %dth failed", idx) @@ -3104,13 +3099,8 @@ func TestCustomizedSliceFlagSeparator(t *testing.T) { } func TestFlagSplitMultiValues_Disabled(t *testing.T) { - disableSliceFlagSeparator = true - defer func() { - disableSliceFlagSeparator = false - }() - opts := []string{"opt1", "opt2", "opt3,op", "opt4"} - ret := flagSplitMultiValues(strings.Join(opts, defaultSliceFlagSeparator)) + ret := flagSplitMultiValues(strings.Join(opts, defaultSliceFlagSeparator), defaultSliceFlagSeparator, true) require.Equal(t, 1, len(ret), "failed to disable split slice flag") require.Equal(t, strings.Join(opts, defaultSliceFlagSeparator), ret[0]) } From 5be0b1f173ea673ad7bc4c6d620131dc215c23ad Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Sat, 25 Oct 2025 16:14:55 +0200 Subject: [PATCH 4/7] feat: update docs --- godoc-current.txt | 2 ++ testdata/godoc-v3.x.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/godoc-current.txt b/godoc-current.txt index 46fb4d43aa..c6f50b2eaf 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -502,6 +502,8 @@ type Command struct { SliceFlagSeparator string `json:"sliceFlagSeparator"` // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"` + // MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "=" + MapFlagKeyValueSeparator string `json:"mapFlagKeyValueSeparator"` // Boolean to enable short-option handling so user can combine several // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 46fb4d43aa..c6f50b2eaf 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -502,6 +502,8 @@ type Command struct { SliceFlagSeparator string `json:"sliceFlagSeparator"` // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"` + // MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "=" + MapFlagKeyValueSeparator string `json:"mapFlagKeyValueSeparator"` // Boolean to enable short-option handling so user can combine several // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov From 53ab291ec74f7e2148033773a5104b7140eb6766 Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Sat, 25 Oct 2025 16:45:33 +0200 Subject: [PATCH 5/7] fix: parallel running - avoid global VersionFlag reuse in different commands --- command.go | 2 ++ command_setup.go | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 9d890dd215..33104a1968 100644 --- a/command.go +++ b/command.go @@ -159,6 +159,8 @@ type Command struct { shellCompletion bool // whether global help flag was added globaHelpFlagAdded bool + // whether global version flag was added + globaVersionFlagAdded bool } // FullName returns the full name of the command. diff --git a/command_setup.go b/command_setup.go index bed6cbe44f..a0d558001c 100644 --- a/command_setup.go +++ b/command_setup.go @@ -80,7 +80,24 @@ func (cmd *Command) setupDefaults(osArgs []string) { if !cmd.HideVersion && isRoot { tracef("appending version flag (cmd=%[1]q)", cmd.Name) - cmd.appendFlag(VersionFlag) + if !cmd.globaVersionFlagAdded { + var localVersionFlag Flag + if globalVersionFlag, ok := VersionFlag.(*BoolFlag); ok { + // clone VersionFlag + localVersionFlag = &BoolFlag{ + Name: globalVersionFlag.Name, + Aliases: globalVersionFlag.Aliases, + Usage: globalVersionFlag.Usage, + HideDefault: globalVersionFlag.HideDefault, + Local: globalVersionFlag.Local, + } + } else { + localVersionFlag = VersionFlag + } + + cmd.appendFlag(localVersionFlag) + cmd.globaVersionFlagAdded = true + } } if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil { From ff292b25a5095d896458ecc8f30c84e9ab3f6ad8 Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Sun, 26 Oct 2025 12:21:15 +0100 Subject: [PATCH 6/7] fix: parallel running - improve clonning of global flags --- command_setup.go | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/command_setup.go b/command_setup.go index a0d558001c..cac4a30314 100644 --- a/command_setup.go +++ b/command_setup.go @@ -83,14 +83,8 @@ func (cmd *Command) setupDefaults(osArgs []string) { if !cmd.globaVersionFlagAdded { var localVersionFlag Flag if globalVersionFlag, ok := VersionFlag.(*BoolFlag); ok { - // clone VersionFlag - localVersionFlag = &BoolFlag{ - Name: globalVersionFlag.Name, - Aliases: globalVersionFlag.Aliases, - Usage: globalVersionFlag.Usage, - HideDefault: globalVersionFlag.HideDefault, - Local: globalVersionFlag.Local, - } + flag := *globalVersionFlag + localVersionFlag = &flag } else { localVersionFlag = VersionFlag } @@ -212,14 +206,8 @@ func (cmd *Command) ensureHelp() { if !cmd.globaHelpFlagAdded { var localHelpFlag Flag if globalHelpFlag, ok := HelpFlag.(*BoolFlag); ok { - // clone HelpFlag - localHelpFlag = &BoolFlag{ - Name: globalHelpFlag.Name, - Aliases: globalHelpFlag.Aliases, - Usage: globalHelpFlag.Usage, - HideDefault: globalHelpFlag.HideDefault, - Local: globalHelpFlag.Local, - } + flag := *globalHelpFlag + localHelpFlag = &flag } else { localHelpFlag = HelpFlag } From 798146bcdae633f89ef44707c2e527de3412f0a6 Mon Sep 17 00:00:00 2001 From: Oleksii Prudkyi Date: Mon, 27 Oct 2025 23:26:58 +0100 Subject: [PATCH 7/7] fix: parallel running - refactor code --- flag.go | 7 +++---- flag_map_impl.go | 35 ++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/flag.go b/flag.go index 7b9383b1b6..6ff83f5207 100644 --- a/flag.go +++ b/flag.go @@ -227,9 +227,8 @@ func flagSplitMultiValues(val string, sliceSeparator string, disableSliceSeparat return []string{val} } - if len(sliceSeparator) != 0 { - return strings.Split(val, sliceSeparator) - } else { - return strings.Split(val, defaultSliceFlagSeparator) + if len(sliceSeparator) == 0 { + sliceSeparator = defaultSliceFlagSeparator } + return strings.Split(val, sliceSeparator) } diff --git a/flag_map_impl.go b/flag_map_impl.go index a793356cae..b56d0a9733 100644 --- a/flag_map_impl.go +++ b/flag_map_impl.go @@ -10,12 +10,10 @@ import ( // MapBase wraps map[string]T to satisfy flag.Value type MapBase[T any, C any, VC ValueCreator[T, C]] struct { - dict *map[string]T - hasBeenSet bool - value Value - sliceSeparator string - disableSliceSeparator bool - keyValueSeparator string + dict *map[string]T + hasBeenSet bool + value Value + multiValueConfig multiValueParsingConfig } func (i MapBase[T, C, VC]) Create(val map[string]T, p *map[string]T, c C) Value { @@ -41,10 +39,14 @@ func NewMapBase[T any, C any, VC ValueCreator[T, C]](defaults map[string]T) *Map // configuration of slicing func (i *MapBase[T, C, VC]) setMultiValueParsingConfig(c multiValueParsingConfig) { - i.disableSliceSeparator = c.DisableSliceFlagSeparator - i.sliceSeparator = c.SliceFlagSeparator - i.keyValueSeparator = c.MapFlagKeyValueSeparator - tracef("set map parsing config - keyValueSeparator '%s', slice separator '%s', disable separator:%v", i.keyValueSeparator, i.sliceSeparator, i.disableSliceSeparator) + i.multiValueConfig = c + mvc := &i.multiValueConfig + tracef( + "set map parsing config - keyValueSeparator '%s', slice separator '%s', disable separator:%v", + mvc.MapFlagKeyValueSeparator, + mvc.SliceFlagSeparator, + mvc.DisableSliceFlagSeparator, + ) } // Set parses the value and appends it to the list of values @@ -61,13 +63,20 @@ func (i *MapBase[T, C, VC]) Set(value string) error { return nil } - keyValueSeparator := i.keyValueSeparator + mvc := &i.multiValueConfig + keyValueSeparator := mvc.MapFlagKeyValueSeparator if len(keyValueSeparator) == 0 { keyValueSeparator = defaultMapFlagKeyValueSeparator } - tracef("splitting map value '%s', keyValueSeparator '%s', slice separator '%s', disable separator:%v", value, keyValueSeparator, i.sliceSeparator, i.disableSliceSeparator) - for _, item := range flagSplitMultiValues(value, i.sliceSeparator, i.disableSliceSeparator) { + tracef( + "splitting map value '%s', keyValueSeparator '%s', slice separator '%s', disable separator:%v", + value, + keyValueSeparator, + mvc.SliceFlagSeparator, + mvc.DisableSliceFlagSeparator, + ) + for _, item := range flagSplitMultiValues(value, mvc.SliceFlagSeparator, mvc.DisableSliceFlagSeparator) { key, value, ok := strings.Cut(item, keyValueSeparator) if !ok { return fmt.Errorf("item %q is missing separator %q", item, keyValueSeparator)