From 4381738fb5799d9300598562adbb86c0944d8eb6 Mon Sep 17 00:00:00 2001 From: "[[ BOT ]] Lynn Cyrin" Date: Thu, 22 Aug 2019 20:43:52 -0700 Subject: [PATCH 01/36] wip regression test --- app_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app_test.go b/app_test.go index bccede4..a0d17d1 100644 --- a/app_test.go +++ b/app_test.go @@ -1187,6 +1187,43 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { } } +func TestAppRunPassThroughRegression(t *testing.T) { + tdata := []struct { + testCase string + appFlags []Flag + appRunInput []string + appCommands []Command + expectedAnError bool + }{ + { // docker run --rm ubuntu bash + appRunInput: []string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}, + appCommands: []Command{{ + Name: "myCommand", + Flags: []Flag{StringFlag{Name: "someFlag"}}, + }}, + }, + } + for _, test := range tdata { + t.Run(test.testCase, func(t *testing.T) { + // setup + app := NewApp() + app.Flags = test.appFlags + app.Commands = test.appCommands + + // logic under test + err := app.Run(test.appRunInput) + + // assertions + if test.expectedAnError && err == nil { + t.Errorf("expected an error, but there was none") + } + if !test.expectedAnError && err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } + }) + } +} + func TestAppHelpPrinter(t *testing.T) { oldPrinter := HelpPrinter defer func() { From e265f5087fd795a5314286e8e5ec19d91f58f4ae Mon Sep 17 00:00:00 2001 From: "[[ BOT ]] Lynn Cyrin" Date: Thu, 22 Aug 2019 21:22:37 -0700 Subject: [PATCH 02/36] cleanup tests --- app_test.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app_test.go b/app_test.go index a0d17d1..9caa543 100644 --- a/app_test.go +++ b/app_test.go @@ -1189,13 +1189,12 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { func TestAppRunPassThroughRegression(t *testing.T) { tdata := []struct { - testCase string - appFlags []Flag - appRunInput []string - appCommands []Command - expectedAnError bool + testCase string + appRunInput []string + appCommands []Command }{ - { // docker run --rm ubuntu bash + { + testCase: "test_case", appRunInput: []string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}, appCommands: []Command{{ Name: "myCommand", @@ -1207,17 +1206,13 @@ func TestAppRunPassThroughRegression(t *testing.T) { t.Run(test.testCase, func(t *testing.T) { // setup app := NewApp() - app.Flags = test.appFlags app.Commands = test.appCommands // logic under test err := app.Run(test.appRunInput) // assertions - if test.expectedAnError && err == nil { - t.Errorf("expected an error, but there was none") - } - if !test.expectedAnError && err != nil { + if err != nil { t.Errorf("did not expected an error, but there was one: %s", err) } }) From 6e8917398c47b08f8069779668b8767f24abe559 Mon Sep 17 00:00:00 2001 From: "[[ BOT ]] Lynn Cyrin" Date: Thu, 22 Aug 2019 21:23:34 -0700 Subject: [PATCH 03/36] comment out tests --- app_test.go | 62 ++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/app_test.go b/app_test.go index 9caa543..84329ee 100644 --- a/app_test.go +++ b/app_test.go @@ -1187,37 +1187,37 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { } } -func TestAppRunPassThroughRegression(t *testing.T) { - tdata := []struct { - testCase string - appRunInput []string - appCommands []Command - }{ - { - testCase: "test_case", - appRunInput: []string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}, - appCommands: []Command{{ - Name: "myCommand", - Flags: []Flag{StringFlag{Name: "someFlag"}}, - }}, - }, - } - for _, test := range tdata { - t.Run(test.testCase, func(t *testing.T) { - // setup - app := NewApp() - app.Commands = test.appCommands - - // logic under test - err := app.Run(test.appRunInput) - - // assertions - if err != nil { - t.Errorf("did not expected an error, but there was one: %s", err) - } - }) - } -} +// func TestAppRunPassThroughRegression(t *testing.T) { +// tdata := []struct { +// testCase string +// appRunInput []string +// appCommands []Command +// }{ +// { +// testCase: "test_case", +// appRunInput: []string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}, +// appCommands: []Command{{ +// Name: "myCommand", +// Flags: []Flag{StringFlag{Name: "someFlag"}}, +// }}, +// }, +// } +// for _, test := range tdata { +// t.Run(test.testCase, func(t *testing.T) { +// // setup +// app := NewApp() +// app.Commands = test.appCommands + +// // logic under test +// err := app.Run(test.appRunInput) + +// // assertions +// if err != nil { +// t.Errorf("did not expected an error, but there was one: %s", err) +// } +// }) +// } +// } func TestAppHelpPrinter(t *testing.T) { oldPrinter := HelpPrinter From 74cd3bc3fe03403d513c2084c48f7cbdd1d5bba6 Mon Sep 17 00:00:00 2001 From: "[[ BOT ]] Lynn Cyrin" Date: Thu, 22 Aug 2019 21:29:33 -0700 Subject: [PATCH 04/36] cleanup tests --- app_test.go | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/app_test.go b/app_test.go index 84329ee..37950f8 100644 --- a/app_test.go +++ b/app_test.go @@ -1187,37 +1187,22 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { } } -// func TestAppRunPassThroughRegression(t *testing.T) { -// tdata := []struct { -// testCase string -// appRunInput []string -// appCommands []Command -// }{ -// { -// testCase: "test_case", -// appRunInput: []string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}, -// appCommands: []Command{{ -// Name: "myCommand", -// Flags: []Flag{StringFlag{Name: "someFlag"}}, -// }}, -// }, -// } -// for _, test := range tdata { -// t.Run(test.testCase, func(t *testing.T) { -// // setup -// app := NewApp() -// app.Commands = test.appCommands - -// // logic under test -// err := app.Run(test.appRunInput) - -// // assertions -// if err != nil { -// t.Errorf("did not expected an error, but there was one: %s", err) -// } -// }) -// } -// } +func TestRegression(t *testing.T) { + // setup + app := NewApp() + app.Commands = []Command{{ + Name: "myCommand", + Flags: []Flag{StringFlag{Name: "someFlag"}}, + }} + + // logic under test + err := app.Run([]string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}) + + // assertions + if err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } +} func TestAppHelpPrinter(t *testing.T) { oldPrinter := HelpPrinter From 4fb1878febcd7f6e4b8c8b29b5ba9e7dfb77b370 Mon Sep 17 00:00:00 2001 From: "[[ BOT ]] Lynn Cyrin" Date: Thu, 22 Aug 2019 21:42:07 -0700 Subject: [PATCH 05/36] show test failures --- build.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/build.go b/build.go index 2a38f8b..125d25f 100644 --- a/build.go +++ b/build.go @@ -6,12 +6,13 @@ import ( "bufio" "bytes" "fmt" - "github.com/urfave/cli" "io/ioutil" "log" "os" "os/exec" "strings" + + "github.com/urfave/cli" ) var packages = []string{"cli", "altsrc"} @@ -52,7 +53,13 @@ func main() { } func VetActionFunc(_ *cli.Context) error { - return exec.Command("go", "vet").Run() + cmd := exec.Command("go", "vet") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() } func TestActionFunc(c *cli.Context) error { @@ -67,10 +74,13 @@ func TestActionFunc(c *cli.Context) error { coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg) - err := exec.Command( - "go", "test", "-v", coverProfile, packageName, - ).Run() + cmd := exec.Command("go", "test", "-v", coverProfile, packageName) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() if err != nil { return err } @@ -142,16 +152,34 @@ func GfmrunActionFunc(_ *cli.Context) error { return err } - return exec.Command("gfmrun", "-c", fmt.Sprint(counter), "-s", "README.md").Run() + cmd := exec.Command("gfmrun", "-c", fmt.Sprint(counter), "-s", "README.md") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() } func TocActionFunc(_ *cli.Context) error { - err := exec.Command("node_modules/.bin/markdown-toc", "-i", "README.md").Run() + cmd := exec.Command("node_modules/.bin/markdown-toc", "-i", "README.md") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() if err != nil { return err } - err = exec.Command("git", "diff", "--exit-code").Run() + cmd = exec.Command("git", "diff", "--exit-code") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() if err != nil { return err } @@ -160,17 +188,35 @@ func TocActionFunc(_ *cli.Context) error { } func GenActionFunc(_ *cli.Context) error { - err := exec.Command("go", "generate", "flag-gen/main.go").Run() + cmd := exec.Command("go", "generate", "flag-gen/main.go") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() if err != nil { return err } - err = exec.Command("go", "generate", "cli.go").Run() + cmd = exec.Command("go", "generate", "cli.go") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() if err != nil { return err } - err = exec.Command("git", "diff", "--exit-code").Run() + cmd = exec.Command("git", "diff", "--exit-code") + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() if err != nil { return err } From 15dc34ea12041906545f1466fd1af97c8f97ab69 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 08:54:06 -0700 Subject: [PATCH 06/36] more accurate regression reproduction --- app_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app_test.go b/app_test.go index 37950f8..2006bb8 100644 --- a/app_test.go +++ b/app_test.go @@ -1191,12 +1191,17 @@ func TestRegression(t *testing.T) { // setup app := NewApp() app.Commands = []Command{{ - Name: "myCommand", - Flags: []Flag{StringFlag{Name: "someFlag"}}, + Name: "command", + Flags: []Flag{ + StringFlag{ + Name: "flagone", + }, + }, + Action: func(c *Context) error { return nil }, }} // logic under test - err := app.Run([]string{"myCLI", "myCommand", "--someFlag", "someInput", "docker", "run", "--rm", "ubuntu", "bash"}) + err := app.Run([]string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}) // assertions if err != nil { From c542fb3bed0cbf052655db02a80ea97f03383421 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 09:04:51 -0700 Subject: [PATCH 07/36] temp remove --- app_test.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app_test.go b/app_test.go index 2006bb8..bccede4 100644 --- a/app_test.go +++ b/app_test.go @@ -1187,28 +1187,6 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { } } -func TestRegression(t *testing.T) { - // setup - app := NewApp() - app.Commands = []Command{{ - Name: "command", - Flags: []Flag{ - StringFlag{ - Name: "flagone", - }, - }, - Action: func(c *Context) error { return nil }, - }} - - // logic under test - err := app.Run([]string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}) - - // assertions - if err != nil { - t.Errorf("did not expected an error, but there was one: %s", err) - } -} - func TestAppHelpPrinter(t *testing.T) { oldPrinter := HelpPrinter defer func() { From b0ed044b01bed3c0e940840aeb9d314ffa117330 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 09:16:41 -0700 Subject: [PATCH 08/36] add regression test to its own file --- app_regression_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app_regression_test.go diff --git a/app_regression_test.go b/app_regression_test.go new file mode 100644 index 0000000..91e65aa --- /dev/null +++ b/app_regression_test.go @@ -0,0 +1,29 @@ +package cli + +import ( + "testing" +) + +// TestRegression tests a regression that was merged between versions 1.20.0 and 1.21.0 +// The included app.Run line worked in 1.20.0, and then was broken in 1.21.0. +func TestRegression(t *testing.T) { + // setup + app := NewApp() + app.Commands = []Command{{ + Name: "command", + Flags: []Flag{ + StringFlag{ + Name: "flagone", + }, + }, + Action: func(c *Context) error { return nil }, + }} + + // logic under test + err := app.Run([]string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}) + + // assertions + if err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } +} From 192ce003d9a36d0002b487f8d725e520f1db51cc Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 09:17:16 -0700 Subject: [PATCH 09/36] expand name --- app_regression_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_regression_test.go b/app_regression_test.go index 91e65aa..1dff458 100644 --- a/app_regression_test.go +++ b/app_regression_test.go @@ -6,7 +6,7 @@ import ( // TestRegression tests a regression that was merged between versions 1.20.0 and 1.21.0 // The included app.Run line worked in 1.20.0, and then was broken in 1.21.0. -func TestRegression(t *testing.T) { +func TestVersionOneTwoOneRegression(t *testing.T) { // setup app := NewApp() app.Commands = []Command{{ From 2172d382b6adcde4289a1ffcad9797c2f880da35 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 10:18:00 -0700 Subject: [PATCH 10/36] update tests --- app_regression_test.go | 49 +++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/app_regression_test.go b/app_regression_test.go index 1dff458..dde2a1a 100644 --- a/app_regression_test.go +++ b/app_regression_test.go @@ -6,24 +6,43 @@ import ( // TestRegression tests a regression that was merged between versions 1.20.0 and 1.21.0 // The included app.Run line worked in 1.20.0, and then was broken in 1.21.0. +// Relevant PR: https://github.com/urfave/cli/pull/872 func TestVersionOneTwoOneRegression(t *testing.T) { - // setup - app := NewApp() - app.Commands = []Command{{ - Name: "command", - Flags: []Flag{ - StringFlag{ - Name: "flagone", - }, + testData := []struct { + testCase string + appRunInput []string + }{ + // assertion: empty input, when a required flag is present, errors + { + testCase: "with_dash_dash", + appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, }, - Action: func(c *Context) error { return nil }, - }} + { + testCase: "without_dash_dash", + appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, + }, + } + for _, test := range testData { + t.Run(test.testCase, func(t *testing.T) { + // setup + app := NewApp() + app.Commands = []Command{{ + Name: "command", + Flags: []Flag{ + StringFlag{ + Name: "flagone", + }, + }, + Action: func(c *Context) error { return nil }, + }} - // logic under test - err := app.Run([]string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}) + // logic under test + err := app.Run(test.appRunInput) - // assertions - if err != nil { - t.Errorf("did not expected an error, but there was one: %s", err) + // assertions + if err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } + }) } } From 810b9714d3eb5b8cd41a62527c39fb2730c6d151 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 10:34:15 -0700 Subject: [PATCH 11/36] cleanup --- app_regression_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app_regression_test.go b/app_regression_test.go index dde2a1a..2299df5 100644 --- a/app_regression_test.go +++ b/app_regression_test.go @@ -12,7 +12,6 @@ func TestVersionOneTwoOneRegression(t *testing.T) { testCase string appRunInput []string }{ - // assertion: empty input, when a required flag is present, errors { testCase: "with_dash_dash", appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, From 0f9d8a9abdbb755069f45e89d90b3e06499302c2 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 11:14:20 -0700 Subject: [PATCH 12/36] expand test cases --- app_regression_test.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app_regression_test.go b/app_regression_test.go index 2299df5..3c8681b 100644 --- a/app_regression_test.go +++ b/app_regression_test.go @@ -9,24 +9,36 @@ import ( // Relevant PR: https://github.com/urfave/cli/pull/872 func TestVersionOneTwoOneRegression(t *testing.T) { testData := []struct { - testCase string - appRunInput []string + testCase string + appRunInput []string + skipArgReorder bool }{ { testCase: "with_dash_dash", appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, }, + { + testCase: "with_dash_dash_and_skip_reorder", + appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, + skipArgReorder: true, + }, { testCase: "without_dash_dash", appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, }, + { + testCase: "without_dash_dash_and_skip_reorder", + appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, + skipArgReorder: true, + }, } for _, test := range testData { t.Run(test.testCase, func(t *testing.T) { // setup app := NewApp() app.Commands = []Command{{ - Name: "command", + Name: "command", + SkipArgReorder: test.skipArgReorder, Flags: []Flag{ StringFlag{ Name: "flagone", From 406a1c31b08754a4553634c1fbb0caaed22865b1 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 25 Aug 2019 14:12:13 -0700 Subject: [PATCH 13/36] update vars and args --- command.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/command.go b/command.go index 44a90de..37387d1 100644 --- a/command.go +++ b/command.go @@ -190,7 +190,7 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { } if !c.SkipArgReorder { - args = reorderArgs(args) + args = reorderArgs(c.Flags, args) } set, err := parseIter(c, args) @@ -214,34 +214,36 @@ func (c *Command) useShortOptionHandling() bool { return c.UseShortOptionHandling } -// reorderArgs moves all flags before arguments as this is what flag expects -func reorderArgs(args []string) []string { - var nonflags, flags []string +// reorderArgs moves all flags (reorderedFlags) before arguments (remainingArgs) +// as this is what flag expects. +func reorderArgs(commandFlags []Flag, args []string) []string { + var remainingArgs []string + var reorderedFlags []string readFlagValue := false for i, arg := range args { if arg == "--" { - nonflags = append(nonflags, args[i:]...) + remainingArgs = append(remainingArgs, args[i:]...) break } if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { readFlagValue = false - flags = append(flags, arg) + reorderedFlags = append(reorderedFlags, arg) continue } readFlagValue = false if arg != "-" && strings.HasPrefix(arg, "-") { - flags = append(flags, arg) + reorderedFlags = append(reorderedFlags, arg) readFlagValue = !strings.Contains(arg, "=") } else { - nonflags = append(nonflags, arg) + remainingArgs = append(remainingArgs, arg) } } - return append(flags, nonflags...) + return append(reorderedFlags, remainingArgs...) } // Names returns the names including short names and aliases. From 2071bcfb8328cb13e5575a586f06a33563b04054 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Mon, 26 Aug 2019 00:18:36 -0700 Subject: [PATCH 14/36] checkpoint --- command.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/command.go b/command.go index 37387d1..d33435e 100644 --- a/command.go +++ b/command.go @@ -214,14 +214,18 @@ func (c *Command) useShortOptionHandling() bool { return c.UseShortOptionHandling } -// reorderArgs moves all flags (reorderedFlags) before arguments (remainingArgs) -// as this is what flag expects. +// reorderArgs moves all flags (via reorderedArgs) before the rest of +// the arguments (remainingArgs) as this is what flag expects. func reorderArgs(commandFlags []Flag, args []string) []string { var remainingArgs []string - var reorderedFlags []string + var reorderedArgs []string readFlagValue := false for i, arg := range args { + + // dont reorder any args after a -- + // read about -- here: + // https://unix.stackexchange.com/questions/11376/what-does-double-dash-mean-also-known-as-bare-double-dash if arg == "--" { remainingArgs = append(remainingArgs, args[i:]...) break @@ -229,21 +233,22 @@ func reorderArgs(commandFlags []Flag, args []string) []string { if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { readFlagValue = false - reorderedFlags = append(reorderedFlags, arg) + reorderedArgs = append(reorderedArgs, arg) continue } readFlagValue = false if arg != "-" && strings.HasPrefix(arg, "-") { - reorderedFlags = append(reorderedFlags, arg) + reorderedArgs = append(reorderedArgs, arg) readFlagValue = !strings.Contains(arg, "=") - } else { - remainingArgs = append(remainingArgs, arg) + continue } + + remainingArgs = append(remainingArgs, arg) } - return append(reorderedFlags, remainingArgs...) + return append(reorderedArgs, remainingArgs...) } // Names returns the names including short names and aliases. From 7d0751fa1309a01b405cde327cc43275d13016a2 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 19:25:51 -0700 Subject: [PATCH 15/36] add argIsFlag check --- command.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index d33435e..6471f10 100644 --- a/command.go +++ b/command.go @@ -238,7 +238,7 @@ func reorderArgs(commandFlags []Flag, args []string) []string { } readFlagValue = false - if arg != "-" && strings.HasPrefix(arg, "-") { + if arg != "-" && strings.HasPrefix(arg, "-") && argIsFlag(commandFlags, arg) { reorderedArgs = append(reorderedArgs, arg) readFlagValue = !strings.Contains(arg, "=") @@ -251,6 +251,18 @@ func reorderArgs(commandFlags []Flag, args []string) []string { return append(reorderedArgs, remainingArgs...) } +func argIsFlag(commandFlags []Flag, arg string) bool { + strippedArg := strings.ReplaceAll(arg, "-", "") + for _, flag := range commandFlags { + for _, key := range strings.Split(flag.GetName(), ",") { + if key == strippedArg { + return true + } + } + } + return false +} + // Names returns the names including short names and aliases. func (c Command) Names() []string { names := []string{c.Name} From 49dbeed6877737d97ec6e121763a41c617d7b272 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 20:24:42 -0700 Subject: [PATCH 16/36] handle `=` input --- command.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 6471f10..d52080e 100644 --- a/command.go +++ b/command.go @@ -252,10 +252,11 @@ func reorderArgs(commandFlags []Flag, args []string) []string { } func argIsFlag(commandFlags []Flag, arg string) bool { - strippedArg := strings.ReplaceAll(arg, "-", "") + arg = strings.ReplaceAll(arg, "-", "") + arg = strings.Split(arg, "=")[0] for _, flag := range commandFlags { for _, key := range strings.Split(flag.GetName(), ",") { - if key == strippedArg { + if key == arg { return true } } From 10682fbde6f00b1008d6748d7ffb090bce421da7 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 20:28:48 -0700 Subject: [PATCH 17/36] docs --- command.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/command.go b/command.go index d52080e..af91dbc 100644 --- a/command.go +++ b/command.go @@ -252,8 +252,11 @@ func reorderArgs(commandFlags []Flag, args []string) []string { } func argIsFlag(commandFlags []Flag, arg string) bool { + // this line turns `--flag` into `flag` arg = strings.ReplaceAll(arg, "-", "") + // this line turns `flag=value` into `flag` arg = strings.Split(arg, "=")[0] + // look through all the flags, to see if the `arg` is one of our flags for _, flag := range commandFlags { for _, key := range strings.Split(flag.GetName(), ",") { if key == arg { From 09cdbbfe281fd79e92616a28f1fdf701e9007c22 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 20:29:31 -0700 Subject: [PATCH 18/36] more docs --- command.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command.go b/command.go index af91dbc..6e09a32 100644 --- a/command.go +++ b/command.go @@ -264,6 +264,7 @@ func argIsFlag(commandFlags []Flag, arg string) bool { } } } + // return false if this arg was not one of our flags return false } From 223e21796c35c3f4a456db83c0d450ef5fe4db01 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 21:08:52 -0700 Subject: [PATCH 19/36] swap a test case --- command_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command_test.go b/command_test.go index 56698fe..47792e1 100644 --- a/command_test.go +++ b/command_test.go @@ -18,7 +18,7 @@ func TestCommandFlagParsing(t *testing.T) { UseShortOptionHandling bool }{ // Test normal "not ignoring flags" flow - {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false}, + {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, nil, false}, // Test no arg reorder {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, From 6da413ad825dea1f74563bdfee1f8e5c23c1c650 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 22:45:51 -0700 Subject: [PATCH 20/36] fix go version support? --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index 6e09a32..0a6b6ed 100644 --- a/command.go +++ b/command.go @@ -253,7 +253,7 @@ func reorderArgs(commandFlags []Flag, args []string) []string { func argIsFlag(commandFlags []Flag, arg string) bool { // this line turns `--flag` into `flag` - arg = strings.ReplaceAll(arg, "-", "") + arg = strings.Replace(arg, "-", "", -1) // this line turns `flag=value` into `flag` arg = strings.Split(arg, "=")[0] // look through all the flags, to see if the `arg` is one of our flags From bfdd794eb3d0f8d99af8939a3a8c24547ef262ca Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 11 Sep 2019 23:05:26 -0700 Subject: [PATCH 21/36] docs --- command.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/command.go b/command.go index 0a6b6ed..3993e0f 100644 --- a/command.go +++ b/command.go @@ -220,7 +220,7 @@ func reorderArgs(commandFlags []Flag, args []string) []string { var remainingArgs []string var reorderedArgs []string - readFlagValue := false + nextIndexMayContainValue := false for i, arg := range args { // dont reorder any args after a -- @@ -229,28 +229,30 @@ func reorderArgs(commandFlags []Flag, args []string) []string { if arg == "--" { remainingArgs = append(remainingArgs, args[i:]...) break - } - if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { - readFlagValue = false + // checks if this arg is a value that should be re-ordered next to its associated flag + } else if nextIndexMayContainValue && !strings.HasPrefix(arg, "-") { + nextIndexMayContainValue = false reorderedArgs = append(reorderedArgs, arg) - continue - } - readFlagValue = false - if arg != "-" && strings.HasPrefix(arg, "-") && argIsFlag(commandFlags, arg) { + // checks if this is an arg that should be re-ordered + } else if arg != "-" && strings.HasPrefix(arg, "-") && argIsFlag(commandFlags, arg) { + + // we have determined that this is a flag that we should re-order reorderedArgs = append(reorderedArgs, arg) + // if this arg does not contain a "=", then the next index may contain the value for this flag + nextIndexMayContainValue = !strings.Contains(arg, "=") - readFlagValue = !strings.Contains(arg, "=") - continue + // simply append any remaining args + } else { + remainingArgs = append(remainingArgs, arg) } - - remainingArgs = append(remainingArgs, arg) } return append(reorderedArgs, remainingArgs...) } +// argIsFlag checks if an arg is one of our command flags func argIsFlag(commandFlags []Flag, arg string) bool { // this line turns `--flag` into `flag` arg = strings.Replace(arg, "-", "", -1) From 3f6f97754aa8af1b6bd7b9324bf1e4c72023a269 Mon Sep 17 00:00:00 2001 From: "Lynn Cyrin (they/them)" Date: Fri, 13 Sep 2019 07:27:16 -0700 Subject: [PATCH 22/36] Update command.go Co-Authored-By: Sascha Grunert --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index 3993e0f..037abe9 100644 --- a/command.go +++ b/command.go @@ -217,7 +217,7 @@ func (c *Command) useShortOptionHandling() bool { // reorderArgs moves all flags (via reorderedArgs) before the rest of // the arguments (remainingArgs) as this is what flag expects. func reorderArgs(commandFlags []Flag, args []string) []string { - var remainingArgs []string + var remainingArgs, reorderedArgs []string var reorderedArgs []string nextIndexMayContainValue := false From 72c249389c590a6817f21cf21c68bc0a076823bf Mon Sep 17 00:00:00 2001 From: "Lynn Cyrin (they/them)" Date: Fri, 13 Sep 2019 07:35:16 -0700 Subject: [PATCH 23/36] Update command.go --- command.go | 1 - 1 file changed, 1 deletion(-) diff --git a/command.go b/command.go index 037abe9..9724a3b 100644 --- a/command.go +++ b/command.go @@ -218,7 +218,6 @@ func (c *Command) useShortOptionHandling() bool { // the arguments (remainingArgs) as this is what flag expects. func reorderArgs(commandFlags []Flag, args []string) []string { var remainingArgs, reorderedArgs []string - var reorderedArgs []string nextIndexMayContainValue := false for i, arg := range args { From 43f9b3ff1cf349db21bd029d4c58e457cb91a82f Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 15 Sep 2019 20:41:43 -0700 Subject: [PATCH 24/36] setup versioned docs --- README.md | 1522 +---------------------- CONTRIBUTING.md => docs/CONTRIBUTING.md | 0 CHANGELOG.md => docs/v1/CHANGELOG.md | 0 docs/v1/manual.md | 1478 ++++++++++++++++++++++ docs/v2/.keep | 0 5 files changed, 1485 insertions(+), 1515 deletions(-) rename CONTRIBUTING.md => docs/CONTRIBUTING.md (100%) rename CHANGELOG.md => docs/v1/CHANGELOG.md (100%) create mode 100644 docs/v1/manual.md create mode 100644 docs/v2/.keep diff --git a/README.md b/README.md index 96720b6..a15c38f 100644 --- a/README.md +++ b/README.md @@ -13,60 +13,12 @@ cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. - - -- [Overview](#overview) -- [Installation](#installation) - * [Supported platforms](#supported-platforms) - * [Using the `v2` branch](#using-the-v2-branch) - * [Using `v1` releases](#using-v1-releases) -- [Getting Started](#getting-started) -- [Examples](#examples) - * [Arguments](#arguments) - * [Flags](#flags) - + [Placeholder Values](#placeholder-values) - + [Alternate Names](#alternate-names) - + [Ordering](#ordering) - + [Values from the Environment](#values-from-the-environment) - + [Values from files](#values-from-files) - + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) - + [Precedence](#precedence) - * [Subcommands](#subcommands) - * [Subcommands categories](#subcommands-categories) - * [Exit code](#exit-code) - * [Combining short options](#combining-short-options) - * [Bash Completion](#bash-completion) - + [Enabling](#enabling) - + [Distribution](#distribution) - + [Customization](#customization) - * [Generated Help Text](#generated-help-text) - + [Customization](#customization-1) - * [Version Flag](#version-flag) - + [Customization](#customization-2) - + [Full API Example](#full-api-example) -- [Contribution Guidelines](#contribution-guidelines) - - - -## Overview - -Command line apps are usually so tiny that there is absolutely no reason why -your code should *not* be self-documenting. Things like generating help text and -parsing command flags/options should not hinder productivity when writing a -command line app. - -**This is where cli comes into play.** cli makes command line programming fun, -organized, and expressive! - ## Installation Make sure you have a working Go environment. Go version 1.10+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html). -To install cli, simply run: -``` -$ go get github.com/urfave/cli -``` +### GOPATH Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can be easily used: @@ -80,30 +32,6 @@ cli is tested against multiple versions of Go on Linux, and against the latest released version of Go on OS X and Windows. For full details, see [`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml). -### Using the `v2` branch - -**Warning**: The `v2` branch is currently unreleased and considered unstable. - -There is currently a long-lived branch named `v2` that is intended to land as -the new `master` branch once development there has settled down. The current -`master` branch (mirrored as `v1`) is being manually merged into `v2` on -an irregular human-based schedule, but generally if one wants to "upgrade" to -`v2` *now* and accept the volatility (read: "awesomeness") that comes along with -that, please use whatever version pinning of your preference, such as via -`gopkg.in`: - -``` -$ go get gopkg.in/urfave/cli.v2 -``` - -``` go -... -import ( - "gopkg.in/urfave/cli.v2" // imports as package "cli" -) -... -``` - ### Using `v1` releases ``` @@ -118,1454 +46,18 @@ import ( ... ``` +### Using `v2` releases -## Getting Started - -One of the philosophies behind cli is that an API should be playful and full of -discovery. So a cli app can be as little as one line of code in `main()`. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -This app will run and show help text, but is not very useful. Let's give an -action to execute and some help documentation: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Running this already gives you a ton of functionality, plus support for things -like subcommands and flags, which are covered below. - -## Examples - -Being a programmer can be a lonely job. Thankfully by the power of automation -that is not the case! Let's create a greeter app to fend off our demons of -loneliness! - -Start by creating a directory named `greet`, and within it, add a file, -`greet.go` with the following code in it: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "greet" - app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Install our command to the `$GOPATH/bin` directory: - -``` -$ go install -``` - -Finally run our new command: - -``` -$ greet -Hello friend! -``` - -cli also generates neat help text: - -``` -$ greet help -NAME: - greet - fight the loneliness! - -USAGE: - greet [global options] command [command options] [arguments...] - -VERSION: - 0.0.0 - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --version Shows version information -``` - -### Arguments - -You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Action = func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Flags - -Setting and querying flags is simple. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - } - - app.Action = func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -You can also set a destination variable for a flag, to which the content will be -scanned. - - -``` go -package main - -import ( - "log" - "os" - "fmt" - - "github.com/urfave/cli" -) - -func main() { - var language string - - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - } - - app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -See full list of flags at http://godoc.org/github.com/urfave/cli - -#### Placeholder Values - -Sometimes it's useful to specify a flag's value within the usage string itself. -Such placeholders are indicated with back quotes. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE -``` - -Note that only the first placeholder is used. Subsequent back-quoted words will -be left as-is. - -#### Alternate Names - -You can set alternate (or short) names for flags by providing a comma-delimited -list for the `Name`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -That flag can then be set with `--lang spanish` or `-l spanish`. Note that -giving two different forms of the same flag in the same command invocation is an -error. - -#### Ordering - -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` -or `CommandsByName` with `sort`. - -For example this: - - -``` go -package main - -import ( - "log" - "os" - "sort" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "Language for the greeting", - }, - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - 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)) - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE ---lang value, -l value Language for the greeting (default: "english") -``` - -#### Values from the Environment - -You can also have the default value set from the environment via `EnvVar`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -The `EnvVar` may also be given as a comma-delimited "cascade", where the first -environment variable that resolves is used as the default. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Values from files - -You can also have the default value set from file via `FilePath`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "password, p", - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the environment (e.g. `EnvVar`). - -#### Values from alternate input sources (YAML, TOML, and others) - -There is a separate package altsrc that adds support for getting flag values -from other file input sources. - -Currently supported input source formats: -* YAML -* JSON -* TOML - -In order to get values for a flag from an alternate input source the following -code would be added to wrap an existing cli.Flag like below: - -``` go - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) -``` - -Initialization must also occur for these flags. Below is an example initializing -getting data from a yaml file below. - -``` go - command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) -``` - -The code above will use the "load" string as a flag name to get the file name of -a yaml file from the cli.Context. It will then use that file name to initialize -the yaml input source for any flags that are defined on that command. As a note -the "load" flag used would also have to be defined on the command flags in order -for this code snipped to work. - -Currently only YAML, JSON, and TOML files are supported but developers can add support -for other input sources by implementing the altsrc.InputSourceContext for their -given sources. - -Here is a more complete sample of a command using YAML support: - - -``` go -package notmain - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" - "github.com/urfave/cli/altsrc" -) - -func main() { - app := cli.NewApp() - - flags := []cli.Flag{ - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}, - } - - app.Action = func(c *cli.Context) error { - fmt.Println("yaml ist rad") - return nil - } - - app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) - app.Flags = flags +**Warning**: `v2` is in a pre-release state. - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} ``` - -#### Precedence - -The precedence for flag value sources is as follows (highest to lowest): - -0. Command line flag value from user -0. Environment variable (if specified) -0. Configuration file (if specified) -0. Default defined on the flag - -### Subcommands - -Subcommands can be defined for a more git-like command line app. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} +$ go get gopkg.in/urfave/cli.v2 ``` -### Subcommands categories - -For additional organization in apps that have many subcommands, you can -associate a category for each command to group them together in the help -output. - -E.g. - ```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "Template actions", - }, - { - Name: "remove", - Category: "Template actions", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will include: - -``` -COMMANDS: - noop - - Template actions: - add - remove -``` - -### Exit code - -Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. An explicit exit code -may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Flags = []cli.Flag{ - cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "Add ginger croutons to the soup", - }, - } - app.Action = func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("Ginger croutons are not in the soup", 86) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Combining short options - -Traditional use of options using their shortnames look like this: - -``` -$ cmd -s -o -m "Some message" -``` - -Suppose you want users to be able to combine options with their shortnames. This -can be done using the `UseShortOptionHandling` bool in your app configuration, -or for individual commands by attaching it to the command configuration. For -example: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.UseShortOptionHandling = true - app.Commands = []cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "serve, s"}, - cli.BoolFlag{Name: "option, o"}, - cli.StringFlag{Name: "message, m"}, - }, - Action: func(c *cli.Context) error { - fmt.Println("serve:", c.Bool("serve")) - fmt.Println("option:", c.Bool("option")) - fmt.Println("message:", c.String("message")) - return nil - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If your program has any number of bool flags such as `serve` and `option`, and -optionally one non-bool flag `message`, with the short options of `-s`, `-o`, -and `-m` respectively, setting `UseShortOptionHandling` will also support the -following syntax: - -``` -$ cmd -som "Some message" -``` - -If you enable `UseShortOptionHandling`, then you must not use any flags that -have a single leading `-` or this will result in failures. For example, -`-option` can no longer be used. Flags with two leading dashes (such as -`--options`) are still valid. - -### Bash Completion - -You can enable completion commands by setting the `EnableBashCompletion` -flag on the `App` object. By default, this setting will only auto-complete to -show an app's subcommands, but you can write your own completion methods for -the App or its subcommands. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Enabling - -Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while -setting the `PROG` variable to the name of your program: - -`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` - -#### Distribution - -Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename -it to the name of the program you wish to add autocomplete support for (or -automatically install it there if you are distributing a package). Don't forget -to source the file to make it active in the current shell. - -``` -sudo cp src/bash_autocomplete /etc/bash_completion.d/ -source /etc/bash_completion.d/ -``` - -Alternatively, you can just document that users should source the generic -`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set -to the name of their program (as above). - -#### Customization - -The default bash completion flag (`--generate-bash-completion`) is defined as -`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.BashCompletionFlag = cli.BoolFlag{ - Name: "compgen", - Hidden: true, - } - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "wat", - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Generated Help Text - -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked -by the cli internals in order to print generated help text for the app, command, -or subcommand, and break execution. - -#### Customization - -All of the help text generation may be customized, and at multiple levels. The -templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and -`SubcommandHelpTemplate` which may be reassigned or augmented, and full override -is possible by assigning a compatible func to the `cli.HelpPrinter` variable, -e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "io" - "os" - - "github.com/urfave/cli" -) - -func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s - -WEBSITE: http://awesometown.example.com - -SUPPORT: support@awesometown.example.com - -`, cli.AppHelpTemplate) - - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} -USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if len .Authors}} -AUTHOR: - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} -COPYRIGHT: - {{.Copyright}} - {{end}}{{if .Version}} -VERSION: - {{.Version}} - {{end}} -` - - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } - - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.HelpFlag = cli.BoolFlag{ - Name: "halp, haaaaalp", - Usage: "HALP", - EnvVar: "SHOW_HALP,HALPPLZ", - } - - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Version Flag - -The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which -is checked by the cli internals in order to print the `App.Version` via -`cli.VersionPrinter` and break execution. - -#### Customization - -The default flag may be customized to something other than `-v/--version` by -setting `cli.VersionFlag`, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.VersionFlag = cli.BoolFlag{ - Name: "print-version, V", - Usage: "print only the version", - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Full API Example - -**Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. - - -``` go -package main - +... import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" - - "github.com/urfave/cli" + "github.com/urfave/cli" // imports as package "cli" ) - -func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" - - cli.HelpFlag = cli.BoolFlag{Name: "halp"} - cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} - cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} - - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) - } - cli.OsExiter = func(c int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.GetName()) - } -} - -type hexWriter struct{} - -func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") - - return len(p), nil -} - -type genericType struct{ - s string -} - -func (g *genericType) Set(value string) error { - g.s = value - return nil -} - -func (g *genericType) String() string { - return g.s -} - -func main() { - app := cli.NewApp() - app.Name = "kənˈtrīv" - app.Version = "19.99.0" - app.Compiled = time.Now() - app.Authors = []cli.Author{ - cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - } - app.Copyright = "(c) 1999 Serious Enterprise" - app.HelpName = "contrive" - app.Usage = "demonstrate available API" - app.UsageText = "contrive - demonstrating the available API" - app.ArgsUsage = "[args and such]" - app.Commands = []cli.Command{ - cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "forever, forevvarr"}, - }, - Subcommands: cli.Commands{ - cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, - }, - } - app.Flags = []cli.Flag{ - cli.BoolFlag{Name: "fancy"}, - cli.BoolTFlag{Name: "fancier"}, - cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, - cli.Float64Flag{Name: "howmuch"}, - cli.GenericFlag{Name: "wat", Value: &genericType{}}, - cli.Int64Flag{Name: "longdistance"}, - cli.Int64SliceFlag{Name: "intervals"}, - cli.IntFlag{Name: "distance"}, - cli.IntSliceFlag{Name: "times"}, - cli.StringFlag{Name: "dance-move, d"}, - cli.StringSliceFlag{Name: "names, N"}, - cli.UintFlag{Name: "age"}, - cli.Uint64Flag{Name: "bigage"}, - } - app.EnableBashCompletion = true - app.UseShortOptionHandling = true - app.HideHelp = false - app.HideVersion = false - app.BashComplete = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - } - app.Before = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - } - app.After = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - } - app.CommandNotFound = func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - } - app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } - - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - } - app.Action = func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) - - categories := c.App.Categories() - categories.AddCommand("sounds", cli.Command{ - Name: "bloop", - }) - - for _, category := range c.App.Categories() { - fmt.Fprintf(c.App.Writer, "%s\n", category.Name) - fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) - fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) - } - - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } - - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) - - fmt.Printf("%#v\n", c.Args().First()) - if len(c.Args()) > 0 { - fmt.Printf("%#v\n", c.Args()[1]) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) - - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) - - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", nc.BoolT("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) - fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) - fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) - fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) - fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) - fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) - fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) - fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) - fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) - fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) - - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.GlobalFlagNames()) - fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) - fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Parent()) - - nc.Set("wat", "also-nope") - - ec := cli.NewExitError("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return nil - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Metadata = map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - } - - - // ignore error so we don't exit non-zero and break gfmrun README example tests - _ = app.Run(os.Args) -} - -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil -} +... ``` - -## Contribution Guidelines - -See [./CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md diff --git a/CHANGELOG.md b/docs/v1/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to docs/v1/CHANGELOG.md diff --git a/docs/v1/manual.md b/docs/v1/manual.md new file mode 100644 index 0000000..ca6a6c9 --- /dev/null +++ b/docs/v1/manual.md @@ -0,0 +1,1478 @@ +cli v1 manual +=== + + + +- [Getting Started](#getting-started) +- [Examples](#examples) + * [Arguments](#arguments) + * [Flags](#flags) + + [Placeholder Values](#placeholder-values) + + [Alternate Names](#alternate-names) + + [Ordering](#ordering) + + [Values from the Environment](#values-from-the-environment) + + [Values from files](#values-from-files) + + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Precedence](#precedence) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Combining short options](#combining-short-options) + * [Bash Completion](#bash-completion) + + [Enabling](#enabling) + + [Distribution](#distribution) + + [Customization](#customization) + * [Generated Help Text](#generated-help-text) + + [Customization](#customization-1) + * [Version Flag](#version-flag) + + [Customization](#customization-2) + + [Full API Example](#full-api-example) + + + +## Getting Started + +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Action = func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. + +## Examples + +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "greet" + app.Usage = "fight the loneliness!" + app.Action = func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +cli also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` + +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Action = func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Flags + +Setting and querying flags is simple. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Action = func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +``` go +package main + +import ( + "log" + "os" + "fmt" + + "github.com/urfave/cli" +) + +func main() { + var language string + + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + } + + app.Action = func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +See full list of flags at http://godoc.org/github.com/urfave/cli + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +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` +or `CommandsByName` with `sort`. + +For example this: + + +``` go +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + 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)) + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +The `EnvVar` may also be given as a comma-delimited "cascade", where the first +environment variable that resolves is used as the default. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: +* YAML +* JSON +* TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snipped to work. + +Currently only YAML, JSON, and TOML files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package notmain + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" + "github.com/urfave/cli/altsrc" +) + +func main() { + app := cli.NewApp() + + flags := []cli.Flag{ + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}, + } + + app.Action = func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + } + + app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) + app.Flags = flags + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag + +### Subcommands + +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "Template actions", + }, + { + Name: "remove", + Category: "Template actions", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` + +### Exit code + +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "Add ginger croutons to the soup", + }, + } + app.Action = func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.NewExitError("Ginger croutons are not in the soup", 86) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Combining short options + +Traditional use of options using their shortnames look like this: + +``` +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.UseShortOptionHandling = true + app.Commands = []cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "serve, s"}, + cli.BoolFlag{Name: "option, o"}, + cli.StringFlag{Name: "message, m"}, + }, + Action: func(c *cli.Context) error { + fmt.Println("serve:", c.Bool("serve")) + fmt.Println("option:", c.Bool("option")) + fmt.Println("message:", c.String("message")) + return nil + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +``` +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Enabling + +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: + +`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` + +#### Distribution + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +#### Customization + +The default bash completion flag (`--generate-bash-completion`) is defined as +`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.BashCompletionFlag = cli.BoolFlag{ + Name: "compgen", + Hidden: true, + } + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "wat", + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "io" + "os" + + "github.com/urfave/cli" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.HelpFlag = cli.BoolFlag{ + Name: "halp, haaaaalp", + Usage: "HALP", + EnvVar: "SHOW_HALP,HALPPLZ", + } + + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.VersionFlag = cli.BoolFlag{ + Name: "print-version, V", + Usage: "print only the version", + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = cli.BoolFlag{Name: "halp"} + cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} + cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.GetName()) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct{ + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := cli.NewApp() + app.Name = "kənˈtrīv" + app.Version = "19.99.0" + app.Compiled = time.Now() + app.Authors = []cli.Author{ + cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + } + app.Copyright = "(c) 1999 Serious Enterprise" + app.HelpName = "contrive" + app.Usage = "demonstrate available API" + app.UsageText = "contrive - demonstrating the available API" + app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "forever, forevvarr"}, + }, + Subcommands: cli.Commands{ + cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + } + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancier"}, + cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + cli.Float64Flag{Name: "howmuch"}, + cli.GenericFlag{Name: "wat", Value: &genericType{}}, + cli.Int64Flag{Name: "longdistance"}, + cli.Int64SliceFlag{Name: "intervals"}, + cli.IntFlag{Name: "distance"}, + cli.IntSliceFlag{Name: "times"}, + cli.StringFlag{Name: "dance-move, d"}, + cli.StringSliceFlag{Name: "names, N"}, + cli.UintFlag{Name: "age"}, + cli.Uint64Flag{Name: "bigage"}, + } + app.EnableBashCompletion = true + app.UseShortOptionHandling = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + } + app.Before = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + } + app.After = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + } + app.CommandNotFound = func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + } + app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + } + app.Action = func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories() + categories.AddCommand("sounds", cli.Command{ + Name: "bloop", + }) + + for _, category := range c.App.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if len(c.Args()) > 0 { + fmt.Printf("%#v\n", c.Args()[1]) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", nc.BoolT("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) + fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) + fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) + fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) + fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) + fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) + fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) + fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) + fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.GlobalFlagNames()) + fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) + fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Parent()) + + nc.Set("wat", "also-nope") + + ec := cli.NewExitError("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return nil + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Metadata = map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + } + + + // ignore error so we don't exit non-zero and break gfmrun README example tests + _ = app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` diff --git a/docs/v2/.keep b/docs/v2/.keep new file mode 100644 index 0000000..e69de29 From ebc25651f0fc5d51acf8182565706d670edff596 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 15 Sep 2019 21:21:52 -0700 Subject: [PATCH 26/36] add filename handling --- build.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/build.go b/build.go index a828ae4..a78ded3 100644 --- a/build.go +++ b/build.go @@ -127,8 +127,13 @@ func testCleanup() error { return nil } -func GfmrunActionFunc(_ *cli.Context) error { - file, err := os.Open("README.md") +func GfmrunActionFunc(c *cli.Context) error { + filename := c.Args().Get(0) + if filename == "" { + filename = "README.md" + } + + file, err := os.Open(filename) if err != nil { return err } @@ -146,11 +151,16 @@ func GfmrunActionFunc(_ *cli.Context) error { return err } - return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", "README.md") + return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename) } -func TocActionFunc(_ *cli.Context) error { - err := runCmd("node_modules/.bin/markdown-toc", "-i", "README.md") +func TocActionFunc(c *cli.Context) error { + filename := c.Args().Get(0) + if filename == "" { + filename = "README.md" + } + + err := runCmd("node_modules/.bin/markdown-toc", "-i", filename) if err != nil { return err } From 5646b9ecb883d22f4438272ad6414edfafe3cb3f Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 15 Sep 2019 21:48:54 -0700 Subject: [PATCH 27/36] update test paths --- .travis.yml | 4 ++-- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e500b38..d36c224 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,8 @@ before_script: script: - go run build.go vet - go run build.go test - - go run build.go gfmrun - - go run build.go toc + - go run build.go gfmrun docs/v1/manual.md + - go run build.go toc docs/v1/manual.md after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/appveyor.yml b/appveyor.yml index 6d2dcee..66e740a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,4 +20,4 @@ install: build_script: - go run build.go vet - go run build.go test - - go run build.go gfmrun + - go run build.go gfmrun docs/v1/manual.md From 0e915773a99f967b8d8303ea3820817c921abf5e Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 15 Sep 2019 22:02:58 -0700 Subject: [PATCH 28/36] test ci --- docs/v1/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v1/manual.md b/docs/v1/manual.md index ca6a6c9..eda7cba 100644 --- a/docs/v1/manual.md +++ b/docs/v1/manual.md @@ -26,7 +26,7 @@ cli v1 manual + [Customization](#customization-1) * [Version Flag](#version-flag) + [Customization](#customization-2) - + [Full API Example](#full-api-example) + + [Full API Example FIX ME](#full-api-example) From 7b94268970917a1a6695644a623f4f9e60d9ee51 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sun, 15 Sep 2019 22:09:31 -0700 Subject: [PATCH 29/36] Revert "test ci" This reverts commit 0e915773a99f967b8d8303ea3820817c921abf5e. --- docs/v1/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v1/manual.md b/docs/v1/manual.md index eda7cba..ca6a6c9 100644 --- a/docs/v1/manual.md +++ b/docs/v1/manual.md @@ -26,7 +26,7 @@ cli v1 manual + [Customization](#customization-1) * [Version Flag](#version-flag) + [Customization](#customization-2) - + [Full API Example FIX ME](#full-api-example) + + [Full API Example](#full-api-example) From 51259808fff1f7384b556f6e127a090fe0362305 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Tue, 1 Oct 2019 20:13:04 -0700 Subject: [PATCH 30/36] better leading dash handling --- command.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/command.go b/command.go index dcaf2b4..70502e8 100644 --- a/command.go +++ b/command.go @@ -240,8 +240,7 @@ func reorderArgs(commandFlags []Flag, args []string) []string { reorderedArgs = append(reorderedArgs, arg) // checks if this is an arg that should be re-ordered - } else if arg != "-" && strings.HasPrefix(arg, "-") && argIsFlag(commandFlags, arg) { - + } else if argIsFlag(commandFlags, arg) { // we have determined that this is a flag that we should re-order reorderedArgs = append(reorderedArgs, arg) // if this arg does not contain a "=", then the next index may contain the value for this flag @@ -258,8 +257,18 @@ func reorderArgs(commandFlags []Flag, args []string) []string { // argIsFlag checks if an arg is one of our command flags func argIsFlag(commandFlags []Flag, arg string) bool { + // checks if this is just a `-`, and so definitely not a flag + if arg == "-" { + return false + } // this line turns `--flag` into `flag` - arg = strings.Replace(arg, "-", "", -1) + if strings.HasPrefix(arg, "--") { + arg = strings.Replace(arg, "-", "", 2) + } + // this line turns `-flag` into `flag` + if strings.HasPrefix(arg, "-") { + arg = strings.Replace(arg, "-", "", 1) + } // this line turns `flag=value` into `flag` arg = strings.Split(arg, "=")[0] // look through all the flags, to see if the `arg` is one of our flags From be0cc4b8715d2a57f03b395ae928db61048bb679 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Tue, 1 Oct 2019 20:18:53 -0700 Subject: [PATCH 31/36] add a check --- command.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/command.go b/command.go index 70502e8..d41a1bf 100644 --- a/command.go +++ b/command.go @@ -261,6 +261,10 @@ func argIsFlag(commandFlags []Flag, arg string) bool { if arg == "-" { return false } + // flags always start with a - + if !strings.HasPrefix(arg, "-") { + return false + } // this line turns `--flag` into `flag` if strings.HasPrefix(arg, "--") { arg = strings.Replace(arg, "-", "", 2) From 64d3555482218336121cd318c27454041cc479a1 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Tue, 1 Oct 2019 20:21:12 -0700 Subject: [PATCH 32/36] trim whitespace --- command.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command.go b/command.go index d41a1bf..e7cb97a 100644 --- a/command.go +++ b/command.go @@ -278,6 +278,7 @@ func argIsFlag(commandFlags []Flag, arg string) bool { // look through all the flags, to see if the `arg` is one of our flags for _, flag := range commandFlags { for _, key := range strings.Split(flag.GetName(), ",") { + key := strings.TrimSpace(key) if key == arg { return true } From 0b26e2fa446f6667e3146692d5ca977f6aed0758 Mon Sep 17 00:00:00 2001 From: "Lynn Cyrin (they/them)" Date: Sat, 12 Oct 2019 00:54:09 -0700 Subject: [PATCH 33/36] update install path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a15c38f..5463ec3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ import ( **Warning**: `v2` is in a pre-release state. ``` -$ go get gopkg.in/urfave/cli.v2 +$ go get github.com/urfave/cli.v2 ``` ```go From 3cf1eb69b9f423ac39aef528c2b3c6e21a2652da Mon Sep 17 00:00:00 2001 From: "Lynn Cyrin (they/them)" Date: Sat, 12 Oct 2019 00:54:37 -0700 Subject: [PATCH 34/36] update import path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5463ec3..4794b79 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ $ go get github.com/urfave/cli.v2 ```go ... import ( - "github.com/urfave/cli" // imports as package "cli" + "github.com/urfave/cli.v2" // imports as package "cli" ) ... ``` From 9b0f38824e7acd439470786cff53f5952135a904 Mon Sep 17 00:00:00 2001 From: "Lynn Cyrin (they/them)" Date: Sat, 12 Oct 2019 00:59:50 -0700 Subject: [PATCH 35/36] add usage documentation links --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 4794b79..b2abbcf 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,13 @@ cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. +## Usage Documentation + +Usage documentation exists for each major version + +- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md) +- `v2` - 🚧 documentation for `v2` is WIP 🚧 + ## Installation Make sure you have a working Go environment. Go version 1.10+ is supported. [See From 89063681e413cf8fbb3cf125c9665e473ca21d38 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Sat, 12 Oct 2019 01:05:32 -0700 Subject: [PATCH 36/36] update to latest docs --- docs/v1/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v1/manual.md b/docs/v1/manual.md index ca6a6c9..51c5b72 100644 --- a/docs/v1/manual.md +++ b/docs/v1/manual.md @@ -602,7 +602,7 @@ The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context. It will then use that file name to initialize the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order -for this code snipped to work. +for this code snippet to work. Currently only YAML, JSON, and TOML files are supported but developers can add support for other input sources by implementing the altsrc.InputSourceContext for their