Skip to content

Commit 79ff07c

Browse files
authored
Merge branch 'main' into custom-usage-help
2 parents b4919fd + 9dad0d4 commit 79ff07c

14 files changed

+202
-72
lines changed

command.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ type Command struct {
101101
SliceFlagSeparator string `json:"sliceFlagSeparator"`
102102
// DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false
103103
DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"`
104+
// MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "="
105+
MapFlagKeyValueSeparator string `json:"mapFlagKeyValueSeparator"`
104106
// Boolean to enable short-option handling so user can combine several
105107
// single-character bool arguments into one
106108
// i.e. foobar -o -v -> foobar -ov
@@ -155,6 +157,10 @@ type Command struct {
155157
didSetupDefaults bool
156158
// whether in shell completion mode
157159
shellCompletion bool
160+
// whether global help flag was added
161+
globaHelpFlagAdded bool
162+
// whether global version flag was added
163+
globaVersionFlagAdded bool
158164
}
159165

160166
// FullName returns the full name of the command.
@@ -349,6 +355,7 @@ func (cmd *Command) Root() *Command {
349355

350356
func (cmd *Command) set(fName string, f Flag, val string) error {
351357
cmd.setFlags[f] = struct{}{}
358+
cmd.setMultiValueParsingConfig(f)
352359
if err := f.Set(fName, val); err != nil {
353360
return fmt.Errorf("invalid value %q for flag -%s: %v", val, fName, err)
354361
}
@@ -440,9 +447,21 @@ func (cmd *Command) NumFlags() int {
440447
return count // cmd.flagSet.NFlag()
441448
}
442449

450+
func (cmd *Command) setMultiValueParsingConfig(f Flag) {
451+
tracef("setMultiValueParsingConfig %T, %+v", f, f)
452+
if cf, ok := f.(multiValueParsingConfigSetter); ok {
453+
cf.setMultiValueParsingConfig(multiValueParsingConfig{
454+
SliceFlagSeparator: cmd.SliceFlagSeparator,
455+
DisableSliceFlagSeparator: cmd.DisableSliceFlagSeparator,
456+
MapFlagKeyValueSeparator: cmd.MapFlagKeyValueSeparator,
457+
})
458+
}
459+
}
460+
443461
// Set sets a context flag to a value.
444462
func (cmd *Command) Set(name, value string) error {
445463
if f := cmd.lookupFlag(name); f != nil {
464+
cmd.setMultiValueParsingConfig(f)
446465
return f.Set(name, value)
447466
}
448467

command_setup.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,18 @@ func (cmd *Command) setupDefaults(osArgs []string) {
8080

8181
if !cmd.HideVersion && isRoot {
8282
tracef("appending version flag (cmd=%[1]q)", cmd.Name)
83-
cmd.appendFlag(VersionFlag)
83+
if !cmd.globaVersionFlagAdded {
84+
var localVersionFlag Flag
85+
if globalVersionFlag, ok := VersionFlag.(*BoolFlag); ok {
86+
flag := *globalVersionFlag
87+
localVersionFlag = &flag
88+
} else {
89+
localVersionFlag = VersionFlag
90+
}
91+
92+
cmd.appendFlag(localVersionFlag)
93+
cmd.globaVersionFlagAdded = true
94+
}
8495
}
8596

8697
if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil {
@@ -130,14 +141,6 @@ func (cmd *Command) setupDefaults(osArgs []string) {
130141
cmd.Metadata = map[string]any{}
131142
}
132143

133-
if len(cmd.SliceFlagSeparator) != 0 {
134-
tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator (cmd=%[1]q)", cmd.Name)
135-
defaultSliceFlagSeparator = cmd.SliceFlagSeparator
136-
}
137-
138-
tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator (cmd=%[1]q)", cmd.Name)
139-
disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator
140-
141144
cmd.setFlags = map[Flag]struct{}{}
142145
}
143146

@@ -200,15 +203,21 @@ func (cmd *Command) ensureHelp() {
200203
}
201204

202205
if HelpFlag != nil {
203-
// TODO need to remove hack
204-
if hf, ok := HelpFlag.(*BoolFlag); ok {
205-
hf.applied = false
206-
hf.hasBeenSet = false
207-
hf.Value = false
208-
hf.value = nil
206+
if !cmd.globaHelpFlagAdded {
207+
var localHelpFlag Flag
208+
if globalHelpFlag, ok := HelpFlag.(*BoolFlag); ok {
209+
flag := *globalHelpFlag
210+
localHelpFlag = &flag
211+
} else {
212+
localHelpFlag = HelpFlag
213+
}
214+
215+
tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
216+
cmd.appendFlag(localHelpFlag)
217+
cmd.globaHelpFlagAdded = true
218+
} else {
219+
tracef("HelpFlag already added, skip (cmd=%[1]q)", cmd.Name)
209220
}
210-
tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
211-
cmd.appendFlag(HelpFlag)
212221
}
213222
}
214223
}

command_test.go

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4386,11 +4386,6 @@ func TestCommandCategories(t *testing.T) {
43864386
}
43874387

43884388
func TestCommandSliceFlagSeparator(t *testing.T) {
4389-
oldSep := defaultSliceFlagSeparator
4390-
defer func() {
4391-
defaultSliceFlagSeparator = oldSep
4392-
}()
4393-
43944389
cmd := &Command{
43954390
SliceFlagSeparator: ";",
43964391
Flags: []Flag{
@@ -4405,6 +4400,26 @@ func TestCommandSliceFlagSeparator(t *testing.T) {
44054400
r.Equal([]string{"ff", "dd", "gg", "t,u"}, cmd.Value("foo"))
44064401
}
44074402

4403+
func TestCommandMapKeyValueFlagSeparator(t *testing.T) {
4404+
cmd := &Command{
4405+
MapFlagKeyValueSeparator: ":",
4406+
Flags: []Flag{
4407+
&StringMapFlag{
4408+
Name: "f_string_map",
4409+
},
4410+
},
4411+
}
4412+
4413+
r := require.New(t)
4414+
r.NoError(cmd.Run(buildTestContext(t), []string{"app", "--f_string_map", "s1:s2,s3:", "--f_string_map", "s4:s5"}))
4415+
exp := map[string]string{
4416+
"s1": "s2",
4417+
"s3": "",
4418+
"s4": "s5",
4419+
}
4420+
r.Equal(exp, cmd.Value("f_string_map"))
4421+
}
4422+
44084423
// TestStringFlagTerminator tests the string flag "--flag" with "--" terminator.
44094424
func TestStringFlagTerminator(t *testing.T) {
44104425
tests := []struct {
@@ -4754,6 +4769,7 @@ func TestJSONExportCommand(t *testing.T) {
47544769
"metadata": null,
47554770
"sliceFlagSeparator": "",
47564771
"disableSliceFlagSeparator": false,
4772+
"mapFlagKeyValueSeparator": "",
47574773
"useShortOptionHandling": false,
47584774
"suggest": false,
47594775
"allowExtFlags": false,
@@ -4817,6 +4833,7 @@ func TestJSONExportCommand(t *testing.T) {
48174833
"metadata": null,
48184834
"sliceFlagSeparator": "",
48194835
"disableSliceFlagSeparator": false,
4836+
"mapFlagKeyValueSeparator": "",
48204837
"useShortOptionHandling": false,
48214838
"suggest": false,
48224839
"allowExtFlags": false,
@@ -4851,6 +4868,7 @@ func TestJSONExportCommand(t *testing.T) {
48514868
"metadata": null,
48524869
"sliceFlagSeparator": "",
48534870
"disableSliceFlagSeparator": false,
4871+
"mapFlagKeyValueSeparator": "",
48544872
"useShortOptionHandling": false,
48554873
"suggest": false,
48564874
"allowExtFlags": false,
@@ -4882,6 +4900,7 @@ func TestJSONExportCommand(t *testing.T) {
48824900
"metadata": null,
48834901
"sliceFlagSeparator": "",
48844902
"disableSliceFlagSeparator": false,
4903+
"mapFlagKeyValueSeparator": "",
48854904
"useShortOptionHandling": false,
48864905
"suggest": false,
48874906
"allowExtFlags": false,
@@ -4932,6 +4951,7 @@ func TestJSONExportCommand(t *testing.T) {
49324951
"metadata": null,
49334952
"sliceFlagSeparator": "",
49344953
"disableSliceFlagSeparator": false,
4954+
"mapFlagKeyValueSeparator": "",
49354955
"useShortOptionHandling": false,
49364956
"suggest": false,
49374957
"allowExtFlags": false,
@@ -4999,6 +5019,7 @@ func TestJSONExportCommand(t *testing.T) {
49995019
"metadata": null,
50005020
"sliceFlagSeparator": "",
50015021
"disableSliceFlagSeparator": false,
5022+
"mapFlagKeyValueSeparator": "",
50025023
"useShortOptionHandling": false,
50035024
"suggest": false,
50045025
"allowExtFlags": false,
@@ -5062,6 +5083,7 @@ func TestJSONExportCommand(t *testing.T) {
50625083
"metadata": null,
50635084
"sliceFlagSeparator": "",
50645085
"disableSliceFlagSeparator": false,
5086+
"mapFlagKeyValueSeparator": "",
50655087
"useShortOptionHandling": false,
50665088
"suggest": false,
50675089
"allowExtFlags": false,
@@ -5169,6 +5191,7 @@ func TestJSONExportCommand(t *testing.T) {
51695191
"metadata": null,
51705192
"sliceFlagSeparator": "",
51715193
"disableSliceFlagSeparator": false,
5194+
"mapFlagKeyValueSeparator": "",
51725195
"useShortOptionHandling": false,
51735196
"suggest": false,
51745197
"allowExtFlags": false,
@@ -5284,3 +5307,31 @@ func TestCommand_ExclusiveFlagsWithAfter(t *testing.T) {
52845307
}))
52855308
require.True(t, called)
52865309
}
5310+
5311+
func TestCommand_ParallelRun(t *testing.T) {
5312+
t.Parallel()
5313+
5314+
for i := 0; i < 10; i++ {
5315+
t.Run(fmt.Sprintf("run_%d", i), func(t *testing.T) {
5316+
t.Parallel()
5317+
5318+
defer func() {
5319+
if r := recover(); r != nil {
5320+
t.Errorf("unexpected panic - '%s'", r)
5321+
}
5322+
}()
5323+
5324+
cmd := &Command{
5325+
Name: "debug",
5326+
Usage: "make an explosive entrance",
5327+
Action: func(_ context.Context, cmd *Command) error {
5328+
return nil
5329+
},
5330+
}
5331+
5332+
if err := cmd.Run(context.Background(), nil); err != nil {
5333+
fmt.Printf("%s\n", err)
5334+
}
5335+
})
5336+
}
5337+
}

docs.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,12 @@ func stringifyFlag(f Flag) string {
107107

108108
// don't print default text for required flags
109109
if rf, ok := f.(RequiredFlag); !ok || !rf.IsRequired() {
110-
isVisible := df.IsDefaultVisible()
111-
if s := df.GetDefaultText(); isVisible && s != "" {
112-
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
110+
if df.IsDefaultVisible() {
111+
if s := df.GetDefaultText(); s != "" {
112+
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
113+
} else if df.TakesValue() && df.GetValue() != "" {
114+
defaultValueString = fmt.Sprintf(formatDefault("%s"), df.GetValue())
115+
}
113116
}
114117
}
115118

flag.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
const defaultPlaceholder = "value"
1212

13-
var (
13+
const (
1414
defaultSliceFlagSeparator = ","
1515
defaultMapFlagKeyValueSeparator = "="
1616
disableSliceFlagSeparator = false
@@ -222,10 +222,13 @@ func hasFlag(flags []Flag, fl Flag) bool {
222222
return false
223223
}
224224

225-
func flagSplitMultiValues(val string) []string {
226-
if disableSliceFlagSeparator {
225+
func flagSplitMultiValues(val string, sliceSeparator string, disableSliceSeparator bool) []string {
226+
if disableSliceSeparator {
227227
return []string{val}
228228
}
229229

230-
return strings.Split(val, defaultSliceFlagSeparator)
230+
if len(sliceSeparator) == 0 {
231+
sliceSeparator = defaultSliceFlagSeparator
232+
}
233+
return strings.Split(val, sliceSeparator)
231234
}

flag_impl.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ type boolFlag interface {
1919
IsBoolFlag() bool
2020
}
2121

22+
type multiValueParsingConfig struct {
23+
// SliceFlagSeparator is used to customize the separator for SliceFlag, the default is ","
24+
SliceFlagSeparator string
25+
// DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false
26+
DisableSliceFlagSeparator bool
27+
// MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "="
28+
MapFlagKeyValueSeparator string
29+
}
30+
31+
type multiValueParsingConfigSetter interface {
32+
// configuration of parsing
33+
setMultiValueParsingConfig(c multiValueParsingConfig)
34+
}
35+
2236
// ValueCreator is responsible for creating a flag.Value emulation
2337
// as well as custom formatting
2438
//
@@ -70,10 +84,8 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
7084
// GetValue returns the flags value as string representation and an empty
7185
// string if the flag takes no value at all.
7286
func (f *FlagBase[T, C, V]) GetValue() string {
73-
if !f.TakesValue() {
74-
return ""
75-
}
76-
return fmt.Sprintf("%v", f.Value)
87+
var v V
88+
return v.ToString(f.Value)
7789
}
7890

7991
// TypeName returns the type of the flag.
@@ -136,6 +148,14 @@ func (f *FlagBase[T, C, V]) PostParse() error {
136148
return nil
137149
}
138150

151+
// pass configuration of parsing to value
152+
func (f *FlagBase[T, C, V]) setMultiValueParsingConfig(c multiValueParsingConfig) {
153+
tracef("setMultiValueParsingConfig %T, %+v", f.value, f.value)
154+
if cf, ok := f.value.(multiValueParsingConfigSetter); ok {
155+
cf.setMultiValueParsingConfig(c)
156+
}
157+
}
158+
139159
func (f *FlagBase[T, C, V]) PreParse() error {
140160
newVal := f.Value
141161

@@ -253,11 +273,7 @@ func (f *FlagBase[T, C, V]) TakesValue() bool {
253273

254274
// GetDefaultText returns the default text for this flag
255275
func (f *FlagBase[T, C, V]) GetDefaultText() string {
256-
if f.DefaultText != "" {
257-
return f.DefaultText
258-
}
259-
var v V
260-
return v.ToString(f.Value)
276+
return f.DefaultText
261277
}
262278

263279
// RunAction executes flag action if set

0 commit comments

Comments
 (0)