From 002bde2233182425d5f6d7f3d2456db482d2bb1f Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Fri, 29 Nov 2019 14:09:46 +0100 Subject: [PATCH 01/18] Add suggestions support The new option `app.Suggest` enables command and flag suggestions via the jaro-winkler distance algorithm. Flags are scoped to their appropriate commands whereas command suggestions are scoped to the current command level. Signed-off-by: Sascha Grunert --- app.go | 12 +++++ command.go | 6 +++ docs/v2/manual.md | 8 +++ go.mod | 1 + go.sum | 2 + help.go | 21 ++++++-- parse.go | 18 +++++-- suggestions.go | 75 +++++++++++++++++++++++++++ suggestions_test.go | 122 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 257 insertions(+), 8 deletions(-) create mode 100644 suggestions.go create mode 100644 suggestions_test.go diff --git a/app.go b/app.go index dd8f1deb39..f3d24d0059 100644 --- a/app.go +++ b/app.go @@ -88,6 +88,8 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool didSetup bool } @@ -250,6 +252,11 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + if a.Suggest { + if suggestion, err := a.suggestFlagFromError(err, ""); err == nil { + fmt.Fprintf(a.Writer, suggestion) + } + } _ = ShowAppHelp(context) return err } @@ -381,6 +388,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + if a.Suggest { + if suggestion, err := a.suggestFlagFromError(err, context.Command.Name); err == nil { + fmt.Fprintf(a.Writer, suggestion) + } + } _ = ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index db6c8024ba..5300ee8a9d 100644 --- a/command.go +++ b/command.go @@ -116,6 +116,11 @@ func (c *Command) Run(ctx *Context) (err error) { } _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) _, _ = fmt.Fprintln(context.App.Writer) + if ctx.App.Suggest { + if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { + fmt.Fprintf(context.App.Writer, suggestion) + } + } _ = ShowCommandHelp(context, c.Name) return err } @@ -244,6 +249,7 @@ func (c *Command) startApp(ctx *Context) error { app.ErrWriter = ctx.App.ErrWriter app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling + app.Suggest = ctx.App.Suggest app.categories = newCommandCategories() for _, command := range c.Subcommands { diff --git a/docs/v2/manual.md b/docs/v2/manual.md index ce61075f21..29c263880f 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -34,6 +34,7 @@ cli v2 manual * [Version Flag](#version-flag) + [Customization](#customization-2) * [Timestamp Flag](#timestamp-flag) + * [Suggestions](#suggestions) * [Full API Example](#full-api-example) @@ -1426,6 +1427,13 @@ In this example the flag could be used like this : Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) +### Suggestions + +To enable flag and command suggestions, set `app.Suggest = true`. If the suggest +feature is enabled, then the help output of the corresponding command will +provide an appropriate suggestion for the provided flag or subcommand if +available. + ### Full API Example **Notice**: This is a contrived (functioning) example meant strictly for API diff --git a/go.mod b/go.mod index c38d41c14b..1c280a6f17 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.11 require ( github.com/BurntSushi/toml v0.3.1 + github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index ef121ff5db..6165e94c45 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k= +github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/help.go b/help.go index c1e974a481..9162661cdc 100644 --- a/help.go +++ b/help.go @@ -10,9 +10,14 @@ import ( "unicode/utf8" ) +const ( + helpName = "help" + helpAlias = "h" +) + var helpCommand = &Command{ - Name: "help", - Aliases: []string{"h"}, + Name: helpName, + Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(c *Context) error { @@ -27,8 +32,8 @@ var helpCommand = &Command{ } var helpSubcommand = &Command{ - Name: "help", - Aliases: []string{"h"}, + Name: helpName, + Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(c *Context) error { @@ -207,7 +212,13 @@ func ShowCommandHelp(ctx *Context, command string) error { } if ctx.App.CommandNotFound == nil { - return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) + errMsg := fmt.Sprintf("No help topic for '%v'", command) + if ctx.App.Suggest { + if suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" { + errMsg += ". " + suggestion + } + } + return Exit(errMsg, 3) } ctx.App.CommandNotFound(ctx, command) diff --git a/parse.go b/parse.go index 7df17296a4..a2db306e12 100644 --- a/parse.go +++ b/parse.go @@ -26,9 +26,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple return err } - errStr := err.Error() - trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") - if errStr == trimmed { + trimmed, trimErr := flagFromError(err) + if trimErr != nil { return err } @@ -67,6 +66,19 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple } } +const providedButNotDefinedErrMsg = "flag provided but not defined: -" + +// flagFromError tries to parse a provided flag from an error message. If the +// parsing fials, it returns the input error and an empty string +func flagFromError(err error) (string, error) { + errStr := err.Error() + trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg) + if errStr == trimmed { + return "", err + } + return trimmed, nil +} + func splitShortOptions(set *flag.FlagSet, arg string) []string { shortFlagsExist := func(s string) bool { for _, c := range s[1:] { diff --git a/suggestions.go b/suggestions.go new file mode 100644 index 0000000000..476af4de50 --- /dev/null +++ b/suggestions.go @@ -0,0 +1,75 @@ +package cli + +import ( + "fmt" + + "github.com/antzucaro/matchr" +) + +const didYouMeanTemplate = "Did you mean '%s'?" + +func (a *App) suggestFlagFromError(err error, command string) (string, error) { + flag, parseErr := flagFromError(err) + if parseErr != nil { + return "", err + } + + flags := a.Flags + if command != "" { + cmd := a.Command(command) + if cmd == nil { + return "", err + } + flags = cmd.Flags + } + + suggestion := a.suggestFlag(flags, flag) + if len(suggestion) == 0 { + return "", err + } + + return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil +} + +func (a *App) suggestFlag(flags []Flag, provided string) (suggestion string) { + distance := 0.0 + + for _, flag := range flags { + flagNames := flag.Names() + if !a.HideHelp { + flagNames = append(flagNames, HelpFlag.Names()...) + } + for _, name := range flagNames { + newDistance := matchr.JaroWinkler(name, provided, true) + if newDistance > distance { + distance = newDistance + suggestion = name + } + } + } + + if len(suggestion) == 1 { + suggestion = "-" + suggestion + } else if len(suggestion) > 1 { + suggestion = "--" + suggestion + } + + return suggestion +} + +// suggestCommand takes a list of commands and a provided string to suggest a +// command name +func suggestCommand(commands []*Command, provided string) (suggestion string) { + distance := 0.0 + for _, command := range commands { + for _, name := range append(command.Names(), helpName, helpAlias) { + newDistance := matchr.JaroWinkler(name, provided, true) + if newDistance > distance { + distance = newDistance + suggestion = name + } + } + } + + return fmt.Sprintf(didYouMeanTemplate, suggestion) +} diff --git a/suggestions_test.go b/suggestions_test.go new file mode 100644 index 0000000000..3b31250e8a --- /dev/null +++ b/suggestions_test.go @@ -0,0 +1,122 @@ +package cli + +import ( + "errors" + "fmt" + "testing" +) + +func TestSuggestFlag(t *testing.T) { + // Given + app := testApp() + + for _, testCase := range []struct { + provided, expected string + }{ + {"", ""}, + {"a", "--another-flag"}, + {"hlp", "--help"}, + {"k", ""}, + {"s", "-s"}, + } { + // When + res := app.suggestFlag(app.Flags, testCase.provided) + + // Then + expect(t, res, testCase.expected) + } +} + +func TestSuggestFlagHideHelp(t *testing.T) { + // Given + app := testApp() + app.HideHelp = true + + // When + res := app.suggestFlag(app.Flags, "hlp") + + // Then + expect(t, res, "--fl") +} + +func TestSuggestFlagFromError(t *testing.T) { + // Given + app := testApp() + + for _, testCase := range []struct { + command, provided, expected string + }{ + {"", "hel", "--help"}, + {"", "soccer", "--socket"}, + {"config", "anot", "--another-flag"}, + } { + // When + res, _ := app.suggestFlagFromError( + errors.New(providedButNotDefinedErrMsg+testCase.provided), + testCase.command, + ) + + // Then + expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected)) + } +} + +func TestSuggestFlagFromErrorWrongError(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.suggestFlagFromError(errors.New("invalid"), "") + + // Then + expect(t, true, err != nil) +} + +func TestSuggestFlagFromErrorWrongCommand(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.suggestFlagFromError( + errors.New(providedButNotDefinedErrMsg+"flag"), + "invalid", + ) + + // Then + expect(t, true, err != nil) +} + +func TestSuggestFlagFromErrorNoSuggestion(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.suggestFlagFromError( + errors.New(providedButNotDefinedErrMsg+""), + "", + ) + + // Then + expect(t, true, err != nil) +} + +func TestSuggestCommand(t *testing.T) { + // Given + app := testApp() + + for _, testCase := range []struct { + provided, expected string + }{ + {"", ""}, + {"conf", "config"}, + {"i", "i"}, + {"information", "info"}, + {"not-existing", "info"}, + } { + // When + res := suggestCommand(app.Commands, testCase.provided) + + // Then + expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected)) + } +} From 63b1a7deee832fd3b2d5e235fe22b0699c5c9163 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 14:43:15 -0400 Subject: [PATCH 02/18] A few follow-up conflict resolutions --- app.go | 2 +- command.go | 2 +- godoc-current.txt | 2 ++ testdata/godoc-v2.x.txt | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 5edfa460ed..463437a58a 100644 --- a/app.go +++ b/app.go @@ -391,7 +391,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) if a.Suggest { - if suggestion, err := a.suggestFlagFromError(err, context.Command.Name); err == nil { + if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil { fmt.Fprintf(a.Writer, suggestion) } } diff --git a/command.go b/command.go index 16ac7c37c9..3b9b837034 100644 --- a/command.go +++ b/command.go @@ -121,7 +121,7 @@ func (c *Command) Run(ctx *Context) (err error) { _, _ = fmt.Fprintln(cCtx.App.Writer) if ctx.App.Suggest { if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { - fmt.Fprintf(context.App.Writer, suggestion) + fmt.Fprintf(cCtx.App.Writer, suggestion) } } _ = ShowCommandHelp(cCtx, c.Name) diff --git a/godoc-current.txt b/godoc-current.txt index 1bddc54736..90700ebde7 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -305,6 +305,8 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool // Has unexported fields. } diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1bddc54736..90700ebde7 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -305,6 +305,8 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool // Has unexported fields. } From 974f2d410de5e80cdf8b51850ec15934332620d5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 15:21:00 -0400 Subject: [PATCH 03/18] Guard suggestion capability (+ dependency) with build tag --- .github/workflows/cli.yml | 10 ++++++++-- Makefile | 4 ++-- README.md | 12 +++++++++++- docs.go | 4 ++-- docs_test.go | 4 ++-- go.mod | 2 +- go.sum | 10 ++++++---- internal/build/build.go | 2 +- suggestions.go | 3 +++ suggestions_stubs.go | 12 ++++++++++++ suggestions_test.go | 3 +++ 11 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 suggestions_stubs.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 84f9cae01d..cad0389d72 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -37,9 +37,15 @@ jobs: - name: vet run: go run internal/build/build.go vet - - name: test with tags + - name: test with urfave_cli_core tag + run: go run internal/build/build.go -tags urfave_cli_core test + + - name: test with urfave_cli_no_docs tag run: go run internal/build/build.go -tags urfave_cli_no_docs test + - name: test with urfave_cli_no_suggest tag + run: go run internal/build/build.go -tags urfave_cli_no_suggest test + - name: test run: go run internal/build/build.go test @@ -47,7 +53,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: check-binary-size with tags (informational only) - run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size || true + run: go run internal/build/build.go -tags urfave_cli_core check-binary-size - name: Upload coverage to Codecov if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' diff --git a/Makefile b/Makefile index 52e9204d6c..9ca3c350a7 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,11 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun t .PHONY: tag-test tag-test: - go run internal/build/build.go -tags urfave_cli_no_docs test + go run internal/build/build.go -tags urfave_cli_core test .PHONY: tag-check-binary-size tag-check-binary-size: - go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size + go run internal/build/build.go -tags urfave_cli_core check-binary-size .PHONY: gfmrun gfmrun: diff --git a/README.md b/README.md index 6e4d698c24..35694d2195 100644 --- a/README.md +++ b/README.md @@ -59,11 +59,21 @@ import ( You can use the following build tags: +#### `urfave_cli_core` + +When set, applies all `urfave_cli_no.+` build tags to minimize resulting binary +size. + #### `urfave_cli_no_docs` When set, this removes `ToMarkdown` and `ToMan` methods, so your application won't be able to call those. This reduces the resulting binary size by about -300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies. +300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies. + +#### `urfave_cli_no_suggest` + +When set, the capability enabled by setting `App.Suggest` will be a no-op. This +reduces the resulting binary size due to fewer dependencies. ### GOPATH diff --git a/docs.go b/docs.go index 8b1c9c8a2c..4aba31bc40 100644 --- a/docs.go +++ b/docs.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs -// +build !urfave_cli_no_docs +//go:build !urfave_cli_no_docs && !urfave_cli_core +// +build !urfave_cli_no_docs,!urfave_cli_core package cli diff --git a/docs_test.go b/docs_test.go index 12d5d3c8b1..bc3703f514 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs -// +build !urfave_cli_no_docs +//go:build !urfave_cli_no_docs && !urfave_cli_core +// +build !urfave_cli_no_docs,!urfave_cli_core package cli diff --git a/go.mod b/go.mod index 7042c4e0b2..f9111b0b37 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/BurntSushi/toml v1.1.0 - github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c + github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 github.com/cpuguy83/go-md2man/v2 v2.0.1 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 3cb2b0b55d..afac06b6c4 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,16 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k= github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc= +github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= diff --git a/internal/build/build.go b/internal/build/build.go index 929ab0fbf9..e74acfbf41 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -242,7 +242,7 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { tags := c.String("tags") - if strings.Contains(tags, "urfave_cli_no_docs") { + if strings.Contains(tags, "urfave_cli_core") || strings.Contains(tags, "urfave_cli_no_docs") { desiredMinBinarySize = 1.39 } diff --git a/suggestions.go b/suggestions.go index 476af4de50..301c598e9e 100644 --- a/suggestions.go +++ b/suggestions.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_suggest && !urfave_cli_core +// +build !urfave_cli_no_suggest,!urfave_cli_core + package cli import ( diff --git a/suggestions_stubs.go b/suggestions_stubs.go new file mode 100644 index 0000000000..48643bb401 --- /dev/null +++ b/suggestions_stubs.go @@ -0,0 +1,12 @@ +//go:build urfave_cli_no_suggest || urfave_cli_core +// +build urfave_cli_no_suggest urfave_cli_core + +package cli + +func (a *App) suggestFlagFromError(err error, _ string) (string, error) { + return "", err +} + +func suggestCommand([]*Command, string) string { + return "" +} diff --git a/suggestions_test.go b/suggestions_test.go index 3b31250e8a..f87e702b59 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_suggest && !urfave_cli_core +// +build !urfave_cli_no_suggest,!urfave_cli_core + package cli import ( From 9da2c564f8b1563222e3e60a1761de1d0a3c43c4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 20:57:44 -0400 Subject: [PATCH 04/18] Add example app with `Suggest` support --- suggestions_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/suggestions_test.go b/suggestions_test.go index f87e702b59..55ef29e563 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -123,3 +123,36 @@ func TestSuggestCommand(t *testing.T) { expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected)) } } + +func ExampleApp_Suggest() { + app := &App{ + Name: "greet", + Suggest: true, + Flags: []Flag{ + &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, + }, + Action: func(c *Context) error { + fmt.Printf("Hello %v\n", c.String("name")) + return nil + }, + } + + app.Run([]string{"greet", "--nema", "chipmunk"}) + // Output: + // Incorrect Usage. flag provided but not defined: -nema + // + // Did you mean '--name'? + // + // NAME: + // greet - A new cli application + // + // USAGE: + // greet [global options] command [command options] [arguments...] + // + // COMMANDS: + // help, h Shows a list of commands or help for one command + // + // GLOBAL OPTIONS: + // --name value a name to say (default: "squirrel") + // --help, -h show help (default: false) +} From 34eed95a8ee897b19e8abe77ddffcc7f7e39bdfb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 22:06:39 -0400 Subject: [PATCH 05/18] Reduce amount of text compared in suggest example app --- suggestions_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/suggestions_test.go b/suggestions_test.go index 55ef29e563..359c793151 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -126,8 +126,10 @@ func TestSuggestCommand(t *testing.T) { func ExampleApp_Suggest() { app := &App{ - Name: "greet", - Suggest: true, + Name: "greet", + Suggest: true, + HideHelp: true, + HideHelpCommand: true, Flags: []Flag{ &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, }, @@ -147,12 +149,8 @@ func ExampleApp_Suggest() { // greet - A new cli application // // USAGE: - // greet [global options] command [command options] [arguments...] - // - // COMMANDS: - // help, h Shows a list of commands or help for one command + // greet [global options] [arguments...] // // GLOBAL OPTIONS: // --name value a name to say (default: "squirrel") - // --help, -h show help (default: false) } From 9bd6349ed21eb7338d28f2c58c3a17eef0895741 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 23:06:43 -0400 Subject: [PATCH 06/18] Add example app for suggestion in command and further reduce example output diff potential --- suggestions_test.go | 59 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/suggestions_test.go b/suggestions_test.go index 359c793151..fa7b0c9c8c 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -126,15 +126,16 @@ func TestSuggestCommand(t *testing.T) { func ExampleApp_Suggest() { app := &App{ - Name: "greet", - Suggest: true, - HideHelp: true, - HideHelpCommand: true, + Name: "greet", + Suggest: true, + HideHelp: true, + HideHelpCommand: true, + CustomAppHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, }, - Action: func(c *Context) error { - fmt.Printf("Hello %v\n", c.String("name")) + Action: func(cCtx *Context) error { + fmt.Printf("Hello %v\n", cCtx.String("name")) return nil }, } @@ -145,12 +146,46 @@ func ExampleApp_Suggest() { // // Did you mean '--name'? // - // NAME: - // greet - A new cli application + // (this space intentionally left blank) +} + +func ExampleApp_Suggest_command() { + app := &App{ + Name: "greet", + Suggest: true, + HideHelp: true, + HideHelpCommand: true, + CustomAppHelpTemplate: "(this space intentionally left blank)\n", + Flags: []Flag{ + &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, + }, + Action: func(cCtx *Context) error { + fmt.Printf("Hello %v\n", cCtx.String("name")) + return nil + }, + Commands: []*Command{ + { + Name: "neighbors", + CustomHelpTemplate: "(this space intentionally left blank)\n", + Flags: []Flag{ + &BoolFlag{Name: "smiling"}, + }, + Action: func(cCtx *Context) error { + if cCtx.Bool("smiling") { + fmt.Println("😀") + } + fmt.Println("Hello, neighbors") + return nil + }, + }, + }, + } + + app.Run([]string{"greet", "neighbors", "--sliming"}) + // Output: + // Incorrect Usage: flag provided but not defined: -sliming // - // USAGE: - // greet [global options] [arguments...] + // Did you mean '--smiling'? // - // GLOBAL OPTIONS: - // --name value a name to say (default: "squirrel") + // (this space intentionally left blank) } From 858ce7ee6dde4e030be5cb1733eecb4d33c9b794 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 11:36:03 -0400 Subject: [PATCH 07/18] Initial mkdocs setup Connected to #1343 --- docs/index.md | 20 +++++++++++ docs/migrate-v1-to-v2.md | 23 +----------- docs/v1/{manual.md => index.md} | 33 +----------------- docs/v2/{manual.md => index.md} | 57 +++++------------------------- mkdocs-requirements.txt | 4 +++ mkdocs.yml | 62 +++++++++++++++++++++++++++++++++ 6 files changed, 97 insertions(+), 102 deletions(-) create mode 100644 docs/index.md rename docs/v1/{manual.md => index.md} (96%) rename docs/v2/{manual.md => index.md} (95%) create mode 100644 mkdocs-requirements.txt create mode 100644 mkdocs.yml diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..d9d5d465b9 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,20 @@ +# Welcome to urfave/cli + +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli) + +`urfave/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. + +These are the guides for each major supported version: + +- [`v2`](./v2/) +- [`v1`](./v1/) + +In addition to the version-specific guides, these other documents are available: + +- [`CONTRIBUTING`](./CONTRIBUTING/) +- [`RELEASING`](./RELEASING/) diff --git a/docs/migrate-v1-to-v2.md b/docs/migrate-v1-to-v2.md index c555468beb..7c703c847f 100644 --- a/docs/migrate-v1-to-v2.md +++ b/docs/migrate-v1-to-v2.md @@ -1,6 +1,4 @@ -Migration Guide: v1 to v2 -=== - +# Migration Guide: v1 to v2 v2 has a number of breaking changes but converting is relatively straightforward: make the changes documented below then resolve any @@ -11,25 +9,6 @@ If you find any issues not covered by this document, please post a comment on [Issue 921](https://github.com/urfave/cli/issues/921) or consider sending a PR to help improve this guide. - - - * [Flags before args](#flags-before-args) - * [Import string changed](#import-string-changed) - * [Flag aliases are done differently](#flag-aliases-are-done-differently) - * [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars) - * [Actions returns errors](#actions-returns-errors) - * [cli.Flag changed](#cliflag-changed) - * [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) - * [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) - * [Appending Commands](#appending-commands) - * [GlobalString, GlobalBool and its likes are deprecated](#globalstring-globalbool-and-its-likes-are-deprecated) - * [BoolTFlag and BoolT are deprecated](#booltflag-and-boolt-are-deprecated) - * [&cli.StringSlice{""} replaced with cli.NewStringSlice("")](#clistringslice-replaced-with-clinewstringslice) - * [Replace deprecated functions](#replace-deprecated-functions) - * [Everything else](#everything-else) - - - # Flags before args In v2 flags must come before args. This is more POSIX-compliant. You diff --git a/docs/v1/manual.md b/docs/v1/index.md similarity index 96% rename from docs/v1/manual.md rename to docs/v1/index.md index dd22bdbdc7..6f568b73f1 100644 --- a/docs/v1/manual.md +++ b/docs/v1/index.md @@ -1,35 +1,4 @@ -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) - * [Migrating to V2](#migrating-to-v2) - - +# v1 guide ## Getting Started diff --git a/docs/v2/manual.md b/docs/v2/index.md similarity index 95% rename from docs/v2/manual.md rename to docs/v2/index.md index b480dd685f..9c261006f3 100644 --- a/docs/v2/manual.md +++ b/docs/v2/index.md @@ -1,51 +1,4 @@ -cli v2 manual -=== - - - -- [Migrating From Older Releases](#migrating-from-older-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) - + [Required Flags](#required-flags) - + [Default Values for help output](#default-values-for-help-output) - + [Precedence](#precedence) - * [Subcommands](#subcommands) - * [Subcommands categories](#subcommands-categories) - * [Exit code](#exit-code) - * [Combining short options](#combining-short-options) - * [Bash Completion](#bash-completion) - + [Default auto-completion](#default-auto-completion) - + [Custom auto-completion](#custom-auto-completion) - + [Enabling](#enabling) - + [Distribution and Persistent Autocompletion](#distribution-and-persistent-autocompletion) - + [Customization](#customization) - + [ZSH Support](#zsh-support) - + [ZSH default auto-complete example](#zsh-default-auto-complete-example) - + [ZSH custom auto-complete example](#zsh-custom-auto-complete-example) - + [PowerShell Support](#powershell-support) - * [Generated Help Text](#generated-help-text) - + [Customization](#customization-1) - * [Version Flag](#version-flag) - + [Customization](#customization-2) - * [Timestamp Flag](#timestamp-flag) - * [Full API Example](#full-api-example) - - - -## Migrating From Older Releases - -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. +# v2 guide ## Getting Started @@ -1714,3 +1667,11 @@ func wopAction(c *cli.Context) error { return nil } ``` + +## Migrating From Older Releases + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. + diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt new file mode 100644 index 0000000000..94dae76e86 --- /dev/null +++ b/mkdocs-requirements.txt @@ -0,0 +1,4 @@ +mkdocs +mkdocs-material +mkdocs-git-revision-date-localized-plugin +pygments diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..c963d37285 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,62 @@ +# NOTE: the mkdocs dependencies will need to be installed out of +# band until this whole thing gets more automated: +# +# pip install -r mkdocs-requirements.txt +# + +site_name: urfave/cli +site_url: https://cli.urfave.org/ +repo_url: https://github.com/urvafe/cli +edit_uri: edit/main/docs/ +nav: + - Home: index.md + - v2 Manual: v2/index.md + - v1 Manual: v1/index.md +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-4 + name: dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-7 + name: light mode +plugins: + - git-revision-date-localized + - search +# NOTE: this is the recommended configuration from +# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - meta + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde From 8da1afc62be381a58a0d029a7e11bf51ec634970 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 11:38:41 -0400 Subject: [PATCH 08/18] Un-rename manual docs + symlink instead --- docs/v1/index.md | 1456 +-------------------------------------- docs/v1/manual.md | 1455 +++++++++++++++++++++++++++++++++++++++ docs/v2/index.md | 1678 +-------------------------------------------- docs/v2/manual.md | 1677 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3134 insertions(+), 3132 deletions(-) mode change 100644 => 120000 docs/v1/index.md create mode 100644 docs/v1/manual.md mode change 100644 => 120000 docs/v2/index.md create mode 100644 docs/v2/manual.md diff --git a/docs/v1/index.md b/docs/v1/index.md deleted file mode 100644 index 6f568b73f1..0000000000 --- a/docs/v1/index.md +++ /dev/null @@ -1,1455 +0,0 @@ -# v1 guide - -## 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 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 -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 -} -``` - -## Migrating to V2 - -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). diff --git a/docs/v1/index.md b/docs/v1/index.md new file mode 120000 index 0000000000..9d0493a7f2 --- /dev/null +++ b/docs/v1/index.md @@ -0,0 +1 @@ +manual.md \ No newline at end of file diff --git a/docs/v1/manual.md b/docs/v1/manual.md new file mode 100644 index 0000000000..6f568b73f1 --- /dev/null +++ b/docs/v1/manual.md @@ -0,0 +1,1455 @@ +# v1 guide + +## 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 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 +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 +} +``` + +## Migrating to V2 + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). diff --git a/docs/v2/index.md b/docs/v2/index.md deleted file mode 100644 index 9c261006f3..0000000000 --- a/docs/v2/index.md +++ /dev/null @@ -1,1677 +0,0 @@ -# v2 guide - -## 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 ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - (&cli.App{}).Run(os.Args) -} -``` - -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/v2" -) - -func main() { - app := &cli.App{ - Name: "boom", - Usage: "make an explosive entrance", - 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/v2" -) - -func main() { - app := &cli.App{ - Name: "greet", - Usage: "fight the loneliness!", - 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...] - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --help, -h show help (default: false) -``` - -### 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/v2" -) - -func main() { - app := &cli.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/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - }, - 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/v2" -) - -func main() { - var language string - - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - }, - Action: func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args().Get(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 https://pkg.go.dev/github.com/urfave/cli/v2 - -#### 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/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Aliases: []string{"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/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"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/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "Language for the greeting", - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - 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 `EnvVars`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"APP_LANG"}, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If `EnvVars` contains more than one string, the first environment variable that -resolves is used. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"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/v2" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "password", - Aliases: []string{"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 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 -given sources. - -Here is a more complete sample of a command using YAML support: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" -) - -func main() { - flags := []cli.Flag{ - altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), - &cli.StringFlag{Name: "load"}, - } - - app := &cli.App{ - Action: func(c *cli.Context) error { - fmt.Println("--test value.*default: 0") - return nil - }, - Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), - Flags: flags, - } - - app.Run(os.Args) -} -``` - -#### Required Flags - -You can make a flag required by setting the `Required` field to `true`. If a user -does not provide a required flag, they will be shown an error message. - -Take for example this app that requires the `lang` flag: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Required: true, - }, - } - - app.Action = func(c *cli.Context) error { - var output string - if c.String("lang") == "spanish" { - output = "Hola" - } else { - output = "Hello" - } - fmt.Println(output) - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If the app is run without the `lang` flag, the user will see the following message - -``` -Required flag "lang" not set -``` - -#### Default Values for help output - -Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Usage: "Use a randomized port", - Value: 0, - DefaultText: "random", - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---port value Use a randomized port (default: random) -``` - -#### 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/v2" -) - -func main() { - app := &cli.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/v2" -) - -func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", - }, - }, - } - - 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/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "is it in the soup?", - }, - }, - Action: func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.Exit("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/v2" -) - -func main() { - app := &cli.App{} - app.UseShortOptionHandling = true - app.Commands = []*cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, - &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, - &cli.StringFlag{Name: "message", Aliases: []string{"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 to `true`. By default, this setting will allow auto-completion -for an app's subcommands, but you can write your own completion methods for -the App or its subcommands as well. - -#### Default auto-completion - -```go -package main -import ( - "fmt" - "log" - "os" - "github.com/urfave/cli/v2" -) -func main() { - app := cli.NewApp() - app.EnableBashCompletion = true - 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) - } -} -``` -![](/docs/v2/images/default-bash-autocomplete.gif) - -#### Custom auto-completion - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := &cli.App{ - EnableBashCompletion: true, - 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) - } -} -``` -![](/docs/v2/images/custom-bash-autocomplete.gif) - -#### Enabling - -To enable auto-completion for the current shell session, a bash script, -`autocomplete/bash_autocomplete` is included in this repo. - -To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to -the name of your program and then `source` the `autocomplete/bash_autocomplete` file. - -For example, if your cli program is called `myprogram`: - -`PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete` - -Auto-completion is now enabled for the current shell, but will not persist into a new shell. - -#### Distribution and Persistent Autocompletion - -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 or restart your shell to activate the auto-completion. - -``` -sudo cp path/to/autocomplete/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` and set `$PROG` within their bash configuration -file, adding these lines: - -``` -PROG= -source path/to/cli/autocomplete/bash_autocomplete -``` -Keep in mind that if they are enabling auto-completion for more than one program, -they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each -program, like so: - -``` -PROG= -source path/to/cli/autocomplete/bash_autocomplete -PROG= -source path/to/cli/autocomplete/bash_autocomplete -``` - -#### Customization - -The default shell completion flag (`--generate-bash-completion`) is defined as -`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "wat", - }, - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### ZSH Support -Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` -file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. -Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and -then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH -configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: - -``` -PROG= -_CLI_ZSH_AUTOCOMPLETE_HACK=1 -source path/to/autocomplete/zsh_autocomplete -``` -#### ZSH default auto-complete example -![](/docs/v2/images/default-zsh-autocomplete.gif) -#### ZSH custom auto-complete example -![](/docs/v2/images/custom-zsh-autocomplete.gif) - -#### PowerShell Support -Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` -file included in this repo. - -Rename the script to `.ps1` and move it anywhere in your file system. -The location of script does not matter, only the file name of the script has to match -the your program's binary name. - -To activate it, enter `& path/to/autocomplete/.ps1` - -To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) -and add the line: -``` -& path/to/autocomplete/.ps1 -``` - - -### 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" - "io" - "os" - - "github.com/urfave/cli/v2" -) - -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") - } - - (&cli.App{}).Run(os.Args) -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - cli.HelpFlag = &cli.BoolFlag{ - Name: "haaaaalp", - Aliases: []string{"halp"}, - Usage: "HALP", - EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, - } - - (&cli.App{}).Run(os.Args) -} -``` - -### 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 ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - cli.VersionFlag = &cli.BoolFlag{ - Name: "print-version", - Aliases: []string{"V"}, - Usage: "print only the version", - } - - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli/v2" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } - - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) -} -``` - -### Timestamp Flag - -Using the timestamp flag is simple. Please refer to [`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible formats. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, - }, - Action: func(c *cli.Context) error { - fmt.Printf("%s", c.Timestamp("meeting").String()) - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -In this example the flag could be used like this : - -`myapp --meeting 2019-08-12T15:04:05` - -Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) - -### 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/v2" -) - -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.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"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.Names()[0]) - } -} - -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.App{ - Name: "kənˈtrīv", - Version: "v19.99.0", - Compiled: time.Now(), - Authors: []*cli.Author{ - &cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - }, - Copyright: "(c) 1999 Serious Enterprise", - HelpName: "contrive", - Usage: "demonstrate available API", - UsageText: "contrive - demonstrating the available API", - ArgsUsage: "[args and such]", - 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", Aliases: []string{"forevvarr"}}, - }, - Subcommands: []*cli.Command{ - &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 - }, - }, - }, - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "fancy"}, - &cli.BoolFlag{Value: true, Name: "fancier"}, - &cli.DurationFlag{Name: "howlong", Aliases: []string{"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", Aliases: []string{"d"}}, - &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, - &cli.UintFlag{Name: "age"}, - &cli.Uint64Flag{Name: "bigage"}, - }, - EnableBashCompletion: true, - HideHelp: false, - HideVersion: false, - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - }, - CommandNotFound: func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - }, - 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 - }, - 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) - - 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 c.Args().Len() > 0 { - fmt.Printf("%#v\n", c.Args().Get(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.Bool("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.FlagNames()) - fmt.Printf("%#v\n", nc.IsSet("wat")) - fmt.Printf("%#v\n", nc.Set("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Lineage()[1]) - nc.Set("wat", "also-nope") - - ec := cli.Exit("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return ec - }, - Metadata: map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - }, - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Run(os.Args) -} - -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil -} -``` - -## Migrating From Older Releases - -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. - diff --git a/docs/v2/index.md b/docs/v2/index.md new file mode 120000 index 0000000000..9d0493a7f2 --- /dev/null +++ b/docs/v2/index.md @@ -0,0 +1 @@ +manual.md \ No newline at end of file diff --git a/docs/v2/manual.md b/docs/v2/manual.md new file mode 100644 index 0000000000..9c261006f3 --- /dev/null +++ b/docs/v2/manual.md @@ -0,0 +1,1677 @@ +# v2 guide + +## 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 ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + (&cli.App{}).Run(os.Args) +} +``` + +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/v2" +) + +func main() { + app := &cli.App{ + Name: "boom", + Usage: "make an explosive entrance", + 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/v2" +) + +func main() { + app := &cli.App{ + Name: "greet", + Usage: "fight the loneliness!", + 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...] + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --help, -h show help (default: false) +``` + +### 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/v2" +) + +func main() { + app := &cli.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/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + 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/v2" +) + +func main() { + var language string + + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + }, + Action: func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args().Get(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 https://pkg.go.dev/github.com/urfave/cli/v2 + +#### 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/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"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/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"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/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + 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 `EnvVars`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If `EnvVars` contains more than one string, the first environment variable that +resolves is used. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"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/v2" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + &cli.StringFlag{ + Name: "password", + Aliases: []string{"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 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 +given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" +) + +func main() { + flags := []cli.Flag{ + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}, + } + + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Println("--test value.*default: 0") + return nil + }, + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), + Flags: flags, + } + + app.Run(os.Args) +} +``` + +#### Required Flags + +You can make a flag required by setting the `Required` field to `true`. If a user +does not provide a required flag, they will be shown an error message. + +Take for example this app that requires the `lang` flag: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Required: true, + }, + } + + app.Action = func(c *cli.Context) error { + var output string + if c.String("lang") == "spanish" { + output = "Hola" + } else { + output = "Hello" + } + fmt.Println(output) + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If the app is run without the `lang` flag, the user will see the following message + +``` +Required flag "lang" not set +``` + +#### Default Values for help output + +Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Usage: "Use a randomized port", + Value: 0, + DefaultText: "random", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--port value Use a randomized port (default: random) +``` + +#### 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/v2" +) + +func main() { + app := &cli.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/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + }, + } + + 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/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + }, + Action: func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.Exit("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/v2" +) + +func main() { + app := &cli.App{} + app.UseShortOptionHandling = true + app.Commands = []*cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, + &cli.StringFlag{Name: "message", Aliases: []string{"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 to `true`. By default, this setting will allow auto-completion +for an app's subcommands, but you can write your own completion methods for +the App or its subcommands as well. + +#### Default auto-completion + +```go +package main +import ( + "fmt" + "log" + "os" + "github.com/urfave/cli/v2" +) +func main() { + app := cli.NewApp() + app.EnableBashCompletion = true + 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) + } +} +``` +![](/docs/v2/images/default-bash-autocomplete.gif) + +#### Custom auto-completion + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := &cli.App{ + EnableBashCompletion: true, + 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) + } +} +``` +![](/docs/v2/images/custom-bash-autocomplete.gif) + +#### Enabling + +To enable auto-completion for the current shell session, a bash script, +`autocomplete/bash_autocomplete` is included in this repo. + +To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to +the name of your program and then `source` the `autocomplete/bash_autocomplete` file. + +For example, if your cli program is called `myprogram`: + +`PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete` + +Auto-completion is now enabled for the current shell, but will not persist into a new shell. + +#### Distribution and Persistent Autocompletion + +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 or restart your shell to activate the auto-completion. + +``` +sudo cp path/to/autocomplete/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` and set `$PROG` within their bash configuration +file, adding these lines: + +``` +PROG= +source path/to/cli/autocomplete/bash_autocomplete +``` +Keep in mind that if they are enabling auto-completion for more than one program, +they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each +program, like so: + +``` +PROG= +source path/to/cli/autocomplete/bash_autocomplete +PROG= +source path/to/cli/autocomplete/bash_autocomplete +``` + +#### Customization + +The default shell completion flag (`--generate-bash-completion`) is defined as +`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "wat", + }, + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### ZSH Support +Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` +file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. +Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and +then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH +configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: + +``` +PROG= +_CLI_ZSH_AUTOCOMPLETE_HACK=1 +source path/to/autocomplete/zsh_autocomplete +``` +#### ZSH default auto-complete example +![](/docs/v2/images/default-zsh-autocomplete.gif) +#### ZSH custom auto-complete example +![](/docs/v2/images/custom-zsh-autocomplete.gif) + +#### PowerShell Support +Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` +file included in this repo. + +Rename the script to `.ps1` and move it anywhere in your file system. +The location of script does not matter, only the file name of the script has to match +the your program's binary name. + +To activate it, enter `& path/to/autocomplete/.ps1` + +To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) +and add the line: +``` +& path/to/autocomplete/.ps1 +``` + + +### 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" + "io" + "os" + + "github.com/urfave/cli/v2" +) + +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") + } + + (&cli.App{}).Run(os.Args) +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.HelpFlag = &cli.BoolFlag{ + Name: "haaaaalp", + Aliases: []string{"halp"}, + Usage: "HALP", + EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, + } + + (&cli.App{}).Run(os.Args) +} +``` + +### 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 ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.VersionFlag = &cli.BoolFlag{ + Name: "print-version", + Aliases: []string{"V"}, + Usage: "print only the version", + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +### Timestamp Flag + +Using the timestamp flag is simple. Please refer to [`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible formats. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, + }, + Action: func(c *cli.Context) error { + fmt.Printf("%s", c.Timestamp("meeting").String()) + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +In this example the flag could be used like this : + +`myapp --meeting 2019-08-12T15:04:05` + +Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) + +### 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/v2" +) + +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.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"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.Names()[0]) + } +} + +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.App{ + Name: "kənˈtrīv", + Version: "v19.99.0", + Compiled: time.Now(), + Authors: []*cli.Author{ + &cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + }, + Copyright: "(c) 1999 Serious Enterprise", + HelpName: "contrive", + Usage: "demonstrate available API", + UsageText: "contrive - demonstrating the available API", + ArgsUsage: "[args and such]", + 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", Aliases: []string{"forevvarr"}}, + }, + Subcommands: []*cli.Command{ + &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 + }, + }, + }, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "fancy"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"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", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, + }, + EnableBashCompletion: true, + HideHelp: false, + HideVersion: false, + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + }, + CommandNotFound: func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + }, + 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 + }, + 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) + + 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 c.Args().Len() > 0 { + fmt.Printf("%#v\n", c.Args().Get(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.Bool("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.FlagNames()) + fmt.Printf("%#v\n", nc.IsSet("wat")) + fmt.Printf("%#v\n", nc.Set("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Lineage()[1]) + nc.Set("wat", "also-nope") + + ec := cli.Exit("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + }, + Metadata: map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + }, + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` + +## Migrating From Older Releases + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. + From bc9ad9fede4d530aaec478c0dc21edcdfc8b1b6a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 13:52:32 -0400 Subject: [PATCH 09/18] Generate RequiredFlag and VisibleFlag implementations --- flag_bool.go | 10 -- flag_duration.go | 10 -- flag_float64.go | 10 -- flag_float64_slice.go | 10 -- flag_generic.go | 10 -- flag_int.go | 10 -- flag_int64.go | 10 -- flag_int64_slice.go | 10 -- flag_int_slice.go | 10 -- flag_path.go | 10 -- flag_string.go | 10 -- flag_string_slice.go | 10 -- flag_timestamp.go | 10 -- flag_uint.go | 10 -- flag_uint64.go | 10 -- internal/genflags/generated.gotmpl | 16 ++- internal/genflags/generated_test.gotmpl | 16 +++ internal/genflags/spec.go | 8 ++ zz_generated.flags.go | 150 ++++++++++++++++++++ zz_generated.flags_test.go | 180 ++++++++++++++++++++++++ 20 files changed, 369 insertions(+), 151 deletions(-) diff --git a/flag_bool.go b/flag_bool.go index f984acfc2f..12775043f2 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *BoolFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *BoolFlag) TakesValue() bool { return false @@ -27,11 +22,6 @@ func (f *BoolFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *BoolFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *BoolFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_duration.go b/flag_duration.go index a6677ad288..236056cd4b 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -6,11 +6,6 @@ import ( "time" ) -// IsRequired returns whether or not the flag is required -func (f *DurationFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *DurationFlag) TakesValue() bool { return true @@ -27,11 +22,6 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *DurationFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *DurationFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_float64.go b/flag_float64.go index 62a1973007..aa2f359465 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *Float64Flag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Float64Flag) TakesValue() bool { return true @@ -40,11 +35,6 @@ func (f *Float64Flag) GetEnvVars() []string { return f.EnvVars } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *Float64Flag) IsVisible() bool { - return !f.Hidden -} - // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 0f09c8cfe3..e2bfc4c97d 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -81,11 +81,6 @@ func (f *Float64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *Float64SliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true if the flag takes a value, otherwise false func (f *Float64SliceFlag) TakesValue() bool { return true @@ -105,11 +100,6 @@ func (f *Float64SliceFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *Float64SliceFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *Float64SliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_generic.go b/flag_generic.go index 71e7173166..8be32b8729 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -11,11 +11,6 @@ type Generic interface { String() string } -// IsRequired returns whether or not the flag is required -func (f *GenericFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *GenericFlag) TakesValue() bool { return true @@ -35,11 +30,6 @@ func (f *GenericFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *GenericFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *GenericFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int.go b/flag_int.go index b68c3e86e3..3f5dec551e 100644 --- a/flag_int.go +++ b/flag_int.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *IntFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *IntFlag) TakesValue() bool { return true @@ -27,11 +22,6 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *IntFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *IntFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int64.go b/flag_int64.go index 3f71134bcc..d005866255 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *Int64Flag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Int64Flag) TakesValue() bool { return true @@ -27,11 +22,6 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *Int64Flag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *Int64Flag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 14d8c41c6a..b61bd7f855 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -82,11 +82,6 @@ func (f *Int64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *Int64SliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Int64SliceFlag) TakesValue() bool { return true @@ -106,11 +101,6 @@ func (f *Int64SliceFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *Int64SliceFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *Int64SliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int_slice.go b/flag_int_slice.go index 4ed7f2c59e..f9713cc4d4 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -93,11 +93,6 @@ func (f *IntSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *IntSliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *IntSliceFlag) TakesValue() bool { return true @@ -117,11 +112,6 @@ func (f *IntSliceFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *IntSliceFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *IntSliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_path.go b/flag_path.go index 095a59648a..b0c2215b22 100644 --- a/flag_path.go +++ b/flag_path.go @@ -7,11 +7,6 @@ import ( type Path = string -// IsRequired returns whether or not the flag is required -func (f *PathFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *PathFlag) TakesValue() bool { return true @@ -28,11 +23,6 @@ func (f *PathFlag) GetValue() string { return f.Value } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *PathFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *PathFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_string.go b/flag_string.go index 4831c17959..24adbe9055 100644 --- a/flag_string.go +++ b/flag_string.go @@ -5,11 +5,6 @@ import ( "fmt" ) -// IsRequired returns whether or not the flag is required -func (f *StringFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *StringFlag) TakesValue() bool { return true @@ -26,11 +21,6 @@ func (f *StringFlag) GetValue() string { return f.Value } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *StringFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *StringFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_string_slice.go b/flag_string_slice.go index 9e69d0008b..d0195d5479 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -76,11 +76,6 @@ func (f *StringSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *StringSliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *StringSliceFlag) TakesValue() bool { return true @@ -100,11 +95,6 @@ func (f *StringSliceFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *StringSliceFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *StringSliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_timestamp.go b/flag_timestamp.go index 32899f5321..ed480cfc94 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -58,11 +58,6 @@ func (t *Timestamp) Get() interface{} { return *t } -// IsRequired returns whether or not the flag is required -func (f *TimestampFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *TimestampFlag) TakesValue() bool { return true @@ -82,11 +77,6 @@ func (f *TimestampFlag) GetValue() string { return "" } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *TimestampFlag) IsVisible() bool { - return !f.Hidden -} - // GetDefaultText returns the default text for this flag func (f *TimestampFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_uint.go b/flag_uint.go index 625f11c15d..1ec9713f8b 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *UintFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *UintFlag) TakesValue() bool { return true @@ -21,11 +16,6 @@ func (f *UintFlag) GetUsage() string { return f.Usage } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *UintFlag) IsVisible() bool { - return !f.Hidden -} - // Apply populates the flag given the flag set and environment func (f *UintFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { diff --git a/flag_uint64.go b/flag_uint64.go index 58969a5d13..55ba08a0c9 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *Uint64Flag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Uint64Flag) TakesValue() bool { return true @@ -21,11 +16,6 @@ func (f *Uint64Flag) GetUsage() string { return f.Usage } -// IsVisible returns true if the flag is not hidden, otherwise false -func (f *Uint64Flag) IsVisible() bool { - return !f.Hidden -} - // Apply populates the flag given the flag set and environment func (f *Uint64Flag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index 5d6f0dde1a..99c292aa7c 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -31,7 +31,7 @@ type {{.TypeName}} struct { func (f *{{.TypeName}}) String() string { return {{$.UrfaveCLINamespace}}FlagStringer(f) } -{{end}} +{{end}}{{/* /if .GenerateFmtStringerInterface */}} {{if .GenerateFlagInterface}} // IsSet returns whether or not the flag has been set through env or file @@ -45,6 +45,20 @@ func (f *{{.TypeName}}) Names() []string { } {{end}}{{/* /if .GenerateFlagInterface */}} + +{{if .GenerateRequiredFlagInterface}} +// IsRequired returns whether or not the flag is required +func (f *{{.TypeName}}) IsRequired() bool { + return f.Required +} +{{end}}{{/* /if .GenerateRequiredFlagInterface */}} + +{{if .GenerateVisibleFlagInterface}} +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *{{.TypeName}}) IsVisible() bool { + return !f.Hidden +} +{{end}}{{/* /if .GenerateVisibleFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} // vim{{/* 👻 */}}:ro diff --git a/internal/genflags/generated_test.gotmpl b/internal/genflags/generated_test.gotmpl index 44e9ad4428..52de4e4bf4 100644 --- a/internal/genflags/generated_test.gotmpl +++ b/internal/genflags/generated_test.gotmpl @@ -19,6 +19,22 @@ func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } {{end}} + +{{if .GenerateRequiredFlagInterface}} +func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { + var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.IsRequired() +} +{{end}} + +{{if .GenerateVisibleFlagInterface}} +func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { + var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.IsVisible() +} +{{end}} {{end}} // vim{{/* 👻 */}}:ro diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go index d9f18db03f..a7afb22143 100644 --- a/internal/genflags/spec.go +++ b/internal/genflags/spec.go @@ -83,6 +83,14 @@ func (ft *FlagType) GenerateFlagInterface() bool { return ft.skipInterfaceNamed("Flag") } +func (ft *FlagType) GenerateRequiredFlagInterface() bool { + return ft.skipInterfaceNamed("RequiredFlag") +} + +func (ft *FlagType) GenerateVisibleFlagInterface() bool { + return ft.skipInterfaceNamed("VisibleFlag") +} + func (ft *FlagType) skipInterfaceNamed(name string) bool { if ft.Config == nil { return true diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 703e66a406..6c16c3e75a 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -33,6 +33,16 @@ func (f *Float64SliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Float64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64SliceFlag) IsVisible() bool { + return !f.Hidden +} + // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -69,6 +79,16 @@ func (f *GenericFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *GenericFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *GenericFlag) IsVisible() bool { + return !f.Hidden +} + // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -98,6 +118,16 @@ func (f *Int64SliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Int64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64SliceFlag) IsVisible() bool { + return !f.Hidden +} + // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -127,6 +157,16 @@ func (f *IntSliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *IntSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntSliceFlag) IsVisible() bool { + return !f.Hidden +} + // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -163,6 +203,16 @@ func (f *PathFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *PathFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *PathFlag) IsVisible() bool { + return !f.Hidden +} + // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { Name string @@ -194,6 +244,16 @@ func (f *StringSliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *StringSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringSliceFlag) IsVisible() bool { + return !f.Hidden +} + // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -230,6 +290,16 @@ func (f *TimestampFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *TimestampFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *TimestampFlag) IsVisible() bool { + return !f.Hidden +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string @@ -264,6 +334,16 @@ func (f *BoolFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *BoolFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *BoolFlag) IsVisible() bool { + return !f.Hidden +} + // Float64Flag is a flag with type float64 type Float64Flag struct { Name string @@ -298,6 +378,16 @@ func (f *Float64Flag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Float64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64Flag) IsVisible() bool { + return !f.Hidden +} + // IntFlag is a flag with type int type IntFlag struct { Name string @@ -332,6 +422,16 @@ func (f *IntFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *IntFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntFlag) IsVisible() bool { + return !f.Hidden +} + // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -366,6 +466,16 @@ func (f *Int64Flag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Int64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64Flag) IsVisible() bool { + return !f.Hidden +} + // StringFlag is a flag with type string type StringFlag struct { Name string @@ -402,6 +512,16 @@ func (f *StringFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *StringFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringFlag) IsVisible() bool { + return !f.Hidden +} + // DurationFlag is a flag with type time.Duration type DurationFlag struct { Name string @@ -436,6 +556,16 @@ func (f *DurationFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *DurationFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *DurationFlag) IsVisible() bool { + return !f.Hidden +} + // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -470,6 +600,16 @@ func (f *UintFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *UintFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *UintFlag) IsVisible() bool { + return !f.Hidden +} + // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -504,4 +644,14 @@ func (f *Uint64Flag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Uint64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Uint64Flag) IsVisible() bool { + return !f.Hidden +} + // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index b8363d4a35..1d9afdaa71 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -16,6 +16,18 @@ func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestFloat64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Float64SliceFlag{} + + _ = f.IsRequired() +} + +func TestFloat64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Float64SliceFlag{} + + _ = f.IsVisible() +} + func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.GenericFlag{} @@ -29,6 +41,18 @@ func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.GenericFlag{} + + _ = f.IsRequired() +} + +func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.GenericFlag{} + + _ = f.IsVisible() +} + func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Int64SliceFlag{} @@ -36,6 +60,18 @@ func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestInt64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Int64SliceFlag{} + + _ = f.IsRequired() +} + +func TestInt64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Int64SliceFlag{} + + _ = f.IsVisible() +} + func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.IntSliceFlag{} @@ -43,6 +79,18 @@ func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestIntSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.IntSliceFlag{} + + _ = f.IsRequired() +} + +func TestIntSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.IntSliceFlag{} + + _ = f.IsVisible() +} + func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.PathFlag{} @@ -56,6 +104,18 @@ func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.PathFlag{} + + _ = f.IsRequired() +} + +func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.PathFlag{} + + _ = f.IsVisible() +} + func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.StringSliceFlag{} @@ -63,6 +123,18 @@ func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestStringSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.StringSliceFlag{} + + _ = f.IsRequired() +} + +func TestStringSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.StringSliceFlag{} + + _ = f.IsVisible() +} + func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.TimestampFlag{} @@ -76,6 +148,18 @@ func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.TimestampFlag{} + + _ = f.IsRequired() +} + +func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.TimestampFlag{} + + _ = f.IsVisible() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} @@ -89,6 +173,18 @@ func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.BoolFlag{} + + _ = f.IsRequired() +} + +func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.BoolFlag{} + + _ = f.IsVisible() +} + func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Float64Flag{} @@ -102,6 +198,18 @@ func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Float64Flag{} + + _ = f.IsRequired() +} + +func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Float64Flag{} + + _ = f.IsVisible() +} + func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.IntFlag{} @@ -115,6 +223,18 @@ func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.IntFlag{} + + _ = f.IsRequired() +} + +func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.IntFlag{} + + _ = f.IsVisible() +} + func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Int64Flag{} @@ -128,6 +248,18 @@ func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Int64Flag{} + + _ = f.IsRequired() +} + +func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Int64Flag{} + + _ = f.IsVisible() +} + func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.StringFlag{} @@ -141,6 +273,18 @@ func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.StringFlag{} + + _ = f.IsRequired() +} + +func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.StringFlag{} + + _ = f.IsVisible() +} + func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.DurationFlag{} @@ -154,6 +298,18 @@ func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.DurationFlag{} + + _ = f.IsRequired() +} + +func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.DurationFlag{} + + _ = f.IsVisible() +} + func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintFlag{} @@ -167,6 +323,18 @@ func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.UintFlag{} + + _ = f.IsRequired() +} + +func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.UintFlag{} + + _ = f.IsVisible() +} + func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Uint64Flag{} @@ -180,4 +348,16 @@ func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Uint64Flag{} + + _ = f.IsRequired() +} + +func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Uint64Flag{} + + _ = f.IsVisible() +} + // vim:ro From c46856627277addb4291b2e7371119de440ff835 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 20:49:08 -0400 Subject: [PATCH 10/18] Touching up more mkdocs details --- .github/workflows/cli.yml | 14 ++++++++++++++ .gitignore | 2 +- Makefile | 12 ++++++++++++ docs/CNAME | 1 + docs/CODE_OF_CONDUCT.md | 1 + docs/CONTRIBUTING.md | 22 ++++++++++++++++++++++ docs/index.md | 5 +++-- mkdocs-requirements.txt | 8 ++++---- 8 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 docs/CNAME create mode 120000 docs/CODE_OF_CONDUCT.md diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 84f9cae01d..6983404ee8 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -92,3 +92,17 @@ jobs: run: | git diff --exit-code git diff --cached --exit-code + + publish: + if: startswith(github.ref, 'refs/tags/') + name: publish + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Publish Docs + uses: mhausenblas/mkdocs-deploy-gh-pages@master + env: + GITHUB_TOKEN: ${{ secrets.MKDOCS_PUBLISH_GITHUB_TOKEN }} + REQUIREMENTS: mkdocs-requirements.txt diff --git a/.gitignore b/.gitignore index e0c50aba49..c04fcc5389 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ *.coverprofile *.orig -node_modules/ vendor .idea internal/*/built-example coverage.txt /.local/ +/site/ *.exe diff --git a/Makefile b/Makefile index 52e9204d6c..ba783cb426 100644 --- a/Makefile +++ b/Makefile @@ -30,3 +30,15 @@ gfmrun: .PHONY: toc toc: go run internal/build/build.go toc docs/v2/manual.md + +.PHONY: docs +docs: + mkdocs build + +.PHONY: docs-deps +docs-deps: + pip install -r mkdocs-requirements.txt + +.PHONY: serve-docs +serve-docs: + mkdocs serve diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000000..8654f8f01b --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +cli.urfave.org diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 120000 index 0000000000..0400d57460 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +../CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 37055fff38..7e80bdbf13 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -98,6 +98,28 @@ line help system which may be consulted for further information, e.g.: go run internal/genflags/cmd/genflags/main.go --help ``` +#### docs output + +The documentation in the `docs` directory is automatically built via `mkdocs` into a +static site and published when releases are pushed (see [RELEASING](./RELEASING/)). There +is no strict requirement to build the documentation when developing locally, but the +following `make` targets may be used if desired: + +```sh +# install documentation dependencies with `pip` +make docs-deps +``` + +```sh +# build the static site in `./site` +make docs +``` + +```sh +# start an mkdocs development server +make serve-docs +``` + ### pull requests Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli diff --git a/docs/index.md b/docs/index.md index d9d5d465b9..792704494a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,5 +16,6 @@ These are the guides for each major supported version: In addition to the version-specific guides, these other documents are available: -- [`CONTRIBUTING`](./CONTRIBUTING/) -- [`RELEASING`](./RELEASING/) +- [CONTRIBUTING](./CONTRIBUTING/) +- [CODE OF CONDUCT](./CODE_OF_CONDUCT/) +- [RELEASING](./RELEASING/) diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt index 94dae76e86..1c2f87447a 100644 --- a/mkdocs-requirements.txt +++ b/mkdocs-requirements.txt @@ -1,4 +1,4 @@ -mkdocs -mkdocs-material -mkdocs-git-revision-date-localized-plugin -pygments +mkdocs~=1.3 +mkdocs-material~=8.2 +mkdocs-git-revision-date-localized-plugin~=1.0 +pygments~=2.12 From f3cf7640c7b846daeb4bedfb4b1e70334ffe8039 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2022 08:49:15 -0400 Subject: [PATCH 11/18] Backing out build tag for suggestions per recommendation from @kolyshkin :bow: --- .github/workflows/cli.yml | 8 +------- Makefile | 4 ++-- README.md | 10 ---------- docs.go | 4 ++-- docs_test.go | 4 ++-- internal/build/build.go | 2 +- suggestions.go | 3 --- suggestions_stubs.go | 12 ------------ suggestions_test.go | 3 --- 9 files changed, 8 insertions(+), 42 deletions(-) delete mode 100644 suggestions_stubs.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index caab03dfa3..a7c395af9b 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -37,15 +37,9 @@ jobs: - name: vet run: go run internal/build/build.go vet - - name: test with urfave_cli_core tag - run: go run internal/build/build.go -tags urfave_cli_core test - - name: test with urfave_cli_no_docs tag run: go run internal/build/build.go -tags urfave_cli_no_docs test - - name: test with urfave_cli_no_suggest tag - run: go run internal/build/build.go -tags urfave_cli_no_suggest test - - name: test run: go run internal/build/build.go test @@ -53,7 +47,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: check-binary-size with tags (informational only) - run: go run internal/build/build.go -tags urfave_cli_core check-binary-size + run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size - name: Upload coverage to Codecov if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' diff --git a/Makefile b/Makefile index 6ab812a5af..ba783cb426 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,11 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun t .PHONY: tag-test tag-test: - go run internal/build/build.go -tags urfave_cli_core test + go run internal/build/build.go -tags urfave_cli_no_docs test .PHONY: tag-check-binary-size tag-check-binary-size: - go run internal/build/build.go -tags urfave_cli_core check-binary-size + go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size .PHONY: gfmrun gfmrun: diff --git a/README.md b/README.md index 35694d2195..beb5963e2c 100644 --- a/README.md +++ b/README.md @@ -59,22 +59,12 @@ import ( You can use the following build tags: -#### `urfave_cli_core` - -When set, applies all `urfave_cli_no.+` build tags to minimize resulting binary -size. - #### `urfave_cli_no_docs` When set, this removes `ToMarkdown` and `ToMan` methods, so your application won't be able to call those. This reduces the resulting binary size by about 300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies. -#### `urfave_cli_no_suggest` - -When set, the capability enabled by setting `App.Suggest` will be a no-op. This -reduces the resulting binary size due to fewer dependencies. - ### GOPATH Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can diff --git a/docs.go b/docs.go index 4aba31bc40..8b1c9c8a2c 100644 --- a/docs.go +++ b/docs.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs && !urfave_cli_core -// +build !urfave_cli_no_docs,!urfave_cli_core +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs package cli diff --git a/docs_test.go b/docs_test.go index bc3703f514..12d5d3c8b1 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs && !urfave_cli_core -// +build !urfave_cli_no_docs,!urfave_cli_core +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs package cli diff --git a/internal/build/build.go b/internal/build/build.go index e74acfbf41..929ab0fbf9 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -242,7 +242,7 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { tags := c.String("tags") - if strings.Contains(tags, "urfave_cli_core") || strings.Contains(tags, "urfave_cli_no_docs") { + if strings.Contains(tags, "urfave_cli_no_docs") { desiredMinBinarySize = 1.39 } diff --git a/suggestions.go b/suggestions.go index 301c598e9e..476af4de50 100644 --- a/suggestions.go +++ b/suggestions.go @@ -1,6 +1,3 @@ -//go:build !urfave_cli_no_suggest && !urfave_cli_core -// +build !urfave_cli_no_suggest,!urfave_cli_core - package cli import ( diff --git a/suggestions_stubs.go b/suggestions_stubs.go deleted file mode 100644 index 48643bb401..0000000000 --- a/suggestions_stubs.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build urfave_cli_no_suggest || urfave_cli_core -// +build urfave_cli_no_suggest urfave_cli_core - -package cli - -func (a *App) suggestFlagFromError(err error, _ string) (string, error) { - return "", err -} - -func suggestCommand([]*Command, string) string { - return "" -} diff --git a/suggestions_test.go b/suggestions_test.go index fa7b0c9c8c..4ebe9c0c7f 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -1,6 +1,3 @@ -//go:build !urfave_cli_no_suggest && !urfave_cli_core -// +build !urfave_cli_no_suggest,!urfave_cli_core - package cli import ( From 061250ade5928b4d48941efc8b1e828b8a651a1e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:30:07 -0400 Subject: [PATCH 12/18] Use correctly spelled repo name in mkdocs config --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index c963d37285..73b88c5093 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,7 +6,7 @@ site_name: urfave/cli site_url: https://cli.urfave.org/ -repo_url: https://github.com/urvafe/cli +repo_url: https://github.com/urfave/cli edit_uri: edit/main/docs/ nav: - Home: index.md From 522b7e0d929530d0a753bac01742028585c68473 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:46:36 -0400 Subject: [PATCH 13/18] Use existing secret name in docs publish step --- .github/workflows/cli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index a7c395af9b..61c9474cef 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -94,7 +94,7 @@ jobs: git diff --cached --exit-code publish: - if: startswith(github.ref, 'refs/tags/') + #if: startswith(github.ref, 'refs/tags/') name: publish runs-on: ubuntu-latest steps: @@ -104,5 +104,5 @@ jobs: - name: Publish Docs uses: mhausenblas/mkdocs-deploy-gh-pages@master env: - GITHUB_TOKEN: ${{ secrets.MKDOCS_PUBLISH_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REQUIREMENTS: mkdocs-requirements.txt From 70e1ed4c9d8f80940cbfc3c7328ecb626e709cc2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:58:46 -0400 Subject: [PATCH 14/18] Attempt to work around materialx yaml loader boom --- .github/workflows/cli.yml | 1 + mkdocs-requirements.txt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 61c9474cef..77840f79c0 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -96,6 +96,7 @@ jobs: publish: #if: startswith(github.ref, 'refs/tags/') name: publish + needs: [test-docs] runs-on: ubuntu-latest steps: - name: Checkout Code diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt index 1c2f87447a..482ad0622d 100644 --- a/mkdocs-requirements.txt +++ b/mkdocs-requirements.txt @@ -1,4 +1,5 @@ -mkdocs~=1.3 -mkdocs-material~=8.2 mkdocs-git-revision-date-localized-plugin~=1.0 +mkdocs-material-extensions~=1.0 +mkdocs-material~=8.2 +mkdocs~=1.3 pygments~=2.12 From 5988cc604001d76f5a32195f7a3390dfbd3e3570 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:13:45 -0400 Subject: [PATCH 15/18] Try running mkdocs steps directly --- .github/workflows/cli.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 77840f79c0..cf88e912ba 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -12,6 +12,7 @@ on: jobs: test: + if: false strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -102,8 +103,13 @@ jobs: - name: Checkout Code uses: actions/checkout@v3 + - name: Setup mkdocs + run: | + pip install -U pip + pip install -r mkdocs-requirements.txt + git remote rm origin + git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/urfave/cli.git + - name: Publish Docs - uses: mhausenblas/mkdocs-deploy-gh-pages@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REQUIREMENTS: mkdocs-requirements.txt + run: | + mkdocs gh-deploy --force From e592640fdb667f72992236770f1c76551b66b9b2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:17:57 -0400 Subject: [PATCH 16/18] Unshallow the clone + toc cleanups --- .github/workflows/cli.yml | 14 ++++++-------- Makefile | 6 +----- internal/build/build.go | 13 ------------- 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index cf88e912ba..cedc051bbd 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -77,18 +77,14 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies - run: - mkdir -p "${GITHUB_WORKSPACE}/.local/bin" && - curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" && - chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" && - npm install -g markdown-toc@1.2.0 + run: > + mkdir -p "${GITHUB_WORKSPACE}/.local/bin" + curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" + chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" - name: gfmrun run: go run internal/build/build.go gfmrun docs/v2/manual.md - - name: toc - run: go run internal/build/build.go toc docs/v2/manual.md - - name: diff check run: | git diff --exit-code @@ -102,6 +98,8 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Setup mkdocs run: | diff --git a/Makefile b/Makefile index ba783cb426..3b0e5e0bbd 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # attention on files that are primarily Go. .PHONY: all -all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun toc v2diff +all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff # NOTE: this is a special catch-all rule to run any of the commands # defined in internal/build/build.go with optional arguments passed @@ -27,10 +27,6 @@ tag-check-binary-size: gfmrun: go run internal/build/build.go gfmrun docs/v2/manual.md -.PHONY: toc -toc: - go run internal/build/build.go toc docs/v2/manual.md - .PHONY: docs docs: mkdocs build diff --git a/internal/build/build.go b/internal/build/build.go index 929ab0fbf9..26967605b5 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -63,10 +63,6 @@ func main() { Name: "gfmrun", Action: GfmrunActionFunc, }, - { - Name: "toc", - Action: TocActionFunc, - }, { Name: "check-binary-size", Action: checkBinarySizeActionFunc, @@ -216,15 +212,6 @@ func GfmrunActionFunc(c *cli.Context) error { return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename) } -func TocActionFunc(c *cli.Context) error { - filename := c.Args().Get(0) - if filename == "" { - filename = "README.md" - } - - return runCmd("markdown-toc", "-i", filename) -} - // checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down // this was originally inspired by https://github.com/urfave/cli/issues/1055, and followed up on as a part // of https://github.com/urfave/cli/issues/1057 From e7f3925a5ab86b2df8f7c3b19d5f48651553c3ce Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:19:10 -0400 Subject: [PATCH 17/18] Use correct yaml multiline :facepalm: --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index cedc051bbd..7c55139621 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -77,7 +77,7 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies - run: > + run: | mkdir -p "${GITHUB_WORKSPACE}/.local/bin" curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" From b3359c3e2754318c4b52948c8779d41c37b9e1cc Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:20:47 -0400 Subject: [PATCH 18/18] Re-enable workflow conditions --- .github/workflows/cli.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7c55139621..3b31afc5c0 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -12,7 +12,6 @@ on: jobs: test: - if: false strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -91,7 +90,7 @@ jobs: git diff --cached --exit-code publish: - #if: startswith(github.ref, 'refs/tags/') + if: startswith(github.ref, 'refs/tags/') name: publish needs: [test-docs] runs-on: ubuntu-latest