diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fb40948 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: CI + +on: push + +jobs: + test: + strategy: + matrix: + go: ["1.12", "1.13", "1.14"] + runs-on: ubuntu-latest + steps: + - name: Set up Go ${{ matrix.go }} + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go }} + id: go + - name: Checkout the code + uses: actions/checkout@v2 + + - name: Test with Go ${{ matrix.go }} + run: go test -v -coverprofile=profile.cov ./... + + - name: Send coverage with Go ${{ matrix.go }} + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: profile.cov + flag-name: Go-${{ matrix.go }} + parallel: true + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.14 + id: go + + - name: lint + uses: golangci/golangci-lint-action@v1 + with: + version: v1.28 + + readme: + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.14 + id: go + + + - name: Checkout the code + uses: actions/checkout@v2 + + - name: Set GOPATH + run: echo ::set-env name=GOPATH::$GITHUB_WORKSPACE/go + + - name: Readme Check + run: export PATH="$GOPATH/bin:$PATH"; make readmecheck.setup readmecheck + + finish: + needs: test + runs-on: ubuntu-latest + steps: + - uses: shogo82148/actions-goveralls@v1 + with: + parallel-finished: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d0b996c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release + +# Triggered only when a tag is pushed +on: + push: + branches: + - "!*" + tags: + - "v*" +jobs: + release: + runs-on: ubuntu-latest + name: Release + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Release the application + uses: goreleaser/goreleaser-action@v2 + with: + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..91f026f --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,12 @@ +project_name: mow.cli +build: + skip: true +release: + github: + prerelease: auto +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f5ebe78..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: go -go: -- "1.12" -- "1.11" - -sudo: false - -env: - - GO111MODULE=on - -install: make setup - -script: make check test-cover - -deploy: - provider: releases - api_key: - secure: D2fwuS7n54ZkjfRmLMEAnwR2lQLa5xd+4s0l65pI6UN4ljVfQwQiFdZXaZWFr8PYSTKTm0UPj6Iqqw7VOl8I6iDzcaOqN3hoh5mDaJ7kyo7GZRPodwK9MKdOp5dPw459L2Atll8kxb4iYmfnjmcl0lDCUuWvMxXx+zgiFTB7BO0= - skip_cleanup: true - on: - tags: true - go: "1.12" diff --git a/Makefile b/Makefile index 4e7f2a6..7e8d7dd 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,5 @@ test: go test ./... - -test-cover: - go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' ./... | xargs -L 1 sh -c - gover - goveralls -coverprofile=gover.coverprofile -service=travis-ci check: readmecheck bin/golangci-lint run @@ -13,16 +8,16 @@ autoreadme -f readmecheck: - sed '$ d' README.md > README.original.md + head -n -1 README.md > README.original.md autoreadme -f - sed '$ d' README.md > README.generated.md + head -n -1 README.md > README.generated.md diff README.generated.md README.original.md -setup: - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls - go get github.com/modocache/gover - go get github.com/divan/autoreadme - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.16.0 +lint.setup: + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.28.0 -.PHONY: test check lint vet fmtcheck ineffassign readmecheck + +readmecheck.setup: + go get github.com/jawher/autoreadme + +.PHONY: test check doc readmecheck lint.setup readmecheck.setup diff --git a/README.md b/README.md index 873c0b4..3532925 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # mow.cli -[![Build Status](https://travis-ci.org/jawher/mow.cli.svg?branch=master)](https://travis-ci.org/jawher/mow.cli) -[![GoDoc](https://godoc.org/github.com/github.com/jawher/mow.cli?status.svg)](https://godoc.org/github.com/jawher/mow.cli) +![CI](https://github.com/jawher/mow.cli/workflows/CI/badge.svg) +[![GoDoc](https://pkg.go.dev/badge/github.com/jawher/mow.cli)](https://pkg.go.dev/github.com/jawher/mow.cli) [![Coverage Status](https://coveralls.io/repos/github/jawher/mow.cli/badge.svg?branch=master)](https://coveralls.io/github/jawher/mow.cli?branch=master) Package cli provides a framework to build command line applications in Go with most of the burden of arguments parsing and validation placed on the framework instead of the user. @@ -107,7 +107,7 @@ familiar commands such as git and docker. We build a fictional utility called uman to manage users in a system. It provides two commands that can be invoked: list and get. The list command takes an optional flag to specify all users -including disabled ones. The get command requries one argument, the user ID, +including disabled ones. The get command requires one argument, the user ID, and takes an optional flag to specify a detailed listing. ```go @@ -155,7 +155,7 @@ // Declare our second command, which is invocable with "uman get" app.Command("get", "get a user details", func(cmd *cli.Cmd) { var ( - detailed = cmd.BoolOpt("detailed", false, "Disaply detailed info") + detailed = cmd.BoolOpt("detailed", false, "Display detailed info") id = cmd.StringArg("ID", "", "The user id to display") ) @@ -616,6 +616,16 @@ $ docker job log clear ``` +Commands can be hidden in the help messages. +This can prove useful to deprecate a command so that it does not appear to new users in the help, but still exists to not break existing scripts. +To hide a command, set the Hidden field to true: + +``` +app.Command("login", "login to the backend (DEPRECATED: please use auth instead)", func(cmd *cli.Cmd)) { + cmd.Hidden = true +} +``` + As a convenience, to assign an Action to a func with no arguments, use ActionCommand when defining the Command. For example, the following two statements are equivalent: @@ -1062,4 +1072,4 @@ Please see the `LICENSE` file for details. * * * -Automatically generated by [autoreadme](https://github.com/jimmyfrasche/autoreadme) on 2019.02.24 +Automatically generated by [autoreadme](https://github.com/jawher/autoreadme) on 2020.08.08 diff --git a/README.md.template b/README.md.template index 73d4b89..dc3872f 100644 --- a/README.md.template +++ b/README.md.template @@ -1,7 +1,7 @@ # mow.cli -[![Build Status](https://travis-ci.org/{{.RepoPath}}.svg?branch=master)](https://travis-ci.org/{{.RepoPath}}) -[![GoDoc](https://godoc.org/github.com/{{.Import}}?status.svg)](https://godoc.org/{{.Import}}) -[![Coverage Status](https://coveralls.io/repos/github/{{.RepoPath}}/badge.svg?branch=master)](https://coveralls.io/github/{{.RepoPath}}?branch=master) +![CI](https://github.com/jawher/mow.cli/workflows/CI/badge.svg) +[![GoDoc](https://pkg.go.dev/badge/github.com/jawher/mow.cli)](https://pkg.go.dev/github.com/jawher/mow.cli) +[![Coverage Status](https://coveralls.io/repos/github/jawher/mow.cli/badge.svg?branch=master)](https://coveralls.io/github/jawher/mow.cli?branch=master) {{.Synopsis}} @@ -107,7 +107,7 @@ familiar commands such as git and docker. We build a fictional utility called uman to manage users in a system. It provides two commands that can be invoked: list and get. The list command takes an optional flag to specify all users -including disabled ones. The get command requries one argument, the user ID, +including disabled ones. The get command requires one argument, the user ID, and takes an optional flag to specify a detailed listing. ```go @@ -155,7 +155,7 @@ // Declare our second command, which is invocable with "uman get" app.Command("get", "get a user details", func(cmd *cli.Cmd) { var ( - detailed = cmd.BoolOpt("detailed", false, "Disaply detailed info") + detailed = cmd.BoolOpt("detailed", false, "Display detailed info") id = cmd.StringArg("ID", "", "The user id to display") ) @@ -305,4 +305,4 @@ Please see the `LICENSE` file for details. * * * -Automatically generated by [autoreadme](https://github.com/jimmyfrasche/autoreadme) on {{.Today}} +Automatically generated by [autoreadme](https://github.com/jawher/autoreadme) on {{.Today}} diff --git a/args.go b/args.go index 3fb69a9..be80b2f 100644 --- a/args.go +++ b/args.go @@ -394,6 +394,8 @@ panic(fmt.Sprintf("duplicate argument name %q", arg.Name)) } + arg.DefaultValue = values.DefaultValue(arg.Value) + arg.ValueSetFromEnv = values.SetFromEnv(arg.Value, arg.EnvVar) c.args = append(c.args, &arg) diff --git a/cli_test.go b/cli_test.go index d750eb1..6617d16 100644 --- a/cli_test.go +++ b/cli_test.go @@ -780,82 +780,119 @@ func TestHelpAndVersionWithOptionsEnd(t *testing.T) { for _, flag := range []string{"-h", "--help", "-v", "--version"} { - testHelpAndVersionWithOptionsEnd(flag, t) + t.Run(flag, func(t *testing.T) { + testHelpAndVersionWithOptionsEnd(flag, t) + }) } } var genGolden = flag.Bool("g", false, "Generate golden file(s)") func TestHelpMessage(t *testing.T) { - var out, err string - defer captureAndRestoreOutput(&out, &err)() - - exitCalled := false - defer exitShouldBeCalledWith(t, 0, &exitCalled)() - - app := App("app", "App Desc") - app.Spec = "[-bdsuikqs] BOOL1 [STR1] INT3..." - - // Options - app.Bool(BoolOpt{Name: "b bool1 u uuu", Value: false, EnvVar: "BOOL1", Desc: "Bool Option 1"}) - app.Bool(BoolOpt{Name: "bool2", Value: true, EnvVar: " ", Desc: "Bool Option 2"}) - app.Bool(BoolOpt{Name: "d", Value: true, EnvVar: "BOOL3", Desc: "Bool Option 3", HideValue: true}) - - app.String(StringOpt{Name: "s str1", Value: "", EnvVar: "STR1", Desc: "String Option 1"}) - app.String(StringOpt{Name: "str2", Value: "a value", Desc: "String Option 2"}) - app.String(StringOpt{Name: "v", Value: "another value", EnvVar: "STR3", Desc: "String Option 3", HideValue: true}) - - app.Int(IntOpt{Name: "i int1", Value: 0, EnvVar: "INT1 ALIAS_INT1"}) - app.Int(IntOpt{Name: "int2", Value: 1, EnvVar: "INT2", Desc: "Int Option 2"}) - app.Int(IntOpt{Name: "k", Value: 1, EnvVar: "INT3", Desc: "Int Option 3", HideValue: true}) - - app.Strings(StringsOpt{Name: "x strs1", Value: nil, EnvVar: "STRS1", Desc: "Strings Option 1"}) - app.Strings(StringsOpt{Name: "strs2", Value: []string{"value1", "value2"}, EnvVar: "STRS2", Desc: "Strings Option 2"}) - app.Strings(StringsOpt{Name: "z", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Option 3", HideValue: true}) - - app.Ints(IntsOpt{Name: "q ints1", Value: nil, EnvVar: "INTS1", Desc: "Ints Option 1"}) - app.Ints(IntsOpt{Name: "ints2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Option 2"}) - app.Ints(IntsOpt{Name: "j", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Option 3", HideValue: true}) - - // Args - app.Bool(BoolArg{Name: "BOOL1", Value: false, EnvVar: "BOOL1", Desc: "Bool Argument 1"}) - app.Bool(BoolArg{Name: "BOOL2", Value: true, Desc: "Bool Argument 2"}) - app.Bool(BoolArg{Name: "BOOL3", Value: true, EnvVar: "BOOL3", Desc: "Bool Argument 3", HideValue: true}) - - app.String(StringArg{Name: "STR1", Value: "", EnvVar: "STR1", Desc: "String Argument 1"}) - app.String(StringArg{Name: "STR2", Value: "a value", EnvVar: "STR2", Desc: "String Argument 2"}) - app.String(StringArg{Name: "STR3", Value: "another value", EnvVar: "STR3", Desc: "String Argument 3", HideValue: true}) - - app.Int(IntArg{Name: "INT1", Value: 0, EnvVar: "INT1", Desc: "Int Argument 1"}) - app.Int(IntArg{Name: "INT2", Value: 1, EnvVar: "INT2", Desc: "Int Argument 2"}) - app.Int(IntArg{Name: "INT3", Value: 1, EnvVar: "INT3", Desc: "Int Argument 3", HideValue: true}) - - app.Strings(StringsArg{Name: "STRS1", Value: nil, EnvVar: "STRS1", Desc: "Strings Argument 1"}) - app.Strings(StringsArg{Name: "STRS2", Value: []string{"value1", "value2"}, EnvVar: "STRS2"}) - app.Strings(StringsArg{Name: "STRS3", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Argument 3", HideValue: true}) - - app.Ints(IntsArg{Name: "INTS1", Value: nil, EnvVar: "INTS1", Desc: "Ints Argument 1"}) - app.Ints(IntsArg{Name: "INTS2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Argument 2"}) - app.Ints(IntsArg{Name: "INTS3", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Argument 3", HideValue: true}) - - app.Action = func() {} - - app.Command("command1", "command1 description", func(cmd *Cmd) {}) - app.Command("command2", "command2 description", func(cmd *Cmd) {}) - app.Command("command3", "command3 description", func(cmd *Cmd) {}) - - require.NoError(t, - app.Run([]string{"app", "-h"})) - - if *genGolden { - require.NoError(t, - ioutil.WriteFile("testdata/help-output.txt.golden", []byte(err), 0644)) - } - - expected, e := ioutil.ReadFile("testdata/help-output.txt") - require.NoError(t, e, "Failed to read the expected help output from testdata/help-output.txt") - - require.Equal(t, expected, []byte(err)) + cases := []struct { + name string + params []string + env map[string]string + exitCode int + }{ + {name: "top-help", params: []string{"app", "-h"}}, + {name: "top-help-i-user", params: []string{"app", "-i=5"}, exitCode: 2}, + {name: "top-help-i-env", params: []string{"app"}, env: map[string]string{"INT1": "25"}, exitCode: 2}, + {name: "command1", params: []string{"app", "command1", "-h"}}, + {name: "command2", params: []string{"app", "command2", "-h"}}, + {name: "command3", params: []string{"app", "command3", "-h"}}, + {name: "command3-child1", params: []string{"app", "command3", "child1", "-h"}}, + {name: "command3-child2", params: []string{"app", "command3", "child2", "-h"}}, + {name: "command4", params: []string{"app", "command4", "-h"}}, + } + for _, cas := range cases { + cas := cas + t.Run(cas.name, func(t *testing.T) { + t.Logf("case: %+v", cas) + var out, stdErr string + defer captureAndRestoreOutput(&out, &stdErr)() + defer setAndRestoreEnv(cas.env)() + + exitCalled := false + defer exitShouldBeCalledWith(t, cas.exitCode, &exitCalled)() + + app := App("app", "App Desc") + app.Spec = "[-bdsuikqs] [BOOL1 STR1 INT3...]" + + // Options + app.Bool(BoolOpt{Name: "b bool1 u uuu", Value: false, EnvVar: "BOOL1", Desc: "Bool Option 1"}) + app.Bool(BoolOpt{Name: "bool2", Value: true, EnvVar: " ", Desc: "Bool Option 2"}) + app.Bool(BoolOpt{Name: "d", Value: true, EnvVar: "BOOL3", Desc: "Bool Option 3", HideValue: true}) + + app.String(StringOpt{Name: "s str1", Value: "", EnvVar: "STR1", Desc: "String Option 1"}) + app.String(StringOpt{Name: "str2", Value: "a value", Desc: "String Option 2"}) + app.String(StringOpt{Name: "v", Value: "another value", EnvVar: "STR3", Desc: "String Option 3", HideValue: true}) + + app.Int(IntOpt{Name: "i int1", Value: 0, EnvVar: "INT1 ALIAS_INT1"}) + app.Int(IntOpt{Name: "int2", Value: 1, EnvVar: "INT2", Desc: "Int Option 2"}) + app.Int(IntOpt{Name: "k", Value: 1, EnvVar: "INT3", Desc: "Int Option 3", HideValue: true}) + + app.Strings(StringsOpt{Name: "x strs1", Value: nil, EnvVar: "STRS1", Desc: "Strings Option 1"}) + app.Strings(StringsOpt{Name: "strs2", Value: []string{"value1", "value2"}, EnvVar: "STRS2", Desc: "Strings Option 2"}) + app.Strings(StringsOpt{Name: "z", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Option 3", HideValue: true}) + + app.Ints(IntsOpt{Name: "q ints1", Value: nil, EnvVar: "INTS1", Desc: "Ints Option 1"}) + app.Ints(IntsOpt{Name: "ints2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Option 2"}) + app.Ints(IntsOpt{Name: "j", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Option 3", HideValue: true}) + + // Args + app.Bool(BoolArg{Name: "BOOL1", Value: false, EnvVar: "BOOL1", Desc: "Bool Argument 1"}) + app.Bool(BoolArg{Name: "BOOL2", Value: true, Desc: "Bool Argument 2"}) + app.Bool(BoolArg{Name: "BOOL3", Value: true, EnvVar: "BOOL3", Desc: "Bool Argument 3", HideValue: true}) + + app.String(StringArg{Name: "STR1", Value: "", EnvVar: "STR1", Desc: "String Argument 1"}) + app.String(StringArg{Name: "STR2", Value: "a value", EnvVar: "STR2", Desc: "String Argument 2"}) + app.String(StringArg{Name: "STR3", Value: "another value", EnvVar: "STR3", Desc: "String Argument 3", HideValue: true}) + + app.Int(IntArg{Name: "INT1", Value: 0, EnvVar: "INT1", Desc: "Int Argument 1"}) + app.Int(IntArg{Name: "INT2", Value: 1, EnvVar: "INT2", Desc: "Int Argument 2"}) + app.Int(IntArg{Name: "INT3", Value: 1, EnvVar: "INT3", Desc: "Int Argument 3", HideValue: true}) + + app.Strings(StringsArg{Name: "STRS1", Value: nil, EnvVar: "STRS1", Desc: "Strings Argument 1"}) + app.Strings(StringsArg{Name: "STRS2", Value: []string{"value1", "value2"}, EnvVar: "STRS2"}) + app.Strings(StringsArg{Name: "STRS3", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Argument 3", HideValue: true}) + + app.Ints(IntsArg{Name: "INTS1", Value: nil, EnvVar: "INTS1", Desc: "Ints Argument 1"}) + app.Ints(IntsArg{Name: "INTS2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Argument 2"}) + app.Ints(IntsArg{Name: "INTS3", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Argument 3", HideValue: true}) + + app.Command("command1", "command1 description", func(cmd *Cmd) {}) + app.Command("command2", "command2 description", func(cmd *Cmd) {}) + app.Command("command3", "command3 description", func(cmd *Cmd) { + cmd.Command("child1", "child1 description", func(cmd *Cmd) { + cmd.StringArg("ARG1", "", "arg1 desc") + }) + cmd.Command("child2", "child2 description", func(cmd *Cmd) { + cmd.Hidden = true + cmd.StringOpt("o opt", "", "opt desc") + }) + }) + app.Command("command4", "command4 description", func(cmd *Cmd) { + cmd.Hidden = true + }) + + fmt.Printf("calling app with %+v\n", cas.params) + require.NoError(t, + app.Run(cas.params)) + + filename := fmt.Sprintf("testdata/help-output-%s.txt", cas.name) + + if *genGolden { + require.NoError(t, + ioutil.WriteFile(filename, []byte(stdErr), 0644)) + } + + expected, e := ioutil.ReadFile(filename) + require.NoError(t, e, "Failed to read the expected help output from %s", filename) + + require.Equal(t, string(expected), stdErr) + }) + } } func TestLongHelpMessage(t *testing.T) { @@ -2201,6 +2238,20 @@ return captureAndRestoreOutput(nil, nil) } +func setAndRestoreEnv(env map[string]string) func() { + backup := map[string]string{} + for k, v := range env { + backup[k] = os.Getenv(k) + os.Setenv(k, v) + } + + return func() { + for k, v := range backup { + os.Setenv(k, v) + } + } +} + func captureAndRestoreOutput(out, err *string) func() { oldStdOut := stdOut oldStdErr := stdErr diff --git a/commands.go b/commands.go index 775c52e..2bcb335 100644 --- a/commands.go +++ b/commands.go @@ -12,7 +12,6 @@ "github.com/jawher/mow.cli/internal/fsm" "github.com/jawher/mow.cli/internal/lexer" "github.com/jawher/mow.cli/internal/parser" - "github.com/jawher/mow.cli/internal/values" ) /* @@ -31,6 +30,8 @@ Spec string // The command long description to be shown when help is requested LongDesc string + // Hide this command in the help messages + Hidden bool // The command error handling strategy ErrorHandling flag.ErrorHandling @@ -544,7 +545,7 @@ for _, arg := range c.args { var ( env = formatEnvVarsForHelp(arg.EnvVar) - value = formatValueForHelp(arg.HideValue, arg.Value) + value = formatValueForHelp(arg.HideValue, arg.DefaultValue) ) printTabbedRow(w, arg.Name, joinStrings(arg.Desc, env, value)) } @@ -557,21 +558,34 @@ var ( optNames = formatOptNamesForHelp(opt) env = formatEnvVarsForHelp(opt.EnvVar) - value = formatValueForHelp(opt.HideValue, opt.Value) + value = formatValueForHelp(opt.HideValue, opt.DefaultValue) ) printTabbedRow(w, optNames, joinStrings(opt.Desc, env, value)) } } - if len(c.commands) > 0 { + commands := make([]*Cmd, 0, len(c.commands)) + for _, c := range c.commands { + if err := c.doInit(); err != nil { + panic(err) + } + + if c.Hidden { + continue + } + + commands = append(commands, c) + } + + if len(commands) > 0 { fmt.Fprint(w, "\t\nCommands:\t\n") - for _, c := range c.commands { + for _, c := range commands { fmt.Fprintf(w, " %s\t%s\n", strings.Join(c.aliases, ", "), c.desc) } } - if len(c.commands) > 0 { + if len(commands) > 0 { fmt.Fprintf(w, "\t\nRun '%s COMMAND --help' for more information on a command.\n", path) } @@ -604,18 +618,16 @@ } } -func formatValueForHelp(hide bool, v flag.Value) string { +func formatValueForHelp(hide bool, v string) string { if hide { return "" } - if dv, ok := v.(values.DefaultValued); ok { - if dv.IsDefault() { - return "" - } - } - - return fmt.Sprintf("(default %s)", v.String()) + if v == "" { + return "" + } + + return fmt.Sprintf("(default %s)", v) } func formatEnvVarsForHelp(envVars string) string { diff --git a/debian/changelog b/debian/changelog index bc2f0e6..2b376f4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +golang-github-jawher-mow.cli (1.2.0-0kali1) UNRELEASED; urgency=low + + * New upstream release. + + -- Kali Janitor Wed, 14 Sep 2022 16:47:56 -0000 + golang-github-jawher-mow.cli (1.1.0-0kali1) kali-dev; urgency=medium * Initial release diff --git a/doc.go b/doc.go index 42ab57e..979e727 100644 --- a/doc.go +++ b/doc.go @@ -287,6 +287,14 @@ $ docker job r # using the alias we defined $ docker job log show $ docker job log clear + +Commands can be hidden in the help messages. +This can prove useful to deprecate a command so that it does not appear to new users in the help, but still exists to not break existing scripts. +To hide a command, set the Hidden field to true: + + app.Command("login", "login to the backend (DEPRECATED: please use auth instead)", func(cmd *cli.Cmd)) { + cmd.Hidden = true + } As a convenience, to assign an Action to a func with no arguments, use ActionCommand when defining the Command. For example, the following two diff --git a/go.mod b/go.mod index 4ab5f9b..04ddba2 100644 --- a/go.mod +++ b/go.mod @@ -2,5 +2,8 @@ require ( github.com/stretchr/objx v0.2.0 // indirect - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 + gopkg.in/yaml.v2 v2.2.5 // indirect ) + +go 1.13 diff --git a/go.sum b/go.sum index 5d84424..be96aa5 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,16 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/container/container.go b/internal/container/container.go index d5d858f..88c1cc1 100644 --- a/internal/container/container.go +++ b/internal/container/container.go @@ -14,4 +14,5 @@ ValueSetFromEnv bool ValueSetByUser *bool Value flag.Value + DefaultValue string } diff --git a/internal/values/utils.go b/internal/values/utils.go index af66c4b..1df142b 100644 --- a/internal/values/utils.go +++ b/internal/values/utils.go @@ -54,3 +54,12 @@ return nil } + +func DefaultValue(v flag.Value) string { + if dv, ok := v.(DefaultValued); ok { + if dv.IsDefault() { + return "" + } + } + return v.String() +} diff --git a/options.go b/options.go index 469d97d..cb723b1 100644 --- a/options.go +++ b/options.go @@ -467,6 +467,7 @@ } func (c *Cmd) mkOpt(opt container.Container) { + opt.DefaultValue = values.DefaultValue(opt.Value) opt.ValueSetFromEnv = values.SetFromEnv(opt.Value, opt.EnvVar) opt.Names = mkOptStrs(opt.Name) diff --git a/testdata/help-output-command1.txt b/testdata/help-output-command1.txt new file mode 100644 index 0000000..df6279d --- /dev/null +++ b/testdata/help-output-command1.txt @@ -0,0 +1,4 @@ + +Usage: app command1 + +command1 description diff --git a/testdata/help-output-command2.txt b/testdata/help-output-command2.txt new file mode 100644 index 0000000..add3d75 --- /dev/null +++ b/testdata/help-output-command2.txt @@ -0,0 +1,4 @@ + +Usage: app command2 + +command2 description diff --git a/testdata/help-output-command3-child1.txt b/testdata/help-output-command3-child1.txt new file mode 100644 index 0000000..543e181 --- /dev/null +++ b/testdata/help-output-command3-child1.txt @@ -0,0 +1,7 @@ + +Usage: app command3 child1 ARG1 + +child1 description + +Arguments: + ARG1 arg1 desc diff --git a/testdata/help-output-command3-child2.txt b/testdata/help-output-command3-child2.txt new file mode 100644 index 0000000..9707a7c --- /dev/null +++ b/testdata/help-output-command3-child2.txt @@ -0,0 +1,7 @@ + +Usage: app command3 child2 [OPTIONS] + +child2 description + +Options: + -o, --opt opt desc diff --git a/testdata/help-output-command3.txt b/testdata/help-output-command3.txt new file mode 100644 index 0000000..ff2ed90 --- /dev/null +++ b/testdata/help-output-command3.txt @@ -0,0 +1,9 @@ + +Usage: app command3 COMMAND [arg...] + +command3 description + +Commands: + child1 child1 description + +Run 'app command3 COMMAND --help' for more information on a command. diff --git a/testdata/help-output-command4.txt b/testdata/help-output-command4.txt new file mode 100644 index 0000000..db9c539 --- /dev/null +++ b/testdata/help-output-command4.txt @@ -0,0 +1,4 @@ + +Usage: app command4 + +command4 description diff --git a/testdata/help-output-top-help-i-env.txt b/testdata/help-output-top-help-i-env.txt new file mode 100644 index 0000000..dd4772f --- /dev/null +++ b/testdata/help-output-top-help-i-env.txt @@ -0,0 +1,45 @@ + +Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...] + +App Desc + +Arguments: + BOOL1 Bool Argument 1 (env $BOOL1) + BOOL2 Bool Argument 2 (default true) + BOOL3 Bool Argument 3 (env $BOOL3) + STR1 String Argument 1 (env $STR1) + STR2 String Argument 2 (env $STR2) (default "a value") + STR3 String Argument 3 (env $STR3) + INT1 Int Argument 1 (env $INT1) (default 0) + INT2 Int Argument 2 (env $INT2) (default 1) + INT3 Int Argument 3 (env $INT3) + STRS1 Strings Argument 1 (env $STRS1) + STRS2 (env $STRS2) (default ["value1", "value2"]) + STRS3 Strings Argument 3 (env $STRS3) + INTS1 Ints Argument 1 (env $INTS1) + INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3]) + INTS3 Ints Argument 3 (env $INTS3) + +Options: + -b, --bool1 Bool Option 1 (env $BOOL1) + --bool2 Bool Option 2 (default true) + -d Bool Option 3 (env $BOOL3) + -s, --str1 String Option 1 (env $STR1) + --str2 String Option 2 (default "a value") + -v String Option 3 (env $STR3) + -i, --int1 (env $INT1, $ALIAS_INT1) (default 0) + --int2 Int Option 2 (env $INT2) (default 1) + -k Int Option 3 (env $INT3) + -x, --strs1 Strings Option 1 (env $STRS1) + --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"]) + -z Strings Option 3 (env $STRS3) + -q, --ints1 Ints Option 1 (env $INTS1) + --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3]) + -j Ints Option 3 (env $INTS3) + +Commands: + command1 command1 description + command2 command2 description + command3 command3 description + +Run 'app COMMAND --help' for more information on a command. diff --git a/testdata/help-output-top-help-i-user.txt b/testdata/help-output-top-help-i-user.txt new file mode 100644 index 0000000..dd4772f --- /dev/null +++ b/testdata/help-output-top-help-i-user.txt @@ -0,0 +1,45 @@ + +Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...] + +App Desc + +Arguments: + BOOL1 Bool Argument 1 (env $BOOL1) + BOOL2 Bool Argument 2 (default true) + BOOL3 Bool Argument 3 (env $BOOL3) + STR1 String Argument 1 (env $STR1) + STR2 String Argument 2 (env $STR2) (default "a value") + STR3 String Argument 3 (env $STR3) + INT1 Int Argument 1 (env $INT1) (default 0) + INT2 Int Argument 2 (env $INT2) (default 1) + INT3 Int Argument 3 (env $INT3) + STRS1 Strings Argument 1 (env $STRS1) + STRS2 (env $STRS2) (default ["value1", "value2"]) + STRS3 Strings Argument 3 (env $STRS3) + INTS1 Ints Argument 1 (env $INTS1) + INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3]) + INTS3 Ints Argument 3 (env $INTS3) + +Options: + -b, --bool1 Bool Option 1 (env $BOOL1) + --bool2 Bool Option 2 (default true) + -d Bool Option 3 (env $BOOL3) + -s, --str1 String Option 1 (env $STR1) + --str2 String Option 2 (default "a value") + -v String Option 3 (env $STR3) + -i, --int1 (env $INT1, $ALIAS_INT1) (default 0) + --int2 Int Option 2 (env $INT2) (default 1) + -k Int Option 3 (env $INT3) + -x, --strs1 Strings Option 1 (env $STRS1) + --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"]) + -z Strings Option 3 (env $STRS3) + -q, --ints1 Ints Option 1 (env $INTS1) + --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3]) + -j Ints Option 3 (env $INTS3) + +Commands: + command1 command1 description + command2 command2 description + command3 command3 description + +Run 'app COMMAND --help' for more information on a command. diff --git a/testdata/help-output-top-help.txt b/testdata/help-output-top-help.txt new file mode 100644 index 0000000..dd4772f --- /dev/null +++ b/testdata/help-output-top-help.txt @@ -0,0 +1,45 @@ + +Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...] + +App Desc + +Arguments: + BOOL1 Bool Argument 1 (env $BOOL1) + BOOL2 Bool Argument 2 (default true) + BOOL3 Bool Argument 3 (env $BOOL3) + STR1 String Argument 1 (env $STR1) + STR2 String Argument 2 (env $STR2) (default "a value") + STR3 String Argument 3 (env $STR3) + INT1 Int Argument 1 (env $INT1) (default 0) + INT2 Int Argument 2 (env $INT2) (default 1) + INT3 Int Argument 3 (env $INT3) + STRS1 Strings Argument 1 (env $STRS1) + STRS2 (env $STRS2) (default ["value1", "value2"]) + STRS3 Strings Argument 3 (env $STRS3) + INTS1 Ints Argument 1 (env $INTS1) + INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3]) + INTS3 Ints Argument 3 (env $INTS3) + +Options: + -b, --bool1 Bool Option 1 (env $BOOL1) + --bool2 Bool Option 2 (default true) + -d Bool Option 3 (env $BOOL3) + -s, --str1 String Option 1 (env $STR1) + --str2 String Option 2 (default "a value") + -v String Option 3 (env $STR3) + -i, --int1 (env $INT1, $ALIAS_INT1) (default 0) + --int2 Int Option 2 (env $INT2) (default 1) + -k Int Option 3 (env $INT3) + -x, --strs1 Strings Option 1 (env $STRS1) + --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"]) + -z Strings Option 3 (env $STRS3) + -q, --ints1 Ints Option 1 (env $INTS1) + --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3]) + -j Ints Option 3 (env $INTS3) + +Commands: + command1 command1 description + command2 command2 description + command3 command3 description + +Run 'app COMMAND --help' for more information on a command. diff --git a/testdata/help-output.txt b/testdata/help-output.txt deleted file mode 100644 index 903748f..0000000 --- a/testdata/help-output.txt +++ /dev/null @@ -1,45 +0,0 @@ - -Usage: app [-bdsuikqs] BOOL1 [STR1] INT3... COMMAND [arg...] - -App Desc - -Arguments: - BOOL1 Bool Argument 1 (env $BOOL1) - BOOL2 Bool Argument 2 (default true) - BOOL3 Bool Argument 3 (env $BOOL3) - STR1 String Argument 1 (env $STR1) - STR2 String Argument 2 (env $STR2) (default "a value") - STR3 String Argument 3 (env $STR3) - INT1 Int Argument 1 (env $INT1) (default 0) - INT2 Int Argument 2 (env $INT2) (default 1) - INT3 Int Argument 3 (env $INT3) - STRS1 Strings Argument 1 (env $STRS1) - STRS2 (env $STRS2) (default ["value1", "value2"]) - STRS3 Strings Argument 3 (env $STRS3) - INTS1 Ints Argument 1 (env $INTS1) - INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3]) - INTS3 Ints Argument 3 (env $INTS3) - -Options: - -b, --bool1 Bool Option 1 (env $BOOL1) - --bool2 Bool Option 2 (default true) - -d Bool Option 3 (env $BOOL3) - -s, --str1 String Option 1 (env $STR1) - --str2 String Option 2 (default "a value") - -v String Option 3 (env $STR3) - -i, --int1 (env $INT1, $ALIAS_INT1) (default 0) - --int2 Int Option 2 (env $INT2) (default 1) - -k Int Option 3 (env $INT3) - -x, --strs1 Strings Option 1 (env $STRS1) - --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"]) - -z Strings Option 3 (env $STRS3) - -q, --ints1 Ints Option 1 (env $INTS1) - --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3]) - -j Ints Option 3 (env $INTS3) - -Commands: - command1 command1 description - command2 command2 description - command3 command3 description - -Run 'app COMMAND --help' for more information on a command.