From 60e3dcaf6dd45d08ad1974c70ff8c93242a5c635 Mon Sep 17 00:00:00 2001 From: John Hopper Date: Thu, 12 Jun 2014 00:39:13 -0700 Subject: [PATCH 1/4] Allow a writer to be set that represents Stdout so that redirection of App output may occur. --- app.go | 36 +++++++++++++++++++++++++++++------- app_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ command.go | 11 ++++++----- help.go | 26 ++++++++------------------ 4 files changed, 87 insertions(+), 30 deletions(-) diff --git a/app.go b/app.go index 4efba5e..0125dae 100644 --- a/app.go +++ b/app.go @@ -2,8 +2,11 @@ package cli import ( "fmt" + "io" "io/ioutil" "os" + "text/tabwriter" + "text/template" "time" ) @@ -37,6 +40,8 @@ type App struct { Author string // Author e-mail Email string + // Stdout writer to write output to + Stdout io.Writer } // Tries to find out when this binary was compiled. @@ -60,11 +65,28 @@ func NewApp() *App { Compiled: compileTime(), Author: "Author", Email: "unknown@email", + Stdout: os.Stdout, } } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination func (a *App) Run(arguments []string) error { + if HelpPrinter == nil { + defer func() { + HelpPrinter = nil + }() + + HelpPrinter = func(templ string, data interface{}) { + w := tabwriter.NewWriter(a.Stdout, 0, 8, 1, '\t', 0) + t := template.Must(template.New("help").Parse(templ)) + err := t.Execute(w, data) + if err != nil { + panic(err) + } + w.Flush() + } + } + // append help to commands if a.Command(helpCommand.Name) == nil { a.Commands = append(a.Commands, helpCommand) @@ -83,18 +105,18 @@ func (a *App) Run(arguments []string) error { err := set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { - fmt.Println(nerr) + io.WriteString(a.Stdout, fmt.Sprintln(nerr)) context := NewContext(a, set, set) ShowAppHelp(context) - fmt.Println("") + io.WriteString(a.Stdout, fmt.Sprintln("")) return nerr } context := NewContext(a, set, set) if err != nil { - fmt.Printf("Incorrect Usage.\n\n") + io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) ShowAppHelp(context) - fmt.Println("") + io.WriteString(a.Stdout, fmt.Sprintln("")) return err } @@ -154,18 +176,18 @@ func (a *App) RunAsSubcommand(ctx *Context) error { context := NewContext(a, set, set) if nerr != nil { - fmt.Println(nerr) + io.WriteString(a.Stdout, fmt.Sprintln(nerr)) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - fmt.Println("") + io.WriteString(a.Stdout, fmt.Sprintln("")) return nerr } if err != nil { - fmt.Printf("Incorrect Usage.\n\n") + io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) ShowSubcommandHelp(context) return err } diff --git a/app_test.go b/app_test.go index 0b9e154..e8937e1 100644 --- a/app_test.go +++ b/app_test.go @@ -262,6 +262,50 @@ func TestApp_ParseSliceFlags(t *testing.T) { } } +func TestApp_DefaultStdout(t *testing.T) { + app := cli.NewApp() + + if app.Stdout != os.Stdout { + t.Error("Default output writer not set.") + } +} + +type fakeWriter struct { + written []byte +} + +func (fw *fakeWriter) Write(p []byte) (n int, err error) { + if fw.written == nil { + fw.written = p + } else { + fw.written = append(fw.written, p...) + } + + return len(p), nil +} + +func (fw *fakeWriter) GetWritten() (b []byte) { + return fw.written +} + +func TestApp_SetStdout(t *testing.T) { + mockWriter := &fakeWriter{} + + app := cli.NewApp() + app.Name = "test" + app.Stdout = mockWriter + + err := app.Run([]string{"help"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if len(mockWriter.written) == 0 { + t.Error("App did not write output to desired writer.") + } +} + func TestApp_BeforeFunc(t *testing.T) { beforeRun, subcommandRun := false, false beforeError := fmt.Errorf("fail") diff --git a/command.go b/command.go index 9d8fff4..3d470c8 100644 --- a/command.go +++ b/command.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "io" "io/ioutil" "strings" ) @@ -70,18 +71,18 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - fmt.Printf("Incorrect Usage.\n\n") + io.WriteString(ctx.App.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) ShowCommandHelp(ctx, c.Name) - fmt.Println("") + io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) return err } nerr := normalizeFlags(c.Flags, set) if nerr != nil { - fmt.Println(nerr) - fmt.Println("") + io.WriteString(ctx.App.Stdout, fmt.Sprintln(nerr)) + io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) ShowCommandHelp(ctx, c.Name) - fmt.Println("") + io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) return nerr } context := NewContext(ctx.App, set, ctx.globalSet) diff --git a/help.go b/help.go index 7c04005..ae60a13 100644 --- a/help.go +++ b/help.go @@ -2,9 +2,7 @@ package cli import ( "fmt" - "os" - "text/tabwriter" - "text/template" + "io" ) // The text template for the Default help topic. @@ -90,7 +88,9 @@ var helpSubcommand = Command{ } // Prints help for the App -var HelpPrinter = printHelp +type helpPrinter func(templ string, data interface{}) + +var HelpPrinter helpPrinter = nil func ShowAppHelp(c *Context) { HelpPrinter(AppHelpTemplate, c.App) @@ -99,9 +99,9 @@ func ShowAppHelp(c *Context) { // Prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { - fmt.Println(command.Name) + io.WriteString(c.App.Stdout, fmt.Sprintln(command.Name)) if command.ShortName != "" { - fmt.Println(command.ShortName) + io.WriteString(c.App.Stdout, fmt.Sprintln(command.ShortName)) } } } @@ -118,7 +118,7 @@ func ShowCommandHelp(c *Context, command string) { if c.App.CommandNotFound != nil { c.App.CommandNotFound(c, command) } else { - fmt.Printf("No help topic for '%v'\n", command) + io.WriteString(c.App.Stdout, fmt.Sprintf("No help topic for '%v'\n", command)) } } @@ -129,7 +129,7 @@ func ShowSubcommandHelp(c *Context) { // Prints the version number of the App func ShowVersion(c *Context) { - fmt.Printf("%v version %v\n", c.App.Name, c.App.Version) + io.WriteString(c.App.Stdout, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) } // Prints the lists of commands within a given context @@ -148,16 +148,6 @@ func ShowCommandCompletions(ctx *Context, command string) { } } -func printHelp(templ string, data interface{}) { - w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) - t := template.Must(template.New("help").Parse(templ)) - err := t.Execute(w, data) - if err != nil { - panic(err) - } - w.Flush() -} - func checkVersion(c *Context) bool { if c.GlobalBool("version") { ShowVersion(c) From 0d4870d63e0ed75d9e34109af558a46ccb10801a Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 1 Dec 2014 23:50:04 -0500 Subject: [PATCH 2/4] Rename Stdout -> Writer --- app.go | 22 +++++++++++----------- app_test.go | 4 ++-- command.go | 10 +++++----- help.go | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index 0125dae..24e0961 100644 --- a/app.go +++ b/app.go @@ -40,8 +40,8 @@ type App struct { Author string // Author e-mail Email string - // Stdout writer to write output to - Stdout io.Writer + // Writer writer to write output to + Writer io.Writer } // Tries to find out when this binary was compiled. @@ -65,7 +65,7 @@ func NewApp() *App { Compiled: compileTime(), Author: "Author", Email: "unknown@email", - Stdout: os.Stdout, + Writer: os.Stdout, } } @@ -77,7 +77,7 @@ func (a *App) Run(arguments []string) error { }() HelpPrinter = func(templ string, data interface{}) { - w := tabwriter.NewWriter(a.Stdout, 0, 8, 1, '\t', 0) + w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0) t := template.Must(template.New("help").Parse(templ)) err := t.Execute(w, data) if err != nil { @@ -105,18 +105,18 @@ func (a *App) Run(arguments []string) error { err := set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { - io.WriteString(a.Stdout, fmt.Sprintln(nerr)) + io.WriteString(a.Writer, fmt.Sprintln(nerr)) context := NewContext(a, set, set) ShowAppHelp(context) - io.WriteString(a.Stdout, fmt.Sprintln("")) + io.WriteString(a.Writer, fmt.Sprintln("")) return nerr } context := NewContext(a, set, set) if err != nil { - io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) + io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) ShowAppHelp(context) - io.WriteString(a.Stdout, fmt.Sprintln("")) + io.WriteString(a.Writer, fmt.Sprintln("")) return err } @@ -176,18 +176,18 @@ func (a *App) RunAsSubcommand(ctx *Context) error { context := NewContext(a, set, set) if nerr != nil { - io.WriteString(a.Stdout, fmt.Sprintln(nerr)) + io.WriteString(a.Writer, fmt.Sprintln(nerr)) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - io.WriteString(a.Stdout, fmt.Sprintln("")) + io.WriteString(a.Writer, fmt.Sprintln("")) return nerr } if err != nil { - io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) + io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) ShowSubcommandHelp(context) return err } diff --git a/app_test.go b/app_test.go index e8937e1..b436bb8 100644 --- a/app_test.go +++ b/app_test.go @@ -265,7 +265,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { func TestApp_DefaultStdout(t *testing.T) { app := cli.NewApp() - if app.Stdout != os.Stdout { + if app.Writer != os.Stdout { t.Error("Default output writer not set.") } } @@ -293,7 +293,7 @@ func TestApp_SetStdout(t *testing.T) { app := cli.NewApp() app.Name = "test" - app.Stdout = mockWriter + app.Writer = mockWriter err := app.Run([]string{"help"}) diff --git a/command.go b/command.go index 3d470c8..58d1f97 100644 --- a/command.go +++ b/command.go @@ -71,18 +71,18 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - io.WriteString(ctx.App.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) + io.WriteString(ctx.App.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) + io.WriteString(ctx.App.Writer, fmt.Sprintln("")) return err } nerr := normalizeFlags(c.Flags, set) if nerr != nil { - io.WriteString(ctx.App.Stdout, fmt.Sprintln(nerr)) - io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) + io.WriteString(ctx.App.Writer, fmt.Sprintln(nerr)) + io.WriteString(ctx.App.Writer, fmt.Sprintln("")) ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) + io.WriteString(ctx.App.Writer, fmt.Sprintln("")) return nerr } context := NewContext(ctx.App, set, ctx.globalSet) diff --git a/help.go b/help.go index ae60a13..a90780b 100644 --- a/help.go +++ b/help.go @@ -99,9 +99,9 @@ func ShowAppHelp(c *Context) { // Prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { - io.WriteString(c.App.Stdout, fmt.Sprintln(command.Name)) + io.WriteString(c.App.Writer, fmt.Sprintln(command.Name)) if command.ShortName != "" { - io.WriteString(c.App.Stdout, fmt.Sprintln(command.ShortName)) + io.WriteString(c.App.Writer, fmt.Sprintln(command.ShortName)) } } } @@ -118,7 +118,7 @@ func ShowCommandHelp(c *Context, command string) { if c.App.CommandNotFound != nil { c.App.CommandNotFound(c, command) } else { - io.WriteString(c.App.Stdout, fmt.Sprintf("No help topic for '%v'\n", command)) + io.WriteString(c.App.Writer, fmt.Sprintf("No help topic for '%v'\n", command)) } } @@ -129,7 +129,7 @@ func ShowSubcommandHelp(c *Context) { // Prints the version number of the App func ShowVersion(c *Context) { - io.WriteString(c.App.Stdout, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) + io.WriteString(c.App.Writer, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) } // Prints the lists of commands within a given context From e72094e6a406a21eb79abf5c02cee2404ffcbb3b Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 1 Dec 2014 23:57:35 -0500 Subject: [PATCH 3/4] Prefer fmt.Fprint* functions over io.WriteString Less composition needed. --- app.go | 16 ++++++++-------- command.go | 11 +++++------ help.go | 13 +++++-------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index 7dcae60..edf02fb 100644 --- a/app.go +++ b/app.go @@ -112,18 +112,18 @@ func (a *App) Run(arguments []string) error { err := set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { - io.WriteString(a.Writer, fmt.Sprintln(nerr)) + fmt.Fprintln(a.Writer, nerr) context := NewContext(a, set, set) ShowAppHelp(context) - io.WriteString(a.Writer, fmt.Sprintln("")) + fmt.Fprintln(a.Writer) return nerr } context := NewContext(a, set, set) if err != nil { - io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) + fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") ShowAppHelp(context) - io.WriteString(a.Writer, fmt.Sprintln("")) + fmt.Fprintln(a.Writer) return err } @@ -163,7 +163,7 @@ func (a *App) Run(arguments []string) error { // Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { if err := a.Run(os.Args); err != nil { - os.Stderr.WriteString(fmt.Sprintln(err)) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } } @@ -191,18 +191,18 @@ func (a *App) RunAsSubcommand(ctx *Context) error { context := NewContext(a, set, ctx.globalSet) if nerr != nil { - io.WriteString(a.Writer, fmt.Sprintln(nerr)) + fmt.Fprintln(a.Writer, nerr) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - io.WriteString(a.Writer, fmt.Sprintln("")) + fmt.Fprintln(a.Writer) return nerr } if err != nil { - io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) + fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index befb2a7..1536b15 100644 --- a/command.go +++ b/command.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "io" "io/ioutil" "strings" ) @@ -75,18 +74,18 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - io.WriteString(ctx.App.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) + fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n") ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Writer, fmt.Sprintln("")) + fmt.Fprintln(ctx.App.Writer) return err } nerr := normalizeFlags(c.Flags, set) if nerr != nil { - io.WriteString(ctx.App.Writer, fmt.Sprintln(nerr)) - io.WriteString(ctx.App.Writer, fmt.Sprintln("")) + fmt.Fprintln(ctx.App.Writer, nerr) + fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Writer, fmt.Sprintln("")) + fmt.Fprintln(ctx.App.Writer) return nerr } context := NewContext(ctx.App, set, ctx.globalSet) diff --git a/help.go b/help.go index e089655..c6743db 100644 --- a/help.go +++ b/help.go @@ -1,9 +1,6 @@ package cli -import ( - "fmt" - "io" -) +import "fmt" // The text template for the Default help topic. // cli.go uses text/template to render templates. You can @@ -106,9 +103,9 @@ func ShowAppHelp(c *Context) { // Prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { - io.WriteString(c.App.Writer, fmt.Sprintln(command.Name)) + fmt.Fprintln(c.App.Writer, command.Name) if command.ShortName != "" { - io.WriteString(c.App.Writer, fmt.Sprintln(command.ShortName)) + fmt.Fprintln(c.App.Writer, command.ShortName) } } } @@ -125,7 +122,7 @@ func ShowCommandHelp(c *Context, command string) { if c.App.CommandNotFound != nil { c.App.CommandNotFound(c, command) } else { - io.WriteString(c.App.Writer, fmt.Sprintf("No help topic for '%v'\n", command)) + fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command) } } @@ -140,7 +137,7 @@ func ShowVersion(c *Context) { } func printVersion(c *Context) { - io.WriteString(c.App.Writer, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) + fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) } // Prints the lists of commands within a given context From 69b84ea8049f1a89469bc9dbbf9609ec9793f04b Mon Sep 17 00:00:00 2001 From: jszwedko Date: Wed, 10 Dec 2014 10:31:53 -0500 Subject: [PATCH 4/4] Renaming fakeWriter to mockWriter for consistency --- app_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app_test.go b/app_test.go index 413a4b2..1413199 100644 --- a/app_test.go +++ b/app_test.go @@ -273,11 +273,11 @@ func TestApp_DefaultStdout(t *testing.T) { } } -type fakeWriter struct { +type mockWriter struct { written []byte } -func (fw *fakeWriter) Write(p []byte) (n int, err error) { +func (fw *mockWriter) Write(p []byte) (n int, err error) { if fw.written == nil { fw.written = p } else { @@ -287,16 +287,16 @@ func (fw *fakeWriter) Write(p []byte) (n int, err error) { return len(p), nil } -func (fw *fakeWriter) GetWritten() (b []byte) { +func (fw *mockWriter) GetWritten() (b []byte) { return fw.written } func TestApp_SetStdout(t *testing.T) { - mockWriter := &fakeWriter{} + w := &mockWriter{} app := cli.NewApp() app.Name = "test" - app.Writer = mockWriter + app.Writer = w err := app.Run([]string{"help"}) @@ -304,7 +304,7 @@ func TestApp_SetStdout(t *testing.T) { t.Fatalf("Run error: %s", err) } - if len(mockWriter.written) == 0 { + if len(w.written) == 0 { t.Error("App did not write output to desired writer.") } }