diff --git a/acmd.go b/acmd.go index a12fca4..52b3894 100644 --- a/acmd.go +++ b/acmd.go @@ -210,10 +210,10 @@ func validateSubcommands(cmds []Command) error { // Run commands. func (r *Runner) Run() error { if r.errInit != nil { - return fmt.Errorf("acmd: cannot init runner: %w", r.errInit) + return fmt.Errorf("cannot init runner: %w", r.errInit) } if err := r.rootCmd.Do(r.ctx, r.args); err != nil { - return fmt.Errorf("acmd: cannot run command: %w", err) + return fmt.Errorf("cannot run command: %w", err) } return nil } @@ -233,7 +233,7 @@ func rootDo(cfg Config, cmds []Command) func(ctx context.Context, args []string) // go deeper into subcommands if c.Do == nil { if len(params) == 0 { - return errors.New("no args for subcmd provided") + return errors.New("no args for command provided") } cmds, args = c.Subcommands, params found = true @@ -243,16 +243,20 @@ func rootDo(cfg Config, cmds []Command) func(ctx context.Context, args []string) } if !found { - return errNotFoundAndSuggest(cfg.Output, selected, cmds) + return errNotFoundAndSuggest(cfg.Output, cfg.AppName, selected, cmds) } } } } -func errNotFoundAndSuggest(w io.Writer, selected string, cmds []Command) error { - if suggestion := suggestCommand(selected, cmds); suggestion != "" { - fmt.Fprintf(w, "%q is not a subcommand, did you mean %q?\n", selected, suggestion) +func errNotFoundAndSuggest(w io.Writer, appName, selected string, cmds []Command) error { + suggestion := suggestCommand(selected, cmds) + if suggestion != "" { + fmt.Fprintf(w, "%q unknown command, did you mean %q?\n", selected, suggestion) + } else { + fmt.Fprintf(w, "%q unknown command\n", selected) } + fmt.Fprintf(w, "Run %q for usage.\n\n", appName+" help") return fmt.Errorf("no such command %q", selected) } diff --git a/acmd_test.go b/acmd_test.go index 4d19944..daf3783 100644 --- a/acmd_test.go +++ b/acmd_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "os" "sort" "strings" @@ -11,6 +12,8 @@ import ( "time" ) +var nopUsage = func(cfg Config, cmds []Command) {} + func TestRunner(t *testing.T) { buf := &bytes.Buffer{} @@ -65,13 +68,16 @@ func TestRunner(t *testing.T) { func TestRunnerMustSetDefaults(t *testing.T) { cmds := []Command{{Name: "foo", Do: nopFunc}} - r := RunnerOf(cmds, Config{}) + r := RunnerOf(cmds, Config{ + Output: io.Discard, + Usage: nopUsage, + }) err := r.Run() if err == nil { t.Fatal() } - if errStr := err.Error(); !strings.Contains(errStr, "acmd: cannot run command: no such command") { + if errStr := err.Error(); !strings.Contains(errStr, "cannot run command: no such command") { t.Fatal(err) } @@ -81,9 +87,6 @@ func TestRunnerMustSetDefaults(t *testing.T) { if r.ctx == nil { t.Fatal("context must be set") } - if r.cfg.Output != os.Stderr { - t.Fatal("incorrect output") - } if r.cfg.Usage == nil { t.Fatal("usage nust be set") } @@ -225,30 +228,32 @@ func TestRunner_suggestCommand(t *testing.T) { {Name: "bar", Do: nopFunc}, }, args: []string{"fooo"}, - want: `"fooo" is not a subcommand, did you mean "foo"?` + "\n", + want: `"fooo" unknown command, did you mean "foo"?` + "\n" + `Run "ci help" for usage.` + "\n\n", }, { cmds: []Command{{Name: "for", Do: nopFunc}}, args: []string{"hell"}, - want: `"hell" is not a subcommand, did you mean "help"?` + "\n", + want: `"hell" unknown command, did you mean "help"?` + "\n" + `Run "ci help" for usage.` + "\n\n", }, { cmds: []Command{{Name: "for", Do: nopFunc}}, args: []string{"verZION"}, - want: "", + want: `"verZION" unknown command` + "\n" + `Run "ci help" for usage.` + "\n\n", }, { cmds: []Command{{Name: "for", Do: nopFunc}}, args: []string{"verZion"}, - want: `"verZion" is not a subcommand, did you mean "version"?` + "\n", + want: `"verZion" unknown command, did you mean "version"?` + "\n" + `Run "ci help" for usage.` + "\n\n", }, } for _, tc := range testCases { buf := &bytes.Buffer{} r := RunnerOf(tc.cmds, Config{ - Args: tc.args, - Output: buf, + Args: tc.args, + AppName: "ci", + Output: buf, + Usage: nopUsage, }) if err := r.Run(); err != nil && !strings.Contains(err.Error(), "no such command") { t.Fatal(err) diff --git a/example_test.go b/example_test.go index e95fed1..a950371 100644 --- a/example_test.go +++ b/example_test.go @@ -11,14 +11,17 @@ import ( "github.com/cristalhq/acmd" ) -var nopFunc = func(context.Context, []string) error { return nil } +var ( + nopFunc = func(context.Context, []string) error { return nil } + nopUsage = func(cfg acmd.Config, cmds []acmd.Command) {} +) func ExampleRunner() { testOut := os.Stdout testArgs := []string{"now", "--times", "3"} const format = "15:04:05" - now, _ := time.Parse("15:04:05", "10:20:30") + now, _ := time.Parse(format, "10:20:30") cmds := []acmd.Command{ { @@ -59,6 +62,7 @@ func ExampleRunner() { Version: "the best v0.x.y", Output: testOut, Args: testArgs, + Usage: nopUsage, }) if err := r.Run(); err != nil { @@ -136,6 +140,7 @@ func ExampleVersion() { Version: "the best v0.x.y", Output: testOut, Args: testArgs, + Usage: nopUsage, }) if err := r.Run(); err != nil { @@ -174,6 +179,7 @@ func ExampleAlias() { Version: "the best v0.x.y", Output: testOut, Args: testArgs, + Usage: nopUsage, }) if err := r.Run(); err != nil { @@ -198,13 +204,16 @@ func ExampleAutosuggestion() { Version: "the best v0.x.y", Output: testOut, Args: testArgs, + Usage: nopUsage, }) if err := r.Run(); err == nil { panic("must fail with command not found") } - // Output: "baz" is not a subcommand, did you mean "bar"? + // Output: + // "baz" unknown command, did you mean "bar"? + // Run "acmd-example help" for usage. } func ExampleNestedCommands() {