From ce428377024188ae8ce8aae411881b7e993f9553 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Fri, 30 Sep 2016 19:23:44 +0900 Subject: [PATCH 01/10] Call HandleExitCoder for all members of MultiError.Errors --- errors.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/errors.go b/errors.go index c7d8c2f..fd67b96 100644 --- a/errors.go +++ b/errors.go @@ -86,8 +86,9 @@ func HandleExitCoder(err error) { if multiErr, ok := err.(MultiError); ok { for _, merr := range multiErr.Errors { - HandleExitCoder(merr) + fmt.Fprintln(ErrWriter, merr) } + OsExiter(1) return } From 6c50b15a273d29dc3820b5e4d50d78eeb113d335 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Fri, 11 Nov 2016 13:11:50 +0900 Subject: [PATCH 02/10] Exit with the code of ExitCoder if exists --- errors.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/errors.go b/errors.go index fd67b96..583f89f 100644 --- a/errors.go +++ b/errors.go @@ -85,10 +85,8 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors { - fmt.Fprintln(ErrWriter, merr) - } - OsExiter(1) + code := handleMultiError(multiErr) + OsExiter(code) return } @@ -97,3 +95,18 @@ func HandleExitCoder(err error) { } OsExiter(1) } + +func handleMultiError(multiErr MultiError) int { + code := 1 + for _, merr := range multiErr.Errors { + if multiErr2, ok := merr.(MultiError); ok { + code = handleMultiError(multiErr2) + } else { + fmt.Fprintln(ErrWriter, merr) + if exitErr, ok := merr.(ExitCoder); ok { + code = exitErr.ExitCode() + } + } + } + return code +} From 4267cd827cbfb066cf031bcb01ddbecdf7d0c49e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 4 Jan 2017 10:28:58 +0800 Subject: [PATCH 03/10] [ci skip] Fix template syntax error --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bb5f61e..6ba60c4 100644 --- a/README.md +++ b/README.md @@ -940,16 +940,13 @@ SUPPORT: support@awesometown.example.com cli.AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command -[command options]{{end}} {{if -.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if len .Authors}} -AUTHOR(S): +AUTHOR: {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" -}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}}{{if .Copyright }} From ac772237b96509d5150567da31ba6c6c3897d452 Mon Sep 17 00:00:00 2001 From: Maximilian Meister Date: Sun, 18 Dec 2016 17:43:48 +0100 Subject: [PATCH 04/10] command: enable ordering commands by name --- README.md | 24 ++++++++++++++++++++++-- command.go | 14 ++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ba60c4..2bbbd8e 100644 --- a/README.md +++ b/README.md @@ -455,13 +455,13 @@ error. Flags for the application and commands are shown in the order they are defined. However, it's possible to sort them from outside this library by using `FlagsByName` -with `sort`. +or `CommandsByName` with `sort`. For example this: ``` go package main @@ -488,7 +488,27 @@ func main() { }, } + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + } + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) app.Run(os.Args) } diff --git a/command.go b/command.go index 2628fbf..d297eb9 100644 --- a/command.go +++ b/command.go @@ -61,6 +61,20 @@ type Command struct { commandNamePath []string } +type CommandsByName []Command + +func (c CommandsByName) Len() int { + return len(c) +} + +func (c CommandsByName) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandsByName) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + // FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path func (c Command) FullName() string { From 71ced406af64ee9961533d518dab28d455a66666 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Thu, 12 Jan 2017 18:59:38 +0900 Subject: [PATCH 05/10] Treat `rc` first called as exit code Because default OsExiter is os.Exit. --- errors_test.go | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/errors_test.go b/errors_test.go index 131bd38..d3764b7 100644 --- a/errors_test.go +++ b/errors_test.go @@ -12,8 +12,10 @@ func TestHandleExitCoder_nil(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -29,8 +31,10 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -46,8 +50,10 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -65,8 +71,10 @@ func TestHandleExitCoder_ErrorWithMessage(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } ErrWriter = &bytes.Buffer{} @@ -88,8 +96,10 @@ func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } ErrWriter = &bytes.Buffer{} @@ -123,7 +133,9 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { called := false OsExiter = func(rc int) { - called = true + if !called { + called = true + } } ErrWriter = &bytes.Buffer{} @@ -143,7 +155,9 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { called := false OsExiter = func(rc int) { - called = true + if !called { + called = true + } } ErrWriter = &bytes.Buffer{} From 286b4b56d988e0ac6da075615f1da496db94da87 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Thu, 12 Jan 2017 19:12:17 +0900 Subject: [PATCH 06/10] Document on exit code in case of MultiError is given --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 8aa8d9e..f9d648e 100644 --- a/errors.go +++ b/errors.go @@ -74,7 +74,7 @@ func (ee *ExitError) ExitCode() int { // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // so prints the error to stderr (if it is non-empty) and calls OsExiter with the // given exit code. If the given error is a MultiError, then this func is -// called on all members of the Errors slice. +// called on all members of the Errors slice and calls OsExiter with the last exit code. func HandleExitCoder(err error) { if err == nil { return From adec15acf537c7b696c299223a16c60cb3eeeed8 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Fri, 13 Jan 2017 15:37:09 +0900 Subject: [PATCH 07/10] Add another ExitCoder to assert that it uses last one --- errors_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/errors_test.go b/errors_test.go index d3764b7..d9e0da6 100644 --- a/errors_test.go +++ b/errors_test.go @@ -59,10 +59,11 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = fakeOsExiter }() exitErr := NewExitError("galactic perimeter breach", 9) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + exitErr2 := NewExitError("last ExitCoder", 11) + err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) HandleExitCoder(err) - expect(t, exitCode, 9) + expect(t, exitCode, 11) expect(t, called, true) } From 7b912d9f8f8a78f192061ed5b0f40918dfda5a07 Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Thu, 17 Nov 2016 16:58:46 +0100 Subject: [PATCH 08/10] allow to load YAML configuration files on Windows The funtion `loadDataFrom` does not take care of Windows users since any of the conditions met and it returns an error. The change looks for the runtime where it's running and then if the filePath contains a `\` --- altsrc/yaml_file_loader.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 335356f..433023d 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -78,6 +78,11 @@ func loadDataFrom(filePath string) ([]byte, error) { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } return ioutil.ReadFile(filePath) + } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { + return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) + } + return ioutil.ReadFile(filePath) } else { return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } From 341ca93b01a20fe6fc361e005b45d968fd559aa5 Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Thu, 17 Nov 2016 17:08:01 +0100 Subject: [PATCH 09/10] fix imports Sorry, forgot to add imports correctly, needed to edit the file and make the commit using the github online editor, since I can't access from my current location from git. --- altsrc/yaml_file_loader.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 433023d..3965fe4 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -11,6 +11,8 @@ import ( "net/http" "net/url" "os" + "runtime" + "strings" "gopkg.in/urfave/cli.v1" From f1be59ff3d239f0942b201619030d302bce912cc Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Sat, 19 Nov 2016 22:37:11 +0100 Subject: [PATCH 10/10] added comment to windows filePath check --- altsrc/yaml_file_loader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 3965fe4..dd808d5 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -81,6 +81,7 @@ func loadDataFrom(filePath string) ([]byte, error) { } return ioutil.ReadFile(filePath) } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + // on Windows systems u.Path is always empty, so we need to check the string directly. if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) }