diff --git a/cli.go b/cli.go index c3ada92..bfe3b2d 100644 --- a/cli.go +++ b/cli.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "io" "os" "sync" @@ -26,6 +27,8 @@ type CLI struct { // Version of the CLI. Version string + SubcommandChooser func(*CLI) (CommandFactory, error) + // HelpFunc and HelpWriter are used to output help information, if // requested. // @@ -47,6 +50,28 @@ type CLI struct { isVersion bool } +// DefaultSubcommandChooser is the default SubcommandChooser. It will return +// the proper subcommand CommandFactory, or if that cannot be found and +// IsVersion() return true, return versionCommanFactory. The fallback of +// helpCommandFactory will be returned when there's nothing matched. error will +// be non-nil if a proper subcommand can't be found, so client user can wrap +// this function according to error value. +func DefaultSubcommandChooser(c *CLI) (CommandFactory, error) { + if commandFunc, ok := c.Commands[c.Subcommand()]; ok { + return commandFunc, nil + } else if c.IsVersion() { + versionCommandFactory := func() (Command, error) { + return OutputTextCommand{c.HelpWriter, c.Version}, nil + } + return versionCommandFactory, fmt.Errorf("Failed to find subcommand") + } else { + helpCommandFactory := func() (Command, error) { + return OutputTextCommand{c.HelpWriter, c.HelpFunc(c.Commands)}, nil + } + return helpCommandFactory, fmt.Errorf("Failed to find subcommand") + } +} + // NewClI returns a new CLI instance with sensible defaults. func NewCLI(app, version string) *CLI { return &CLI{ @@ -75,12 +100,6 @@ func (c *CLI) IsVersion() bool { func (c *CLI) Run() (int, error) { c.once.Do(c.init) - // Just show the version and exit if instructed. - if c.IsVersion() && c.Version != "" { - c.HelpWriter.Write([]byte(c.Version + "\n")) - return 1, nil - } - // If there is an invalid flag, then error if len(c.topFlags) > 0 { c.HelpWriter.Write([]byte( @@ -90,25 +109,16 @@ func (c *CLI) Run() (int, error) { return 1, nil } - // Attempt to get the factory function for creating the command - // implementation. If the command is invalid or blank, it is an error. - commandFunc, ok := c.Commands[c.Subcommand()] - if !ok { - c.HelpWriter.Write([]byte(c.HelpFunc(c.Commands) + "\n")) - return 1, nil - } + commandFunc, _ := c.SubcommandChooser(c) command, err := commandFunc() + if err != nil { return 0, err } - - // If we've been instructed to just print the help, then print it if c.IsHelp() { - c.HelpWriter.Write([]byte(command.Help() + "\n")) - return 1, nil + command = OutputTextCommand{c.HelpWriter, command.Help()} } - return command.Run(c.SubcommandArgs()), nil } @@ -140,6 +150,10 @@ func (c *CLI) init() { c.HelpWriter = os.Stderr } + if c.SubcommandChooser == nil { + c.SubcommandChooser = DefaultSubcommandChooser + } + c.processArgs() } diff --git a/cli_test.go b/cli_test.go index d49d8d4..8c24658 100644 --- a/cli_test.go +++ b/cli_test.go @@ -158,8 +158,11 @@ func TestCLIRun_printHelp(t *testing.T) { continue } - if !strings.Contains(buf.String(), helpText) { - t.Errorf("Args: %#v. Text: %v", testCase, buf.String()) + expect := strings.TrimSpace(buf.String()) + got := strings.TrimSpace(helpText) + + if !strings.Contains(expect, got) { + t.Errorf("Args: %#v, expect: %#v, got %#v", testCase, expect, got) } } } @@ -195,8 +198,10 @@ func TestCLIRun_printCommandHelp(t *testing.T) { t.Fatalf("bad exit code: %d", exitCode) } - if buf.String() != (command.HelpText + "\n") { - t.Fatalf("bad: %#v", buf.String()) + expect := strings.TrimSpace(command.HelpText) + got := strings.TrimSpace(buf.String()) + if expect != got { + t.Fatalf("Expect %#v, got %#v.", expect, got) } } } diff --git a/command.go b/command.go index b18d3ef..583804d 100644 --- a/command.go +++ b/command.go @@ -1,5 +1,7 @@ package cli +import "io" + // A command is a runnable sub-command of a CLI. type Command interface { // Help should return long-form help text that includes the command-line @@ -21,3 +23,26 @@ type Command interface { // We need a factory because we may need to setup some state on the // struct that implements the command itself. type CommandFactory func() (Command, error) + +// OutputTextCommand implemented Command interface and is used to write text to +// given writer +type OutputTextCommand struct { + writer io.Writer + text string +} + +// Help is part of Command interface +func (c OutputTextCommand) Help() string { + return c.text +} + +// Synopsis is part of Command interface. Return Help() +func (c OutputTextCommand) Synopsis() string { + return c.Help() +} + +// Run is part of Command interface. Args will be ignored. +func (c OutputTextCommand) Run(_ []string) int { + c.writer.Write([]byte(c.Help())) + return 1 +}