Skip to content

Commit 2c38e7b

Browse files
authored
Merge pull request #2360 from dearchap/issue_2126
fix: let --help flag take precedence over parse errors
2 parents 213b98c + 3a5e3e1 commit 2c38e7b

3 files changed

Lines changed: 123 additions & 11 deletions

File tree

command_run.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
175175
deferErr = err
176176

177177
cmd.isInError = true
178+
if cmd.checkHelp() {
179+
if cmd.parent == nil {
180+
_ = ShowRootCommandHelp(cmd)
181+
} else {
182+
_ = ShowSubcommandHelp(cmd)
183+
}
184+
return ctx, nil
185+
}
178186
if cmd.OnUsageError != nil {
179187
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
180188
err = cmd.handleExitCoder(ctx, err)
@@ -193,10 +201,8 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
193201
tracef("SILENTLY IGNORING ERROR running ShowRootCommandHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
194202
}
195203
} else {
196-
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
197-
if err := ShowCommandHelp(ctx, cmd.parent, cmd.Name); err != nil {
198-
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
199-
}
204+
tracef("running ShowSubcommandHelp for %[1]q", cmd.Name)
205+
_ = ShowSubcommandHelp(cmd)
200206
}
201207
}
202208

command_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,104 @@ func TestCommand_OrderOfOperations(t *testing.T) {
23632363
})
23642364
}
23652365

2366+
func TestCommand_HelpFlagTakesPrecedenceOverParseErrors(t *testing.T) {
2367+
t.Run("root command with --help and bad flag", func(t *testing.T) {
2368+
r := require.New(t)
2369+
var buf bytes.Buffer
2370+
var errBuf bytes.Buffer
2371+
2372+
cmd := &Command{
2373+
Writer: &buf,
2374+
ErrWriter: &errBuf,
2375+
Action: func(ctx context.Context, cmd *Command) error {
2376+
return nil
2377+
},
2378+
}
2379+
2380+
err := cmd.Run(buildTestContext(t), []string{"command", "--help", "--undefined"})
2381+
r.NoError(err)
2382+
r.Contains(buf.String(), "NAME:")
2383+
r.Contains(buf.String(), "command")
2384+
r.NotContains(errBuf.String(), "Incorrect Usage")
2385+
})
2386+
2387+
t.Run("root command with -h and bad flag", func(t *testing.T) {
2388+
r := require.New(t)
2389+
var buf bytes.Buffer
2390+
var errBuf bytes.Buffer
2391+
2392+
cmd := &Command{
2393+
Writer: &buf,
2394+
ErrWriter: &errBuf,
2395+
Action: func(ctx context.Context, cmd *Command) error {
2396+
return nil
2397+
},
2398+
}
2399+
2400+
err := cmd.Run(buildTestContext(t), []string{"command", "-h", "--undefined"})
2401+
r.NoError(err)
2402+
r.Contains(buf.String(), "NAME:")
2403+
r.Contains(buf.String(), "command")
2404+
r.NotContains(errBuf.String(), "Incorrect Usage")
2405+
})
2406+
2407+
t.Run("subcommand with --help and bad flag", func(t *testing.T) {
2408+
r := require.New(t)
2409+
var buf bytes.Buffer
2410+
var errBuf bytes.Buffer
2411+
2412+
cmd := &Command{
2413+
Writer: &buf,
2414+
ErrWriter: &errBuf,
2415+
Action: func(ctx context.Context, cmd *Command) error {
2416+
return nil
2417+
},
2418+
Commands: []*Command{
2419+
{
2420+
Name: "foo",
2421+
Action: func(ctx context.Context, cmd *Command) error {
2422+
return nil
2423+
},
2424+
},
2425+
},
2426+
}
2427+
2428+
err := cmd.Run(buildTestContext(t), []string{"command", "foo", "--help", "--undefined"})
2429+
r.NoError(err)
2430+
r.Contains(buf.String(), "NAME:")
2431+
r.Contains(buf.String(), "foo")
2432+
r.NotContains(errBuf.String(), "Incorrect Usage")
2433+
})
2434+
2435+
t.Run("subcommand with bad flag shows Incorrect Usage and help", func(t *testing.T) {
2436+
r := require.New(t)
2437+
var buf bytes.Buffer
2438+
var errBuf bytes.Buffer
2439+
2440+
cmd := &Command{
2441+
Writer: &buf,
2442+
ErrWriter: &errBuf,
2443+
Action: func(ctx context.Context, cmd *Command) error {
2444+
return nil
2445+
},
2446+
Commands: []*Command{
2447+
{
2448+
Name: "foo",
2449+
Action: func(ctx context.Context, cmd *Command) error {
2450+
return nil
2451+
},
2452+
},
2453+
},
2454+
}
2455+
2456+
err := cmd.Run(buildTestContext(t), []string{"command", "foo", "--undefined"})
2457+
r.Error(err)
2458+
r.Contains(buf.String(), "NAME:")
2459+
r.Contains(buf.String(), "foo")
2460+
r.Contains(errBuf.String(), "Incorrect Usage")
2461+
})
2462+
}
2463+
23662464
func TestFlagActionOrder(t *testing.T) {
23672465
tests := []struct {
23682466
Name string

help.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,22 @@ func helpCommandAction(ctx context.Context, cmd *Command) error {
8686

8787
tracef("doing help for cmd %[1]q with args %[2]q", cmd, args)
8888

89-
// This action can be triggered by a "default" action of a command
90-
// or via cmd.Run when cmd == helpCmd. So we have following possibilities
89+
// helpCommandAction is triggered in several ways:
9190
//
92-
// 1 $ app
93-
// 2 $ app help
94-
// 3 $ app foo
95-
// 4 $ app help foo
96-
// 5 $ app foo help
91+
// * the command has no user-defined Action (default action fallback)
92+
// * the --help / -h flag was parsed (via cmd.checkHelp())
93+
// * the "help" subcommand (or "h" alias) was dispatched
94+
//
95+
// Possible invocations:
96+
//
97+
// $ app # default action; show root help
98+
// $ app --help / -h # flag; show root help (ignores subsequent args)
99+
// $ app help / h # subcommand; show root help
100+
// $ app help / h foo # subcommand; show help for subcommand "foo"
101+
// $ app --help / -h foo # flag; show help for subcommand "foo"
102+
// $ app foo --help / -h # flag on subcommand; show help for "foo"
103+
// $ app foo help / h # subcommand on subcommand; show help for "foo"
104+
// $ app foo (no action) # default action on subcommand; show help for "foo"
97105

98106
// Case 4. when executing a help command set the context to parent
99107
// to allow resolution of subsequent args. This will transform

0 commit comments

Comments
 (0)