Skip to content

Commit 5bbe3e3

Browse files
authored
Merge branch 'main' into fix/fish-dynamic-completions
2 parents 8ea5fdc + ef08e2e commit 5bbe3e3

6 files changed

Lines changed: 190 additions & 30 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
run: make v3diff
5454

5555
- if: success() && matrix.go == 'stable' && matrix.os == 'ubuntu-24.04'
56-
uses: codecov/codecov-action@v5
56+
uses: codecov/codecov-action@v6
5757
with:
5858
token: ${{ secrets.CODECOV_TOKEN }}
5959
fail_ci_if_error: true

command.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -582,19 +582,14 @@ func (cmd *Command) NArg() int {
582582

583583
func (cmd *Command) runFlagActions(ctx context.Context) error {
584584
tracef("runFlagActions")
585-
for fl := range cmd.setFlags {
586-
/*tracef("checking %v:%v", fl.Names(), fl.IsSet())
587-
if !fl.IsSet() {
588-
continue
589-
}*/
590-
591-
//if pf, ok := fl.(LocalFlag); ok && !pf.IsLocal() {
592-
// continue
593-
//}
594-
595-
if af, ok := fl.(ActionableFlag); ok {
596-
if err := af.RunAction(ctx, cmd); err != nil {
597-
return err
585+
// run the flag actions in the same order that they are defined
586+
// to maintain consistency.
587+
for _, fl := range cmd.appliedFlags {
588+
if _, inSet := cmd.setFlags[fl]; inSet {
589+
if af, ok := fl.(ActionableFlag); ok {
590+
if err := af.RunAction(ctx, cmd); err != nil {
591+
return err
592+
}
598593
}
599594
}
600595
}

command_parse.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
8080

8181
firstArg := strings.TrimSpace(rargs[0])
8282
if len(firstArg) == 0 {
83-
break
83+
posArgs = append(posArgs, rargs[0])
84+
continue
8485
}
8586

8687
// stop parsing once we see a "--"
@@ -163,8 +164,8 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
163164

164165
tracef("processing non bool flag (fName=%[1]q)", flagName)
165166
// not a bool flag so need to get the next arg
166-
if flagVal == "" {
167-
if len(rargs) == 1 || valFromEqual {
167+
if flagVal == "" && !valFromEqual {
168+
if len(rargs) == 1 {
168169
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
169170
}
170171
flagVal = rargs[1]

command_test.go

Lines changed: 173 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,7 @@ var defaultCommandTests = []struct {
823823
{"f", "", nil, true},
824824
{"", "foobar", nil, true},
825825
{"", "", nil, true},
826-
{" ", "", nil, true},
826+
{" ", "", nil, false},
827827
{"bat", "batbaz", nil, true},
828828
{"nothing", "batbaz", nil, true},
829829
{"nothing", "", nil, false},
@@ -911,10 +911,10 @@ var defaultCommandSubCommandTests = []struct {
911911
{"", "carly", "foobar", true},
912912
{"", "jimmers", "foobar", false},
913913
{"", "jimmers", "", false},
914-
{" ", "jimmers", "foobar", true},
914+
{" ", "jimmers", "foobar", false},
915915
{"", "", "", true},
916-
{" ", "", "", true},
917-
{" ", "j", "", true},
916+
{" ", "", "", false},
917+
{" ", "j", "", false},
918918
{"bat", "", "batbaz", false},
919919
{"nothing", "", "batbaz", false},
920920
{"nothing", "", "", false},
@@ -977,10 +977,10 @@ var defaultCommandFlagTests = []struct {
977977
{"", "--carly=derp", "foobar", true},
978978
{"", "-j", "foobar", true},
979979
{"", "-j", "", true},
980-
{" ", "-j", "foobar", true},
980+
{" ", "-j", "foobar", false},
981981
{"", "", "", true},
982-
{" ", "", "", true},
983-
{" ", "-j", "", true},
982+
{" ", "", "", false},
983+
{" ", "-j", "", false},
984984
{"bat", "", "batbaz", false},
985985
{"nothing", "", "batbaz", false},
986986
{"nothing", "", "", false},
@@ -2185,6 +2185,54 @@ func TestCommand_OrderOfOperations(t *testing.T) {
21852185
})
21862186
}
21872187

2188+
func TestFlagActionOrder(t *testing.T) {
2189+
tests := []struct {
2190+
Name string
2191+
Args []string
2192+
}{
2193+
{
2194+
Name: "abc",
2195+
Args: []string{"", "--a", "--b", "--c"},
2196+
},
2197+
{
2198+
Name: "bca",
2199+
Args: []string{"", "--b", "--c", "--a"},
2200+
},
2201+
{
2202+
Name: "cba",
2203+
Args: []string{"", "--c", "--b", "--a"},
2204+
},
2205+
}
2206+
for _, tt := range tests {
2207+
t.Run(tt.Name, func(t *testing.T) {
2208+
str := ""
2209+
action := func(name string) func(context.Context, *Command, bool) error {
2210+
return func(_ context.Context, _ *Command, _ bool) error {
2211+
str += name
2212+
return nil
2213+
}
2214+
}
2215+
cmd := &Command{
2216+
Flags: []Flag{
2217+
&BoolFlag{Name: "a", Action: action("a")},
2218+
&BoolFlag{Name: "b", Action: action("b")},
2219+
&BoolFlag{Name: "c", Action: action("c")},
2220+
},
2221+
Action: func(_ context.Context, cmd *Command) error {
2222+
return nil
2223+
},
2224+
}
2225+
2226+
err := cmd.Run(buildTestContext(t), tt.Args)
2227+
require.NoError(t, err)
2228+
2229+
if str != "abc" {
2230+
t.Errorf("expected 'abc' got '%s'", str)
2231+
}
2232+
})
2233+
}
2234+
}
2235+
21882236
func TestCommand_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
21892237
subcommandHelpTopics := [][]string{
21902238
{"foo", "--help"},
@@ -2801,12 +2849,12 @@ func TestFlagAction(t *testing.T) {
28012849
{
28022850
name: "flag_string_error",
28032851
args: []string{"app", "--f_string="},
2804-
err: "flag needs an argument: --f_string=",
2852+
err: "empty string",
28052853
},
28062854
{
28072855
name: "flag_string_error2",
28082856
args: []string{"app", "--f_string=", "--f_bool"},
2809-
err: "flag needs an argument: --f_string=",
2857+
err: "empty string",
28102858
},
28112859
{
28122860
name: "flag_string_slice",
@@ -3105,6 +3153,39 @@ func TestFlagAction(t *testing.T) {
31053153
}
31063154
}
31073155

3156+
func TestLocalSliceFlagAccumulation(t *testing.T) {
3157+
var got []string
3158+
3159+
app := &Command{
3160+
Name: "app",
3161+
Commands: []*Command{
3162+
{
3163+
Name: "sub",
3164+
Flags: []Flag{
3165+
&StringSliceFlag{
3166+
Name: "paths",
3167+
Aliases: []string{"p"},
3168+
Local: true,
3169+
Destination: &got,
3170+
},
3171+
},
3172+
Action: func(_ context.Context, cmd *Command) error {
3173+
return nil
3174+
},
3175+
},
3176+
},
3177+
}
3178+
3179+
err := app.Run(context.Background(), []string{"app", "sub", "-p", "/a", "-p", "/b", "-p", "/c"})
3180+
if err != nil {
3181+
t.Fatal(err)
3182+
}
3183+
3184+
if len(got) != 3 {
3185+
t.Errorf("expected 3 values, got %d: %v", len(got), got)
3186+
}
3187+
}
3188+
31083189
func TestLocalFlagError(t *testing.T) {
31093190
var topInt int64
31103191

@@ -5542,3 +5623,86 @@ func TestCommand_ExclusiveFlagsPersistent(t *testing.T) {
55425623
})
55435624
}
55445625
}
5626+
5627+
func TestEmptyPositionalArgs(t *testing.T) {
5628+
testCases := []struct {
5629+
Name string
5630+
Args []string
5631+
Expected []string
5632+
}{
5633+
{
5634+
Name: "empty arg between values",
5635+
Args: []string{"app", "hello", "", "world"},
5636+
Expected: []string{"hello", "", "world"},
5637+
},
5638+
{
5639+
Name: "empty arg at start",
5640+
Args: []string{"app", "", "hello"},
5641+
Expected: []string{"", "hello"},
5642+
},
5643+
{
5644+
Name: "whitespace-only arg",
5645+
Args: []string{"app", "hello", " ", "world"},
5646+
Expected: []string{"hello", " ", "world"},
5647+
},
5648+
}
5649+
5650+
for _, tc := range testCases {
5651+
t.Run(tc.Name, func(t *testing.T) {
5652+
var args []string
5653+
5654+
cmd := &Command{
5655+
Action: func(_ context.Context, cmd *Command) error {
5656+
args = cmd.Args().Slice()
5657+
return nil
5658+
},
5659+
}
5660+
5661+
err := cmd.Run(buildTestContext(t), tc.Args)
5662+
assert.NoError(t, err)
5663+
assert.Equal(t, tc.Expected, args)
5664+
})
5665+
}
5666+
}
5667+
5668+
func TestFlagEqualsEmptyValue(t *testing.T) {
5669+
t.Run("--flag= sets empty string", func(t *testing.T) {
5670+
var val string
5671+
5672+
cmd := &Command{
5673+
Flags: []Flag{
5674+
&StringFlag{
5675+
Name: "name",
5676+
Destination: &val,
5677+
},
5678+
},
5679+
}
5680+
5681+
err := cmd.Run(buildTestContext(t), []string{"app", "--name="})
5682+
assert.NoError(t, err)
5683+
assert.Equal(t, "", val)
5684+
})
5685+
5686+
t.Run("--flag= does not consume next positional arg", func(t *testing.T) {
5687+
var val string
5688+
var args []string
5689+
5690+
cmd := &Command{
5691+
Flags: []Flag{
5692+
&StringFlag{
5693+
Name: "name",
5694+
Destination: &val,
5695+
},
5696+
},
5697+
Action: func(_ context.Context, cmd *Command) error {
5698+
args = cmd.Args().Slice()
5699+
return nil
5700+
},
5701+
}
5702+
5703+
err := cmd.Run(buildTestContext(t), []string{"app", "--name=", "positional"})
5704+
assert.NoError(t, err)
5705+
assert.Equal(t, "", val)
5706+
assert.Equal(t, []string{"positional"}, args)
5707+
})
5708+
}

flag_impl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func (f *FlagBase[T, C, V]) Set(_ string, val string) error {
185185
// lots of units tests prior to persistent flags assumed that the
186186
// flag can be applied to different flag sets multiple times while still
187187
// keeping the env set.
188-
if !f.applied || f.Local {
188+
if !f.applied {
189189
if err := f.PreParse(); err != nil {
190190
return err
191191
}

mkdocs-requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mkdocs-git-revision-date-localized-plugin==1.5.1
2-
mkdocs-material==9.7.5
2+
mkdocs-material==9.7.6
33
mkdocs==1.6.1
4-
mkdocs-redirects==1.2.2
5-
pygments==2.19.2
4+
mkdocs-redirects==1.2.3
5+
pygments==2.20.0

0 commit comments

Comments
 (0)