From bd2655e76c8b499761dc8b664c8b04dbfd828354 Mon Sep 17 00:00:00 2001 From: montag451 Date: Fri, 8 Mar 2024 01:36:58 +0100 Subject: [PATCH 01/22] Add Incus to the list of projects using Cobra (#2118) --- site/content/projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/site/content/projects_using_cobra.md b/site/content/projects_using_cobra.md index 5de771a..52e4e80 100644 --- a/site/content/projects_using_cobra.md +++ b/site/content/projects_using_cobra.md @@ -24,6 +24,7 @@ - [GoReleaser](https://goreleaser.com) - [Helm](https://helm.sh) - [Hugo](https://gohugo.io) +- [Incus](https://linuxcontainers.org/incus/) - [Infracost](https://github.com/infracost/infracost) - [Istio](https://istio.io) - [Kool](https://github.com/kool-dev/kool) From f34069ccf5d77bdc4799389a1700d8f7b3e38cf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:35:05 -0600 Subject: [PATCH 02/22] build(deps): bump golangci/golangci-lint-action from 3.7.0 to 4.0.0 (#2108) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.7.0 to 4.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.7.0...v4.0.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74ed331..d6b28e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,7 +47,7 @@ jobs: check-latest: true cache: true - - uses: golangci/golangci-lint-action@v3.7.0 + - uses: golangci/golangci-lint-action@v4.0.0 with: version: latest args: --verbose From a30cee5e5ab0949cc888ef00ae6aee24e091e042 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:36:37 -0600 Subject: [PATCH 03/22] build(deps): bump actions/cache from 3 to 4 (#2102) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6b28e2..8a5115b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -108,7 +108,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} From c69ae4c36b134dd69e5ab9d3d6b9f571ca5afe1e Mon Sep 17 00:00:00 2001 From: damas <19289022+cyrilico@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:40:01 +0000 Subject: [PATCH 04/22] ci: test golang 1.22 (#2113) --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a5115b..21f81e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '^1.21' + go-version: '^1.22' check-latest: true cache: true @@ -66,6 +66,7 @@ jobs: - 19 - 20 - 21 + - 22 name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x' runs-on: ${{ matrix.platform }}-latest steps: From 1f80fa2e23cc550c131e8a54dc72d11b265c6fcf Mon Sep 17 00:00:00 2001 From: racerole <148756161+racerole@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:40:19 +0800 Subject: [PATCH 05/22] chore: remove repetitive words (#2122) Signed-off-by: racerole --- doc/md_docs.go | 2 +- doc/rest_docs.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/md_docs.go b/doc/md_docs.go index f98fe2a..1259222 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -128,7 +128,7 @@ func GenMarkdownTree(cmd *cobra.Command, dir string) error { return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) } -// GenMarkdownTreeCustom is the the same as GenMarkdownTree, but +// GenMarkdownTreeCustom is the same as GenMarkdownTree, but // with custom filePrepender and linkHandler. func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error { for _, c := range cmd.Commands() { diff --git a/doc/rest_docs.go b/doc/rest_docs.go index 2cca6fd..c33acc2 100644 --- a/doc/rest_docs.go +++ b/doc/rest_docs.go @@ -140,7 +140,7 @@ func GenReSTTree(cmd *cobra.Command, dir string) error { return GenReSTTreeCustom(cmd, dir, emptyStr, defaultLinkHandler) } -// GenReSTTreeCustom is the the same as GenReSTTree, but +// GenReSTTreeCustom is the same as GenReSTTree, but // with custom filePrepender and linkHandler. func GenReSTTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string) string, linkHandler func(string, string) string) error { for _, c := range cmd.Commands() { From bd914e58d69d65e494b45bdb40e90ca816b92fcc Mon Sep 17 00:00:00 2001 From: Pedro Mota Date: Tue, 12 Mar 2024 07:42:46 -0300 Subject: [PATCH 06/22] fix: remove deprecated io/ioutils package (#2120) ioutils.ReadAll is deprecated since Go 1.16. This commit replaces it with io.ReadAll. See https://pkg.go.dev/io/ioutil\#ReadAll for reference Issue #2119 --- command_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command_test.go b/command_test.go index b7d88e4..db33692 100644 --- a/command_test.go +++ b/command_test.go @@ -18,7 +18,7 @@ import ( "bytes" "context" "fmt" - "io/ioutil" + "io" "os" "reflect" "strings" @@ -2092,12 +2092,12 @@ func TestCommandPrintRedirection(t *testing.T) { t.Error(err) } - gotErrBytes, err := ioutil.ReadAll(errBuff) + gotErrBytes, err := io.ReadAll(errBuff) if err != nil { t.Error(err) } - gotOutBytes, err := ioutil.ReadAll(outBuff) + gotOutBytes, err := io.ReadAll(outBuff) if err != nil { t.Error(err) } From 6b5f577ebce858ee70fcdd1f062ea3af4b1c03ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 1 Apr 2024 12:42:08 +0000 Subject: [PATCH 07/22] More linting (#2099) * Address gocritic findings, enable it * Enable gosimple, no new findings to address --- .golangci.yml | 4 ++-- bash_completions.go | 21 +++++++++------------ command_test.go | 2 +- doc/man_docs.go | 2 +- doc/util.go | 2 +- powershell_completions.go | 4 ++-- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a618ec2..22ae622 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,7 +29,7 @@ linters: - gas #- gochecknoinits - goconst - #- gocritic + - gocritic #- gocyclo #- gofmt - goimports @@ -37,7 +37,7 @@ linters: #- gomnd #- goprintffuncname #- gosec - #- gosimple + - gosimple - govet - ineffassign - interfacer diff --git a/bash_completions.go b/bash_completions.go index be83570..f4d198c 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -597,19 +597,16 @@ func writeRequiredFlag(buf io.StringWriter, cmd *Command) { if nonCompletableFlag(flag) { return } - for key := range flag.Annotations { - switch key { - case BashCompOneRequiredFlag: - format := " must_have_one_flag+=(\"--%s" - if flag.Value.Type() != "bool" { - format += "=" - } - format += cbn - WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name)) + if _, ok := flag.Annotations[BashCompOneRequiredFlag]; ok { + format := " must_have_one_flag+=(\"--%s" + if flag.Value.Type() != "bool" { + format += "=" + } + format += cbn + WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name)) - if len(flag.Shorthand) > 0 { - WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand)) - } + if len(flag.Shorthand) > 0 { + WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand)) } } }) diff --git a/command_test.go b/command_test.go index db33692..9ce7a52 100644 --- a/command_test.go +++ b/command_test.go @@ -2777,7 +2777,7 @@ func TestFind(t *testing.T) { func TestUnknownFlagShouldReturnSameErrorRegardlessOfArgPosition(t *testing.T) { testCases := [][]string{ - //{"--unknown", "--namespace", "foo", "child", "--bar"}, // FIXME: This test case fails, returning the error `unknown command "foo" for "root"` instead of the expected error `unknown flag: --unknown` + // {"--unknown", "--namespace", "foo", "child", "--bar"}, // FIXME: This test case fails, returning the error `unknown command "foo" for "root"` instead of the expected error `unknown flag: --unknown` {"--namespace", "foo", "--unknown", "child", "--bar"}, {"--namespace", "foo", "child", "--unknown", "--bar"}, {"--namespace", "foo", "child", "--bar", "--unknown"}, diff --git a/doc/man_docs.go b/doc/man_docs.go index b8c15ce..2138f24 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -133,7 +133,7 @@ func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { } header.Date = &now } - header.date = (*header.Date).Format("Jan 2006") + header.date = header.Date.Format("Jan 2006") if header.Source == "" && !disableAutoGen { header.Source = "Auto generated by spf13/cobra" } diff --git a/doc/util.go b/doc/util.go index 0aaa07a..4de4cee 100644 --- a/doc/util.go +++ b/doc/util.go @@ -40,7 +40,7 @@ func hasSeeAlso(cmd *cobra.Command) bool { // that do not contain \n. func forceMultiLine(s string) string { if len(s) > 60 && !strings.Contains(s, "\n") { - s = s + "\n" + s += "\n" } return s } diff --git a/powershell_completions.go b/powershell_completions.go index 5519519..a830b7b 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -28,8 +28,8 @@ import ( func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) { // Variables should not contain a '-' or ':' character nameForVar := name - nameForVar = strings.Replace(nameForVar, "-", "_", -1) - nameForVar = strings.Replace(nameForVar, ":", "_", -1) + nameForVar = strings.ReplaceAll(nameForVar, "-", "_") + nameForVar = strings.ReplaceAll(nameForVar, ":", "_") compCmd := ShellCompRequestCmd if !includeDesc { From 0fc86c2ffd0326b6f6ed5fa36803d26993655c08 Mon Sep 17 00:00:00 2001 From: Xinwei Xiong <3293172751NSS@gmail.com> Date: Mon, 8 Apr 2024 18:47:35 +0800 Subject: [PATCH 08/22] docs: update user guide (#2128) --- site/content/user_guide.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 3b42ef0..93e87d6 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -3,7 +3,7 @@ While you are welcome to provide your own organization, typically a Cobra-based application will follow the following organizational structure: -``` +```test ▾ appName/ ▾ cmd/ add.go @@ -301,6 +301,7 @@ command := cobra.Command{ ### Bind Flags with Config You can also bind your flags with [viper](https://github.com/spf13/viper): + ```go var author string @@ -320,12 +321,14 @@ More in [viper documentation](https://github.com/spf13/viper#working-with-flags) Flags are optional by default. If instead you wish your command to report an error when a flag has not been set, mark it as required: + ```go rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") rootCmd.MarkFlagRequired("region") ``` Or, for persistent flags: + ```go rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)") rootCmd.MarkPersistentFlagRequired("region") @@ -335,6 +338,7 @@ rootCmd.MarkPersistentFlagRequired("region") If you have different flags that must be provided together (e.g. if they provide the `--username` flag they MUST provide the `--password` flag as well) then Cobra can enforce that requirement: + ```go rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)") rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)") @@ -343,6 +347,7 @@ rootCmd.MarkFlagsRequiredTogether("username", "password") You can also prevent different flags from being provided together if they represent mutually exclusive options such as specifying an output format as either `--json` or `--yaml` but never both: + ```go rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON") rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML") @@ -351,6 +356,7 @@ rootCmd.MarkFlagsMutuallyExclusive("json", "yaml") If you want to require at least one flag from a group to be present, you can use `MarkFlagsOneRequired`. This can be combined with `MarkFlagsMutuallyExclusive` to enforce exactly one flag from a given group: + ```go rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON") rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML") @@ -428,7 +434,7 @@ by not providing a 'Run' for the 'rootCmd'. We have only defined one flag for a single command. -More documentation about flags is available at https://github.com/spf13/pflag +More documentation about flags is available at https://github.com/spf13/pflag. ```go package main @@ -722,7 +728,7 @@ command.SuggestionsMinimumDistance = 1 You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but make sense in your set of commands but for which you don't want aliases. Example: -``` +```bash $ kubectl remove Error: unknown command "remove" for "kubectl" @@ -787,7 +793,7 @@ func main() { Example run as a kubectl plugin: -``` +```bash $ kubectl myplugin Usage: kubectl myplugin [command] From 5a1acea3210649f3d70002818ec04b09f6347062 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Apr 2024 02:21:03 +0000 Subject: [PATCH 09/22] build(deps): bump github.com/cpuguy83/go-md2man/v2 from 2.0.3 to 2.0.4 (#2127) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a79e66a..8c80da0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/spf13/cobra go 1.15 require ( - github.com/cpuguy83/go-md2man/v2 v2.0.3 + github.com/cpuguy83/go-md2man/v2 v2.0.4 github.com/inconshreveable/mousetrap v1.1.0 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 871c3a8..ab40b43 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= From 5c2c1d627d35a00153764a3d37400efc66eaca1c Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sat, 18 May 2024 16:41:31 +0300 Subject: [PATCH 10/22] Consistent annotation names (#2140) Add `Annotation` suffix to the private annotations to allow nicer code using the constants. For example one can use the current annotation names as a temporary variable instead of unclear shortcut. Instead of this: rag := flagsFromAnnotation(c, f, requiredAsGroup) me := flagsFromAnnotation(c, f, mutuallyExclusive) or := flagsFromAnnotation(c, f, oneRequired) We can use now: requiredAsGrop := flagsFromAnnotation(c, f, requiredAsGroupAnnotation) mutuallyExclusive := flagsFromAnnotation(c, f, mutuallyExclusiveAnnotation) oneRequired := flagsFromAnnotation(c, f, oneRequiredAnnotation) Example taken from #2105. --- flag_groups.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flag_groups.go b/flag_groups.go index 2be3b18..560612f 100644 --- a/flag_groups.go +++ b/flag_groups.go @@ -23,9 +23,9 @@ import ( ) const ( - requiredAsGroup = "cobra_annotation_required_if_others_set" - oneRequired = "cobra_annotation_one_required" - mutuallyExclusive = "cobra_annotation_mutually_exclusive" + requiredAsGroupAnnotation = "cobra_annotation_required_if_others_set" + oneRequiredAnnotation = "cobra_annotation_one_required" + mutuallyExclusiveAnnotation = "cobra_annotation_mutually_exclusive" ) // MarkFlagsRequiredTogether marks the given flags with annotations so that Cobra errors @@ -37,7 +37,7 @@ func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) { if f == nil { panic(fmt.Sprintf("Failed to find flag %q and mark it as being required in a flag group", v)) } - if err := c.Flags().SetAnnotation(v, requiredAsGroup, append(f.Annotations[requiredAsGroup], strings.Join(flagNames, " "))); err != nil { + if err := c.Flags().SetAnnotation(v, requiredAsGroupAnnotation, append(f.Annotations[requiredAsGroupAnnotation], strings.Join(flagNames, " "))); err != nil { // Only errs if the flag isn't found. panic(err) } @@ -53,7 +53,7 @@ func (c *Command) MarkFlagsOneRequired(flagNames ...string) { if f == nil { panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a one-required flag group", v)) } - if err := c.Flags().SetAnnotation(v, oneRequired, append(f.Annotations[oneRequired], strings.Join(flagNames, " "))); err != nil { + if err := c.Flags().SetAnnotation(v, oneRequiredAnnotation, append(f.Annotations[oneRequiredAnnotation], strings.Join(flagNames, " "))); err != nil { // Only errs if the flag isn't found. panic(err) } @@ -70,7 +70,7 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) { panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a mutually exclusive flag group", v)) } // Each time this is called is a single new entry; this allows it to be a member of multiple groups if needed. - if err := c.Flags().SetAnnotation(v, mutuallyExclusive, append(f.Annotations[mutuallyExclusive], strings.Join(flagNames, " "))); err != nil { + if err := c.Flags().SetAnnotation(v, mutuallyExclusiveAnnotation, append(f.Annotations[mutuallyExclusiveAnnotation], strings.Join(flagNames, " "))); err != nil { panic(err) } } @@ -91,9 +91,9 @@ func (c *Command) ValidateFlagGroups() error { oneRequiredGroupStatus := map[string]map[string]bool{} mutuallyExclusiveGroupStatus := map[string]map[string]bool{} flags.VisitAll(func(pflag *flag.Flag) { - processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus) - processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus) - processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus) + processFlagForGroupAnnotation(flags, pflag, requiredAsGroupAnnotation, groupStatus) + processFlagForGroupAnnotation(flags, pflag, oneRequiredAnnotation, oneRequiredGroupStatus) + processFlagForGroupAnnotation(flags, pflag, mutuallyExclusiveAnnotation, mutuallyExclusiveGroupStatus) }) if err := validateRequiredFlagGroups(groupStatus); err != nil { @@ -232,9 +232,9 @@ func (c *Command) enforceFlagGroupsForCompletion() { oneRequiredGroupStatus := map[string]map[string]bool{} mutuallyExclusiveGroupStatus := map[string]map[string]bool{} c.Flags().VisitAll(func(pflag *flag.Flag) { - processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus) - processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus) - processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus) + processFlagForGroupAnnotation(flags, pflag, requiredAsGroupAnnotation, groupStatus) + processFlagForGroupAnnotation(flags, pflag, oneRequiredAnnotation, oneRequiredGroupStatus) + processFlagForGroupAnnotation(flags, pflag, mutuallyExclusiveAnnotation, mutuallyExclusiveGroupStatus) }) // If a flag that is part of a group is present, we make all the other flags From 8003b74a10ef0d0d84fe3c408d3939d86fdeb210 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sun, 19 May 2024 05:12:02 +0300 Subject: [PATCH 11/22] Remove fully inactivated linters (#2148) * Remove fully inactivated linters Currently golangci-lint fails with these errors: ERRO [linters_context] golint: This linter is fully inactivated: it will not produce any reports. ERRO [linters_context] interfacer: This linter is fully inactivated: it will not produce any reports. ERRO [linters_context] maligned: This linter is fully inactivated: it will not produce any reports. I could not find any docs explaining what "fully inactivated" mean, but based this PR[1] it seems that these linters do nothing now. Removing the linters fixes this issue without changing linting, as they did not produce any report. Looking in the linters docs[2] I did not find a replacement for "interfacer" and "malinged" linters. "stylecheck" seems to be a replacement for "golint", but we need to fix the code to enable it. [1] https://github.com/golangci/golangci-lint/pull/4436 [2] https://golangci-lint.run/usage/linters/ * Add stylecheck linter, replacement for golint This revealed 2 capitalized error messages. https://golangci-lint.run/usage/linters/#stylecheck --- .golangci.yml | 5 +---- command.go | 2 +- completions.go | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 22ae622..7b6d302 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -33,16 +33,13 @@ linters: #- gocyclo #- gofmt - goimports - - golint #- gomnd #- goprintffuncname #- gosec - gosimple - govet - ineffassign - - interfacer #- lll - - maligned - megacheck #- misspell #- nakedret @@ -52,7 +49,7 @@ linters: #- scopelint #- staticcheck #- structcheck ! deprecated since v1.49.0; replaced by 'unused' - #- stylecheck + - stylecheck #- typecheck - unconvert #- unparam diff --git a/command.go b/command.go index b6f8f4b..b31e22c 100644 --- a/command.go +++ b/command.go @@ -875,7 +875,7 @@ func (c *Command) ArgsLenAtDash() int { func (c *Command) execute(a []string) (err error) { if c == nil { - return fmt.Errorf("Called Execute() on a nil Command") + return fmt.Errorf("called Execute() on a nil Command") } if len(c.Deprecated) > 0 { diff --git a/completions.go b/completions.go index ad7b6d0..c0c08b0 100644 --- a/completions.go +++ b/completions.go @@ -298,7 +298,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } if err != nil { // Unable to find the real command. E.g., someInvalidCmd - return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) + return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs) } finalCmd.ctx = c.ctx From e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 1 Jun 2024 13:31:11 +0300 Subject: [PATCH 12/22] Address golangci-lint deprecation warnings, enable some more linters (#2152) * Address golangci-lint linter deprecation warnings 1.59.0 outputs: WARN [lintersdb] The name "gas" is deprecated. The linter has been renamed to: gosec. WARN [lintersdb] The linter named "megacheck" is deprecated. It has been split into: gosimple, staticcheck, unused. * Enable some more linters, address finding --- .golangci.yml | 12 +++++------- command.go | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7b6d302..2c8f480 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,28 +26,26 @@ linters: - errcheck #- exhaustive #- funlen - - gas #- gochecknoinits - goconst - gocritic #- gocyclo - #- gofmt + - gofmt - goimports #- gomnd #- goprintffuncname - #- gosec + - gosec - gosimple - govet - ineffassign #- lll - - megacheck - #- misspell + - misspell #- nakedret #- noctx - #- nolintlint + - nolintlint #- rowserrcheck #- scopelint - #- staticcheck + - staticcheck #- structcheck ! deprecated since v1.49.0; replaced by 'unused' - stylecheck #- typecheck diff --git a/command.go b/command.go index b31e22c..54748fc 100644 --- a/command.go +++ b/command.go @@ -1460,7 +1460,6 @@ func (c *Command) UseLine() string { // DebugFlags used to determine which flags have been assigned to which commands // and which persist. -// nolint:goconst func (c *Command) DebugFlags() { c.Println("DebugFlags called on", c.Name()) var debugflags func(*Command) From 371ae25d2c82e519feb48c82d142e6a696fd06dd Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 17 Jul 2024 01:36:29 +0200 Subject: [PATCH 13/22] Fix deprecation comment for Command.SetOutput (#2172) Deprecation comments should be at the start of a paragraph [1], and because of that have a whitespace above them [2]; > To signal that an identifier should not be used, add a paragraph to its > doc comment that begins with Deprecated: followed by some information > about the deprecation (...) With the whitespace missing, some tools, including pkg.go.dev [3] don't detect it to be deprecated. [1]: https://go.dev/wiki/Deprecated [2]: https://go.dev/doc/comment#paragraphs [3]: https://pkg.go.dev/github.com/spf13/cobra@v1.8.1#Command.SetOutput Signed-off-by: Sebastiaan van Stijn --- command.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command.go b/command.go index 54748fc..2df6975 100644 --- a/command.go +++ b/command.go @@ -281,6 +281,7 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. +// // Deprecated: Use SetOut and/or SetErr instead func (c *Command) SetOutput(output io.Writer) { c.outWriter = output From 756ba6dad61458cbbf7abecfc502d230574c57d2 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sun, 28 Jul 2024 11:18:07 -0500 Subject: [PATCH 14/22] fix(completions): Complete map flags multiple times (#2174) --- completions.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/completions.go b/completions.go index c0c08b0..8fccdaf 100644 --- a/completions.go +++ b/completions.go @@ -401,8 +401,9 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi doCompleteFlags := func(flag *pflag.Flag) { if !flag.Changed || strings.Contains(flag.Value.Type(), "Slice") || - strings.Contains(flag.Value.Type(), "Array") { - // If the flag is not already present, or if it can be specified multiple times (Array or Slice) + strings.Contains(flag.Value.Type(), "Array") || + strings.HasPrefix(flag.Value.Type(), "stringTo") { + // If the flag is not already present, or if it can be specified multiple times (Array, Slice, or stringTo) // we suggest it as a completion completions = append(completions, getFlagNameCompletions(flag, toComplete)...) } From 511af59cb3602540a636d8f5ed52f09e60804ce1 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sat, 24 Aug 2024 14:05:26 +0300 Subject: [PATCH 15/22] Replace deprecated ioutil usage (#2181) Fixing golangci-lint errors[1]: Error: SA1019: "io/ioutil" has been deprecated since Go 1.19: As of Go 1.16, the same functionality is now provided by package [io] or package [os], and those implementations should be preferred in new code. See the specific function documentation for details. (staticcheck) [1] https://github.com/spf13/cobra/actions/runs/10535452454/job/29194442289?pr=2180 --- doc/man_docs_test.go | 5 ++--- doc/md_docs_test.go | 5 ++--- doc/rest_docs_test.go | 5 ++--- doc/yaml_docs_test.go | 5 ++--- site/content/docgen/md.md | 4 ++-- site/content/docgen/rest.md | 4 ++-- site/content/docgen/yaml.md | 4 ++-- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index a4435e6..dfa5e16 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -18,7 +18,6 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -168,7 +167,7 @@ func TestManPrintFlagsHidesShortDeprecated(t *testing.T) { func TestGenManTree(t *testing.T) { c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} header := &GenManHeader{Section: "2"} - tmpdir, err := ioutil.TempDir("", "test-gen-man-tree") + tmpdir, err := os.MkdirTemp("", "test-gen-man-tree") if err != nil { t.Fatalf("Failed to create tmpdir: %s", err.Error()) } @@ -219,7 +218,7 @@ func assertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error { } func BenchmarkGenManToFile(b *testing.B) { - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { b.Fatal(err) } diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index e70cad8..1bf13ab 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -16,7 +16,6 @@ package doc import ( "bytes" - "io/ioutil" "os" "path/filepath" "testing" @@ -94,7 +93,7 @@ func TestGenMdNoTag(t *testing.T) { func TestGenMdTree(t *testing.T) { c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} - tmpdir, err := ioutil.TempDir("", "test-gen-md-tree") + tmpdir, err := os.MkdirTemp("", "test-gen-md-tree") if err != nil { t.Fatalf("Failed to create tmpdir: %v", err) } @@ -110,7 +109,7 @@ func TestGenMdTree(t *testing.T) { } func BenchmarkGenMarkdownToFile(b *testing.B) { - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { b.Fatal(err) } diff --git a/doc/rest_docs_test.go b/doc/rest_docs_test.go index 1a3ea9d..c0b10cc 100644 --- a/doc/rest_docs_test.go +++ b/doc/rest_docs_test.go @@ -16,7 +16,6 @@ package doc import ( "bytes" - "io/ioutil" "os" "path/filepath" "testing" @@ -81,7 +80,7 @@ func TestGenRSTNoTag(t *testing.T) { func TestGenRSTTree(t *testing.T) { c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} - tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree") + tmpdir, err := os.MkdirTemp("", "test-gen-rst-tree") if err != nil { t.Fatalf("Failed to create tmpdir: %s", err.Error()) } @@ -97,7 +96,7 @@ func TestGenRSTTree(t *testing.T) { } func BenchmarkGenReSTToFile(b *testing.B) { - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { b.Fatal(err) } diff --git a/doc/yaml_docs_test.go b/doc/yaml_docs_test.go index 1a6fa7c..ddd4e24 100644 --- a/doc/yaml_docs_test.go +++ b/doc/yaml_docs_test.go @@ -17,7 +17,6 @@ package doc import ( "bytes" "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -58,7 +57,7 @@ func TestGenYamlNoTag(t *testing.T) { func TestGenYamlTree(t *testing.T) { c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} - tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree") + tmpdir, err := os.MkdirTemp("", "test-gen-yaml-tree") if err != nil { t.Fatalf("Failed to create tmpdir: %s", err.Error()) } @@ -85,7 +84,7 @@ func TestGenYamlDocRunnable(t *testing.T) { } func BenchmarkGenYamlToFile(b *testing.B) { - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { b.Fatal(err) } diff --git a/site/content/docgen/md.md b/site/content/docgen/md.md index 1659175..16af916 100644 --- a/site/content/docgen/md.md +++ b/site/content/docgen/md.md @@ -35,7 +35,7 @@ package main import ( "log" - "io/ioutil" + "io" "os" "k8s.io/kubernetes/pkg/kubectl/cmd" @@ -45,7 +45,7 @@ import ( ) func main() { - kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.Discard) err := doc.GenMarkdownTree(kubectl, "./") if err != nil { log.Fatal(err) diff --git a/site/content/docgen/rest.md b/site/content/docgen/rest.md index 3041c57..c17f2b0 100644 --- a/site/content/docgen/rest.md +++ b/site/content/docgen/rest.md @@ -35,7 +35,7 @@ package main import ( "log" - "io/ioutil" + "io" "os" "k8s.io/kubernetes/pkg/kubectl/cmd" @@ -45,7 +45,7 @@ import ( ) func main() { - kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.Discard) err := doc.GenReSTTree(kubectl, "./") if err != nil { log.Fatal(err) diff --git a/site/content/docgen/yaml.md b/site/content/docgen/yaml.md index 172e61d..d1912fe 100644 --- a/site/content/docgen/yaml.md +++ b/site/content/docgen/yaml.md @@ -34,7 +34,7 @@ This program can actually generate docs for the kubectl command in the kubernete package main import ( - "io/ioutil" + "io" "log" "os" @@ -45,7 +45,7 @@ import ( ) func main() { - kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.Discard) err := doc.GenYamlTree(kubectl, "./") if err != nil { log.Fatal(err) From 78bfc837fe358c750faa7e7f0a8016b300044d58 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sun, 25 Aug 2024 02:02:44 +0300 Subject: [PATCH 16/22] Test also with go 1.23 (#2182) --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21f81e0..ffdbf14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,6 +67,7 @@ jobs: - 20 - 21 - 22 + - 23 name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x' runs-on: ${{ matrix.platform }}-latest steps: From 11ab62158ac7079acb819b02c25c9a1ee2eb5eb6 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Fri, 11 Oct 2024 19:40:47 +0900 Subject: [PATCH 17/22] docs: update README.md (#2197) minor fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6444f4b..0fb0373 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ go install github.com/spf13/cobra-cli@latest For complete details on using the Cobra-CLI generator, please read [The Cobra Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md) -For complete details on using the Cobra library, please read the [The Cobra User Guide](site/content/user_guide.md). +For complete details on using the Cobra library, please read [The Cobra User Guide](site/content/user_guide.md). # License From ff7c561cf741ac36ab319f528d93e738ae284835 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sat, 12 Oct 2024 18:35:01 +0300 Subject: [PATCH 18/22] Improve site formatting (#2183) * Improve site formatting - Separate titles with blank lines - Separate code blocks with blank lines - Always use ``` blocks for examples - Use console for console (bash syntax highlighting does work well with example command output) - Start console examples with $ (highlight command and output differently and more friendly to other shells users) * Unify indentation in example project structure * Use single import line in the trivial examples When we want to show a minimal example with single import it looks cleaner and more minimal with a single line. --- site/content/active_help.md | 37 ++++++++----- site/content/user_guide.md | 105 ++++++++++++++++++------------------ 2 files changed, 76 insertions(+), 66 deletions(-) diff --git a/site/content/active_help.md b/site/content/active_help.md index d72acc7..1b02c70 100644 --- a/site/content/active_help.md +++ b/site/content/active_help.md @@ -2,19 +2,21 @@ Active Help is a framework provided by Cobra which allows a program to define messages (hints, warnings, etc) that will be printed during program usage. It aims to make it easier for your users to learn how to use your program. If configured by the program, Active Help is printed when the user triggers shell completion. -For example, -``` -bash-5.1$ helm repo add [tab] +For example, + +```console +$ helm repo add [tab] You must choose a name for the repo you are adding. -bash-5.1$ bin/helm package [tab] +$ bin/helm package [tab] Please specify the path to the chart to package -bash-5.1$ bin/helm package [tab][tab] +$ bin/helm package [tab][tab] bin/ internal/ scripts/ pkg/ testdata/ ``` **Hint**: A good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions to guide the user in knowing what is expected by the program. + ## Supported shells Active Help is currently only supported for the following shells: @@ -52,22 +54,26 @@ cmd := &cobra.Command{ }, } ``` + The example above defines the completions (none, in this specific example) as well as the Active Help messages for the `helm repo add` command. It yields the following behavior: -``` -bash-5.1$ helm repo add [tab] + +```console +$ helm repo add [tab] You must choose a name for the repo you are adding -bash-5.1$ helm repo add grafana [tab] +$ helm repo add grafana [tab] You must specify the URL for the repo you are adding -bash-5.1$ helm repo add grafana https://grafana.github.io/helm-charts [tab] +$ helm repo add grafana https://grafana.github.io/helm-charts [tab] This command does not take any more arguments ``` + **Hint**: As can be seen in the above example, a good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions. ### Active Help for flags Providing Active Help for flags is done in the same fashion as for nouns, but using the completion function registered for the flag. For example: + ```go _ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 2 { @@ -77,11 +83,12 @@ _ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []st }) ``` The example above prints an Active Help message when not enough information was given by the user to complete the `--version` flag. -``` -bash-5.1$ bin/helm install myrelease --version 2.0.[tab] + +```console +$ bin/helm install myrelease --version 2.0.[tab] You must first specify the chart to install before the --version flag can be completed -bash-5.1$ bin/helm install myrelease bitnami/solr --version 2.0.[tab][tab] +$ bin/helm install myrelease bitnami/solr --version 2.0.[tab][tab] 2.0.1 2.0.2 2.0.3 ``` @@ -103,6 +110,7 @@ Active Help configuration using the `cobra.GetActiveHelpConfig(cmd)` function an should or should not be added (instead of reading the environment variable directly). For example: + ```go ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { activeHelpLevel := cobra.GetActiveHelpConfig(cmd) @@ -124,11 +132,13 @@ ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([ return comps, cobra.ShellCompDirectiveNoFileComp }, ``` + **Note 1**: If the `_ACTIVE_HELP` environment variable is set to the string "0", Cobra will automatically disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help without having to call `cobra.GetActiveHelpConfig(cmd)` explicitly. **Note 2**: If a user wants to disable Active Help for every single program based on Cobra, she can set the environment variable `COBRA_ACTIVE_HELP` to "0". In this case `cobra.GetActiveHelpConfig(cmd)` will return "0" no matter what the variable `_ACTIVE_HELP` is set to. **Note 3**: If the user does not set `_ACTIVE_HELP` or `COBRA_ACTIVE_HELP` (which will be a common case), the default value for the Active Help configuration returned by `cobra.GetActiveHelpConfig(cmd)` will be the empty string. + ## Active Help with Cobra's default completion command Cobra provides a default `completion` command for programs that wish to use it. @@ -141,7 +151,8 @@ details for your users. Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details. When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable. That variable is named `_ACTIVE_HELP` where any non-ASCII-alphanumeric characters are replaced by an `_`. For example, we can test deactivating some Active Help as shown below: -``` + +```console $ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h bitnami/haproxy bitnami/harbor diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 93e87d6..dd26de4 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -3,14 +3,14 @@ While you are welcome to provide your own organization, typically a Cobra-based application will follow the following organizational structure: -```test - ▾ appName/ - ▾ cmd/ - add.go - your.go - commands.go - here.go - main.go +```console +▾ appName/ + ▾ cmd/ + add.go + your.go + commands.go + here.go + main.go ``` In a Cobra app, typically the main.go file is very bare. It serves one purpose: initializing Cobra. @@ -18,9 +18,7 @@ In a Cobra app, typically the main.go file is very bare. It serves one purpose: ```go package main -import ( - "{pathToYourApp}/cmd" -) +import "{pathToYourApp}/cmd" func main() { cmd.Execute() @@ -148,9 +146,7 @@ In a Cobra app, typically the main.go file is very bare. It serves one purpose: ```go package main -import ( - "{pathToYourApp}/cmd" -) +import "{pathToYourApp}/cmd" func main() { cmd.Execute() @@ -197,7 +193,7 @@ its own go package. The suggested approach is for the parent command to use `AddCommand` to add its most immediate subcommands. For example, consider the following directory structure: -```text +```console ├── cmd │   ├── root.go │   └── sub1 @@ -508,30 +504,31 @@ create' is called. Every command will automatically have the '--help' flag adde The following output is automatically generated by Cobra. Nothing beyond the command and flag definitions are needed. - $ cobra-cli help +```console +$ cobra-cli help - Cobra is a CLI library for Go that empowers applications. - This application is a tool to generate the needed files - to quickly create a Cobra application. +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application. - Usage: - cobra-cli [command] +Usage: + cobra-cli [command] - Available Commands: - add Add a command to a Cobra Application - completion Generate the autocompletion script for the specified shell - help Help about any command - init Initialize a Cobra Application +Available Commands: + add Add a command to a Cobra Application + completion Generate the autocompletion script for the specified shell + help Help about any command + init Initialize a Cobra Application - Flags: - -a, --author string author name for copyright attribution (default "YOUR NAME") - --config string config file (default is $HOME/.cobra.yaml) - -h, --help help for cobra-cli - -l, --license string name of license for the project - --viper use Viper for configuration - - Use "cobra-cli [command] --help" for more information about a command. +Flags: + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra-cli + -l, --license string name of license for the project + --viper use Viper for configuration +Use "cobra-cli [command] --help" for more information about a command. +``` Help is just a command like any other. There is no special logic or behavior around it. In fact, you can provide your own if you want. @@ -566,25 +563,27 @@ showing the user the 'usage'. You may recognize this from the help above. That's because the default help embeds the usage as part of its output. - $ cobra-cli --invalid - Error: unknown flag: --invalid - Usage: - cobra-cli [command] +```console +$ cobra-cli --invalid +Error: unknown flag: --invalid +Usage: + cobra-cli [command] - Available Commands: - add Add a command to a Cobra Application - completion Generate the autocompletion script for the specified shell - help Help about any command - init Initialize a Cobra Application +Available Commands: + add Add a command to a Cobra Application + completion Generate the autocompletion script for the specified shell + help Help about any command + init Initialize a Cobra Application - Flags: - -a, --author string author name for copyright attribution (default "YOUR NAME") - --config string config file (default is $HOME/.cobra.yaml) - -h, --help help for cobra-cli - -l, --license string name of license for the project - --viper use Viper for configuration +Flags: + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra-cli + -l, --license string name of license for the project + --viper use Viper for configuration - Use "cobra [command] --help" for more information about a command. +Use "cobra [command] --help" for more information about a command. +``` ### Defining your own usage You can provide your own usage function or template for Cobra to use. @@ -701,7 +700,7 @@ Set `EnableTraverseRunHooks` global variable to `true` if you want to execute al Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example: -``` +```console $ hugo srever Error: unknown command "srever" for "hugo" @@ -728,7 +727,7 @@ command.SuggestionsMinimumDistance = 1 You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but make sense in your set of commands but for which you don't want aliases. Example: -```bash +```console $ kubectl remove Error: unknown command "remove" for "kubectl" @@ -793,7 +792,7 @@ func main() { Example run as a kubectl plugin: -```bash +```console $ kubectl myplugin Usage: kubectl myplugin [command] From 5bef9d8d87ad1a1c73e74361bad9e38b3469a58e Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sat, 12 Oct 2024 19:08:27 +0300 Subject: [PATCH 19/22] Fix --version help and output for plugins (#2180) * Fix --version help with CommandDisplayNameAnnotation When setting Command.Version, a --version option is added. The help message for the --version command did not consider the command display name: Flags: -h, --help help for kubectl plugin -v, --version version for kubectl-plugin With this change the help test is consistent with other flags: Flags: -h, --help help for kubectl plugin -v, --version version for kubectl plugin * Make command DisplayName() public This allows using the display name in templates or other code that want to use the same value. * Use display name in version template The version template used `{{.Name}}` but for plugins you want to use `{{.DisplayName}}` to be consistent with other help output. With this change will show: $ kubectl plugin --version kubectl plugin version 1.0.0 --- command.go | 32 +++++++++++++++++--------------- command_test.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/command.go b/command.go index 2df6975..4cd712b 100644 --- a/command.go +++ b/command.go @@ -607,7 +607,7 @@ func (c *Command) VersionTemplate() string { if c.HasParent() { return c.parent.VersionTemplate() } - return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}} + return `{{with .DisplayName}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}} ` } @@ -1190,7 +1190,7 @@ func (c *Command) InitDefaultHelpFlag() { c.mergePersistentFlags() if c.Flags().Lookup("help") == nil { usage := "help for " - name := c.displayName() + name := c.DisplayName() if name == "" { usage += "this command" } else { @@ -1216,7 +1216,7 @@ func (c *Command) InitDefaultVersionFlag() { if c.Name() == "" { usage += "this command" } else { - usage += c.Name() + usage += c.DisplayName() } if c.Flags().ShorthandLookup("v") == nil { c.Flags().BoolP("version", "v", false, usage) @@ -1240,7 +1240,7 @@ func (c *Command) InitDefaultHelpCmd() { Use: "help [command]", Short: "Help about any command", Long: `Help provides help for any command in the application. -Simply type ` + c.displayName() + ` help [path to command] for full details.`, +Simply type ` + c.DisplayName() + ` help [path to command] for full details.`, ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) { var completions []string cmd, _, e := c.Root().Find(args) @@ -1431,10 +1431,12 @@ func (c *Command) CommandPath() string { if c.HasParent() { return c.Parent().CommandPath() + " " + c.Name() } - return c.displayName() + return c.DisplayName() } -func (c *Command) displayName() string { +// DisplayName returns the name to display in help text. Returns command Name() +// If CommandDisplayNameAnnoation is not set +func (c *Command) DisplayName() string { if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok { return displayName } @@ -1444,7 +1446,7 @@ func (c *Command) displayName() string { // UseLine puts out the full usage for a given command (including parents). func (c *Command) UseLine() string { var useline string - use := strings.Replace(c.Use, c.Name(), c.displayName(), 1) + use := strings.Replace(c.Use, c.Name(), c.DisplayName(), 1) if c.HasParent() { useline = c.parent.CommandPath() + " " + use } else { @@ -1650,7 +1652,7 @@ func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) f // to this command (local and persistent declared here and by all parents). func (c *Command) Flags() *flag.FlagSet { if c.flags == nil { - c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.flags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1665,7 +1667,7 @@ func (c *Command) Flags() *flag.FlagSet { func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { persistentFlags := c.PersistentFlags() - out := flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + out := flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) c.LocalFlags().VisitAll(func(f *flag.Flag) { if persistentFlags.Lookup(f.Name) == nil { out.AddFlag(f) @@ -1680,7 +1682,7 @@ func (c *Command) LocalFlags() *flag.FlagSet { c.mergePersistentFlags() if c.lflags == nil { - c.lflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.lflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1708,7 +1710,7 @@ func (c *Command) InheritedFlags() *flag.FlagSet { c.mergePersistentFlags() if c.iflags == nil { - c.iflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.iflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1737,7 +1739,7 @@ func (c *Command) NonInheritedFlags() *flag.FlagSet { // PersistentFlags returns the persistent FlagSet specifically set in the current command. func (c *Command) PersistentFlags() *flag.FlagSet { if c.pflags == nil { - c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.pflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1750,9 +1752,9 @@ func (c *Command) PersistentFlags() *flag.FlagSet { func (c *Command) ResetFlags() { c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf.Reset() - c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.flags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) c.flags.SetOutput(c.flagErrorBuf) - c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.pflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) c.pflags.SetOutput(c.flagErrorBuf) c.lflags = nil @@ -1869,7 +1871,7 @@ func (c *Command) mergePersistentFlags() { // If c.parentsPflags == nil, it makes new. func (c *Command) updateParentsPflags() { if c.parentsPflags == nil { - c.parentsPflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) + c.parentsPflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) c.parentsPflags.SetOutput(c.flagErrorBuf) c.parentsPflags.SortFlags = false } diff --git a/command_test.go b/command_test.go index 9ce7a52..cd44992 100644 --- a/command_test.go +++ b/command_test.go @@ -371,8 +371,9 @@ func TestAliasPrefixMatching(t *testing.T) { // text should reflect the way we run the command. func TestPlugin(t *testing.T) { cmd := &Command{ - Use: "kubectl-plugin", - Args: NoArgs, + Use: "kubectl-plugin", + Version: "1.0.0", + Args: NoArgs, Annotations: map[string]string{ CommandDisplayNameAnnotation: "kubectl plugin", }, @@ -386,13 +387,15 @@ func TestPlugin(t *testing.T) { checkStringContains(t, cmdHelp, "kubectl plugin [flags]") checkStringContains(t, cmdHelp, "help for kubectl plugin") + checkStringContains(t, cmdHelp, "version for kubectl plugin") } // TestPlugin checks usage as plugin with sub commands. func TestPluginWithSubCommands(t *testing.T) { rootCmd := &Command{ - Use: "kubectl-plugin", - Args: NoArgs, + Use: "kubectl-plugin", + Version: "1.0.0", + Args: NoArgs, Annotations: map[string]string{ CommandDisplayNameAnnotation: "kubectl plugin", }, @@ -408,6 +411,7 @@ func TestPluginWithSubCommands(t *testing.T) { checkStringContains(t, rootHelp, "kubectl plugin [command]") checkStringContains(t, rootHelp, "help for kubectl plugin") + checkStringContains(t, rootHelp, "version for kubectl plugin") checkStringContains(t, rootHelp, "kubectl plugin [command] --help") childHelp, err := executeCommand(rootCmd, "sub", "-h") @@ -1090,6 +1094,24 @@ func TestVersionFlagExecuted(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +func TestVersionFlagExecutedDiplayName(t *testing.T) { + rootCmd := &Command{ + Use: "kubectl-plugin", + Version: "1.0.0", + Annotations: map[string]string{ + CommandDisplayNameAnnotation: "kubectl plugin", + }, + Run: emptyRun, + } + + output, err := executeCommand(rootCmd, "--version", "arg1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "kubectl plugin version 1.0.0") +} + func TestVersionFlagExecutedWithNoName(t *testing.T) { rootCmd := &Command{Version: "1.0.0", Run: emptyRun} From 3a5efaede9d389703a792e2f7bfe3a64bc82ced9 Mon Sep 17 00:00:00 2001 From: Maxwel Mazur <52722027+maxwelbm@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:13:47 -0300 Subject: [PATCH 20/22] doc: azion project added to the list of CLIs that use cobra (#2198) --- site/content/projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/site/content/projects_using_cobra.md b/site/content/projects_using_cobra.md index 52e4e80..34ae5a9 100644 --- a/site/content/projects_using_cobra.md +++ b/site/content/projects_using_cobra.md @@ -3,6 +3,7 @@ - [Allero](https://github.com/allero-io/allero) - [Arewefastyet](https://benchmark.vitess.io) - [Arduino CLI](https://github.com/arduino/arduino-cli) +- [Azion](https://github.com/aziontech/azion) - [Bleve](https://blevesearch.com/) - [Cilium](https://cilium.io/) - [CloudQuery](https://github.com/cloudquery/cloudquery) From 5a138f143f25d616c6c195356a6560ed4fbbfdd4 Mon Sep 17 00:00:00 2001 From: Leonhard Stemplinger <42673430+lstemplinger@users.noreply.github.com> Date: Mon, 4 Nov 2024 01:45:01 +0100 Subject: [PATCH 21/22] Make Powershell completion script work in constrained mode (#2196) Creating CompletionResult objects is not allowed in Powershell constrained mode, so return results as strings if constrained mode is enabled Store results as PsCustomObjects instead of hashtables. This prevents Sort-Object from trying to convert the hashtable to a object, which is blocked in constrained mode. PsCustomObjects are created using New-Object to work around https://github.com/PowerShell/PowerShell/issues/20767 --- powershell_completions.go | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/powershell_completions.go b/powershell_completions.go index a830b7b..746dcb9 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -162,7 +162,10 @@ filter __%[1]s_escapeStringWithSpecialChars { if (-Not $Description) { $Description = " " } - @{Name="$Name";Description="$Description"} + New-Object -TypeName PSCustomObject -Property @{ + Name = "$Name" + Description = "$Description" + } } @@ -240,7 +243,12 @@ filter __%[1]s_escapeStringWithSpecialChars { __%[1]s_debug "Only one completion left" # insert space after value - [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + $CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space + if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){ + [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } else { + $CompletionText + } } else { # Add the proper number of spaces to align the descriptions @@ -255,7 +263,12 @@ filter __%[1]s_escapeStringWithSpecialChars { $Description = " ($($comp.Description))" } - [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") + $CompletionText = "$($comp.Name)$Description" + if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){ + [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") + } else { + $CompletionText + } } } @@ -264,7 +277,13 @@ filter __%[1]s_escapeStringWithSpecialChars { # insert space after value # MenuComplete will automatically show the ToolTip of # the highlighted value at the bottom of the suggestions. - [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + + $CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space + if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){ + [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } else { + $CompletionText + } } # TabCompleteNext and in case we get something unknown @@ -272,7 +291,13 @@ filter __%[1]s_escapeStringWithSpecialChars { # Like MenuComplete but we don't want to add a space here because # the user need to press space anyway to get the completion. # Description will not be shown because that's not possible with TabCompleteNext - [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + + $CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){ + [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } else { + $CompletionText + } } } From 02326d52c0704d79169819698693b0698e748f55 Mon Sep 17 00:00:00 2001 From: Vui Lam Date: Tue, 5 Nov 2024 03:19:24 -0800 Subject: [PATCH 22/22] Fix broken links in active_help.md (#2202) Small change to fix a couple of broken links in active_help.md Signed-off-by: Vui Lam --- site/content/active_help.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/content/active_help.md b/site/content/active_help.md index 1b02c70..ae6d723 100644 --- a/site/content/active_help.md +++ b/site/content/active_help.md @@ -20,12 +20,12 @@ bin/ internal/ scripts/ pkg/ testdata/ ## Supported shells Active Help is currently only supported for the following shells: -- Bash (using [bash completion V2](shell_completions.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed. +- Bash (using [bash completion V2](completions/_index.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed. - Zsh ## Adding Active Help messages -As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](shell_completions.md). +As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](completions/_index.md). Adding Active Help is done through the use of the `cobra.AppendActiveHelp(...)` function, where the program repeatedly adds Active Help messages to the list of completions. Keep reading for details. @@ -148,7 +148,7 @@ details for your users. ## Debugging Active Help -Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details. +Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](completions/_index.md#debugging) for details. When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable. That variable is named `_ACTIVE_HELP` where any non-ASCII-alphanumeric characters are replaced by an `_`. For example, we can test deactivating some Active Help as shown below: