Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ type Command struct {
HideHelp bool `json:"hideHelp"`
// Ignored if HideHelp is true.
HideHelpCommand bool `json:"hideHelpCommand"`
// Boolean to enable Before function execution before showing help
EnableBeforeForHelp bool `json:"enableBeforeForHelp"`
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool `json:"hideVersion"`
// Boolean to enable shell completion commands
Expand Down
20 changes: 20 additions & 0 deletions command_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,26 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
}

if cmd.checkHelp() {
if cmd.Root().EnableBeforeForHelp {
var cmdChain []*Command
for p := cmd; p != nil; p = p.parent {
cmdChain = append(cmdChain, p)
}
slices.Reverse(cmdChain)

for _, c := range cmdChain {
if c.Before == nil {
continue
}
if bctx, err := c.Before(ctx, c); err != nil {
deferErr = cmd.handleExitCoder(ctx, err)
return ctx, deferErr
} else if bctx != nil {
ctx = bctx
}
}
}

return ctx, helpCommandAction(ctx, cmd)
} else {
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
Expand Down
157 changes: 157 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,155 @@ func TestCommand_BeforeFuncPersistentFlag(t *testing.T) {
assert.Equal(t, 1, counts.SubCommand, "Subcommand not executed when expected")
}

func TestCommand_BeforeWithHelpCommand(t *testing.T) {
counts := &opCounts{}

cmd := &Command{
Name: "testapp",
EnableBeforeForHelp: true,
Before: func(ctx context.Context, cmd *Command) (context.Context, error) {
counts.Before++
return ctx, nil
},
Commands: []*Command{
{
Name: "subcmd",
Before: func(ctx context.Context, cmd *Command) (context.Context, error) {
counts.SubCommand++
return ctx, nil
},
Action: func(context.Context, *Command) error {
return nil
},
},
},
Writer: io.Discard,
}

testCases := []struct {
name string
args []string
expected opCounts
}{
{
name: "help command should execute Before functions",
args: []string{"testapp", "help"},
expected: opCounts{Before: 1, SubCommand: 0},
},
{
name: "help flag should execute Before functions",
args: []string{"testapp", "--help"},
expected: opCounts{Before: 1, SubCommand: 0},
},
{
name: "help flag short form should execute Before functions",
args: []string{"testapp", "-h"},
expected: opCounts{Before: 1, SubCommand: 0},
},
{
name: "subcommand help command should execute Before functions",
args: []string{"testapp", "subcmd", "help"},
expected: opCounts{Before: 1, SubCommand: 1},
},
{
name: "subcommand help flag should execute Before functions",
args: []string{"testapp", "subcmd", "--help"},
expected: opCounts{Before: 1, SubCommand: 1},
},
{
name: "subcommand help flag short should execute Before functions",
args: []string{"testapp", "subcmd", "-h"},
expected: opCounts{Before: 1, SubCommand: 1},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
*counts = opCounts{}

err := cmd.Run(buildTestContext(t), tc.args)
require.NoError(t, err)

assert.Equal(t, tc.expected.Before, counts.Before, "Before() not executed expected number of times")
assert.Equal(t, tc.expected.SubCommand, counts.SubCommand, "SubCommand Before() not executed expected number of times")
})
}
}

func TestCommand_BeforeWithHelpCommand_DefaultBehavior(t *testing.T) {
counts := &opCounts{}

cmd := &Command{
Name: "testapp",
Before: func(ctx context.Context, cmd *Command) (context.Context, error) {
counts.Before++
return ctx, nil
},
Commands: []*Command{
{
Name: "subcmd",
Before: func(ctx context.Context, cmd *Command) (context.Context, error) {
counts.SubCommand++
return ctx, nil
},
Action: func(context.Context, *Command) error {
return nil
},
},
},
Writer: io.Discard,
}

testCases := []struct {
name string
args []string
expected opCounts
}{
{
name: "help command executes Before functions (normal command behavior)",
args: []string{"testapp", "help"},
expected: opCounts{Before: 1, SubCommand: 0},
},
{
name: "help flag should NOT execute Before functions by default",
args: []string{"testapp", "--help"},
expected: opCounts{Before: 0, SubCommand: 0},
},
{
name: "help flag short form should NOT execute Before functions by default",
args: []string{"testapp", "-h"},
expected: opCounts{Before: 0, SubCommand: 0},
},
{
name: "subcommand help command executes Before functions (normal command behavior)",
args: []string{"testapp", "subcmd", "help"},
expected: opCounts{Before: 1, SubCommand: 1},
},
{
name: "subcommand help flag should NOT execute Before functions by default",
args: []string{"testapp", "subcmd", "--help"},
expected: opCounts{Before: 0, SubCommand: 0},
},
{
name: "subcommand help flag short should NOT execute Before functions by default",
args: []string{"testapp", "subcmd", "-h"},
expected: opCounts{Before: 0, SubCommand: 0},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
*counts = opCounts{}

err := cmd.Run(buildTestContext(t), tc.args)
require.NoError(t, err)

assert.Equal(t, tc.expected.Before, counts.Before, "Before() execution count mismatch")
assert.Equal(t, tc.expected.SubCommand, counts.SubCommand, "SubCommand Before() execution count mismatch")
})
}
}

func TestCommand_BeforeAfterFuncShellCompletion(t *testing.T) {
t.Skip("TODO: is '--generate-shell-completion' (flag) still supported?")

Expand Down Expand Up @@ -4747,6 +4896,7 @@ func TestJSONExportCommand(t *testing.T) {
],
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": null,
Expand Down Expand Up @@ -4810,6 +4960,7 @@ func TestJSONExportCommand(t *testing.T) {
],
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": null,
Expand Down Expand Up @@ -4844,6 +4995,7 @@ func TestJSONExportCommand(t *testing.T) {
"flags": null,
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": null,
Expand Down Expand Up @@ -4875,6 +5027,7 @@ func TestJSONExportCommand(t *testing.T) {
"flags": null,
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": null,
Expand Down Expand Up @@ -4925,6 +5078,7 @@ func TestJSONExportCommand(t *testing.T) {
],
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": true,
"authors": null,
Expand Down Expand Up @@ -4992,6 +5146,7 @@ func TestJSONExportCommand(t *testing.T) {
],
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": null,
Expand Down Expand Up @@ -5055,6 +5210,7 @@ func TestJSONExportCommand(t *testing.T) {
],
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": null,
Expand Down Expand Up @@ -5156,6 +5312,7 @@ func TestJSONExportCommand(t *testing.T) {
],
"hideHelp": false,
"hideHelpCommand": false,
"enableBeforeForHelp": false,
"hideVersion": false,
"hidden": false,
"authors": [
Expand Down
2 changes: 2 additions & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ type Command struct {
HideHelp bool `json:"hideHelp"`
// Ignored if HideHelp is true.
HideHelpCommand bool `json:"hideHelpCommand"`
// Boolean to enable Before function execution before showing help
EnableBeforeForHelp bool `json:"enableBeforeForHelp"`
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool `json:"hideVersion"`
// Boolean to enable shell completion commands
Expand Down
2 changes: 2 additions & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ type Command struct {
HideHelp bool `json:"hideHelp"`
// Ignored if HideHelp is true.
HideHelpCommand bool `json:"hideHelpCommand"`
// Boolean to enable Before function execution before showing help
EnableBeforeForHelp bool `json:"enableBeforeForHelp"`
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool `json:"hideVersion"`
// Boolean to enable shell completion commands
Expand Down