From cc481d6b0ea0e659faecc03f48bd0352b5502b4b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 3 May 2016 06:54:05 -0400 Subject: [PATCH] Adjust command hiding to use similar convention as hidden flags plus breaking out "setup" portion of `App.Run` into its own method, cleaning up some bits of the help templates, and allowing for runtime opt-in of displaying template errors to stderr. --- app.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++--- app_test.go | 4 ++-- category.go | 11 +++++++++++ errors_test.go | 4 ++-- help.go | 26 ++++++++++++++----------- help_test.go | 4 ++-- 6 files changed, 80 insertions(+), 20 deletions(-) diff --git a/app.go b/app.go index 89c741b..7ad070b 100644 --- a/app.go +++ b/app.go @@ -84,6 +84,8 @@ type App struct { Writer io.Writer // Other custom info Metadata map[string]interface{} + + didSetup bool } // Tries to find out when this binary was compiled. @@ -111,8 +113,16 @@ func NewApp() *App { } } -// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination -func (a *App) Run(arguments []string) (err error) { +// Setup runs initialization code to ensure all data structures are ready for +// `Run` or inspection prior to `Run`. It is internally called by `Run`, but +// will return early if setup has already happened. +func (a *App) Setup() { + if a.didSetup { + return + } + + a.didSetup = true + if a.Author != "" || a.Email != "" { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } @@ -148,6 +158,11 @@ func (a *App) Run(arguments []string) (err error) { if !a.HideVersion { a.appendFlag(VersionFlag) } +} + +// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination +func (a *App) Run(arguments []string) (err error) { + a.Setup() // parse flags set := flagSet(a.Name, a.Flags) @@ -357,11 +372,41 @@ func (a *App) Command(name string) *Command { return nil } -// Returnes the array containing all the categories with the commands they contain +// Categories returns a slice containing all the categories with the commands they contain func (a *App) Categories() CommandCategories { return a.categories } +// VisibleCategories returns a slice of categories and commands that are +// Hidden=false +func (a *App) VisibleCategories() []*CommandCategory { + ret := []*CommandCategory{} + for _, category := range a.categories { + if visible := func() *CommandCategory { + for _, command := range category.Commands { + if !command.Hidden { + return category + } + } + return nil + }(); visible != nil { + ret = append(ret, visible) + } + } + return ret +} + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleCommands() []Command { + ret := []Command{} + for _, command := range a.Commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) diff --git a/app_test.go b/app_test.go index bf2887e..b08abb2 100644 --- a/app_test.go +++ b/app_test.go @@ -1124,8 +1124,8 @@ func TestApp_Run_Categories(t *testing.T) { output := buf.String() t.Logf("output: %q\n", buf.Bytes()) - if !strings.Contains(output, "1:\n command1") { - t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) + if !strings.Contains(output, "1:\n command1") { + t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) } } diff --git a/category.go b/category.go index 7dbf218..5c3d4b5 100644 --- a/category.go +++ b/category.go @@ -28,3 +28,14 @@ func (c CommandCategories) AddCommand(category string, command Command) CommandC } return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) } + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *CommandCategory) VisibleCommands() []Command { + ret := []Command{} + for _, command := range c.Commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} diff --git a/errors_test.go b/errors_test.go index 6863105..8f5f284 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - HandleExitCoder(NewExitError("galactic perimiter breach", 9)) + HandleExitCoder(NewExitError("galactic perimeter breach", 9)) expect(t, exitCode, 9) expect(t, called, true) @@ -51,7 +51,7 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - exitErr := NewExitError("galactic perimiter breach", 9) + exitErr := NewExitError("galactic perimeter breach", 9) err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) HandleExitCoder(err) diff --git a/help.go b/help.go index 3f47efd..666791f 100644 --- a/help.go +++ b/help.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "io" + "os" "strings" "text/tabwriter" "text/template" @@ -21,15 +22,15 @@ VERSION: {{.Version}} {{end}}{{end}}{{if len .Authors}} AUTHOR(S): - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS:{{range .Categories}}{{if .Name}} - {{.Name}}{{ ":" }}{{end}}{{range .Commands}} - {{if not .Hidden}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}{{end}} + {{range .Authors}}{{.}}{{end}} + {{end}}{{if .VisibleCommands}} +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} + {{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}} {{end}} @@ -52,7 +53,7 @@ DESCRIPTION: OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{ end }} + {{end}}{{end}} ` // The text template for the subcommand help topic. @@ -64,9 +65,9 @@ var SubcommandHelpTemplate = `NAME: USAGE: {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} -COMMANDS:{{range .Categories}}{{if .Name}} - {{.Name}}{{ ":" }}{{end}}{{range .Commands}} - {{if not .Hidden}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}{{end}} +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} @@ -191,7 +192,10 @@ func printHelp(out io.Writer, templ string, data interface{}) { err := t.Execute(w, data) if err != nil { // If the writer is closed, t.Execute will fail, and there's nothing - // we can do to recover. We could send this to os.Stderr if we need. + // we can do to recover. + if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + fmt.Fprintf(os.Stderr, "CLI TEMPLATE ERROR: %#v\n", err) + } return } w.Flush() diff --git a/help_test.go b/help_test.go index c6f2e57..db0cb21 100644 --- a/help_test.go +++ b/help_test.go @@ -136,10 +136,10 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) { app.Run([]string{"app", "--help"}) if strings.Contains(output.String(), "secretfrob") { - t.Fatalf("expected output to exclude \"secretfrob\"; got: %q", output.String()) + t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) } if !strings.Contains(output.String(), "frobbly") { - t.Fatalf("expected output to include \"frobbly\"; got: %q", output.String()) + t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) } }