Exit non-zero if a unknown subcommand is given

Currently it just prints the help message and exits 0.

We do this by modifying the helpCommand and helpSubcommand cli.Commands
to return an error if they are called with an unknown subcommand. This
propogates up to the app which exits with 3 and prints the error.

Thanks to @danslimmon for the initial approach!

Fixes #276
This commit is contained in:
Jesse Szwedko 2016-05-07 17:22:09 -07:00
parent d3a4d5467b
commit 592f1d97e5
3 changed files with 81 additions and 19 deletions

View File

@ -8,11 +8,15 @@
### Changed ### Changed
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
quoted in help text output. quoted in help text output.
- All flag types now include `(default: {value})` strings following usage when a - All flag types now include `(default: {value})` strings following usage when a
default value can be (reasonably) detected. default value can be (reasonably) detected.
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent - `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
with non-slice flag types with non-slice flag types
- Apps now exit with a code of 3 if an unknown subcommand is specified
(previously they printed "No help topic for...", but still exited 0. This
makes it easier to script around apps built using `cli` since they can trust
that a 0 exit code indicated a successful execution.
## [1.16.0] - 2016-05-02 ## [1.16.0] - 2016-05-02
### Added ### Added

32
help.go
View File

@ -81,10 +81,10 @@ var helpCommand = Command{
Action: func(c *Context) error { Action: func(c *Context) error {
args := c.Args() args := c.Args()
if args.Present() { if args.Present() {
ShowCommandHelp(c, args.First()) return ShowCommandHelp(c, args.First())
} else {
ShowAppHelp(c)
} }
ShowAppHelp(c)
return nil return nil
}, },
} }
@ -97,11 +97,10 @@ var helpSubcommand = Command{
Action: func(c *Context) error { Action: func(c *Context) error {
args := c.Args() args := c.Args()
if args.Present() { if args.Present() {
ShowCommandHelp(c, args.First()) return ShowCommandHelp(c, args.First())
} else {
ShowSubcommandHelp(c)
} }
return nil
return ShowSubcommandHelp(c)
}, },
} }
@ -127,30 +126,31 @@ func DefaultAppComplete(c *Context) {
} }
// Prints help for the given command // Prints help for the given command
func ShowCommandHelp(ctx *Context, command string) { func ShowCommandHelp(ctx *Context, command string) error {
// show the subcommand help for a command with subcommands // show the subcommand help for a command with subcommands
if command == "" { if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return return nil
} }
for _, c := range ctx.App.Commands { for _, c := range ctx.App.Commands {
if c.HasName(command) { if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return return nil
} }
} }
if ctx.App.CommandNotFound != nil { if ctx.App.CommandNotFound == nil {
ctx.App.CommandNotFound(ctx, command) return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
} else {
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
} }
ctx.App.CommandNotFound(ctx, command)
return nil
} }
// Prints help for the given subcommand // Prints help for the given subcommand
func ShowSubcommandHelp(c *Context) { func ShowSubcommandHelp(c *Context) error {
ShowCommandHelp(c, c.Command.Name) return ShowCommandHelp(c, c.Command.Name)
} }
// Prints the version number of the App // Prints the version number of the App

View File

@ -2,6 +2,8 @@ package cli
import ( import (
"bytes" "bytes"
"flag"
"strings"
"testing" "testing"
) )
@ -110,3 +112,59 @@ func Test_Version_Custom_Flags(t *testing.T) {
t.Errorf("unexpected output: %s", output.String()) t.Errorf("unexpected output: %s", output.String())
} }
} }
func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
app := NewApp()
set := flag.NewFlagSet("test", 0)
set.Parse([]string{"foo"})
c := NewContext(app, set, nil)
err := helpCommand.Action.(func(*Context) error)(c)
if err == nil {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
}
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
}
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error())
}
if exitErr.exitCode != 3 {
t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode)
}
}
func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
app := NewApp()
set := flag.NewFlagSet("test", 0)
set.Parse([]string{"foo"})
c := NewContext(app, set, nil)
err := helpSubcommand.Action.(func(*Context) error)(c)
if err == nil {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
}
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
}
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error())
}
if exitErr.exitCode != 3 {
t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode)
}
}