From ad6db7f8f6e485f55b1561df9276fa4d8e278bde Mon Sep 17 00:00:00 2001 From: Brian Pursley Date: Mon, 14 Nov 2022 21:46:57 -0500 Subject: [PATCH 01/76] Create unit test illustrating unknown flag bug (#1854) Created a unit test that tests the unknown flag error message when the unknown flag is located in different arg positions. --- command_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/command_test.go b/command_test.go index 3fd36c7e..18011325 100644 --- a/command_test.go +++ b/command_test.go @@ -2692,3 +2692,46 @@ 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` + {"--namespace", "foo", "--unknown", "child", "--bar"}, + {"--namespace", "foo", "child", "--unknown", "--bar"}, + {"--namespace", "foo", "child", "--bar", "--unknown"}, + + {"--unknown", "--namespace=foo", "child", "--bar"}, + {"--namespace=foo", "--unknown", "child", "--bar"}, + {"--namespace=foo", "child", "--unknown", "--bar"}, + {"--namespace=foo", "child", "--bar", "--unknown"}, + + {"--unknown", "--namespace=foo", "child", "--bar=true"}, + {"--namespace=foo", "--unknown", "child", "--bar=true"}, + {"--namespace=foo", "child", "--unknown", "--bar=true"}, + {"--namespace=foo", "child", "--bar=true", "--unknown"}, + } + + root := &Command{ + Use: "root", + Run: emptyRun, + } + root.PersistentFlags().String("namespace", "", "a string flag") + + c := &Command{ + Use: "child", + Run: emptyRun, + } + c.Flags().Bool("bar", false, "a boolean flag") + + root.AddCommand(c) + + for _, tc := range testCases { + t.Run(strings.Join(tc, " "), func(t *testing.T) { + output, err := executeCommand(root, tc...) + if err == nil { + t.Error("expected unknown flag error") + } + checkStringContains(t, output, "unknown flag: --unknown") + }) + } +} From 430549841b79b3731b27df81e2752b729fd4ae43 Mon Sep 17 00:00:00 2001 From: Enrico Candino Date: Tue, 22 Nov 2022 03:04:26 +0100 Subject: [PATCH 02/76] Update stale.yml (#1863) --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 2db5123d..7d06e463 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -28,7 +28,7 @@ jobs: You can: - Make a comment to remove the stale label and show your support. The 60 days reset. - - If an issue has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interseted in reopening" + - If an issue has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interested in reopening" stale-pr-message: "The Cobra project currently lacks enough contributors to adequately respond to all PRs. This bot triages issues and PRs according to the following rules: @@ -39,7 +39,7 @@ jobs: You can: - Make a comment to remove the stale label and show your support. The 60 days reset. - - If a PR has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interseted in reopening." + - If a PR has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interested in reopening." days-before-stale: 60 days-before-close: 30 From c6b99719235fcb18d950f972f27d95fdb0347f02 Mon Sep 17 00:00:00 2001 From: Norman Dankert Date: Fri, 25 Nov 2022 21:47:20 +0100 Subject: [PATCH 03/76] fix: force ForEach-Object to return array in pwsh completion (#1850) Fixes #1847 --- powershell_completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell_completions.go b/powershell_completions.go index 004de42e..c9d47e61 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -137,7 +137,7 @@ filter __%[1]s_escapeStringWithSpecialChars { } $Longest = 0 - $Values = $Out | ForEach-Object { + [Array]$Values = $Out | ForEach-Object { #Split the output in name and description `+" $Name, $Description = $_.Split(\"`t\",2)"+` __%[1]s_debug "Name: $Name Description: $Description" From 6200c8e551774046849aafbbc28a74f485e15749 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Fri, 25 Nov 2022 20:55:09 +0000 Subject: [PATCH 04/76] Makefile: add target richtest (#1865) Don't require contributors to install richgo but keep it as an option and for CI --- .github/workflows/test.yml | 4 ++-- Makefile | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a55d39fe..7f047352 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,7 +88,7 @@ jobs: go install github.com/kyoh86/richgo"${_version}" go install github.com/mitchellh/gox"${_version}" - - run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin/:$PATH make test + - run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin/:$PATH make richtest test-win: @@ -125,4 +125,4 @@ jobs: go install github.com/kyoh86/richgo@latest go install github.com/mitchellh/gox@latest - - run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin:$PATH make test + - run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin:$PATH make richtest diff --git a/Makefile b/Makefile index c433a01b..0da8d7aa 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,6 @@ ifeq (, $(shell which golangci-lint)) $(warning "could not find golangci-lint in $(PATH), run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh") endif -ifeq (, $(shell which richgo)) -$(warning "could not find richgo in $(PATH), run: go install github.com/kyoh86/richgo@latest") -endif - .PHONY: fmt lint test install_deps clean default: all @@ -25,6 +21,10 @@ lint: test: install_deps $(info ******************** running tests ********************) + go test -v ./... + +richtest: install_deps + $(info ******************** running tests with kyoh86/richgo ********************) richgo test -v ./... install_deps: From 7bb14400030441629c9c78b2317dfe9f2254e022 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 08:18:05 -0500 Subject: [PATCH 05/76] build(deps): bump golangci/golangci-lint-action from 3.2.0 to 3.3.1 (#1851) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.2.0 to 3.3.1. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.2.0...v3.3.1) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... 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 7f047352..71dc5c49 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3.2.0 + - uses: golangci/golangci-lint-action@v3.3.1 with: version: latest args: --verbose From a6f198b635c4b18fff81930c40d464904e55b161 Mon Sep 17 00:00:00 2001 From: David Wertenteil Date: Tue, 6 Dec 2022 15:20:58 +0200 Subject: [PATCH 06/76] Update kubescape org (#1874) Signed-off-by: David Wertenteil --- projects_using_cobra.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 934b04ab..4a61f107 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -26,7 +26,7 @@ - [Istio](https://istio.io) - [Kool](https://github.com/kool-dev/kool) - [Kubernetes](https://kubernetes.io/) -- [Kubescape](https://github.com/armosec/kubescape) +- [Kubescape](https://github.com/kubescape/kubescape) - [KubeVirt](https://github.com/kubevirt/kubevirt) - [Linkerd](https://linkerd.io/) - [Mattermost-server](https://github.com/mattermost/mattermost-server) From 923592041e4ae38e2ae5c3cc66c3eb1e0c27261f Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Fri, 9 Dec 2022 21:57:19 +0000 Subject: [PATCH 07/76] ci: deprecate go 1.15 (#1866) Remove testing for go 1.15 to allow CI to pass, but don't force projects to upgrade. --- .github/workflows/test.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 71dc5c49..3b3d365e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,7 +63,6 @@ jobs: - ubuntu - macOS go: - - 15 - 16 - 17 - 18 @@ -81,12 +80,8 @@ jobs: - run: | export GOBIN=$HOME/go/bin - case "${{ matrix.go }}" in - 14|15) _version='';; - *) _version='@latest';; - esac - go install github.com/kyoh86/richgo"${_version}" - go install github.com/mitchellh/gox"${_version}" + go install github.com/kyoh86/richgo@latest + go install github.com/mitchellh/gox@latest - run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin/:$PATH make richtest From f25a3c6e0b4220c616a3b342b57b055c1256d318 Mon Sep 17 00:00:00 2001 From: Yash Ladha <18033231+yashLadha@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:09:50 +0530 Subject: [PATCH 08/76] fix: conflict import name with variable (#1879) `template` is an import in `cobra.go` file and also used as a variable name, which masks the library in the scope of that function. --- cobra.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobra.go b/cobra.go index fe44bc8a..841cda0c 100644 --- a/cobra.go +++ b/cobra.go @@ -167,8 +167,8 @@ func appendIfNotPresent(s, stringToAppend string) string { // rpad adds padding to the right of a string. func rpad(s string, padding int) string { - template := fmt.Sprintf("%%-%ds", padding) - return fmt.Sprintf(template, s) + formattedString := fmt.Sprintf("%%-%ds", padding) + return fmt.Sprintf(formattedString, s) } // tmpl executes the given template text on data, writing the result to w. From fdffa5a4c760e30a38e596299b91c328ffd461bc Mon Sep 17 00:00:00 2001 From: Seonghyeon Cho Date: Sat, 24 Dec 2022 05:54:16 +0900 Subject: [PATCH 09/76] Update badge route (#1884) Based on https://github.com/badges/shields/issues/8671 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cc726be..063f31a4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Cobra is used in many Go projects such as [Kubernetes](https://kubernetes.io/), [Hugo](https://gohugo.io), and [GitHub CLI](https://github.com/cli/cli) to name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra. -[![](https://img.shields.io/github/workflow/status/spf13/cobra/Test?longCache=tru&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest) +[![](https://img.shields.io/github/actions/workflow/status/spf13/cobra/test.yml?branch=main&longCache=true&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest) [![Go Reference](https://pkg.go.dev/badge/github.com/spf13/cobra.svg)](https://pkg.go.dev/github.com/spf13/cobra) [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra) [![Slack](https://img.shields.io/badge/Slack-cobra-brightgreen)](https://gophers.slack.com/archives/CD3LP1199) From bf11ab6321f2831be5390fdd71ab0bb2edbd9ed5 Mon Sep 17 00:00:00 2001 From: Yash Ladha <18033231+yashLadha@users.noreply.github.com> Date: Mon, 26 Dec 2022 01:38:39 +0530 Subject: [PATCH 10/76] fix: func name in doc strings (#1885) Corrected the function name at the start of doc strings, as per the convention outlined in official go documentation: https://go.dev/blog/godoc --- args.go | 2 +- bash_completions.go | 2 +- command.go | 10 +++++----- completions.go | 2 +- doc/rest_docs.go | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/args.go b/args.go index 2c1f99e7..2f4056c8 100644 --- a/args.go +++ b/args.go @@ -21,7 +21,7 @@ import ( type PositionalArgs func(cmd *Command, args []string) error -// Legacy arg validation has the following behaviour: +// legacyArgs validation has the following behaviour: // - root commands with no subcommands can take arbitrary arguments // - root commands with subcommands will do subcommand validity checking // - subcommands will always accept arbitrary arguments diff --git a/bash_completions.go b/bash_completions.go index 3acdb279..a9f4e4f2 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -532,7 +532,7 @@ func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { } } -// Setup annotations for go completions for registered flags +// prepareCustomAnnotationsForFlags setup annotations for go completions for registered flags func prepareCustomAnnotationsForFlags(cmd *Command) { flagCompletionMutex.RLock() defer flagCompletionMutex.RUnlock() diff --git a/command.go b/command.go index 1b0b17a2..f032dbc4 100644 --- a/command.go +++ b/command.go @@ -35,7 +35,7 @@ const FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra" // FParseErrWhitelist configures Flag parse errors to be ignored type FParseErrWhitelist flag.ParseErrorsWhitelist -// Structure to manage groups for commands +// Group Structure to manage groups for commands type Group struct { ID string Title string @@ -47,7 +47,7 @@ type Group struct { // definition to ensure usability. type Command struct { // Use is the one-line usage message. - // Recommended syntax is as follow: + // Recommended syntax is as follows: // [ ] identifies an optional argument. Arguments that are not enclosed in brackets are required. // ... indicates that you can specify multiple values for the previous argument. // | indicates mutually exclusive information. You can use the argument to the left of the separator or the @@ -321,7 +321,7 @@ func (c *Command) SetHelpCommand(cmd *Command) { c.helpCommand = cmd } -// SetHelpCommandGroup sets the group id of the help command. +// SetHelpCommandGroupID sets the group id of the help command. func (c *Command) SetHelpCommandGroupID(groupID string) { if c.helpCommand != nil { c.helpCommand.GroupID = groupID @@ -330,7 +330,7 @@ func (c *Command) SetHelpCommandGroupID(groupID string) { c.helpCommandGroupID = groupID } -// SetCompletionCommandGroup sets the group id of the completion command. +// SetCompletionCommandGroupID sets the group id of the completion command. func (c *Command) SetCompletionCommandGroupID(groupID string) { // completionCommandGroupID is used if no completion command is defined by the user c.Root().completionCommandGroupID = groupID @@ -1296,7 +1296,7 @@ func (c *Command) AllChildCommandsHaveGroup() bool { return true } -// ContainGroups return if groupID exists in the list of command groups. +// ContainsGroup return if groupID exists in the list of command groups. func (c *Command) ContainsGroup(groupID string) bool { for _, x := range c.commandgroups { if x.ID == groupID { diff --git a/completions.go b/completions.go index e8a0206d..8341959a 100644 --- a/completions.go +++ b/completions.go @@ -169,7 +169,7 @@ func (d ShellCompDirective) string() string { return strings.Join(directives, ", ") } -// Adds a special hidden command that can be used to request custom completions. +// initCompleteCmd adds a special hidden command that can be used to request custom completions. func (c *Command) initCompleteCmd(args []string) { completeCmd := &Command{ Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), diff --git a/doc/rest_docs.go b/doc/rest_docs.go index d6589065..bb0f8ec4 100644 --- a/doc/rest_docs.go +++ b/doc/rest_docs.go @@ -48,7 +48,7 @@ func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error return nil } -// linkHandler for default ReST hyperlink markup +// defaultLinkHandler for default ReST hyperlink markup func defaultLinkHandler(name, ref string) string { return fmt.Sprintf("`%s <%s.rst>`_", name, ref) } @@ -169,7 +169,7 @@ func GenReSTTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string return nil } -// adapted from: https://github.com/kr/text/blob/main/indent.go +// indentString adapted from: https://github.com/kr/text/blob/main/indent.go func indentString(s, p string) string { var res []byte b := []byte(s) From b4f979ae352828a0153281b590771ce9588d67f8 Mon Sep 17 00:00:00 2001 From: Dominik Roos Date: Tue, 3 Jan 2023 01:58:36 +0100 Subject: [PATCH 11/76] completions: do not detect arguments with dash as 2nd char as flag (#1817) Fixes #1816 Previously, arguments with a dash as the second character (e.g., 1-ff00:0:1) were detected as a flag by mistake. This resulted in auto completion misbehaving if such an argument was last in the argument list during invocation. --- command.go | 2 +- completions_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index f032dbc4..b6e3f4a1 100644 --- a/command.go +++ b/command.go @@ -692,7 +692,7 @@ Loop: } func isFlagArg(arg string) bool { - return ((len(arg) >= 3 && arg[1] == '-') || + return ((len(arg) >= 3 && arg[0:2] == "--") || (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) } diff --git a/completions_test.go b/completions_test.go index abac12e4..2364ed2e 100644 --- a/completions_test.go +++ b/completions_test.go @@ -3115,3 +3115,67 @@ func TestCompletionCobraFlags(t *testing.T) { }) } } + +func TestArgsNotDetectedAsFlagsCompletionInGo(t *testing.T) { + // Regression test that ensures the bug described in + // https://github.com/spf13/cobra/issues/1816 does not occur anymore. + + root := Command{ + Use: "root", + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"service", "1-123", "11-123"}, ShellCompDirectiveNoFileComp + }, + } + + completion := `service +1-123 +11-123 +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp +` + + testcases := []struct { + desc string + args []string + expectedOutput string + }{ + { + desc: "empty", + args: []string{""}, + expectedOutput: completion, + }, + { + desc: "service only", + args: []string{"service", ""}, + expectedOutput: completion, + }, + { + desc: "service last", + args: []string{"1-123", "service", ""}, + expectedOutput: completion, + }, + { + desc: "two digit prefixed dash last", + args: []string{"service", "11-123", ""}, + expectedOutput: completion, + }, + { + desc: "one digit prefixed dash last", + args: []string{"service", "1-123", ""}, + expectedOutput: completion, + }, + } + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + args := []string{ShellCompNoDescRequestCmd} + args = append(args, tc.args...) + output, err := executeCommand(&root, args...) + switch { + case err == nil && output != tc.expectedOutput: + t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) + case err != nil: + t.Errorf("Unexpected error %q", err) + } + }) + } +} From 4fa4fdf5cd34cb78973561d077461caea8eeb48a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 02:43:06 +0000 Subject: [PATCH 12/76] build(deps): bump github.com/inconshreveable/mousetrap (#1872) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a1309e53..6361d742 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.15 require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 - github.com/inconshreveable/mousetrap v1.0.1 + 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 a8c544e2..5ccb69dd 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= From d022c0fe2b70e61f7edc264ef5639cdf71e9ceb3 Mon Sep 17 00:00:00 2001 From: Shihta Kuan Date: Tue, 31 Jan 2023 01:58:20 +0800 Subject: [PATCH 13/76] Add documentation about disabling completion descriptions (#1901) --- shell_completions.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/shell_completions.md b/shell_completions.md index 286c2525..f3285e31 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -385,6 +385,19 @@ or ```go ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"} ``` + +If you don't want to show descriptions in the completions, you can add `--no-descriptions` to the default `completion` command to disable them, like: + +```bash +$ source <(helm completion bash) +$ helm completion [tab][tab] +bash (generate autocompletion script for bash) powershell (generate autocompletion script for powershell) +fish (generate autocompletion script for fish) zsh (generate autocompletion script for zsh) + +$ source <(helm completion bash --no-descriptions) +$ helm completion [tab][tab] +bash fish powershell zsh +``` ## Bash completions ### Dependencies From 8b8ee8754cb7ad722506484737722720732992b6 Mon Sep 17 00:00:00 2001 From: janhn Date: Mon, 6 Feb 2023 12:15:40 +0100 Subject: [PATCH 14/76] Improve MarkFlagsMutuallyExclusive example in User Guide (#1904) --- user_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide.md b/user_guide.md index fee9848d..00b53d03 100644 --- a/user_guide.md +++ b/user_guide.md @@ -313,8 +313,8 @@ 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(&u, "json", false, "Output in JSON") -rootCmd.Flags().BoolVar(&pw, "yaml", false, "Output in YAML") +rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON") +rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML") rootCmd.MarkFlagsMutuallyExclusive("json", "yaml") ``` From 87ebcd8f9884bfe15a996767ef1636787773a8b7 Mon Sep 17 00:00:00 2001 From: Ggg6542 <465806+gusega@users.noreply.github.com> Date: Thu, 9 Feb 2023 03:11:53 +0100 Subject: [PATCH 15/76] Update shell_completions.md (#1907) align documentation with the code : completions.go:452 --- shell_completions.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/shell_completions.md b/shell_completions.md index f3285e31..6c48f3e4 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -162,16 +162,7 @@ cmd := &cobra.Command{ } ``` -The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by -the completion algorithm if entered manually, e.g. in: - -```bash -$ kubectl get rc [tab][tab] -backend frontend database -``` - -Note that without declaring `rc` as an alias, the completion algorithm would not know to show the list of -replication controllers following `rc`. +The aliases are shown to the user on tab completion only if no completions were found within sub-commands or `ValidArgs`. ### Dynamic completion of nouns From e839bb342f7914fd1bf62041cebd801292e2ccf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Feb 2023 01:27:13 +0000 Subject: [PATCH 16/76] build(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#1902) --- .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 3b3d365e..b4307724 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3.3.1 + - uses: golangci/golangci-lint-action@v3.4.0 with: version: latest args: --verbose From a516d4132c811a4101ce6d99ac12671217d1ac37 Mon Sep 17 00:00:00 2001 From: John McBride Date: Mon, 13 Feb 2023 20:49:03 -0700 Subject: [PATCH 17/76] Removes stale bot from GitHub action (#1908) Signed-off-by: John McBride --- .github/workflows/stale.yml | 56 ------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 7d06e463..00000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Mark stale issues and pull requests - -on: - schedule: - - cron: "0 0 * * *" - -permissions: - contents: read - -jobs: - stale: - - permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - runs-on: ubuntu-latest - - steps: - - uses: actions/stale@v6 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: "The Cobra project currently lacks enough contributors to adequately respond to all issues. - This bot triages issues and PRs according to the following rules: - - - After 60d of inactivity, lifecycle/stale is applied. - - After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied and the issue is closed. - - You can: - - - Make a comment to remove the stale label and show your support. The 60 days reset. - - If an issue has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interested in reopening" - - stale-pr-message: "The Cobra project currently lacks enough contributors to adequately respond to all PRs. - This bot triages issues and PRs according to the following rules: - - - After 60d of inactivity, lifecycle/stale is applied. - - After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied and the PR is closed. - - You can: - - - Make a comment to remove the stale label and show your support. The 60 days reset. - - If a PR has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interested in reopening." - - days-before-stale: 60 - days-before-close: 30 - stale-issue-label: 'lifecycle/stale' - stale-pr-label: 'lifecycle/stale' - exempt-issue-labels: 'lifecycle/frozen' - exempt-pr-labels: 'lifecycle/frozen' - close-issue-label: 'lifecycle/rotten' - close-pr-label: 'lifecycle/rotten' - - # Since cobra has so many legacy issues and PRs that need to be triaged, - # only label new PRs and issues. - start-date: '2022-02-01T00:00:00Z' - From 3daa4b9c36617509a756832a3d0ee2b6f4b8c129 Mon Sep 17 00:00:00 2001 From: Gyanendra Mishra Date: Sat, 25 Feb 2023 20:57:12 +0000 Subject: [PATCH 18/76] Add keeporder to shell completion (#1903) This allows programs to request the shell to maintain the order of completions that was returned by the program --- bash_completionsV2.go | 18 ++++++++-- completions.go | 7 ++++ fish_completions.go | 76 ++++++++++++++++++++++++++++++++++----- powershell_completions.go | 10 ++++-- shell_completions.md | 4 +++ zsh_completions.go | 14 +++++--- 6 files changed, 112 insertions(+), 17 deletions(-) diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 78f43b91..5a38e38d 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -101,6 +101,7 @@ __%[1]s_process_completion_results() { local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d + local shellCompDirectiveKeepOrder=%[8]d if (((directive & shellCompDirectiveError) != 0)); then # Error code. No completion. @@ -115,6 +116,19 @@ __%[1]s_process_completion_results() { __%[1]s_debug "No space directive not supported in this version of bash" fi fi + if (((directive & shellCompDirectiveKeepOrder) != 0)); then + if [[ $(type -t compopt) == builtin ]]; then + # no sort isn't supported for bash less than < 4.4 + if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then + __%[1]s_debug "No sort directive not supported in this version of bash" + else + __%[1]s_debug "Activating keep order" + compopt -o nosort + fi + else + __%[1]s_debug "No sort directive not supported in this version of bash" + fi + fi if (((directive & shellCompDirectiveNoFileComp) != 0)); then if [[ $(type -t compopt) == builtin ]]; then __%[1]s_debug "Activating no file completion" @@ -183,7 +197,7 @@ __%[1]s_process_completion_results() { # Separate activeHelp lines from real completions. # Fills the $activeHelp and $completions arrays. __%[1]s_extract_activeHelp() { - local activeHelpMarker="%[8]s" + local activeHelpMarker="%[9]s" local endIndex=${#activeHelpMarker} while IFS='' read -r comp; do @@ -360,7 +374,7 @@ fi # ex: ts=4 sw=4 et filetype=sh `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpMarker)) } diff --git a/completions.go b/completions.go index 8341959a..c7b457eb 100644 --- a/completions.go +++ b/completions.go @@ -77,6 +77,10 @@ const ( // obtain the same behavior but only for flags. ShellCompDirectiveFilterDirs + // ShellCompDirectiveKeepOrder indicates that the shell should preserve the order + // in which the completions are provided + ShellCompDirectiveKeepOrder + // =========================================================================== // All directives using iota should be above this one. @@ -159,6 +163,9 @@ func (d ShellCompDirective) string() string { if d&ShellCompDirectiveFilterDirs != 0 { directives = append(directives, "ShellCompDirectiveFilterDirs") } + if d&ShellCompDirectiveKeepOrder != 0 { + directives = append(directives, "ShellCompDirectiveKeepOrder") + } if len(directives) == 0 { directives = append(directives, "ShellCompDirectiveDefault") } diff --git a/fish_completions.go b/fish_completions.go index 97112a17..32c4bea0 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -53,7 +53,7 @@ function __%[1]s_perform_completion __%[1]s_debug "last arg: $lastArg" # Disable ActiveHelp which is not supported for fish shell - set -l requestComp "%[9]s=0 $args[1] %[3]s $args[2..-1] $lastArg" + set -l requestComp "%[10]s=0 $args[1] %[3]s $args[2..-1] $lastArg" __%[1]s_debug "Calling $requestComp" set -l results (eval $requestComp 2> /dev/null) @@ -89,6 +89,60 @@ function __%[1]s_perform_completion printf "%%s\n" "$directiveLine" end +# this function limits calls to __%[1]s_perform_completion, by caching the result behind $__%[1]s_perform_completion_once_result +function __%[1]s_perform_completion_once + __%[1]s_debug "Starting __%[1]s_perform_completion_once" + + if test -n "$__%[1]s_perform_completion_once_result" + __%[1]s_debug "Seems like a valid result already exists, skipping __%[1]s_perform_completion" + return 0 + end + + set --global __%[1]s_perform_completion_once_result (__%[1]s_perform_completion) + if test -z "$__%[1]s_perform_completion_once_result" + __%[1]s_debug "No completions, probably due to a failure" + return 1 + end + + __%[1]s_debug "Performed completions and set __%[1]s_perform_completion_once_result" + return 0 +end + +# this function is used to clear the $__%[1]s_perform_completion_once_result variable after completions are run +function __%[1]s_clear_perform_completion_once_result + __%[1]s_debug "" + __%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable ==========" + set --erase __%[1]s_perform_completion_once_result + __%[1]s_debug "Succesfully erased the variable __%[1]s_perform_completion_once_result" +end + +function __%[1]s_requires_order_preservation + __%[1]s_debug "" + __%[1]s_debug "========= checking if order preservation is required ==========" + + __%[1]s_perform_completion_once + if test -z "$__%[1]s_perform_completion_once_result" + __%[1]s_debug "Error determining if order preservation is required" + return 1 + end + + set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1]) + __%[1]s_debug "Directive is: $directive" + + set -l shellCompDirectiveKeepOrder %[9]d + set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) %% 2) + __%[1]s_debug "Keeporder is: $keeporder" + + if test $keeporder -ne 0 + __%[1]s_debug "This does require order preservation" + return 0 + end + + __%[1]s_debug "This doesn't require order preservation" + return 1 +end + + # This function does two things: # - Obtain the completions and store them in the global __%[1]s_comp_results # - Return false if file completion should be performed @@ -99,17 +153,17 @@ function __%[1]s_prepare_completions # Start fresh set --erase __%[1]s_comp_results - set -l results (__%[1]s_perform_completion) - __%[1]s_debug "Completion results: $results" + __%[1]s_perform_completion_once + __%[1]s_debug "Completion results: $__%[1]s_perform_completion_once_result" - if test -z "$results" + if test -z "$__%[1]s_perform_completion_once_result" __%[1]s_debug "No completion, probably due to a failure" # Might as well do file completion, in case it helps return 1 end - set -l directive (string sub --start 2 $results[-1]) - set --global __%[1]s_comp_results $results[1..-2] + set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1]) + set --global __%[1]s_comp_results $__%[1]s_perform_completion_once_result[1..-2] __%[1]s_debug "Completions are: $__%[1]s_comp_results" __%[1]s_debug "Directive is: $directive" @@ -205,13 +259,17 @@ end # Remove any pre-existing completions for the program since we will be handling all of them. complete -c %[2]s -e +# this will get called after the two calls below and clear the $__%[1]s_perform_completion_once_result global +complete -c %[2]s -n '__%[1]s_clear_perform_completion_once_result' # The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results # which provides the program's completion choices. -complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' - +# If this doesn't require order preservation, we don't use the -k flag +complete -c %[2]s -n 'not __%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' +# otherwise we use the -k flag +complete -k -c %[2]s -n '__%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' `, nameForVar, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name))) } // GenFishCompletion generates fish completion file and writes to the passed writer. diff --git a/powershell_completions.go b/powershell_completions.go index c9d47e61..c766fc78 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -77,6 +77,7 @@ filter __%[1]s_escapeStringWithSpecialChars { $ShellCompDirectiveNoFileComp=%[6]d $ShellCompDirectiveFilterFileExt=%[7]d $ShellCompDirectiveFilterDirs=%[8]d + $ShellCompDirectiveKeepOrder=%[9]d # Prepare the command to request completions for the program. # Split the command at the first space to separate the program and arguments. @@ -112,7 +113,7 @@ filter __%[1]s_escapeStringWithSpecialChars { __%[1]s_debug "Calling $RequestComp" # First disable ActiveHelp which is not supported for Powershell - $env:%[9]s=0 + $env:%[10]s=0 #call the command store the output in $out and redirect stderr and stdout to null # $Out is an array contains each line per element @@ -182,6 +183,11 @@ filter __%[1]s_escapeStringWithSpecialChars { } } + # we sort the values in ascending order by name if keep order isn't passed + if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) { + $Values = $Values | Sort-Object -Property Name + } + if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { __%[1]s_debug "ShellCompDirectiveNoFileComp is called" @@ -267,7 +273,7 @@ filter __%[1]s_escapeStringWithSpecialChars { Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock $__%[2]sCompleterBlock `, name, nameForVar, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name))) } func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { diff --git a/shell_completions.md b/shell_completions.md index 6c48f3e4..065c0621 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -228,6 +228,10 @@ ShellCompDirectiveFilterFileExt // return []string{"themes"}, ShellCompDirectiveFilterDirs // ShellCompDirectiveFilterDirs + +// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order +// in which the completions are provided +ShellCompDirectiveKeepOrder ``` ***Note***: When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. diff --git a/zsh_completions.go b/zsh_completions.go index 84cec76f..29bcb570 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -108,8 +108,9 @@ _%[1]s() local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d + local shellCompDirectiveKeepOrder=%[8]d - local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder local -a completions __%[1]s_debug "\n========= starting completion logic ==========" @@ -177,7 +178,7 @@ _%[1]s() return fi - local activeHelpMarker="%[8]s" + local activeHelpMarker="%[9]s" local endIndex=${#activeHelpMarker} local startIndex=$((${#activeHelpMarker}+1)) local hasActiveHelp=0 @@ -227,6 +228,11 @@ _%[1]s() noSpace="-S ''" fi + if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then + __%[1]s_debug "Activating keep order." + keepOrder="-V" + fi + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local filteringCmd @@ -262,7 +268,7 @@ _%[1]s() return $result else __%[1]s_debug "Calling _describe" - if eval _describe "completions" completions $flagPrefix $noSpace; then + if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then __%[1]s_debug "_describe found some completions" # Return the success of having called _describe @@ -296,6 +302,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then fi `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpMarker)) } From 567ea8ebc9b4385ae671486415cd203fc2a3bab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Sat, 25 Feb 2023 22:30:37 +0100 Subject: [PATCH 19/76] Add support for PowerShell 7.2+ (#1916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PowerShell 7.2 has changed the way arguments are passed to executables. This was originally an experimental feature in 7.2, but as of 7.3 it is built-in. A simple "" is now sufficient for passing empty arguments, no back-tick escaping is required. Fixes #1849 Signed-off-by: Oldřich Jedlička Co-authored-by: Oldřich Jedlička --- powershell_completions.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/powershell_completions.go b/powershell_completions.go index c766fc78..927733b3 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -107,8 +107,17 @@ filter __%[1]s_escapeStringWithSpecialChars { # If the last parameter is complete (there is a space following it) # We add an extra empty parameter so we can indicate this to the go method. __%[1]s_debug "Adding extra empty parameter" -`+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+` -`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+` + # PowerShell 7.2+ changed the way how the arguments are passed to executables, + # so for pre-7.2 or when Legacy argument passing is enabled we need to use +`+" # `\"`\" to pass an empty argument, a \"\" or '' does not work!!!"+` + if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or + ($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or + (($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and + $PSNativeCommandArgumentPassing -eq 'Legacy')) { +`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+` + } else { + $RequestComp="$RequestComp" + ' ""' + } } __%[1]s_debug "Calling $RequestComp" From c7300f0bdd22311c9a823ea0ea773bf968100eac Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Mon, 6 Mar 2023 02:07:19 +0000 Subject: [PATCH 20/76] ci: deprecate go 1.16 (#1926) --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4307724..5ffcebaf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,7 +63,6 @@ jobs: - ubuntu - macOS go: - - 16 - 17 - 18 - 19 From fb3652402b30b582975880f652e7ed81343e8312 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Mon, 6 Mar 2023 02:11:33 +0000 Subject: [PATCH 21/76] ci: test Golang 1.20 (#1925) --- .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 5ffcebaf..f3076b79 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: '^1.19' + go-version: '^1.20' check-latest: true cache: true @@ -66,6 +66,7 @@ jobs: - 17 - 18 - 19 + - 20 name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x' runs-on: ${{ matrix.platform }}-latest steps: From 9e6b58afc70c60a6b3c8a0138fb25acc734d47e3 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Mon, 6 Mar 2023 02:28:31 +0000 Subject: [PATCH 22/76] update copyright year (#1927) --- .github/workflows/test.yml | 2 +- .golangci.yml | 2 +- active_help.go | 2 +- active_help_test.go | 2 +- args.go | 2 +- args_test.go | 2 +- bash_completions.go | 2 +- bash_completionsV2.go | 2 +- bash_completionsV2_test.go | 2 +- bash_completions_test.go | 2 +- cobra.go | 2 +- cobra_test.go | 2 +- command.go | 2 +- command_notwin.go | 2 +- command_test.go | 2 +- command_win.go | 2 +- completions.go | 2 +- completions_test.go | 2 +- doc/cmd_test.go | 2 +- doc/man_docs.go | 2 +- doc/man_docs_test.go | 2 +- doc/man_examples_test.go | 2 +- doc/md_docs.go | 2 +- doc/md_docs_test.go | 2 +- doc/rest_docs.go | 2 +- doc/rest_docs_test.go | 2 +- doc/util.go | 2 +- doc/yaml_docs.go | 2 +- doc/yaml_docs_test.go | 2 +- fish_completions.go | 2 +- fish_completions_test.go | 2 +- flag_groups.go | 2 +- flag_groups_test.go | 2 +- powershell_completions.go | 2 +- powershell_completions_test.go | 2 +- shell_completions.go | 2 +- zsh_completions.go | 2 +- zsh_completions_test.go | 2 +- 38 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3076b79..6a0c7bb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: -v $(pwd):/wrk -w /wrk ghcr.io/google/addlicense -c 'The Cobra Authors' - -y '2013-2022' + -y '2013-2023' -l apache -ignore '.github/**' -check diff --git a/.golangci.yml b/.golangci.yml index 439d3e1d..2578d94b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,4 @@ -# Copyright 2013-2022 The Cobra Authors +# Copyright 2013-2023 The Cobra Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/active_help.go b/active_help.go index 95e03aec..2d023943 100644 --- a/active_help.go +++ b/active_help.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/active_help_test.go b/active_help_test.go index 3653dd87..2d624794 100644 --- a/active_help_test.go +++ b/active_help_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/args.go b/args.go index 2f4056c8..e79ec33a 100644 --- a/args.go +++ b/args.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/args_test.go b/args_test.go index fd2c3b41..90d174cc 100644 --- a/args_test.go +++ b/args_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/bash_completions.go b/bash_completions.go index a9f4e4f2..10c78847 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 5a38e38d..19b09560 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/bash_completionsV2_test.go b/bash_completionsV2_test.go index 9302baf0..88587e29 100644 --- a/bash_completionsV2_test.go +++ b/bash_completionsV2_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/bash_completions_test.go b/bash_completions_test.go index 5c32306d..44412577 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/cobra.go b/cobra.go index 841cda0c..b07b44a0 100644 --- a/cobra.go +++ b/cobra.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/cobra_test.go b/cobra_test.go index 71353c9d..fbb07f9b 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/command.go b/command.go index b6e3f4a1..01f7c6f1 100644 --- a/command.go +++ b/command.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/command_notwin.go b/command_notwin.go index 2b77f8f0..307f0c12 100644 --- a/command_notwin.go +++ b/command_notwin.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/command_test.go b/command_test.go index 18011325..0212f5ae 100644 --- a/command_test.go +++ b/command_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/command_win.go b/command_win.go index 520f23ab..adbef395 100644 --- a/command_win.go +++ b/command_win.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/completions.go b/completions.go index c7b457eb..8a42f889 100644 --- a/completions.go +++ b/completions.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/completions_test.go b/completions_test.go index 2364ed2e..0588da0f 100644 --- a/completions_test.go +++ b/completions_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/cmd_test.go b/doc/cmd_test.go index 4f2a0f58..0d022c77 100644 --- a/doc/cmd_test.go +++ b/doc/cmd_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/man_docs.go b/doc/man_docs.go index b5a2c596..b8c15ce8 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index d296eebe..c111d455 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/man_examples_test.go b/doc/man_examples_test.go index b995bf6d..873b2b6d 100644 --- a/doc/man_examples_test.go +++ b/doc/man_examples_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/md_docs.go b/doc/md_docs.go index bab4b496..c4a27c00 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index b1632e9c..e70cad82 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/rest_docs.go b/doc/rest_docs.go index bb0f8ec4..2cca6fd7 100644 --- a/doc/rest_docs.go +++ b/doc/rest_docs.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/rest_docs_test.go b/doc/rest_docs_test.go index 0c197f8d..1a3ea9dd 100644 --- a/doc/rest_docs_test.go +++ b/doc/rest_docs_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/util.go b/doc/util.go index 499ecdd9..0aaa07a1 100644 --- a/doc/util.go +++ b/doc/util.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/yaml_docs.go b/doc/yaml_docs.go index 12f8cf33..2b26d6ec 100644 --- a/doc/yaml_docs.go +++ b/doc/yaml_docs.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/doc/yaml_docs_test.go b/doc/yaml_docs_test.go index c552077d..1a6fa7c3 100644 --- a/doc/yaml_docs_test.go +++ b/doc/yaml_docs_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/fish_completions.go b/fish_completions.go index 32c4bea0..12ca0d2b 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/fish_completions_test.go b/fish_completions_test.go index 935f5540..10d97d85 100644 --- a/fish_completions_test.go +++ b/fish_completions_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/flag_groups.go b/flag_groups.go index 9c377aaf..b35fde15 100644 --- a/flag_groups.go +++ b/flag_groups.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/flag_groups_test.go b/flag_groups_test.go index b4b65ac0..bf988d73 100644 --- a/flag_groups_test.go +++ b/flag_groups_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/powershell_completions.go b/powershell_completions.go index 927733b3..177d2755 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/powershell_completions_test.go b/powershell_completions_test.go index 3d3d1033..b4092134 100644 --- a/powershell_completions_test.go +++ b/powershell_completions_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/shell_completions.go b/shell_completions.go index 126e83c3..b035742d 100644 --- a/shell_completions.go +++ b/shell_completions.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/zsh_completions.go b/zsh_completions.go index 29bcb570..9afdf3c0 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 258b1f71..fe898b3d 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -1,4 +1,4 @@ -// Copyright 2013-2022 The Cobra Authors +// Copyright 2013-2023 The Cobra Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From b197a24504fb0c99318ad0f07cb899e6074d315a Mon Sep 17 00:00:00 2001 From: Florent Poinsard <35779988+frouioui@users.noreply.github.com> Date: Tue, 14 Mar 2023 14:28:50 +0200 Subject: [PATCH 23/76] Update projects_using_cobra.md (#1932) Signed-off-by: Florent Poinsard --- projects_using_cobra.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 4a61f107..8a291eb2 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -1,6 +1,7 @@ ## Projects using Cobra - [Allero](https://github.com/allero-io/allero) +- [Arewefastyet](https://benchmark.vitess.io) - [Arduino CLI](https://github.com/arduino/arduino-cli) - [Bleve](https://blevesearch.com/) - [Cilium](https://cilium.io/) @@ -57,6 +58,7 @@ - [Tendermint](https://github.com/tendermint/tendermint) - [Twitch CLI](https://github.com/twitchdev/twitch-cli) - [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli) +- [Vitess](https://vitess.io) - VMware's [Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) & [Tanzu Framework](https://github.com/vmware-tanzu/tanzu-framework) - [Werf](https://werf.io/) - [ZITADEL](https://github.com/zitadel/zitadel) From c8a20a16bafa2dc85d304b59b2e677c7f75a73a6 Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Thu, 16 Mar 2023 21:03:29 -0400 Subject: [PATCH 24/76] Document suggested layout for subcommands (#1930) Signed-off-by: Luiz Carvalho --- user_guide.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/user_guide.md b/user_guide.md index 00b53d03..85201d84 100644 --- a/user_guide.md +++ b/user_guide.md @@ -188,6 +188,37 @@ var versionCmd = &cobra.Command{ } ``` +### Organizing subcommands + +A command may have subcommands which in turn may have other subcommands. This is achieved by using +`AddCommand`. In some cases, especially in larger applications, each subcommand may be defined in +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 +├── cmd +│   ├── root.go +│   └── sub1 +│   ├── sub1.go +│   └── sub2 +│   ├── leafA.go +│   ├── leafB.go +│   └── sub2.go +└── main.go +``` + +In this case: + +* The `init` function of `root.go` adds the command defined in `sub1.go` to the root command. +* The `init` function of `sub1.go` adds the command defined in `sub2.go` to the sub1 command. +* The `init` function of `sub2.go` adds the commands defined in `leafA.go` and `leafB.go` to the + sub2 command. + +This approach ensures the subcommands are always included at compile time while avoiding cyclic +references. + ### Returning and handling errors If you wish to return an error to the caller of a command, `RunE` can be used. From 45360a55ccccf909b35f4290242bfb8a6bfbef66 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Wed, 22 Mar 2023 18:41:05 -0400 Subject: [PATCH 25/76] Allow sourcing zsh completion script (#1917) Although it is not the recommended approach, sourcing a completion script is the simplest way to get people to try using shell completion. Not allowing it for zsh has turned out to complicate shell completion adoption. Further, many tools modify the zsh script to allow sourcing. This commit allows sourcing of the zsh completion script. Signed-off-by: Marc Khouzam --- completions.go | 2 +- zsh_completions.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/completions.go b/completions.go index 8a42f889..ee38c4d0 100644 --- a/completions.go +++ b/completions.go @@ -734,7 +734,7 @@ to enable it. You can execute the following once: To load completions in your current shell session: - source <(%[1]s completion zsh); compdef _%[1]s %[1]s + source <(%[1]s completion zsh) To load completions for every new session, execute once: diff --git a/zsh_completions.go b/zsh_completions.go index 9afdf3c0..1856e4c7 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -90,6 +90,7 @@ func genZshComp(buf io.StringWriter, name string, includeDesc bool) { compCmd = ShellCompNoDescRequestCmd } WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s +compdef _%[1]s %[1]s # zsh completion for %-36[1]s -*- shell-script -*- From 4dd4b25de38418174a6e859e8a32eaccca32dccc Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Wed, 22 Mar 2023 18:41:51 -0400 Subject: [PATCH 26/76] Update main image to better handle dark background (#1883) Fixes #1880 Signed-off-by: Marc Khouzam Co-authored-by: Deleplace --- README.md | 2 +- assets/CobraMain.png | Bin 0 -> 73479 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/CobraMain.png diff --git a/README.md b/README.md index 063f31a4..592c0b8a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![cobra logo](https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png) +![cobra logo](assets/CobraMain.png) Cobra is a library for creating powerful modern CLI applications. diff --git a/assets/CobraMain.png b/assets/CobraMain.png new file mode 100644 index 0000000000000000000000000000000000000000..6f1b68a756ad3f615cafdffcd195a75bb2aaa489 GIT binary patch literal 73479 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 kJ0K+vg#82rgLwbg`aUHx3vIVCg!0N Date: Sat, 8 Apr 2023 06:47:33 +0200 Subject: [PATCH 27/76] Fix typo in fish completions (#1945) --- fish_completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fish_completions.go b/fish_completions.go index 12ca0d2b..12d61b69 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -113,7 +113,7 @@ function __%[1]s_clear_perform_completion_once_result __%[1]s_debug "" __%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable ==========" set --erase __%[1]s_perform_completion_once_result - __%[1]s_debug "Succesfully erased the variable __%[1]s_perform_completion_once_result" + __%[1]s_debug "Successfully erased the variable __%[1]s_perform_completion_once_result" end function __%[1]s_requires_order_preservation From 0e3a0bfe91a52670ba78ec020f3657d48954a4f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 02:39:30 +0000 Subject: [PATCH 28/76] build(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.5.0 (#1971) --- .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 6a0c7bb8..2fd68aa8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3.4.0 + - uses: golangci/golangci-lint-action@v3.5.0 with: version: latest args: --verbose From 2246fa82e91dda4486b9466fab691b41b516f747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taavi=20V=C3=A4=C3=A4n=C3=A4nen?= Date: Tue, 13 Jun 2023 18:12:49 +0300 Subject: [PATCH 29/76] Fix grammar: 'allows to' (#1978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use in generated bash completion files is getting flagged by Lintian (the Debian package linting tool). Signed-off-by: Taavi Väänänen --- bash_completions.go | 2 +- bash_completionsV2.go | 2 +- cobra.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 10c78847..8a531518 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -85,7 +85,7 @@ __%[1]s_handle_go_custom_completion() local out requestComp lastParam lastChar comp directive args # Prepare the command to request completions for the program. - # Calling ${words[0]} instead of directly %[1]s allows to handle aliases + # Calling ${words[0]} instead of directly %[1]s allows handling aliases args=("${words[@]:1}") # Disable ActiveHelp which is not supported for bash completion v1 requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}" diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 19b09560..1cce5c32 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -57,7 +57,7 @@ __%[1]s_get_completion_results() { local requestComp lastParam lastChar args # Prepare the command to request completions for the program. - # Calling ${words[0]} instead of directly %[1]s allows to handle aliases + # Calling ${words[0]} instead of directly %[1]s allows handling aliases args=("${words[@]:1}") requestComp="${words[0]} %[2]s ${args[*]}" diff --git a/cobra.go b/cobra.go index b07b44a0..f23f5092 100644 --- a/cobra.go +++ b/cobra.go @@ -48,7 +48,7 @@ const ( defaultCaseInsensitive = false ) -// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing +// EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing // to automatically enable in CLI tools. // Set this to true to enable it. var EnablePrefixMatching = defaultPrefixMatching From 988bd76139210bc1d686c7112b4eb5e784365f1f Mon Sep 17 00:00:00 2001 From: Branch Vincent Date: Fri, 16 Jun 2023 07:25:30 -0700 Subject: [PATCH 30/76] test: make fish_completions_test more robust (#1980) Use temporary files instead of assuming the current directory is writable. Also, if creating a temporary file still returns an error, prevent the test from failing silently by replacing `log.Fatal` with `t.Fatal`. --- fish_completions_test.go | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/fish_completions_test.go b/fish_completions_test.go index 10d97d85..ce2a531d 100644 --- a/fish_completions_test.go +++ b/fish_completions_test.go @@ -16,9 +16,10 @@ package cobra import ( "bytes" + "errors" "fmt" - "log" "os" + "path/filepath" "testing" ) @@ -98,12 +99,12 @@ func TestFishCompletionNoActiveHelp(t *testing.T) { } func TestGenFishCompletionFile(t *testing.T) { - err := os.Mkdir("./tmp", 0755) + tmpFile, err := os.CreateTemp("", "cobra-test") if err != nil { - log.Fatal(err.Error()) + t.Fatal(err.Error()) } - defer os.RemoveAll("./tmp") + defer os.Remove(tmpFile.Name()) rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -113,18 +114,18 @@ func TestGenFishCompletionFile(t *testing.T) { } rootCmd.AddCommand(child) - assertNoErr(t, rootCmd.GenFishCompletionFile("./tmp/test", false)) + assertNoErr(t, rootCmd.GenFishCompletionFile(tmpFile.Name(), false)) } func TestFailGenFishCompletionFile(t *testing.T) { - err := os.Mkdir("./tmp", 0755) + tmpDir, err := os.MkdirTemp("", "cobra-test") if err != nil { - log.Fatal(err.Error()) + t.Fatal(err.Error()) } - defer os.RemoveAll("./tmp") + defer os.RemoveAll(tmpDir) - f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0400) + f, _ := os.OpenFile(filepath.Join(tmpDir, "test"), os.O_CREATE, 0400) defer f.Close() rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} @@ -135,18 +136,8 @@ func TestFailGenFishCompletionFile(t *testing.T) { } rootCmd.AddCommand(child) - got := rootCmd.GenFishCompletionFile("./tmp/test", false) - if got == nil { - t.Error("should raise permission denied error") - } - - if os.Getenv("MSYSTEM") == "MINGW64" { - if got.Error() != "open ./tmp/test: Access is denied." { - t.Errorf("got: %s, want: %s", got.Error(), "open ./tmp/test: Access is denied.") - } - } else { - if got.Error() != "open ./tmp/test: permission denied" { - t.Errorf("got: %s, want: %s", got.Error(), "open ./tmp/test: permission denied") - } + got := rootCmd.GenFishCompletionFile(f.Name(), false) + if !errors.Is(got, os.ErrPermission) { + t.Errorf("got: %s, want: %s", got.Error(), os.ErrPermission.Error()) } } From fdee73b4a0d0be3e233b968ab038866b7cd99772 Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:16:18 +0200 Subject: [PATCH 31/76] powershell: escape variable with curly brackets (#1960) This fixes an issue with program names that include a dot, in our case `podman.exe`. This was caused by the change in commit 6ba7ebbc. Fixes #1853 Signed-off-by: Paul Holzinger --- powershell_completions.go | 6 +++--- powershell_completions_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/powershell_completions.go b/powershell_completions.go index 177d2755..55195193 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -47,7 +47,7 @@ filter __%[1]s_escapeStringWithSpecialChars { `+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+` } -[scriptblock]$__%[2]sCompleterBlock = { +[scriptblock]${__%[2]sCompleterBlock} = { param( $WordToComplete, $CommandAst, @@ -122,7 +122,7 @@ filter __%[1]s_escapeStringWithSpecialChars { __%[1]s_debug "Calling $RequestComp" # First disable ActiveHelp which is not supported for Powershell - $env:%[10]s=0 + ${env:%[10]s}=0 #call the command store the output in $out and redirect stderr and stdout to null # $Out is an array contains each line per element @@ -279,7 +279,7 @@ filter __%[1]s_escapeStringWithSpecialChars { } } -Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock $__%[2]sCompleterBlock +Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock ${__%[2]sCompleterBlock} `, name, nameForVar, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name))) diff --git a/powershell_completions_test.go b/powershell_completions_test.go index b4092134..603b50c9 100644 --- a/powershell_completions_test.go +++ b/powershell_completions_test.go @@ -29,5 +29,5 @@ func TestPwshCompletionNoActiveHelp(t *testing.T) { // check that active help is being disabled activeHelpVar := activeHelpEnvVar(c.Name()) - check(t, output, fmt.Sprintf("%s=0", activeHelpVar)) + check(t, output, fmt.Sprintf("${env:%s}=0", activeHelpVar)) } From cbe4865373c671ff257810b08e9d855aca922cee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 20:46:35 +0000 Subject: [PATCH 32/76] build(deps): bump golangci/golangci-lint-action from 3.5.0 to 3.6.0 (#1976) --- .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 2fd68aa8..498e3bec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3.5.0 + - uses: golangci/golangci-lint-action@v3.6.0 with: version: latest args: --verbose From dcb405a9399ed307eaa0e50f5cb1c290c8979dd4 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:15:36 +0100 Subject: [PATCH 33/76] Move documentation sources to site/content (#1428) --- README.md | 8 ++++---- fish_completions.md | 4 ---- powershell_completions.md | 3 --- active_help.md => site/content/active_help.md | 0 .../content/completions/_index.md | 12 ++++++------ .../content/completions/bash.md | 2 +- site/content/completions/fish.md | 4 ++++ site/content/completions/powershell.md | 3 +++ .../content/completions/zsh.md | 2 +- doc/README.md => site/content/docgen/_index.md | 8 ++++---- doc/man_docs.md => site/content/docgen/man.md | 0 doc/md_docs.md => site/content/docgen/md.md | 0 doc/rest_docs.md => site/content/docgen/rest.md | 0 doc/yaml_docs.md => site/content/docgen/yaml.md | 0 .../content/projects_using_cobra.md | 0 user_guide.md => site/content/user_guide.md | 15 ++++++++++----- 16 files changed, 33 insertions(+), 28 deletions(-) delete mode 100644 fish_completions.md delete mode 100644 powershell_completions.md rename active_help.md => site/content/active_help.md (100%) rename shell_completions.md => site/content/completions/_index.md (98%) rename bash_completions.md => site/content/completions/bash.md (98%) create mode 100644 site/content/completions/fish.md create mode 100644 site/content/completions/powershell.md rename zsh_completions.md => site/content/completions/zsh.md (98%) rename doc/README.md => site/content/docgen/_index.md (73%) rename doc/man_docs.md => site/content/docgen/man.md (100%) rename doc/md_docs.md => site/content/docgen/md.md (100%) rename doc/rest_docs.md => site/content/docgen/rest.md (100%) rename doc/yaml_docs.md => site/content/docgen/yaml.md (100%) rename projects_using_cobra.md => site/content/projects_using_cobra.md (100%) rename user_guide.md => site/content/user_guide.md (97%) diff --git a/README.md b/README.md index 592c0b8a..6444f4b7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Cobra is a library for creating powerful modern CLI applications. Cobra is used in many Go projects such as [Kubernetes](https://kubernetes.io/), [Hugo](https://gohugo.io), and [GitHub CLI](https://github.com/cli/cli) to -name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra. +name a few. [This list](site/content/projects_using_cobra.md) contains a more extensive list of projects using Cobra. [![](https://img.shields.io/github/actions/workflow/status/spf13/cobra/test.yml?branch=main&longCache=true&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest) [![Go Reference](https://pkg.go.dev/badge/github.com/spf13/cobra.svg)](https://pkg.go.dev/github.com/spf13/cobra) @@ -80,7 +80,7 @@ which maintains the same interface while adding POSIX compliance. # Installing Using Cobra is easy. First, use `go get` to install the latest version -of the library. +of the library. ``` go get -u github.com/spf13/cobra@latest @@ -105,8 +105,8 @@ 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](user_guide.md). +For complete details on using the Cobra library, please read the [The Cobra User Guide](site/content/user_guide.md). # License -Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt) +Cobra is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt) diff --git a/fish_completions.md b/fish_completions.md deleted file mode 100644 index 19b2ed12..00000000 --- a/fish_completions.md +++ /dev/null @@ -1,4 +0,0 @@ -## Generating Fish Completions For Your cobra.Command - -Please refer to [Shell Completions](shell_completions.md) for details. - diff --git a/powershell_completions.md b/powershell_completions.md deleted file mode 100644 index c449f1e5..00000000 --- a/powershell_completions.md +++ /dev/null @@ -1,3 +0,0 @@ -# Generating PowerShell Completions For Your Own cobra.Command - -Please refer to [Shell Completions](shell_completions.md#powershell-completions) for details. diff --git a/active_help.md b/site/content/active_help.md similarity index 100% rename from active_help.md rename to site/content/active_help.md diff --git a/shell_completions.md b/site/content/completions/_index.md similarity index 98% rename from shell_completions.md rename to site/content/completions/_index.md index 065c0621..4efad290 100644 --- a/shell_completions.md +++ b/site/content/completions/_index.md @@ -416,7 +416,7 @@ completion firstcommand secondcommand ### Bash legacy dynamic completions For backward compatibility, Cobra still supports its bash legacy dynamic completion solution. -Please refer to [Bash Completions](bash_completions.md) for details. +Please refer to [Bash Completions](bash.md) for details. ### Bash completion V2 @@ -425,13 +425,13 @@ Cobra provides two versions for bash completion. The original bash completion ( A new V2 bash completion version is also available. This version can be used by calling `GenBashCompletionV2()` or `GenBashCompletionFileV2()`. The V2 version does **not** support the legacy dynamic completion -(see [Bash Completions](bash_completions.md)) but instead works only with the Go dynamic completion +(see [Bash Completions](bash.md)) but instead works only with the Go dynamic completion solution described in this document. Unless your program already uses the legacy dynamic completion solution, it is recommended that you use the bash completion V2 solution which provides the following extra features: - Supports completion descriptions (like the other shells) - Small completion script of less than 300 lines (v1 generates scripts of thousands of lines; `kubectl` for example has a bash v1 completion script of over 13K lines) -- Streamlined user experience thanks to a completion behavior aligned with the other shells +- Streamlined user experience thanks to a completion behavior aligned with the other shells `Bash` completion V2 supports descriptions for completions. When calling `GenBashCompletionV2()` or `GenBashCompletionFileV2()` you must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra @@ -448,7 +448,7 @@ show (show information of a chart) $ helm s[tab][tab] search show status ``` -**Note**: Cobra's default `completion` command uses bash completion V2. If for some reason you need to use bash completion V1, you will need to implement your own `completion` command. +**Note**: Cobra's default `completion` command uses bash completion V2. If for some reason you need to use bash completion V1, you will need to implement your own `completion` command. ## Zsh completions Cobra supports native zsh completion generated from the root `cobra.Command`. @@ -482,7 +482,7 @@ search show status ### Zsh completions standardization Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backward-compatible, some small changes in behavior were introduced. -Please refer to [Zsh Completions](zsh_completions.md) for details. +Please refer to [Zsh Completions](zsh.md) for details. ## fish completions @@ -535,7 +535,7 @@ search (search for a keyword in charts) show (show information of a chart) s # With descriptions and Mode 'MenuComplete' The description of the current selected value will be displayed below the suggestions. $ helm s[tab] -search show status +search show status search for a keyword in charts diff --git a/bash_completions.md b/site/content/completions/bash.md similarity index 98% rename from bash_completions.md rename to site/content/completions/bash.md index 52919b2f..6838a3a6 100644 --- a/bash_completions.md +++ b/site/content/completions/bash.md @@ -1,6 +1,6 @@ # Generating Bash Completions For Your cobra.Command -Please refer to [Shell Completions](shell_completions.md) for details. +Please refer to [Shell Completions](_index.md) for details. ## Bash legacy dynamic completions diff --git a/site/content/completions/fish.md b/site/content/completions/fish.md new file mode 100644 index 00000000..5253f7d4 --- /dev/null +++ b/site/content/completions/fish.md @@ -0,0 +1,4 @@ +## Generating Fish Completions For Your cobra.Command + +Please refer to [Shell Completions](_index.md) for details. + diff --git a/site/content/completions/powershell.md b/site/content/completions/powershell.md new file mode 100644 index 00000000..024b119a --- /dev/null +++ b/site/content/completions/powershell.md @@ -0,0 +1,3 @@ +# Generating PowerShell Completions For Your Own cobra.Command + +Please refer to [Shell Completions](_index.md#powershell-completions) for details. diff --git a/zsh_completions.md b/site/content/completions/zsh.md similarity index 98% rename from zsh_completions.md rename to site/content/completions/zsh.md index 7cff6178..3e27208b 100644 --- a/zsh_completions.md +++ b/site/content/completions/zsh.md @@ -1,6 +1,6 @@ ## Generating Zsh Completion For Your cobra.Command -Please refer to [Shell Completions](shell_completions.md) for details. +Please refer to [Shell Completions](_index.md) for details. ## Zsh completions standardization diff --git a/doc/README.md b/site/content/docgen/_index.md similarity index 73% rename from doc/README.md rename to site/content/docgen/_index.md index 8e07baae..eba2a5fc 100644 --- a/doc/README.md +++ b/site/content/docgen/_index.md @@ -1,9 +1,9 @@ # Documentation generation -- [Man page docs](./man_docs.md) -- [Markdown docs](./md_docs.md) -- [Rest docs](./rest_docs.md) -- [Yaml docs](./yaml_docs.md) +- [Man page docs](man.md) +- [Markdown docs](md.md) +- [Rest docs](rest.md) +- [Yaml docs](yaml.md) ## Options ### `DisableAutoGenTag` diff --git a/doc/man_docs.md b/site/content/docgen/man.md similarity index 100% rename from doc/man_docs.md rename to site/content/docgen/man.md diff --git a/doc/md_docs.md b/site/content/docgen/md.md similarity index 100% rename from doc/md_docs.md rename to site/content/docgen/md.md diff --git a/doc/rest_docs.md b/site/content/docgen/rest.md similarity index 100% rename from doc/rest_docs.md rename to site/content/docgen/rest.md diff --git a/doc/yaml_docs.md b/site/content/docgen/yaml.md similarity index 100% rename from doc/yaml_docs.md rename to site/content/docgen/yaml.md diff --git a/projects_using_cobra.md b/site/content/projects_using_cobra.md similarity index 100% rename from projects_using_cobra.md rename to site/content/projects_using_cobra.md diff --git a/user_guide.md b/site/content/user_guide.md similarity index 97% rename from user_guide.md rename to site/content/user_guide.md index 85201d84..04971431 100644 --- a/user_guide.md +++ b/site/content/user_guide.md @@ -29,8 +29,8 @@ func main() { ## Using the Cobra Generator -Cobra-CLI is its own program that will create your application and add any -commands you want. It's the easiest way to incorporate Cobra into your application. +Cobra-CLI is its own program that will create your application and add any commands you want. +It's the easiest way to incorporate Cobra into your application. For complete details on using the Cobra generator, please refer to [The Cobra-CLI Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md) @@ -715,12 +715,17 @@ Run 'kubectl help' for usage. ## Generating documentation for your command -Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md). +Cobra can generate documentation based on subcommands, flags, etc. +Read more about it in the [docs generation documentation](docgen/_index.md). ## Generating shell completions -Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md). +Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell. +If you add more information to your commands, these completions can be amazingly powerful and flexible. +Read more about it in [Shell Completions](completions/_index.md). ## Providing Active Help -Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users. Active Help are messages (hints, warnings, etc) printed as the program is being used. Read more about it in [Active Help](active_help.md). +Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users. +Active Help are messages (hints, warnings, etc) printed as the program is being used. +Read more about it in [Active Help](active_help.md). From c81c46a015b48d9f79c73cc6f528f90eaabb0c7e Mon Sep 17 00:00:00 2001 From: Martijn Evers <94963229+marevers@users.noreply.github.com> Date: Sun, 16 Jul 2023 18:38:22 +0200 Subject: [PATCH 34/76] Add 'one required flag' group (#1952) --- completions_test.go | 98 ++++++++++++++++++++++++++++++++++++++ flag_groups.go | 68 +++++++++++++++++++++++++- flag_groups_test.go | 63 ++++++++++++++++++++---- site/content/user_guide.md | 11 ++++- 4 files changed, 228 insertions(+), 12 deletions(-) diff --git a/completions_test.go b/completions_test.go index 0588da0f..01724660 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2830,6 +2830,104 @@ func TestCompletionForGroupedFlags(t *testing.T) { } } +func TestCompletionForOneRequiredGroupFlags(t *testing.T) { + getCmd := func() *Command { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd := &Command{ + Use: "child", + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"subArg"}, ShellCompDirectiveNoFileComp + }, + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + rootCmd.PersistentFlags().Int("ingroup1", -1, "ingroup1") + rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2") + + childCmd.Flags().Bool("ingroup3", false, "ingroup3") + childCmd.Flags().Bool("nogroup", false, "nogroup") + + // Add flags to a group + childCmd.MarkFlagsOneRequired("ingroup1", "ingroup2", "ingroup3") + + return rootCmd + } + + // Each test case uses a unique command from the function above. + testcases := []struct { + desc string + args []string + expectedOutput string + }{ + { + desc: "flags in group suggested without - prefix", + args: []string{"child", ""}, + expectedOutput: strings.Join([]string{ + "--ingroup1", + "--ingroup2", + "--ingroup3", + "subArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, + { + desc: "flags in group suggested with - prefix", + args: []string{"child", "-"}, + expectedOutput: strings.Join([]string{ + "--ingroup1", + "--ingroup2", + "--ingroup3", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, + { + desc: "when any flag in group present, other flags in group not suggested without - prefix", + args: []string{"child", "--ingroup2", "value", ""}, + expectedOutput: strings.Join([]string{ + "subArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, + { + desc: "when all flags in group present, flags not suggested without - prefix", + args: []string{"child", "--ingroup1", "8", "--ingroup2", "value2", "--ingroup3", ""}, + expectedOutput: strings.Join([]string{ + "subArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, + { + desc: "group ignored if some flags not applicable", + args: []string{"--ingroup2", "value", ""}, + expectedOutput: strings.Join([]string{ + "child", + "completion", + "help", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + c := getCmd() + args := []string{ShellCompNoDescRequestCmd} + args = append(args, tc.args...) + output, err := executeCommand(c, args...) + switch { + case err == nil && output != tc.expectedOutput: + t.Errorf("expected: %q, got: %q", tc.expectedOutput, output) + case err != nil: + t.Errorf("Unexpected error %q", err) + } + }) + } +} + func TestCompletionForMutuallyExclusiveFlags(t *testing.T) { getCmd := func() *Command { rootCmd := &Command{ diff --git a/flag_groups.go b/flag_groups.go index b35fde15..0671ec5f 100644 --- a/flag_groups.go +++ b/flag_groups.go @@ -24,6 +24,7 @@ import ( const ( requiredAsGroup = "cobra_annotation_required_if_others_set" + oneRequired = "cobra_annotation_one_required" mutuallyExclusive = "cobra_annotation_mutually_exclusive" ) @@ -43,6 +44,22 @@ func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) { } } +// MarkFlagsOneRequired marks the given flags with annotations so that Cobra errors +// if the command is invoked without at least one flag from the given set of flags. +func (c *Command) MarkFlagsOneRequired(flagNames ...string) { + c.mergePersistentFlags() + for _, v := range flagNames { + f := c.Flags().Lookup(v) + 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 { + // Only errs if the flag isn't found. + panic(err) + } + } +} + // MarkFlagsMutuallyExclusive marks the given flags with annotations so that Cobra errors // if the command is invoked with more than one flag from the given set of flags. func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) { @@ -59,7 +76,7 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) { } } -// ValidateFlagGroups validates the mutuallyExclusive/requiredAsGroup logic and returns the +// ValidateFlagGroups validates the mutuallyExclusive/oneRequired/requiredAsGroup logic and returns the // first error encountered. func (c *Command) ValidateFlagGroups() error { if c.DisableFlagParsing { @@ -71,15 +88,20 @@ func (c *Command) ValidateFlagGroups() error { // groupStatus format is the list of flags as a unique ID, // then a map of each flag name and whether it is set or not. groupStatus := map[string]map[string]bool{} + 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) }) if err := validateRequiredFlagGroups(groupStatus); err != nil { return err } + if err := validateOneRequiredFlagGroups(oneRequiredGroupStatus); err != nil { + return err + } if err := validateExclusiveFlagGroups(mutuallyExclusiveGroupStatus); err != nil { return err } @@ -142,6 +164,27 @@ func validateRequiredFlagGroups(data map[string]map[string]bool) error { return nil } +func validateOneRequiredFlagGroups(data map[string]map[string]bool) error { + keys := sortedKeys(data) + for _, flagList := range keys { + flagnameAndStatus := data[flagList] + var set []string + for flagname, isSet := range flagnameAndStatus { + if isSet { + set = append(set, flagname) + } + } + if len(set) >= 1 { + continue + } + + // Sort values, so they can be tested/scripted against consistently. + sort.Strings(set) + return fmt.Errorf("at least one of the flags in the group [%v] is required", flagList) + } + return nil +} + func validateExclusiveFlagGroups(data map[string]map[string]bool) error { keys := sortedKeys(data) for _, flagList := range keys { @@ -176,6 +219,7 @@ func sortedKeys(m map[string]map[string]bool) []string { // enforceFlagGroupsForCompletion will do the following: // - when a flag in a group is present, other flags in the group will be marked required +// - when none of the flags in a one-required group are present, all flags in the group will be marked required // - when a flag in a mutually exclusive group is present, other flags in the group will be marked as hidden // This allows the standard completion logic to behave appropriately for flag groups func (c *Command) enforceFlagGroupsForCompletion() { @@ -185,9 +229,11 @@ func (c *Command) enforceFlagGroupsForCompletion() { flags := c.Flags() groupStatus := map[string]map[string]bool{} + 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) }) @@ -204,6 +250,26 @@ func (c *Command) enforceFlagGroupsForCompletion() { } } + // If none of the flags of a one-required group are present, we make all the flags + // of that group required so that the shell completion suggests them automatically + for flagList, flagnameAndStatus := range oneRequiredGroupStatus { + set := 0 + + for _, isSet := range flagnameAndStatus { + if isSet { + set++ + } + } + + // None of the flags of the group are set, mark all flags in the group + // as required + if set == 0 { + for _, fName := range strings.Split(flagList, " ") { + _ = c.MarkFlagRequired(fName) + } + } + } + // If a flag that is mutually exclusive to others is present, we hide the other // flags of that group so the shell completion does not suggest them for flagList, flagnameAndStatus := range mutuallyExclusiveGroupStatus { diff --git a/flag_groups_test.go b/flag_groups_test.go index bf988d73..cffa8552 100644 --- a/flag_groups_test.go +++ b/flag_groups_test.go @@ -43,13 +43,15 @@ func TestValidateFlagGroups(t *testing.T) { // Each test case uses a unique command from the function above. testcases := []struct { - desc string - flagGroupsRequired []string - flagGroupsExclusive []string - subCmdFlagGroupsRequired []string - subCmdFlagGroupsExclusive []string - args []string - expectErr string + desc string + flagGroupsRequired []string + flagGroupsOneRequired []string + flagGroupsExclusive []string + subCmdFlagGroupsRequired []string + subCmdFlagGroupsOneRequired []string + subCmdFlagGroupsExclusive []string + args []string + expectErr string }{ { desc: "No flags no problem", @@ -62,6 +64,11 @@ func TestValidateFlagGroups(t *testing.T) { flagGroupsRequired: []string{"a b c"}, args: []string{"--a=foo"}, expectErr: "if any flags in the group [a b c] are set they must all be set; missing [b c]", + }, { + desc: "One-required flag group not satisfied", + flagGroupsOneRequired: []string{"a b"}, + args: []string{"--c=foo"}, + expectErr: "at least one of the flags in the group [a b] is required", }, { desc: "Exclusive flag group not satisfied", flagGroupsExclusive: []string{"a b c"}, @@ -72,6 +79,11 @@ func TestValidateFlagGroups(t *testing.T) { flagGroupsRequired: []string{"a b c", "a d"}, args: []string{"--c=foo", "--d=foo"}, expectErr: `if any flags in the group [a b c] are set they must all be set; missing [a b]`, + }, { + desc: "Multiple one-required flag group not satisfied returns first error", + flagGroupsOneRequired: []string{"a b", "d e"}, + args: []string{"--c=foo", "--f=foo"}, + expectErr: `at least one of the flags in the group [a b] is required`, }, { desc: "Multiple exclusive flag group not satisfied returns first error", flagGroupsExclusive: []string{"a b c", "a d"}, @@ -82,32 +94,57 @@ func TestValidateFlagGroups(t *testing.T) { flagGroupsRequired: []string{"a d", "a b", "a c"}, args: []string{"--a=foo"}, expectErr: `if any flags in the group [a b] are set they must all be set; missing [b]`, + }, { + desc: "Validation of one-required groups occurs on groups in sorted order", + flagGroupsOneRequired: []string{"d e", "a b", "f g"}, + args: []string{"--c=foo"}, + expectErr: `at least one of the flags in the group [a b] is required`, }, { desc: "Validation of exclusive groups occurs on groups in sorted order", flagGroupsExclusive: []string{"a d", "a b", "a c"}, args: []string{"--a=foo", "--b=foo", "--c=foo"}, expectErr: `if any flags in the group [a b] are set none of the others can be; [a b] were all set`, }, { - desc: "Persistent flags utilize both features and can fail required groups", + desc: "Persistent flags utilize required and exclusive groups and can fail required groups", flagGroupsRequired: []string{"a e", "e f"}, flagGroupsExclusive: []string{"f g"}, args: []string{"--a=foo", "--f=foo", "--g=foo"}, expectErr: `if any flags in the group [a e] are set they must all be set; missing [e]`, }, { - desc: "Persistent flags utilize both features and can fail mutually exclusive groups", + desc: "Persistent flags utilize one-required and exclusive groups and can fail one-required groups", + flagGroupsOneRequired: []string{"a b", "e f"}, + flagGroupsExclusive: []string{"e f"}, + args: []string{"--e=foo"}, + expectErr: `at least one of the flags in the group [a b] is required`, + }, { + desc: "Persistent flags utilize required and exclusive groups and can fail mutually exclusive groups", flagGroupsRequired: []string{"a e", "e f"}, flagGroupsExclusive: []string{"f g"}, args: []string{"--a=foo", "--e=foo", "--f=foo", "--g=foo"}, expectErr: `if any flags in the group [f g] are set none of the others can be; [f g] were all set`, }, { - desc: "Persistent flags utilize both features and can pass", + desc: "Persistent flags utilize required and exclusive groups and can pass", flagGroupsRequired: []string{"a e", "e f"}, flagGroupsExclusive: []string{"f g"}, args: []string{"--a=foo", "--e=foo", "--f=foo"}, + }, { + desc: "Persistent flags utilize one-required and exclusive groups and can pass", + flagGroupsOneRequired: []string{"a e", "e f"}, + flagGroupsExclusive: []string{"f g"}, + args: []string{"--a=foo", "--e=foo", "--f=foo"}, }, { desc: "Subcmds can use required groups using inherited flags", subCmdFlagGroupsRequired: []string{"e subonly"}, args: []string{"subcmd", "--e=foo", "--subonly=foo"}, + }, { + desc: "Subcmds can use one-required groups using inherited flags", + subCmdFlagGroupsOneRequired: []string{"e subonly"}, + args: []string{"subcmd", "--e=foo", "--subonly=foo"}, + }, { + desc: "Subcmds can use one-required groups using inherited flags and fail one-required groups", + subCmdFlagGroupsOneRequired: []string{"e subonly"}, + args: []string{"subcmd"}, + expectErr: "at least one of the flags in the group [e subonly] is required", }, { desc: "Subcmds can use exclusive groups using inherited flags", subCmdFlagGroupsExclusive: []string{"e subonly"}, @@ -130,12 +167,18 @@ func TestValidateFlagGroups(t *testing.T) { for _, flagGroup := range tc.flagGroupsRequired { c.MarkFlagsRequiredTogether(strings.Split(flagGroup, " ")...) } + for _, flagGroup := range tc.flagGroupsOneRequired { + c.MarkFlagsOneRequired(strings.Split(flagGroup, " ")...) + } for _, flagGroup := range tc.flagGroupsExclusive { c.MarkFlagsMutuallyExclusive(strings.Split(flagGroup, " ")...) } for _, flagGroup := range tc.subCmdFlagGroupsRequired { sub.MarkFlagsRequiredTogether(strings.Split(flagGroup, " ")...) } + for _, flagGroup := range tc.subCmdFlagGroupsOneRequired { + sub.MarkFlagsOneRequired(strings.Split(flagGroup, " ")...) + } for _, flagGroup := range tc.subCmdFlagGroupsExclusive { sub.MarkFlagsMutuallyExclusive(strings.Split(flagGroup, " ")...) } diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 04971431..56e8e44a 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -349,7 +349,16 @@ rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML") rootCmd.MarkFlagsMutuallyExclusive("json", "yaml") ``` -In both of these cases: +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") +rootCmd.MarkFlagsOneRequired("json", "yaml") +rootCmd.MarkFlagsMutuallyExclusive("json", "yaml") +``` + +In these cases: - both local and persistent flags can be used - **NOTE:** the group is only enforced on commands where every flag is defined - a flag may appear in multiple groups From 66b215ba186e6e28ccab74b9022149cc2236d863 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:51:36 +0200 Subject: [PATCH 35/76] golangci: enable 'unused' and disable deprecated replaced by it (#1983) --- .golangci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 2578d94b..a618ec24 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,7 +19,7 @@ linters: disable-all: true enable: #- bodyclose - - deadcode + # - deadcode ! deprecated since v1.49.0; replaced by 'unused' #- depguard #- dogsled #- dupl @@ -51,12 +51,12 @@ linters: #- rowserrcheck #- scopelint #- staticcheck - - structcheck + #- structcheck ! deprecated since v1.49.0; replaced by 'unused' #- stylecheck #- typecheck - unconvert #- unparam - #- unused - - varcheck + - unused + # - varcheck ! deprecated since v1.49.0; replaced by 'unused' #- whitespace fast: false From 60d056d1574d04c859638354bc9cacab70bab296 Mon Sep 17 00:00:00 2001 From: gocurr Date: Thu, 20 Jul 2023 23:27:44 +0800 Subject: [PATCH 36/76] doc: fix typo, Deperecated -> Deprecated (#2000) --- doc/man_docs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index c111d455..a4435e6e 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -150,7 +150,7 @@ func TestGenManSeeAlso(t *testing.T) { } } -func TestManPrintFlagsHidesShortDeperecated(t *testing.T) { +func TestManPrintFlagsHidesShortDeprecated(t *testing.T) { c := &cobra.Command{} c.Flags().StringP("foo", "f", "default", "Foo flag") assertNoErr(t, c.Flags().MarkShorthandDeprecated("foo", "don't use it no more")) From fd865a44e3c48afeb6a6dbddadb8a5519173e029 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Sun, 23 Jul 2023 20:31:55 +0900 Subject: [PATCH 37/76] minor corrections to unit tests (#2003) --- command_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command_test.go b/command_test.go index 0212f5ae..b0f5e860 100644 --- a/command_test.go +++ b/command_test.go @@ -438,7 +438,7 @@ func TestFlagLong(t *testing.T) { output, err := executeCommand(c, "--intf=7", "--sf=abc", "one", "--", "two") if output != "" { - t.Errorf("Unexpected output: %v", err) + t.Errorf("Unexpected output: %v", output) } if err != nil { t.Errorf("Unexpected error: %v", err) @@ -475,7 +475,7 @@ func TestFlagShort(t *testing.T) { output, err := executeCommand(c, "-i", "7", "-sabc", "one", "two") if output != "" { - t.Errorf("Unexpected output: %v", err) + t.Errorf("Unexpected output: %v", output) } if err != nil { t.Errorf("Unexpected error: %v", err) @@ -504,7 +504,7 @@ func TestChildFlag(t *testing.T) { output, err := executeCommand(rootCmd, "child", "-i7") if output != "" { - t.Errorf("Unexpected output: %v", err) + t.Errorf("Unexpected output: %v", output) } if err != nil { t.Errorf("Unexpected error: %v", err) From 4955da7c11ea14678cbca3a4016bd4b0b1c51f39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 18:53:34 +0000 Subject: [PATCH 38/76] build(deps): bump golangci/golangci-lint-action from 3.6.0 to 3.7.0 (#2021) --- .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 498e3bec..3a365c85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3.6.0 + - uses: golangci/golangci-lint-action@v3.7.0 with: version: latest args: --verbose From 285460dca6152bb86994fa4a9659c24ca4060e2f Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:04:59 +0200 Subject: [PATCH 39/76] command: temporarily disable G602 due to securego/gosec#1005 (#2022) --- command.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 01f7c6f1..e4945ab8 100644 --- a/command.go +++ b/command.go @@ -752,7 +752,9 @@ func (c *Command) findNext(next string) *Command { } if len(matches) == 1 { - return matches[0] + // Temporarily disable gosec G602, which produces a false positive. + // See https://github.com/securego/gosec/issues/1005. + return matches[0] // #nosec G602 } return nil From c5dacb3ea4512e6766f86907dfa7014ceb9cb041 Mon Sep 17 00:00:00 2001 From: Nuno Adrego <55922671+nunoadrego@users.noreply.github.com> Date: Fri, 8 Sep 2023 01:30:51 +0100 Subject: [PATCH 40/76] ci: test golang 1.21 (#2024) --- .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 3a365c85..6b4c165d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: '^1.20' + go-version: '^1.21' check-latest: true cache: true @@ -67,6 +67,7 @@ jobs: - 18 - 19 - 20 + - 21 name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x' runs-on: ${{ matrix.platform }}-latest steps: From 0c72800b8dba637092b57a955ecee75949e79a73 Mon Sep 17 00:00:00 2001 From: Souma <101255979+5ouma@users.noreply.github.com> Date: Sat, 9 Sep 2023 02:29:06 +0900 Subject: [PATCH 41/76] Customizable error message prefix (#2023) --- command.go | 24 ++++++++++++++++++++++-- command_test.go | 33 +++++++++++++++++++++++++++++++++ site/content/user_guide.md | 6 ++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index e4945ab8..6866f7d0 100644 --- a/command.go +++ b/command.go @@ -181,6 +181,9 @@ type Command struct { // versionTemplate is the version template defined by user. versionTemplate string + // errPrefix is the error message prefix defined by user. + errPrefix string + // inReader is a reader defined by the user that replaces stdin inReader io.Reader // outWriter is a writer defined by the user that replaces stdout @@ -346,6 +349,11 @@ func (c *Command) SetVersionTemplate(s string) { c.versionTemplate = s } +// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix. +func (c *Command) SetErrPrefix(s string) { + c.errPrefix = s +} + // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. // The user should not have a cyclic dependency on commands. func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { @@ -595,6 +603,18 @@ func (c *Command) VersionTemplate() string { ` } +// ErrPrefix return error message prefix for the command +func (c *Command) ErrPrefix() string { + if c.errPrefix != "" { + return c.errPrefix + } + + if c.HasParent() { + return c.parent.ErrPrefix() + } + return "Error:" +} + func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { flag := fs.Lookup(name) if flag == nil { @@ -1050,7 +1070,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { c = cmd } if !c.SilenceErrors { - c.PrintErrln("Error:", err.Error()) + c.PrintErrln(c.ErrPrefix(), err.Error()) c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath()) } return c, err @@ -1079,7 +1099,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // If root command has SilenceErrors flagged, // all subcommands should respect it if !cmd.SilenceErrors && !c.SilenceErrors { - c.PrintErrln("Error:", err.Error()) + c.PrintErrln(cmd.ErrPrefix(), err.Error()) } // If root command has SilenceUsage flagged, diff --git a/command_test.go b/command_test.go index b0f5e860..4afb7f7b 100644 --- a/command_test.go +++ b/command_test.go @@ -1099,6 +1099,39 @@ func TestShorthandVersionTemplate(t *testing.T) { checkStringContains(t, output, "customized version: 1.0.0") } +func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.SetErrPrefix("root error prefix:") + rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) + + output, err := executeCommand(rootCmd, "sub", "--unknown-flag") + if err == nil { + t.Errorf("Expected error") + } + + checkStringContains(t, output, "root error prefix: unknown flag: --unknown-flag") +} + +func TestRootAndSubErrPrefix(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + subCmd := &Command{Use: "sub", Run: emptyRun} + rootCmd.AddCommand(subCmd) + rootCmd.SetErrPrefix("root error prefix:") + subCmd.SetErrPrefix("sub error prefix:") + + if output, err := executeCommand(rootCmd, "--unknown-root-flag"); err == nil { + t.Errorf("Expected error") + } else { + checkStringContains(t, output, "root error prefix: unknown flag: --unknown-root-flag") + } + + if output, err := executeCommand(rootCmd, "sub", "--unknown-sub-flag"); err == nil { + t.Errorf("Expected error") + } else { + checkStringContains(t, output, "sub error prefix: unknown flag: --unknown-sub-flag") + } +} + func TestVersionFlagExecutedOnSubcommand(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0"} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 56e8e44a..93daadf5 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -596,6 +596,12 @@ Running an application with the '--version' flag will print the version to stdou the version template. The template can be customized using the `cmd.SetVersionTemplate(s string)` function. +## Error Message Prefix + +Cobra prints an error message when receiving a non-nil error value. +The default error message is `Error: `. +The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` function. + ## PreRun and PostRun Hooks It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: From bd4d1655f6655f690f45d281fe698dacb9c7d750 Mon Sep 17 00:00:00 2001 From: Alexandru-Claudius Virtopeanu Date: Tue, 26 Sep 2023 03:04:25 +0300 Subject: [PATCH 42/76] feat: add getters for flag completions (#1943) --- completions.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/completions.go b/completions.go index ee38c4d0..55a9f62c 100644 --- a/completions.go +++ b/completions.go @@ -145,6 +145,25 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman return nil } +// GetFlagCompletion returns the completion function for the given flag, if available. +func GetFlagCompletion(flag *pflag.Flag) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) { + flagCompletionMutex.RLock() + defer flagCompletionMutex.RUnlock() + + completionFunc, exists := flagCompletionFunctions[flag] + return completionFunc, exists +} + +// GetFlagCompletionByName returns the completion function for the given flag in the command by name, if available. +func (c *Command) GetFlagCompletionByName(flagName string) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) { + flag := c.Flags().Lookup(flagName) + if flag == nil { + return nil, false + } + + return GetFlagCompletion(flag) +} + // Returns a string listing the different directive enabled in the specified parameter func (d ShellCompDirective) string() string { var directives []string From 95d8a1e45d7719c56dc017e075d3e6099deba85d Mon Sep 17 00:00:00 2001 From: Haoming Meng <41393704+Techming@users.noreply.github.com> Date: Mon, 9 Oct 2023 07:50:40 -0500 Subject: [PATCH 43/76] Add notes to doc on preRun and postRun condition (#2041) --- command.go | 2 ++ site/content/user_guide.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 6866f7d0..909d8585 100644 --- a/command.go +++ b/command.go @@ -115,6 +115,8 @@ type Command struct { // * PostRun() // * PersistentPostRun() // All functions get the same args, the arguments after the command name. + // The *PreRun and *PostRun functions will only be executed if the Run function of the current + // command has been declared. // // PersistentPreRun: children of this command will inherit and execute. PersistentPreRun func(cmd *Command, args []string) diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 93daadf5..55cc252b 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -604,7 +604,7 @@ The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` fu ## PreRun and PostRun Hooks -It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: +It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. The `*PreRun` and `*PostRun` functions will only be executed if the `Run` function of the current command has been declared. These functions are run in the following order: - `PersistentPreRun` - `PreRun` From efe8fa3e4453e41d6419b26c9769a51e42825632 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Oct 2023 11:16:50 +0000 Subject: [PATCH 44/76] build(deps): bump actions/setup-go from 3 to 4 (#1934) --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b4c165d..55085eaf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: '^1.21' check-latest: true @@ -74,7 +74,7 @@ jobs: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: 1.${{ matrix.go }}.x cache: true From 5c962a221e70fd6b12296e5d7075f28b422f98b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:50:33 +0000 Subject: [PATCH 45/76] build(deps): bump github.com/cpuguy83/go-md2man/v2 from 2.0.2 to 2.0.3 (#2047) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6361d742..a79e66a1 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.2 + github.com/cpuguy83/go-md2man/v2 v2.0.3 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 5ccb69dd..871c3a8a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/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 4cafa37bc4bb85633b4245aa118280fe5a9edcd5 Mon Sep 17 00:00:00 2001 From: vkhoroz Date: Sun, 22 Oct 2023 03:36:12 +0300 Subject: [PATCH 46/76] Allow running persistent run hooks of all parents (#2044) Currently, only one of the persistent pre-runs and post-runs is executed. It is always the first one found in the parents chain, starting at this command. Expected behavior is to execute all parents' persistent pre-runs and post-runs. Dependent projects implemented various workarounds for this: - manually building persistent hook chains (in every hook). - applying some kind of monkey-patching on top of Cobra. This change eliminates the necessity for such workarounds by allowing to set a global variable EnableTraverseRunHooks. Tickets: - https://github.com/spf13/cobra/issues/216 - https://github.com/spf13/cobra/issues/252 Signed-off-by: Volodymyr Khoroz --- cobra.go | 11 ++-- command.go | 28 ++++++++-- command_test.go | 106 ++++++++++++++++--------------------- site/content/user_guide.md | 4 ++ 4 files changed, 83 insertions(+), 66 deletions(-) diff --git a/cobra.go b/cobra.go index f23f5092..a6b160ce 100644 --- a/cobra.go +++ b/cobra.go @@ -43,9 +43,10 @@ var initializers []func() var finalizers []func() const ( - defaultPrefixMatching = false - defaultCommandSorting = true - defaultCaseInsensitive = false + defaultPrefixMatching = false + defaultCommandSorting = true + defaultCaseInsensitive = false + defaultTraverseRunHooks = false ) // EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing @@ -60,6 +61,10 @@ var EnableCommandSorting = defaultCommandSorting // EnableCaseInsensitive allows case-insensitive commands names. (case sensitive by default) var EnableCaseInsensitive = defaultCaseInsensitive +// EnableTraverseRunHooks executes persistent pre-run and post-run hooks from all parents. +// By default this is disabled, which means only the first run hook to be found is executed. +var EnableTraverseRunHooks = defaultTraverseRunHooks + // MousetrapHelpText enables an information splash screen on Windows // if the CLI is started from explorer.exe. // To disable the mousetrap, just set this variable to blank string (""). diff --git a/command.go b/command.go index 909d8585..ae3e4e00 100644 --- a/command.go +++ b/command.go @@ -934,15 +934,31 @@ func (c *Command) execute(a []string) (err error) { return err } + parents := make([]*Command, 0, 5) for p := c; p != nil; p = p.Parent() { + if EnableTraverseRunHooks { + // When EnableTraverseRunHooks is set: + // - Execute all persistent pre-runs from the root parent till this command. + // - Execute all persistent post-runs from this command till the root parent. + parents = append([]*Command{p}, parents...) + } else { + // Otherwise, execute only the first found persistent hook. + parents = append(parents, p) + } + } + for _, p := range parents { if p.PersistentPreRunE != nil { if err := p.PersistentPreRunE(c, argWoFlags); err != nil { return err } - break + if !EnableTraverseRunHooks { + break + } } else if p.PersistentPreRun != nil { p.PersistentPreRun(c, argWoFlags) - break + if !EnableTraverseRunHooks { + break + } } } if c.PreRunE != nil { @@ -979,10 +995,14 @@ func (c *Command) execute(a []string) (err error) { if err := p.PersistentPostRunE(c, argWoFlags); err != nil { return err } - break + if !EnableTraverseRunHooks { + break + } } else if p.PersistentPostRun != nil { p.PersistentPostRun(c, argWoFlags) - break + if !EnableTraverseRunHooks { + break + } } } diff --git a/command_test.go b/command_test.go index 4afb7f7b..4d8908d2 100644 --- a/command_test.go +++ b/command_test.go @@ -1530,57 +1530,73 @@ func TestHooks(t *testing.T) { } func TestPersistentHooks(t *testing.T) { - var ( - parentPersPreArgs string - parentPreArgs string - parentRunArgs string - parentPostArgs string - parentPersPostArgs string - ) + EnableTraverseRunHooks = true + testPersistentHooks(t, []string{ + "parent PersistentPreRun", + "child PersistentPreRun", + "child PreRun", + "child Run", + "child PostRun", + "child PersistentPostRun", + "parent PersistentPostRun", + }) - var ( - childPersPreArgs string - childPreArgs string - childRunArgs string - childPostArgs string - childPersPostArgs string - ) + EnableTraverseRunHooks = false + testPersistentHooks(t, []string{ + "child PersistentPreRun", + "child PreRun", + "child Run", + "child PostRun", + "child PersistentPostRun", + }) +} + +func testPersistentHooks(t *testing.T, expectedHookRunOrder []string) { + var hookRunOrder []string + + validateHook := func(args []string, hookName string) { + hookRunOrder = append(hookRunOrder, hookName) + got := strings.Join(args, " ") + if onetwo != got { + t.Errorf("Expected %s %q, got %q", hookName, onetwo, got) + } + } parentCmd := &Command{ Use: "parent", PersistentPreRun: func(_ *Command, args []string) { - parentPersPreArgs = strings.Join(args, " ") + validateHook(args, "parent PersistentPreRun") }, PreRun: func(_ *Command, args []string) { - parentPreArgs = strings.Join(args, " ") + validateHook(args, "parent PreRun") }, Run: func(_ *Command, args []string) { - parentRunArgs = strings.Join(args, " ") + validateHook(args, "parent Run") }, PostRun: func(_ *Command, args []string) { - parentPostArgs = strings.Join(args, " ") + validateHook(args, "parent PostRun") }, PersistentPostRun: func(_ *Command, args []string) { - parentPersPostArgs = strings.Join(args, " ") + validateHook(args, "parent PersistentPostRun") }, } childCmd := &Command{ Use: "child", PersistentPreRun: func(_ *Command, args []string) { - childPersPreArgs = strings.Join(args, " ") + validateHook(args, "child PersistentPreRun") }, PreRun: func(_ *Command, args []string) { - childPreArgs = strings.Join(args, " ") + validateHook(args, "child PreRun") }, Run: func(_ *Command, args []string) { - childRunArgs = strings.Join(args, " ") + validateHook(args, "child Run") }, PostRun: func(_ *Command, args []string) { - childPostArgs = strings.Join(args, " ") + validateHook(args, "child PostRun") }, PersistentPostRun: func(_ *Command, args []string) { - childPersPostArgs = strings.Join(args, " ") + validateHook(args, "child PersistentPostRun") }, } parentCmd.AddCommand(childCmd) @@ -1593,41 +1609,13 @@ func TestPersistentHooks(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - for _, v := range []struct { - name string - got string - }{ - // TODO: currently PersistentPreRun* defined in parent does not - // run if the matching child subcommand has PersistentPreRun. - // If the behavior changes (https://github.com/spf13/cobra/issues/252) - // this test must be fixed. - {"parentPersPreArgs", parentPersPreArgs}, - {"parentPreArgs", parentPreArgs}, - {"parentRunArgs", parentRunArgs}, - {"parentPostArgs", parentPostArgs}, - // TODO: currently PersistentPostRun* defined in parent does not - // run if the matching child subcommand has PersistentPostRun. - // If the behavior changes (https://github.com/spf13/cobra/issues/252) - // this test must be fixed. - {"parentPersPostArgs", parentPersPostArgs}, - } { - if v.got != "" { - t.Errorf("Expected blank %s, got %q", v.name, v.got) - } - } - - for _, v := range []struct { - name string - got string - }{ - {"childPersPreArgs", childPersPreArgs}, - {"childPreArgs", childPreArgs}, - {"childRunArgs", childRunArgs}, - {"childPostArgs", childPostArgs}, - {"childPersPostArgs", childPersPostArgs}, - } { - if v.got != onetwo { - t.Errorf("Expected %s %q, got %q", v.name, onetwo, v.got) + for idx, exp := range expectedHookRunOrder { + if len(hookRunOrder) > idx { + if act := hookRunOrder[idx]; act != exp { + t.Errorf("Expected %q at %d, got %q", exp, idx, act) + } + } else { + t.Errorf("Expected %q at %d, got nothing", exp, idx) } } } diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 55cc252b..4116e8dc 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -687,6 +687,10 @@ Inside subCmd PostRun with args: [arg1 arg2] Inside subCmd PersistentPostRun with args: [arg1 arg2] ``` +By default, only the first persistent hook found in the command chain is executed. +That is why in the above output, the `rootCmd PersistentPostRun` was not called for a child command. +Set `EnableTraverseRunHooks` global variable to `true` if you want to execute all parents' persistent hooks. + ## Suggestions when "unknown command" happens 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: From 8b1eba47616566fc4d258a93da48d5d8741865f0 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 27 Oct 2023 06:23:45 -0400 Subject: [PATCH 47/76] Fix linter errors (#2052) When using golangci-lint v1.55.0 some new errors were being reported. Signed-off-by: Marc Khouzam --- command.go | 1 + doc/md_docs.go | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/command.go b/command.go index ae3e4e00..36494df5 100644 --- a/command.go +++ b/command.go @@ -1446,6 +1446,7 @@ 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) diff --git a/doc/md_docs.go b/doc/md_docs.go index c4a27c00..f98fe2a3 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -27,6 +27,8 @@ import ( "github.com/spf13/cobra" ) +const markdownExtension = ".md" + func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) @@ -83,7 +85,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) if cmd.HasParent() { parent := cmd.Parent() pname := parent.CommandPath() - link := pname + ".md" + link := pname + markdownExtension link = strings.ReplaceAll(link, " ", "_") buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short)) cmd.VisitParents(func(c *cobra.Command) { @@ -101,7 +103,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) continue } cname := name + " " + child.Name() - link := cname + ".md" + link := cname + markdownExtension link = strings.ReplaceAll(link, " ", "_") buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short)) } @@ -138,7 +140,7 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa } } - basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + ".md" + basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + markdownExtension filename := filepath.Join(dir, basename) f, err := os.Create(filename) if err != nil { From b711e8760b73c6aa1b4aa1bef3a26da5926f175d Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 28 Oct 2023 16:10:06 -0400 Subject: [PATCH 48/76] Don't complete --help flag when flag parsing disabled (#2061) Fixes #2060 When a command sets `DisableFlagParsing = true` it requests the responsibility of doing all the flag parsing. Therefore even the `--help/-f/--version/-v` flags should not be automatically completed by Cobra in such a case. Without this change the `--help/-h/--version/-v` flags can end up being completed twice for plugins: one time from cobra and one time from the plugin (which has set `DisableFlagParsing = true`). Signed-off-by: Marc Khouzam --- completions.go | 15 ++++++++++++--- completions_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/completions.go b/completions.go index 55a9f62c..368b92a9 100644 --- a/completions.go +++ b/completions.go @@ -302,9 +302,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // These flags are normally added when `execute()` is called on `finalCmd`, // however, when doing completion, we don't call `finalCmd.execute()`. - // Let's add the --help and --version flag ourselves. - finalCmd.InitDefaultHelpFlag() - finalCmd.InitDefaultVersionFlag() + // Let's add the --help and --version flag ourselves but only if the finalCmd + // has not disabled flag parsing; if flag parsing is disabled, it is up to the + // finalCmd itself to handle the completion of *all* flags. + if !finalCmd.DisableFlagParsing { + finalCmd.InitDefaultHelpFlag() + finalCmd.InitDefaultVersionFlag() + } // Check if we are doing flag value completion before parsing the flags. // This is important because if we are completing a flag value, we need to also @@ -408,6 +412,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { doCompleteFlags(flag) }) + // Try to complete non-inherited flags even if DisableFlagParsing==true. + // This allows programs to tell Cobra about flags for completion even + // if the actual parsing of flags is not done by Cobra. + // For instance, Helm uses this to provide flag name completion for + // some of its plugins. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { doCompleteFlags(flag) }) diff --git a/completions_test.go b/completions_test.go index 01724660..4c6f41bd 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2622,8 +2622,6 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) { expected := strings.Join([]string{ "--persistent", "-p", - "--help", - "-h", "--nonPersistent", "-n", "--flag", @@ -3053,8 +3051,26 @@ func TestCompletionCobraFlags(t *testing.T) { return []string{"extra3"}, ShellCompDirectiveNoFileComp }, } + childCmd4 := &Command{ + Use: "child4", + Version: "1.1.1", + Run: emptyRun, + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"extra4"}, ShellCompDirectiveNoFileComp + }, + DisableFlagParsing: true, + } + childCmd5 := &Command{ + Use: "child5", + Version: "1.1.1", + Run: emptyRun, + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"extra5"}, ShellCompDirectiveNoFileComp + }, + DisableFlagParsing: true, + } - rootCmd.AddCommand(childCmd, childCmd2, childCmd3) + rootCmd.AddCommand(childCmd, childCmd2, childCmd3, childCmd4, childCmd5) _ = childCmd.Flags().Bool("bool", false, "A bool flag") _ = childCmd.MarkFlagRequired("bool") @@ -3066,6 +3082,10 @@ func TestCompletionCobraFlags(t *testing.T) { // Have a command that only adds its own -v flag _ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag") + // Have a command that DisablesFlagParsing but that also adds its own help and version flags + _ = childCmd5.Flags().BoolP("help", "h", false, "My own help") + _ = childCmd5.Flags().BoolP("version", "v", false, "My own version") + return rootCmd } @@ -3196,6 +3216,26 @@ func TestCompletionCobraFlags(t *testing.T) { ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), }, + { + desc: "no completion for --help/-h and --version/-v flags when DisableFlagParsing=true", + args: []string{"child4", "-"}, + expectedOutput: strings.Join([]string{ + "extra4", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, + { + desc: "completions for program-defined --help/-h and --version/-v flags even when DisableFlagParsing=true", + args: []string{"child5", "-"}, + expectedOutput: strings.Join([]string{ + "--help", + "-h", + "--version", + "-v", + "extra5", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), + }, } for _, tc := range testcases { From 00b68a1c260eaf2b9bcb10a3178d36cec81548ca Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 28 Oct 2023 16:11:59 -0400 Subject: [PATCH 49/76] Add tests for flag completion registration (#2053) Different problems have been reported about flag completion registration. These two tests are the cases that were not being verified but had been mentioned as problematic. Ref: - https://github.com/spf13/cobra/issues/1320 - https://github.com/spf13/cobra/pull/1438#issuecomment-872928669 Signed-off-by: Marc Khouzam --- completions_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/completions_test.go b/completions_test.go index 4c6f41bd..7585f88c 100644 --- a/completions_test.go +++ b/completions_test.go @@ -17,7 +17,9 @@ package cobra import ( "bytes" "context" + "fmt" "strings" + "sync" "testing" ) @@ -2040,6 +2042,114 @@ func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) { } } +func TestFlagCompletionForPersistentFlagsCalledFromSubCmd(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.PersistentFlags().String("string", "", "test string flag") + _ = rootCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"myval"}, ShellCompDirectiveDefault + }) + + childCmd := &Command{ + Use: "child", + Run: emptyRun, + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"--validarg", "test"}, ShellCompDirectiveDefault + }, + } + childCmd.Flags().Bool("bool", false, "test bool flag") + rootCmd.AddCommand(childCmd) + + // Test that persistent flag completion works for the subcmd + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "myval", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +// This test tries to register flag completion concurrently to make sure the +// code handles concurrency properly. +// This was reported as a problem when tests are run concurrently: +// https://github.com/spf13/cobra/issues/1320 +// +// NOTE: this test can sometimes pass even if the code were to not handle +// concurrency properly. This is not great but the important part is that +// it should never fail. Therefore, if the tests fails sometimes, we will +// still be able to know there is a problem. +func TestFlagCompletionConcurrentRegistration(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + const maxFlags = 50 + for i := 1; i < maxFlags; i += 2 { + flagName := fmt.Sprintf("flag%d", i) + rootCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on root", flagName)) + } + + childCmd := &Command{ + Use: "child", + Run: emptyRun, + } + for i := 2; i <= maxFlags; i += 2 { + flagName := fmt.Sprintf("flag%d", i) + childCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on child", flagName)) + } + + rootCmd.AddCommand(childCmd) + + // Register completion in different threads to test concurrency. + var wg sync.WaitGroup + for i := 1; i <= maxFlags; i++ { + index := i + flagName := fmt.Sprintf("flag%d", i) + wg.Add(1) + go func() { + defer wg.Done() + cmd := rootCmd + if index%2 == 0 { + cmd = childCmd + } + _ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{fmt.Sprintf("flag%d", index)}, ShellCompDirectiveDefault + }) + }() + } + + wg.Wait() + + // Test that flag completion works for each flag + for i := 1; i <= 6; i++ { + var output string + var err error + flagName := fmt.Sprintf("flag%d", i) + + if i%2 == 1 { + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--"+flagName, "") + } else { + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--"+flagName, "") + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + flagName, + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + } +} + func TestFlagCompletionInGoWithDesc(t *testing.T) { rootCmd := &Command{ Use: "root", From 22953d88453ec9343b4a78b9d67400a3326f3138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 29 Oct 2023 20:06:51 +0200 Subject: [PATCH 50/76] Replace all non-alphanumerics in active help env var program prefix (#1940) * Replace all non-alphanumerics in active help env var program prefix There are other characters besides the dash that are fine in program names, but are problematic in environment variable names. These include (but are not limited to) period, space, and non-ASCII letters. * Another change in docs to mention non-ASCII-alphanumeric instead of just dash --- active_help.go | 10 +++++++--- site/content/active_help.md | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/active_help.go b/active_help.go index 2d023943..5f965e05 100644 --- a/active_help.go +++ b/active_help.go @@ -17,6 +17,7 @@ package cobra import ( "fmt" "os" + "regexp" "strings" ) @@ -29,6 +30,8 @@ const ( activeHelpGlobalDisable = "0" ) +var activeHelpEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`) + // AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp. // Such strings will be processed by the completion script and will be shown as ActiveHelp // to the user. @@ -42,7 +45,7 @@ func AppendActiveHelp(compArray []string, activeHelpStr string) []string { // GetActiveHelpConfig returns the value of the ActiveHelp environment variable // _ACTIVE_HELP where is the name of the root command in upper -// case, with all - replaced by _. +// case, with all non-ASCII-alphanumeric characters replaced by `_`. // It will always return "0" if the global environment variable COBRA_ACTIVE_HELP // is set to "0". func GetActiveHelpConfig(cmd *Command) string { @@ -55,9 +58,10 @@ func GetActiveHelpConfig(cmd *Command) string { // activeHelpEnvVar returns the name of the program-specific ActiveHelp environment // variable. It has the format _ACTIVE_HELP where is the name of the -// root command in upper case, with all - replaced by _. +// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`. func activeHelpEnvVar(name string) string { // This format should not be changed: users will be using it explicitly. activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) - return strings.ReplaceAll(activeHelpEnvVar, "-", "_") + activeHelpEnvVar = activeHelpEnvVarPrefixSubstRegexp.ReplaceAllString(activeHelpEnvVar, "_") + return activeHelpEnvVar } diff --git a/site/content/active_help.md b/site/content/active_help.md index 5e7f59af..d72acc72 100644 --- a/site/content/active_help.md +++ b/site/content/active_help.md @@ -92,7 +92,7 @@ Allowing to configure Active Help is entirely optional; you can use Active Help The way to configure Active Help is to use the program's Active Help environment variable. That variable is named `_ACTIVE_HELP` where `` is the name of your -program in uppercase with any `-` replaced by an `_`. The variable should be set by the user to whatever +program in uppercase with any non-ASCII-alphanumeric characters replaced by an `_`. The variable should be set by the user to whatever Active Help configuration values are supported by the program. For example, say `helm` has chosen to support three levels for Active Help: `on`, `off`, `local`. Then a user @@ -140,7 +140,7 @@ 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 `-` is replaced by an `_`. For example, we can test deactivating some Active Help as shown below: +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: ``` $ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h bitnami/haproxy From 48cea5c87b5299b68c3f5b7f2c67ea948717276f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:21:48 +0000 Subject: [PATCH 51/76] build(deps): bump actions/checkout from 3 to 4 (#2028) --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55085eaf..a9245322 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: >- docker run @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: @@ -47,7 +47,7 @@ jobs: check-latest: true cache: true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: golangci/golangci-lint-action@v3.7.0 with: @@ -72,7 +72,7 @@ jobs: runs-on: ${{ matrix.platform }}-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: @@ -108,7 +108,7 @@ jobs: unzip mingw-w64-x86_64-go - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: From 890302a35f578311404a462b3cdd404f34db3720 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Thu, 2 Nov 2023 14:15:26 +0200 Subject: [PATCH 52/76] Support usage as plugin for tools like kubectl (#2018) In this case the executable is `kubectl-plugin`, but we run it as: kubectl plugin And the help text should reflect the actual usage of the command. To create a plugin, add the cobra.CommandDisplayNameAnnotation: rootCmd := &cobra.Command{ Use: "plugin", Annotations: map[string]string{ cobra.CommandDisplayNameAnnotation: "kubectl plugin", } } Internally this change modifies CommandPath() for the root command to return the command display name instead of the command name. This is used for error messages, help text generation, and completions. CommandPath() is expected to have spaces and code using it already handle spaces (e.g replacing with _), so hopefully this does not break anything. Fixes: #2017 Signed-off-by: Nir Soffer --- command.go | 10 ++++++++-- command_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 36494df5..2fbe6c13 100644 --- a/command.go +++ b/command.go @@ -30,7 +30,10 @@ import ( flag "github.com/spf13/pflag" ) -const FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra" +const ( + FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra" + CommandDisplayNameAnnotation = "cobra_annotation_command_display_name" +) // FParseErrWhitelist configures Flag parse errors to be ignored type FParseErrWhitelist flag.ParseErrorsWhitelist @@ -99,7 +102,7 @@ type Command struct { Deprecated string // Annotations are key/value pairs that can be used by applications to identify or - // group commands. + // group commands or set special options. Annotations map[string]string // Version defines the version for this command. If this value is non-empty and the command does not @@ -1424,6 +1427,9 @@ func (c *Command) CommandPath() string { if c.HasParent() { return c.Parent().CommandPath() + " " + c.Name() } + if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok { + return displayName + } return c.Name() } diff --git a/command_test.go b/command_test.go index 4d8908d2..9f686d65 100644 --- a/command_test.go +++ b/command_test.go @@ -366,6 +366,36 @@ func TestAliasPrefixMatching(t *testing.T) { EnablePrefixMatching = defaultPrefixMatching } +// TestPlugin checks usage as plugin for another command such as kubectl. The +// executable is `kubectl-plugin`, but we run it as `kubectl plugin`. The help +// text should reflect the way we run the command. +func TestPlugin(t *testing.T) { + rootCmd := &Command{ + Use: "plugin", + Args: NoArgs, + Annotations: map[string]string{ + CommandDisplayNameAnnotation: "kubectl plugin", + }, + } + + subCmd := &Command{Use: "sub [flags]", Args: NoArgs, Run: emptyRun} + rootCmd.AddCommand(subCmd) + + rootHelp, err := executeCommand(rootCmd, "-h") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, rootHelp, "kubectl plugin [command]") + + childHelp, err := executeCommand(rootCmd, "sub", "-h") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, childHelp, "kubectl plugin sub [flags]") +} + // TestChildSameName checks the correct behaviour of cobra in cases, // when an application with name "foo" and with subcommand "foo" // is executed with args "foo foo". From a0a6ae020bb3899ff0276067863e50523f897370 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 2 Nov 2023 11:23:08 -0400 Subject: [PATCH 53/76] Improve API to get flag completion function (#2063) The new API is simpler and matches the `c.RegisterFlagCompletionFunc()` API. By removing the global function `GetFlagCompletion()` we are more future proof if we ever move from a global map of flag completion functions to something associated with the command. The commit also makes this API work with persistent flags by using `c.Flag(flagName)` instead of `c.Flags().Lookup(flagName)`. The commit also adds unit tests. Signed-off-by: Marc Khouzam --- completions.go | 19 ++++------ completions_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 12 deletions(-) diff --git a/completions.go b/completions.go index 368b92a9..b60f6b20 100644 --- a/completions.go +++ b/completions.go @@ -145,8 +145,13 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman return nil } -// GetFlagCompletion returns the completion function for the given flag, if available. -func GetFlagCompletion(flag *pflag.Flag) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) { +// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available. +func (c *Command) GetFlagCompletionFunc(flagName string) (func(*Command, []string, string) ([]string, ShellCompDirective), bool) { + flag := c.Flag(flagName) + if flag == nil { + return nil, false + } + flagCompletionMutex.RLock() defer flagCompletionMutex.RUnlock() @@ -154,16 +159,6 @@ func GetFlagCompletion(flag *pflag.Flag) (func(cmd *Command, args []string, toCo return completionFunc, exists } -// GetFlagCompletionByName returns the completion function for the given flag in the command by name, if available. -func (c *Command) GetFlagCompletionByName(flagName string) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) { - flag := c.Flags().Lookup(flagName) - if flag == nil { - return nil, false - } - - return GetFlagCompletion(flag) -} - // Returns a string listing the different directive enabled in the specified parameter func (d ShellCompDirective) string() string { var directives []string diff --git a/completions_test.go b/completions_test.go index 7585f88c..d5aee250 100644 --- a/completions_test.go +++ b/completions_test.go @@ -3427,3 +3427,93 @@ Completion ended with directive: ShellCompDirectiveNoFileComp }) } } + +func TestGetFlagCompletion(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + + rootCmd.Flags().String("rootflag", "", "root flag") + _ = rootCmd.RegisterFlagCompletionFunc("rootflag", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"rootvalue"}, ShellCompDirectiveKeepOrder + }) + + rootCmd.PersistentFlags().String("persistentflag", "", "persistent flag") + _ = rootCmd.RegisterFlagCompletionFunc("persistentflag", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"persistentvalue"}, ShellCompDirectiveDefault + }) + + childCmd := &Command{Use: "child", Run: emptyRun} + + childCmd.Flags().String("childflag", "", "child flag") + _ = childCmd.RegisterFlagCompletionFunc("childflag", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"childvalue"}, ShellCompDirectiveNoFileComp | ShellCompDirectiveNoSpace + }) + + rootCmd.AddCommand(childCmd) + + testcases := []struct { + desc string + cmd *Command + flagName string + exists bool + comps []string + directive ShellCompDirective + }{ + { + desc: "get flag completion function for command", + cmd: rootCmd, + flagName: "rootflag", + exists: true, + comps: []string{"rootvalue"}, + directive: ShellCompDirectiveKeepOrder, + }, + { + desc: "get persistent flag completion function for command", + cmd: rootCmd, + flagName: "persistentflag", + exists: true, + comps: []string{"persistentvalue"}, + directive: ShellCompDirectiveDefault, + }, + { + desc: "get flag completion function for child command", + cmd: childCmd, + flagName: "childflag", + exists: true, + comps: []string{"childvalue"}, + directive: ShellCompDirectiveNoFileComp | ShellCompDirectiveNoSpace, + }, + { + desc: "get persistent flag completion function for child command", + cmd: childCmd, + flagName: "persistentflag", + exists: true, + comps: []string{"persistentvalue"}, + directive: ShellCompDirectiveDefault, + }, + { + desc: "cannot get flag completion function for local parent flag", + cmd: childCmd, + flagName: "rootflag", + exists: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + compFunc, exists := tc.cmd.GetFlagCompletionFunc(tc.flagName) + if tc.exists != exists { + t.Errorf("Unexpected result looking for flag completion function") + } + + if exists { + comps, directive := compFunc(tc.cmd, []string{}, "") + if strings.Join(tc.comps, " ") != strings.Join(comps, " ") { + t.Errorf("Unexpected completions %q", comps) + } + if tc.directive != directive { + t.Errorf("Unexpected directive %q", directive) + } + } + }) + } +} From 283e32d8896fb1beb9d07c742ed16fa2c182ee65 Mon Sep 17 00:00:00 2001 From: SwagPack Date: Mon, 13 Nov 2023 22:25:06 +0530 Subject: [PATCH 54/76] Add LXC to the list of projects using cobra (#2071) --- 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 8a291eb2..f85e9fa0 100644 --- a/site/content/projects_using_cobra.md +++ b/site/content/projects_using_cobra.md @@ -30,6 +30,7 @@ - [Kubescape](https://github.com/kubescape/kubescape) - [KubeVirt](https://github.com/kubevirt/kubevirt) - [Linkerd](https://linkerd.io/) +- [LXC](https://github.com/canonical/lxd) - [Mattermost-server](https://github.com/mattermost/mattermost-server) - [Mercure](https://mercure.rocks/) - [Meroxa CLI](https://github.com/meroxa/cli) From 3d8ac432bdad89db04ab0890754b2444d7b4e1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 23 Nov 2023 19:24:33 +0200 Subject: [PATCH 55/76] Micro-optimizations (#1957) * Avoid redundant string splits There likely isn't actually more than once to split in the source strings in these cases, but avoid doing so anyway as we're only interested in the first. * Avoid redundant completion output target evaluations The target is not to be changed while outputting completions, so resolve it only once. * Avoid redundant active help enablement evaluations The enablement state is not to be changed during completion output, so evaluate it only once. * Preallocate some slices and maps with known size * Avoid some unnecessary looping * Use strings.Builder to construct suggestions --- args.go | 4 ++-- bash_completions.go | 2 +- cobra.go | 2 -- command.go | 10 +++++----- completions.go | 20 ++++++++++---------- flag_groups.go | 10 +++++----- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/args.go b/args.go index e79ec33a..ed1e70ce 100644 --- a/args.go +++ b/args.go @@ -52,9 +52,9 @@ func OnlyValidArgs(cmd *Command, args []string) error { if len(cmd.ValidArgs) > 0 { // Remove any description that may be included in ValidArgs. // A description is following a tab character. - var validArgs []string + validArgs := make([]string, 0, len(cmd.ValidArgs)) for _, v := range cmd.ValidArgs { - validArgs = append(validArgs, strings.Split(v, "\t")[0]) + validArgs = append(validArgs, strings.SplitN(v, "\t", 2)[0]) } for _, v := range args { if !stringInSlice(v, validArgs) { diff --git a/bash_completions.go b/bash_completions.go index 8a531518..be835704 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -621,7 +621,7 @@ func writeRequiredNouns(buf io.StringWriter, cmd *Command) { for _, value := range cmd.ValidArgs { // Remove any description that may be included following a tab character. // Descriptions are not supported by bash completion. - value = strings.Split(value, "\t")[0] + value = strings.SplitN(value, "\t", 2)[0] WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) } if cmd.ValidArgsFunction != nil { diff --git a/cobra.go b/cobra.go index a6b160ce..e0b0947b 100644 --- a/cobra.go +++ b/cobra.go @@ -193,8 +193,6 @@ func ld(s, t string, ignoreCase bool) int { d := make([][]int, len(s)+1) for i := range d { d[i] = make([]int, len(t)+1) - } - for i := range d { d[i][0] = i } for j := range d[0] { diff --git a/command.go b/command.go index 2fbe6c13..11a3e9c9 100644 --- a/command.go +++ b/command.go @@ -706,7 +706,7 @@ Loop: // This is not a flag or a flag value. Check to see if it matches what we're looking for, and if so, // return the args, excluding the one at this position. if s == x { - ret := []string{} + ret := make([]string, 0, len(args)-1) ret = append(ret, args[:pos]...) ret = append(ret, args[pos+1:]...) return ret @@ -754,14 +754,14 @@ func (c *Command) findSuggestions(arg string) string { if c.SuggestionsMinimumDistance <= 0 { c.SuggestionsMinimumDistance = 2 } - suggestionsString := "" + var sb strings.Builder if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 { - suggestionsString += "\n\nDid you mean this?\n" + sb.WriteString("\n\nDid you mean this?\n") for _, s := range suggestions { - suggestionsString += fmt.Sprintf("\t%v\n", s) + _, _ = fmt.Fprintf(&sb, "\t%v\n", s) } } - return suggestionsString + return sb.String() } func (c *Command) findNext(next string) *Command { diff --git a/completions.go b/completions.go index b60f6b20..b0e41df0 100644 --- a/completions.go +++ b/completions.go @@ -212,23 +212,23 @@ func (c *Command) initCompleteCmd(args []string) { } noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) + noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable + out := finalCmd.OutOrStdout() for _, comp := range completions { - if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable { - // Remove all activeHelp entries in this case - if strings.HasPrefix(comp, activeHelpMarker) { - continue - } + if noActiveHelp && strings.HasPrefix(comp, activeHelpMarker) { + // Remove all activeHelp entries if it's disabled. + continue } if noDescriptions { // Remove any description that may be included following a tab character. - comp = strings.Split(comp, "\t")[0] + comp = strings.SplitN(comp, "\t", 2)[0] } // Make sure we only write the first line to the output. // This is needed if a description contains a linebreak. // Otherwise the shell scripts will interpret the other lines as new flags // and could therefore provide a wrong completion. - comp = strings.Split(comp, "\n")[0] + comp = strings.SplitN(comp, "\n", 2)[0] // Finally trim the completion. This is especially important to get rid // of a trailing tab when there are no description following it. @@ -237,14 +237,14 @@ func (c *Command) initCompleteCmd(args []string) { // although there is no description). comp = strings.TrimSpace(comp) - // Print each possible completion to stdout for the completion script to consume. - fmt.Fprintln(finalCmd.OutOrStdout(), comp) + // Print each possible completion to the output for the completion script to consume. + fmt.Fprintln(out, comp) } // As the last printout, print the completion directive for the completion script to parse. // The directive integer must be that last character following a single colon (:). // The completion script expects : - fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive) + fmt.Fprintf(out, ":%d\n", directive) // Print some helpful info to stderr for the user to understand. // Output from stderr must be ignored by the completion script. diff --git a/flag_groups.go b/flag_groups.go index 0671ec5f..2be3b18b 100644 --- a/flag_groups.go +++ b/flag_groups.go @@ -130,7 +130,7 @@ func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annota continue } - groupStatus[group] = map[string]bool{} + groupStatus[group] = make(map[string]bool, len(flagnames)) for _, name := range flagnames { groupStatus[group][name] = false } @@ -253,17 +253,17 @@ func (c *Command) enforceFlagGroupsForCompletion() { // If none of the flags of a one-required group are present, we make all the flags // of that group required so that the shell completion suggests them automatically for flagList, flagnameAndStatus := range oneRequiredGroupStatus { - set := 0 + isSet := false - for _, isSet := range flagnameAndStatus { + for _, isSet = range flagnameAndStatus { if isSet { - set++ + break } } // None of the flags of the group are set, mark all flags in the group // as required - if set == 0 { + if !isSet { for _, fName := range strings.Split(flagList, " ") { _ = c.MarkFlagRequired(fName) } From 236f3c0418a1912d32159ad9c0b74f3f03764f5d Mon Sep 17 00:00:00 2001 From: Marcus Kohlberg <78424526+marcuskohlberg@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:26:29 +0100 Subject: [PATCH 56/76] Update projects_using_cobra.md (#2089) Add Encore to the list of projects using Cobra --- 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 f85e9fa0..73e7fea8 100644 --- a/site/content/projects_using_cobra.md +++ b/site/content/projects_using_cobra.md @@ -12,6 +12,7 @@ - [Datree](https://github.com/datreeio/datree) - [Delve](https://github.com/derekparker/delve) - [Docker (distribution)](https://github.com/docker/distribution) +- [Encore](https://encore.dev) - [Etcd](https://etcd.io/) - [Gardener](https://github.com/gardener/gardenctl) - [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) From e63925d3216142ba2c8d7641d729c6c15f4433e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 17 Dec 2023 20:50:59 +0200 Subject: [PATCH 57/76] Add env variable to suppress completion descriptions on create (#1938) COBRA_COMPLETION_DESCRIPTIONS=0 or _COMPLETION_DESCRIPTIONS=0 can now be used to disable shell completion descriptions. --- active_help.go | 13 +- completions.go | 40 +++++- completions_test.go | 192 +++++++++++++++++++++++++++++ site/content/completions/_index.md | 3 + 4 files changed, 237 insertions(+), 11 deletions(-) diff --git a/active_help.go b/active_help.go index 5f965e05..25c30e3c 100644 --- a/active_help.go +++ b/active_help.go @@ -17,21 +17,17 @@ package cobra import ( "fmt" "os" - "regexp" - "strings" ) const ( activeHelpMarker = "_activeHelp_ " // The below values should not be changed: programs will be using them explicitly // in their user documentation, and users will be using them explicitly. - activeHelpEnvVarSuffix = "_ACTIVE_HELP" - activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP" + activeHelpEnvVarSuffix = "ACTIVE_HELP" + activeHelpGlobalEnvVar = configEnvVarGlobalPrefix + "_" + activeHelpEnvVarSuffix activeHelpGlobalDisable = "0" ) -var activeHelpEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`) - // AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp. // Such strings will be processed by the completion script and will be shown as ActiveHelp // to the user. @@ -60,8 +56,5 @@ func GetActiveHelpConfig(cmd *Command) string { // variable. It has the format _ACTIVE_HELP where is the name of the // root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`. func activeHelpEnvVar(name string) string { - // This format should not be changed: users will be using it explicitly. - activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) - activeHelpEnvVar = activeHelpEnvVarPrefixSubstRegexp.ReplaceAllString(activeHelpEnvVar, "_") - return activeHelpEnvVar + return configEnvVar(name, activeHelpEnvVarSuffix) } diff --git a/completions.go b/completions.go index b0e41df0..ad7b6d0a 100644 --- a/completions.go +++ b/completions.go @@ -17,6 +17,8 @@ package cobra import ( "fmt" "os" + "regexp" + "strconv" "strings" "sync" @@ -211,7 +213,12 @@ func (c *Command) initCompleteCmd(args []string) { // 2- Even without completions, we need to print the directive } - noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) + noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd + if !noDescriptions { + if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil { + noDescriptions = !doDescriptions + } + } noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable out := finalCmd.OutOrStdout() for _, comp := range completions { @@ -899,3 +906,34 @@ func CompError(msg string) { func CompErrorln(msg string) { CompError(fmt.Sprintf("%s\n", msg)) } + +// These values should not be changed: users will be using them explicitly. +const ( + configEnvVarGlobalPrefix = "COBRA" + configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS" +) + +var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`) + +// configEnvVar returns the name of the program-specific configuration environment +// variable. It has the format _ where is the name of the +// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`. +func configEnvVar(name, suffix string) string { + // This format should not be changed: users will be using it explicitly. + v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix)) + v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_") + return v +} + +// getEnvConfig returns the value of the configuration environment variable +// _ where is the name of the root command in upper +// case, with all non-ASCII-alphanumeric characters replaced by `_`. +// If the value is empty or not set, the value of the environment variable +// COBRA_ is returned instead. +func getEnvConfig(cmd *Command, suffix string) string { + v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix)) + if v == "" { + v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix)) + } + return v +} diff --git a/completions_test.go b/completions_test.go index d5aee250..df153fcf 100644 --- a/completions_test.go +++ b/completions_test.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "fmt" + "os" "strings" "sync" "testing" @@ -3517,3 +3518,194 @@ func TestGetFlagCompletion(t *testing.T) { }) } } + +func TestGetEnvConfig(t *testing.T) { + testCases := []struct { + desc string + use string + suffix string + cmdVar string + globalVar string + cmdVal string + globalVal string + expected string + }{ + { + desc: "Command envvar overrides global", + use: "root", + suffix: "test", + cmdVar: "ROOT_TEST", + globalVar: "COBRA_TEST", + cmdVal: "cmd", + globalVal: "global", + expected: "cmd", + }, + { + desc: "Missing/empty command envvar falls back to global", + use: "root", + suffix: "test", + cmdVar: "ROOT_TEST", + globalVar: "COBRA_TEST", + cmdVal: "", + globalVal: "global", + expected: "global", + }, + { + desc: "Missing/empty command and global envvars fall back to empty", + use: "root", + suffix: "test", + cmdVar: "ROOT_TEST", + globalVar: "COBRA_TEST", + cmdVal: "", + globalVal: "", + expected: "", + }, + { + desc: "Periods in command use transform to underscores in env var name", + use: "foo.bar", + suffix: "test", + cmdVar: "FOO_BAR_TEST", + globalVar: "COBRA_TEST", + cmdVal: "cmd", + globalVal: "global", + expected: "cmd", + }, + { + desc: "Dashes in command use transform to underscores in env var name", + use: "quux-BAZ", + suffix: "test", + cmdVar: "QUUX_BAZ_TEST", + globalVar: "COBRA_TEST", + cmdVal: "cmd", + globalVal: "global", + expected: "cmd", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + // Could make env handling cleaner with t.Setenv with Go >= 1.17 + err := os.Setenv(tc.cmdVar, tc.cmdVal) + defer func() { + assertNoErr(t, os.Unsetenv(tc.cmdVar)) + }() + assertNoErr(t, err) + err = os.Setenv(tc.globalVar, tc.globalVal) + defer func() { + assertNoErr(t, os.Unsetenv(tc.globalVar)) + }() + assertNoErr(t, err) + cmd := &Command{Use: tc.use} + got := getEnvConfig(cmd, tc.suffix) + if got != tc.expected { + t.Errorf("expected: %q, got: %q", tc.expected, got) + } + }) + } +} + +func TestDisableDescriptions(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + + childCmd := &Command{ + Use: "thechild", + Short: "The child command", + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + specificDescriptionsEnvVar := configEnvVar(rootCmd.Name(), configEnvVarSuffixDescriptions) + globalDescriptionsEnvVar := configEnvVar(configEnvVarGlobalPrefix, configEnvVarSuffixDescriptions) + + const ( + descLineWithDescription = "first\tdescription" + descLineWithoutDescription = "first" + ) + childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + comps := []string{descLineWithDescription} + return comps, ShellCompDirectiveDefault + } + + testCases := []struct { + desc string + globalEnvValue string + specificEnvValue string + expectedLine string + }{ + { + "No env variables set", + "", + "", + descLineWithDescription, + }, + { + "Global value false", + "false", + "", + descLineWithoutDescription, + }, + { + "Specific value false", + "", + "false", + descLineWithoutDescription, + }, + { + "Both values false", + "false", + "false", + descLineWithoutDescription, + }, + { + "Both values true", + "true", + "true", + descLineWithDescription, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + if err := os.Setenv(specificDescriptionsEnvVar, tc.specificEnvValue); err != nil { + t.Errorf("Unexpected error setting %s: %v", specificDescriptionsEnvVar, err) + } + if err := os.Setenv(globalDescriptionsEnvVar, tc.globalEnvValue); err != nil { + t.Errorf("Unexpected error setting %s: %v", globalDescriptionsEnvVar, err) + } + + var run = func() { + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "thechild", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + tc.expectedLine, + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + } + + run() + + // For empty cases, test also unset state + if tc.specificEnvValue == "" { + if err := os.Unsetenv(specificDescriptionsEnvVar); err != nil { + t.Errorf("Unexpected error unsetting %s: %v", specificDescriptionsEnvVar, err) + } + run() + } + if tc.globalEnvValue == "" { + if err := os.Unsetenv(globalDescriptionsEnvVar); err != nil { + t.Errorf("Unexpected error unsetting %s: %v", globalDescriptionsEnvVar, err) + } + run() + } + }) + } +} diff --git a/site/content/completions/_index.md b/site/content/completions/_index.md index 4efad290..02257ade 100644 --- a/site/content/completions/_index.md +++ b/site/content/completions/_index.md @@ -393,6 +393,9 @@ $ source <(helm completion bash --no-descriptions) $ helm completion [tab][tab] bash fish powershell zsh ``` + +Setting the `_COMPLETION_DESCRIPTIONS` environment variable (falling back to `COBRA_COMPLETION_DESCRIPTIONS` if empty or not set) to a [falsey value](https://pkg.go.dev/strconv#ParseBool) achieves the same. `` is the name of your program with all non-ASCII-alphanumeric characters replaced by `_`. + ## Bash completions ### Dependencies From df547f5fc6ee86071f73c36c16afd885bb4e3f28 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sun, 12 Nov 2023 17:20:47 +0200 Subject: [PATCH 58/76] Fix help text for plugins When using `CommandDisplayNameAnnotation` we want to use it instead of the command name in `--help` message or in the default help command. With current code we get the wrong text in the --help usage text: Flags: -h, --help help for kubectl-plugin And in the long description of the default help command: $ kubectl cobraplugin help -h Help provides help for any command in the application. Simply type kubectl-plugin help [path to command] for full details. The issue was hidden since the test checked only the Usage line. Fixed by extracting a displayName() function and use it when creating FlagSet and when formatting the default help flag usage and the help command long description. Enhance the TestPlugin to check all the lines including the command name. --- command.go | 27 ++++++++++++++++----------- command_test.go | 13 ++++++++++++- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/command.go b/command.go index 11a3e9c9..5c08c007 100644 --- a/command.go +++ b/command.go @@ -1187,10 +1187,11 @@ func (c *Command) InitDefaultHelpFlag() { c.mergePersistentFlags() if c.Flags().Lookup("help") == nil { usage := "help for " - if c.Name() == "" { + name := c.displayName() + if name == "" { usage += "this command" } else { - usage += c.Name() + usage += name } c.Flags().BoolP("help", "h", false, usage) _ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"}) @@ -1236,7 +1237,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.Name() + ` 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) @@ -1427,6 +1428,10 @@ func (c *Command) CommandPath() string { if c.HasParent() { return c.Parent().CommandPath() + " " + c.Name() } + return c.displayName() +} + +func (c *Command) displayName() string { if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok { return displayName } @@ -1642,7 +1647,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.Name(), flag.ContinueOnError) + c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1656,7 +1661,7 @@ func (c *Command) Flags() *flag.FlagSet { func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { persistentFlags := c.PersistentFlags() - out := flag.NewFlagSet(c.Name(), 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) @@ -1670,7 +1675,7 @@ func (c *Command) LocalFlags() *flag.FlagSet { c.mergePersistentFlags() if c.lflags == nil { - c.lflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.lflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1697,7 +1702,7 @@ func (c *Command) InheritedFlags() *flag.FlagSet { c.mergePersistentFlags() if c.iflags == nil { - c.iflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.iflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1725,7 +1730,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.Name(), flag.ContinueOnError) + c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) if c.flagErrorBuf == nil { c.flagErrorBuf = new(bytes.Buffer) } @@ -1738,9 +1743,9 @@ func (c *Command) PersistentFlags() *flag.FlagSet { func (c *Command) ResetFlags() { c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf.Reset() - c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.flags.SetOutput(c.flagErrorBuf) - c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.pflags.SetOutput(c.flagErrorBuf) c.lflags = nil @@ -1857,7 +1862,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.Name(), 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 9f686d65..a30c8364 100644 --- a/command_test.go +++ b/command_test.go @@ -371,7 +371,7 @@ func TestAliasPrefixMatching(t *testing.T) { // text should reflect the way we run the command. func TestPlugin(t *testing.T) { rootCmd := &Command{ - Use: "plugin", + Use: "kubectl-plugin", Args: NoArgs, Annotations: map[string]string{ CommandDisplayNameAnnotation: "kubectl plugin", @@ -387,6 +387,8 @@ func TestPlugin(t *testing.T) { } checkStringContains(t, rootHelp, "kubectl plugin [command]") + checkStringContains(t, rootHelp, "help for kubectl plugin") + checkStringContains(t, rootHelp, "kubectl plugin [command] --help") childHelp, err := executeCommand(rootCmd, "sub", "-h") if err != nil { @@ -394,6 +396,15 @@ func TestPlugin(t *testing.T) { } checkStringContains(t, childHelp, "kubectl plugin sub [flags]") + checkStringContains(t, childHelp, "help for sub") + + helpHelp, err := executeCommand(rootCmd, "help", "-h") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, helpHelp, "kubectl plugin help [path to command]") + checkStringContains(t, helpHelp, "kubectl plugin help [command]") } // TestChildSameName checks the correct behaviour of cobra in cases, From a73b9c391a9489d20f5ee1480e75d3b99fc8c7e2 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sun, 12 Nov 2023 20:07:59 +0200 Subject: [PATCH 59/76] Fix help text for runnable plugin command When creating a plugin without sub commands, the help text included the command name (kubectl-plugin) instead of the display name (kubectl plugin): Usage: kubectl-plugin [flags] The issue is that the usage line for this case does not use the command path but the raw `Use` string, and this case was not tested. Add a test for this case and fix UsageLine() to replace the command name with the display name. Tested using https://github.com/nirs/kubernetes/tree/sample-cli-plugin-help --- command.go | 5 +++-- command_test.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 5c08c007..677dc9b6 100644 --- a/command.go +++ b/command.go @@ -1441,10 +1441,11 @@ 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) if c.HasParent() { - useline = c.parent.CommandPath() + " " + c.Use + useline = c.parent.CommandPath() + " " + use } else { - useline = c.Use + useline = use } if c.DisableFlagsInUseLine { return useline diff --git a/command_test.go b/command_test.go index a30c8364..b7d88e4d 100644 --- a/command_test.go +++ b/command_test.go @@ -370,6 +370,26 @@ func TestAliasPrefixMatching(t *testing.T) { // executable is `kubectl-plugin`, but we run it as `kubectl plugin`. The help // text should reflect the way we run the command. func TestPlugin(t *testing.T) { + cmd := &Command{ + Use: "kubectl-plugin", + Args: NoArgs, + Annotations: map[string]string{ + CommandDisplayNameAnnotation: "kubectl plugin", + }, + Run: emptyRun, + } + + cmdHelp, err := executeCommand(cmd, "-h") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, cmdHelp, "kubectl plugin [flags]") + checkStringContains(t, cmdHelp, "help for kubectl plugin") +} + +// TestPlugin checks usage as plugin with sub commands. +func TestPluginWithSubCommands(t *testing.T) { rootCmd := &Command{ Use: "kubectl-plugin", Args: NoArgs, From 41227856cd731aeeeb5a2f7203b3a4c16b81fc67 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Sun, 12 Nov 2023 15:55:40 +0200 Subject: [PATCH 60/76] Document how to create a plugin Using the new CommandDisplayNameAnnotation annotation introduced in Cobra 1.8.0. --- site/content/user_guide.md | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/site/content/user_guide.md b/site/content/user_guide.md index 4116e8dc..3b42ef04 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -748,3 +748,57 @@ Read more about it in [Shell Completions](completions/_index.md). Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users. Active Help are messages (hints, warnings, etc) printed as the program is being used. Read more about it in [Active Help](active_help.md). + +## Creating a plugin + +When creating a plugin for tools like *kubectl*, the executable is named +`kubectl-myplugin`, but it is used as `kubectl myplugin`. To fix help +messages and completions, annotate the root command with the +`cobra.CommandDisplayNameAnnotation` annotation. + +### Example kubectl plugin + +```go +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func main() { + rootCmd := &cobra.Command{ + Use: "kubectl-myplugin", + Annotations: map[string]string{ + cobra.CommandDisplayNameAnnotation: "kubectl myplugin", + }, + } + subCmd := &cobra.Command{ + Use: "subcmd", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("kubectl myplugin subcmd") + }, + } + rootCmd.AddCommand(subCmd) + rootCmd.Execute() +} +``` + +Example run as a kubectl plugin: + +``` +$ kubectl myplugin +Usage: + kubectl myplugin [command] + +Available Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + subcmd + +Flags: + -h, --help help for kubectl myplugin + +Use "kubectl myplugin [command] --help" for more information about a command. +``` From c054701f6a5522c259dabfd206c54b41e1788519 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:06:36 +0000 Subject: [PATCH 61/76] build(deps): bump actions/setup-go from 4 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9245322..b0cec77c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '^1.21' check-latest: true @@ -74,7 +74,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: 1.${{ matrix.go }}.x cache: true From 531ce793e3a44b949b1d0296a382f4f3a2c9da0d Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 23 Dec 2023 10:11:53 -0500 Subject: [PATCH 62/76] Remove extra actions/checkout Signed-off-by: Marc Khouzam --- .github/workflows/test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b0cec77c..74ed3317 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,8 +47,6 @@ jobs: check-latest: true cache: true - - uses: actions/checkout@v4 - - uses: golangci/golangci-lint-action@v3.7.0 with: version: latest From 199b7abe121caf41a163289041b13b0ca2418a72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Dec 2023 14:52:45 -0500 Subject: [PATCH 63/76] build(deps): bump actions/labeler from 4 to 5 (#2086) * build(deps): bump actions/labeler from 4 to 5 Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 5. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/labeler dependency-type: direct:production update-type: version-update:semver-major ... * Update labeler configuration for v5 Signed-off-by: dependabot[bot] Signed-off-by: Marc Khouzam Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Khouzam --- .github/labeler.yml | 19 +++++++++++++------ .github/workflows/labeler.yml | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 0f0bc3c9..0db3be27 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,17 +1,24 @@ # changes to documentation generation -"area/docs-generation": doc/**/* +"area/docs-generation": +- changed-files: + - any-glob-to-any-file: 'doc/**' # changes to the core cobra command "area/cobra-command": -- any: ['./cobra.go', './cobra_test.go', './*command*.go'] +- changed-files: + - any-glob-to-any-file: ['./cobra.go', './cobra_test.go', './*command*.go'] # changes made to command flags/args -"area/flags": ./args*.go +"area/flags": +- changed-files: + - any-glob-to-any-file: './args*.go' # changes to Github workflows -"area/github": .github/**/* +"area/github": +- changed-files: + - any-glob-to-any-file: '.github/**' # changes to shell completions "area/shell-completion": - - ./*completions* - +- changed-files: + - any-glob-to-any-file: './*completions*' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 17f451fd..cc5c5400 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,7 +12,7 @@ jobs: pull-requests: write # for actions/labeler to add labels to PRs runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ github.token }}" From cbcf75eab98095a631a917692f9667b55d3ccfd6 Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Wed, 27 Dec 2023 21:16:29 -0500 Subject: [PATCH 64/76] [chore]: update projects using cobra (#2093) Signed-off-by: Case Wylie --- 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 73e7fea8..75cc065c 100644 --- a/site/content/projects_using_cobra.md +++ b/site/content/projects_using_cobra.md @@ -63,4 +63,5 @@ - [Vitess](https://vitess.io) - VMware's [Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) & [Tanzu Framework](https://github.com/vmware-tanzu/tanzu-framework) - [Werf](https://werf.io/) +- [Zarf](https://github.com/defenseunicorns/zarf) - [ZITADEL](https://github.com/zitadel/zitadel) From 0dec88e7931d4c5d5583e69b12e245741d9f1353 Mon Sep 17 00:00:00 2001 From: Denis <43725617+korovindenis@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:40:15 +0300 Subject: [PATCH 65/76] Add tests for funcs in cobra.go (#2094) --- cobra_test.go | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/cobra_test.go b/cobra_test.go index fbb07f9b..2bba461c 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -40,3 +40,185 @@ func TestAddTemplateFunctions(t *testing.T) { t.Errorf("Expected UsageString: %v\nGot: %v", expected, got) } } + +func TestLevenshteinDistance(t *testing.T) { + tests := []struct { + name string + s string + t string + ignoreCase bool + expected int + }{ + { + name: "Equal strings (case-sensitive)", + s: "hello", + t: "hello", + ignoreCase: false, + expected: 0, + }, + { + name: "Equal strings (case-insensitive)", + s: "Hello", + t: "hello", + ignoreCase: true, + expected: 0, + }, + { + name: "Different strings (case-sensitive)", + s: "kitten", + t: "sitting", + ignoreCase: false, + expected: 3, + }, + { + name: "Different strings (case-insensitive)", + s: "Kitten", + t: "Sitting", + ignoreCase: true, + expected: 3, + }, + { + name: "Empty strings", + s: "", + t: "", + ignoreCase: false, + expected: 0, + }, + { + name: "One empty string", + s: "abc", + t: "", + ignoreCase: false, + expected: 3, + }, + { + name: "Both empty strings", + s: "", + t: "", + ignoreCase: true, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Act + got := ld(tt.s, tt.t, tt.ignoreCase) + + // Assert + if got != tt.expected { + t.Errorf("Expected ld: %v\nGot: %v", tt.expected, got) + } + }) + } +} + +func TestStringInSlice(t *testing.T) { + tests := []struct { + name string + a string + list []string + expected bool + }{ + { + name: "String in slice (case-sensitive)", + a: "apple", + list: []string{"orange", "banana", "apple", "grape"}, + expected: true, + }, + { + name: "String not in slice (case-sensitive)", + a: "pear", + list: []string{"orange", "banana", "apple", "grape"}, + expected: false, + }, + { + name: "String in slice (case-insensitive)", + a: "APPLE", + list: []string{"orange", "banana", "apple", "grape"}, + expected: false, + }, + { + name: "Empty slice", + a: "apple", + list: []string{}, + expected: false, + }, + { + name: "Empty string", + a: "", + list: []string{"orange", "banana", "apple", "grape"}, + expected: false, + }, + { + name: "Empty strings match", + a: "", + list: []string{"orange", ""}, + expected: true, + }, + { + name: "Empty string in empty slice", + a: "", + list: []string{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Act + got := stringInSlice(tt.a, tt.list) + + // Assert + if got != tt.expected { + t.Errorf("Expected stringInSlice: %v\nGot: %v", tt.expected, got) + } + }) + } +} + +func TestRpad(t *testing.T) { + tests := []struct { + name string + inputString string + padding int + expected string + }{ + { + name: "Padding required", + inputString: "Hello", + padding: 10, + expected: "Hello ", + }, + { + name: "No padding required", + inputString: "World", + padding: 5, + expected: "World", + }, + { + name: "Empty string", + inputString: "", + padding: 8, + expected: " ", + }, + { + name: "Zero padding", + inputString: "cobra", + padding: 0, + expected: "cobra", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Act + got := rpad(tt.inputString, tt.padding) + + // Assert + if got != tt.expected { + t.Errorf("Expected rpad: %v\nGot: %v", tt.expected, got) + } + }) + } +} From 4fb0a66a3436bd34b03b858c729404e99cd3124f Mon Sep 17 00:00:00 2001 From: Dmytro Milinevskyi Date: Sat, 6 Jan 2024 22:49:13 +0100 Subject: [PATCH 66/76] flags: clarify documentation that LocalFlags related function do not modify the state (#2064) --- command.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/command.go b/command.go index 677dc9b6..b6f8f4b1 100644 --- a/command.go +++ b/command.go @@ -154,8 +154,10 @@ type Command struct { // pflags contains persistent flags. pflags *flag.FlagSet // lflags contains local flags. + // This field does not represent internal state, it's used as a cache to optimise LocalFlags function call lflags *flag.FlagSet // iflags contains inherited flags. + // This field does not represent internal state, it's used as a cache to optimise InheritedFlags function call iflags *flag.FlagSet // parentsPflags is all persistent flags of cmd's parents. parentsPflags *flag.FlagSet @@ -1659,6 +1661,7 @@ func (c *Command) Flags() *flag.FlagSet { } // LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands. +// This function does not modify the flags of the current command, it's purpose is to return the current state. func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { persistentFlags := c.PersistentFlags() @@ -1672,6 +1675,7 @@ func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { } // LocalFlags returns the local FlagSet specifically set in the current command. +// This function does not modify the flags of the current command, it's purpose is to return the current state. func (c *Command) LocalFlags() *flag.FlagSet { c.mergePersistentFlags() @@ -1699,6 +1703,7 @@ func (c *Command) LocalFlags() *flag.FlagSet { } // InheritedFlags returns all flags which were inherited from parent commands. +// This function does not modify the flags of the current command, it's purpose is to return the current state. func (c *Command) InheritedFlags() *flag.FlagSet { c.mergePersistentFlags() @@ -1724,6 +1729,7 @@ func (c *Command) InheritedFlags() *flag.FlagSet { } // NonInheritedFlags returns all flags which were not inherited from parent commands. +// This function does not modify the flags of the current command, it's purpose is to return the current state. func (c *Command) NonInheritedFlags() *flag.FlagSet { return c.LocalFlags() } From bcfcff729ecdeb4072ef6f2a9c0b5e4ca1853206 Mon Sep 17 00:00:00 2001 From: Radek Smid Date: Mon, 15 Jan 2024 14:38:50 +0100 Subject: [PATCH 67/76] Add Taikun CloudWorks to list of projects (#2098) --- 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 75cc065c..5de771af 100644 --- a/site/content/projects_using_cobra.md +++ b/site/content/projects_using_cobra.md @@ -57,6 +57,7 @@ - [Scaleway CLI](https://github.com/scaleway/scaleway-cli) - [Sia](https://github.com/SiaFoundation/siad) - [Skaffold](https://skaffold.dev/) +- [Taikun](https://taikun.cloud/) - [Tendermint](https://github.com/tendermint/tendermint) - [Twitch CLI](https://github.com/twitchdev/twitch-cli) - [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli) From bd2655e76c8b499761dc8b664c8b04dbfd828354 Mon Sep 17 00:00:00 2001 From: montag451 Date: Fri, 8 Mar 2024 01:36:58 +0100 Subject: [PATCH 68/76] 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 5de771af..52e4e807 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 69/76] 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 74ed3317..d6b28e24 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 70/76] 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 d6b28e24..8a5115bf 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 71/76] 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 8a5115bf..21f81e0c 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 72/76] 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 f98fe2a3..12592223 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 2cca6fd7..c33acc2b 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 73/76] 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 b7d88e4d..db336922 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 74/76] 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 a618ec24..22ae622e 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 be835704..f4d198cb 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 db336922..9ce7a529 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 b8c15ce8..2138f248 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 0aaa07a1..4de4ceee 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 55195193..a830b7bc 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 75/76] 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 3b42ef04..93e87d66 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 76/76] 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 a79e66a1..8c80da01 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 871c3a8a..ab40b433 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=