From ca456bf9cac906447a1786cd981facdf0e8bba26 Mon Sep 17 00:00:00 2001 From: RAGOT David <35502263+Dav-14@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:19:24 +0100 Subject: [PATCH] feat: update cobra (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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. * Update stale.yml (#1863) * fix: force ForEach-Object to return array in pwsh completion (#1850) Fixes #1847 * Makefile: add target richtest (#1865) Don't require contributors to install richgo but keep it as an option and for CI * 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> * Update kubescape org (#1874) Signed-off-by: David Wertenteil * ci: deprecate go 1.15 (#1866) Remove testing for go 1.15 to allow CI to pass, but don't force projects to upgrade. * 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. * Update badge route (#1884) Based on https://github.com/badges/shields/issues/8671 * 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 * 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. * build(deps): bump github.com/inconshreveable/mousetrap (#1872) * Add documentation about disabling completion descriptions (#1901) * Improve MarkFlagsMutuallyExclusive example in User Guide (#1904) * Update shell_completions.md (#1907) align documentation with the code : completions.go:452 * build(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#1902) * Removes stale bot from GitHub action (#1908) Signed-off-by: John McBride * 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 * Add support for PowerShell 7.2+ (#1916) 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 * ci: deprecate go 1.16 (#1926) * ci: test Golang 1.20 (#1925) * update copyright year (#1927) * Update projects_using_cobra.md (#1932) Signed-off-by: Florent Poinsard * Document suggested layout for subcommands (#1930) Signed-off-by: Luiz Carvalho * 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 * Update main image to better handle dark background (#1883) Fixes #1880 Signed-off-by: Marc Khouzam Co-authored-by: Deleplace * Fix typo in fish completions (#1945) * build(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.5.0 (#1971) * Fix grammar: 'allows to' (#1978) The use in generated bash completion files is getting flagged by Lintian (the Debian package linting tool). Signed-off-by: Taavi Väänänen * 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`. * 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 * build(deps): bump golangci/golangci-lint-action from 3.5.0 to 3.6.0 (#1976) * Move documentation sources to site/content (#1428) * Add 'one required flag' group (#1952) * golangci: enable 'unused' and disable deprecated replaced by it (#1983) * doc: fix typo, Deperecated -> Deprecated (#2000) * minor corrections to unit tests (#2003) * build(deps): bump golangci/golangci-lint-action from 3.6.0 to 3.7.0 (#2021) * command: temporarily disable G602 due to securego/gosec#1005 (#2022) * ci: test golang 1.21 (#2024) * Customizable error message prefix (#2023) * feat: add getters for flag completions (#1943) * Add notes to doc on preRun and postRun condition (#2041) * build(deps): bump actions/setup-go from 3 to 4 (#1934) * build(deps): bump github.com/cpuguy83/go-md2man/v2 from 2.0.2 to 2.0.3 (#2047) * 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 * Fix linter errors (#2052) When using golangci-lint v1.55.0 some new errors were being reported. Signed-off-by: Marc Khouzam * 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 * 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 * 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 * build(deps): bump actions/checkout from 3 to 4 (#2028) * 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 * 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 * feat: expose GetCompletions (was getCompletions) --------- Signed-off-by: dependabot[bot] Signed-off-by: David Wertenteil Signed-off-by: John McBride Signed-off-by: Oldřich Jedlička Signed-off-by: Florent Poinsard Signed-off-by: Luiz Carvalho Signed-off-by: Marc Khouzam Signed-off-by: Taavi Väänänen Signed-off-by: Paul Holzinger Signed-off-by: Volodymyr Khoroz Signed-off-by: Nir Soffer Co-authored-by: Brian Pursley Co-authored-by: Enrico Candino Co-authored-by: Norman Dankert Co-authored-by: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Wertenteil Co-authored-by: Yash Ladha <18033231+yashLadha@users.noreply.github.com> Co-authored-by: Seonghyeon Cho Co-authored-by: Dominik Roos Co-authored-by: Shihta Kuan Co-authored-by: janhn Co-authored-by: Ggg6542 <465806+gusega@users.noreply.github.com> Co-authored-by: John McBride Co-authored-by: Gyanendra Mishra Co-authored-by: Oldřich Jedlička Co-authored-by: Oldřich Jedlička Co-authored-by: Florent Poinsard <35779988+frouioui@users.noreply.github.com> Co-authored-by: Luiz Carvalho Co-authored-by: Marc Khouzam Co-authored-by: Deleplace Co-authored-by: Tom Payne Co-authored-by: Taavi Väänänen Co-authored-by: Branch Vincent Co-authored-by: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Co-authored-by: Martijn Evers <94963229+marevers@users.noreply.github.com> Co-authored-by: gocurr Co-authored-by: Jun Nishimura Co-authored-by: Nuno Adrego <55922671+nunoadrego@users.noreply.github.com> Co-authored-by: Souma <101255979+5ouma@users.noreply.github.com> Co-authored-by: Alexandru-Claudius Virtopeanu Co-authored-by: Haoming Meng <41393704+Techming@users.noreply.github.com> Co-authored-by: vkhoroz Co-authored-by: Ville Skyttä Co-authored-by: Nir Soffer Co-authored-by: Geoffrey Ragot --- .github/workflows/stale.yml | 56 --- .github/workflows/test.yml | 36 +- .golangci.yml | 10 +- Makefile | 8 +- README.md | 12 +- active_help.go | 12 +- active_help_test.go | 2 +- args.go | 4 +- args_test.go | 2 +- assets/CobraMain.png | Bin 0 -> 73479 bytes bash_completions.go | 6 +- bash_completionsV2.go | 22 +- bash_completionsV2_test.go | 2 +- bash_completions_test.go | 2 +- cobra.go | 19 +- cobra_test.go | 2 +- command.go | 83 +++- command_notwin.go | 2 +- command_test.go | 220 +++++++--- command_win.go | 2 +- completions.go | 42 +- completions_test.go | 410 +++++++++++++++++- doc/cmd_test.go | 2 +- doc/man_docs.go | 2 +- doc/man_docs_test.go | 4 +- doc/man_examples_test.go | 2 +- doc/md_docs.go | 10 +- doc/md_docs_test.go | 2 +- doc/rest_docs.go | 6 +- doc/rest_docs_test.go | 2 +- doc/util.go | 2 +- doc/yaml_docs.go | 2 +- doc/yaml_docs_test.go | 2 +- fish_completions.go | 78 +++- fish_completions.md | 4 - fish_completions_test.go | 37 +- flag_groups.go | 70 ++- flag_groups_test.go | 65 ++- go.mod | 4 +- go.sum | 8 +- powershell_completions.go | 31 +- powershell_completions.md | 3 - powershell_completions_test.go | 4 +- shell_completions.go | 2 +- active_help.md => site/content/active_help.md | 4 +- .../content/completions/_index.md | 40 +- .../content/completions/bash.md | 2 +- site/content/completions/fish.md | 4 + site/content/completions/powershell.md | 3 + .../content/completions/zsh.md | 2 +- .../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 .../content/docgen/rest.md | 0 .../content/docgen/yaml.md | 0 .../content/projects_using_cobra.md | 4 +- user_guide.md => site/content/user_guide.md | 73 +++- zsh_completions.go | 17 +- zsh_completions_test.go | 2 +- 59 files changed, 1121 insertions(+), 334 deletions(-) delete mode 100644 .github/workflows/stale.yml create mode 100644 assets/CobraMain.png delete mode 100644 fish_completions.md delete mode 100644 powershell_completions.md rename active_help.md => site/content/active_help.md (96%) rename shell_completions.md => site/content/completions/_index.md (95%) 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 (95%) rename user_guide.md => site/content/user_guide.md (87%) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 2db5123d1..000000000 --- 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 interseted 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 interseted 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' - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a55d39fea..a92453228 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,14 +18,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: >- docker run -v $(pwd):/wrk -w /wrk ghcr.io/google/addlicense -c 'The Cobra Authors' - -y '2013-2022' + -y '2013-2023' -l apache -ignore '.github/**' -check @@ -39,17 +39,17 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: - go-version: '^1.19' + go-version: '^1.21' check-latest: true cache: true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: golangci/golangci-lint-action@v3.2.0 + - uses: golangci/golangci-lint-action@v3.7.0 with: version: latest args: --verbose @@ -63,32 +63,28 @@ jobs: - ubuntu - macOS go: - - 15 - - 16 - 17 - 18 - 19 + - 20 + - 21 name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x' runs-on: ${{ matrix.platform }}-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: 1.${{ matrix.go }}.x cache: true - 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 test + - run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin/:$PATH make richtest test-win: @@ -112,7 +108,7 @@ jobs: unzip mingw-w64-x86_64-go - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: @@ -125,4 +121,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/.golangci.yml b/.golangci.yml index 439d3e1de..a618ec24d 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. @@ -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 diff --git a/Makefile b/Makefile index c433a01bc..0da8d7aa0 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: diff --git a/README.md b/README.md index 7cc726beb..6444f4b7f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -![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. 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/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) @@ -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/active_help.go b/active_help.go index 95e03aecb..5f965e057 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. @@ -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/active_help_test.go b/active_help_test.go index 3653dd87d..2d624794c 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 2c1f99e78..e79ec33a8 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. @@ -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/args_test.go b/args_test.go index fd2c3b415..90d174cce 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/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= 3 && arg[1] == '-') || + return ((len(arg) >= 3 && arg[0:2] == "--") || (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) } @@ -752,7 +777,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 @@ -910,15 +937,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 { @@ -955,10 +998,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 + } } } @@ -1048,7 +1095,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 @@ -1077,7 +1124,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, @@ -1296,7 +1343,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 { @@ -1380,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() } @@ -1402,6 +1452,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/command_notwin.go b/command_notwin.go index 2b77f8f01..307f0c127 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 3fd36c7e0..9f686d65e 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. @@ -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". @@ -438,7 +468,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 +505,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 +534,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) @@ -1099,6 +1129,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}) @@ -1497,57 +1560,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) @@ -1560,41 +1639,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) } } } @@ -2692,3 +2743,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") + }) + } +} diff --git a/command_win.go b/command_win.go index 520f23abf..adbef395c 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 4fd0e339a..9db9a8735 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. @@ -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. @@ -141,6 +145,20 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman return nil } +// 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() + + completionFunc, exists := flagCompletionFunctions[flag] + return completionFunc, exists +} + // Returns a string listing the different directive enabled in the specified parameter func (d ShellCompDirective) string() string { var directives []string @@ -159,6 +177,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") } @@ -169,7 +190,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), @@ -276,9 +297,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 @@ -382,6 +407,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) }) @@ -727,7 +757,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/completions_test.go b/completions_test.go index abac12e46..d5aee2501 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. @@ -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", @@ -2622,8 +2732,6 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) { expected := strings.Join([]string{ "--persistent", "-p", - "--help", - "-h", "--nonPersistent", "-n", "--flag", @@ -2830,6 +2938,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{ @@ -2955,8 +3161,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") @@ -2968,6 +3192,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 } @@ -3098,6 +3326,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 { @@ -3115,3 +3363,157 @@ 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) + } + }) + } +} + +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) + } + } + }) + } +} diff --git a/doc/cmd_test.go b/doc/cmd_test.go index 4f2a0f581..0d022c77d 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 b5a2c596e..b8c15ce88 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 d296eebea..a4435e6e4 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. @@ -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")) diff --git a/doc/man_examples_test.go b/doc/man_examples_test.go index b995bf6db..873b2b6d6 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 bab4b4963..f98fe2a3b 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. @@ -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 { diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index b1632e9c5..e70cad822 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 d65890652..2cca6fd77 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. @@ -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) diff --git a/doc/rest_docs_test.go b/doc/rest_docs_test.go index 0c197f8dd..1a3ea9dd5 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 499ecdd91..0aaa07a16 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 12f8cf33b..2b26d6ec0 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 c552077d8..1a6fa7c31 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 97112a17b..12d61b691 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. @@ -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 "Successfully 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/fish_completions.md b/fish_completions.md deleted file mode 100644 index 19b2ed129..000000000 --- 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/fish_completions_test.go b/fish_completions_test.go index 935f5540c..ce2a531dc 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. @@ -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()) } } diff --git a/flag_groups.go b/flag_groups.go index 9c377aaf9..0671ec5f2 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. @@ -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 b4b65ac05..cffa85525 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. @@ -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/go.mod b/go.mod index a1309e536..a79e66a13 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/spf13/cobra go 1.15 require ( - github.com/cpuguy83/go-md2man/v2 v2.0.2 - github.com/inconshreveable/mousetrap v1.0.1 + 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 a8c544e25..871c3a8af 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/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= 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= diff --git a/powershell_completions.go b/powershell_completions.go index 004de42e4..551951939 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. @@ -47,7 +47,7 @@ filter __%[1]s_escapeStringWithSpecialChars { `+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+` } -[scriptblock]$__%[2]sCompleterBlock = { +[scriptblock]${__%[2]sCompleterBlock} = { param( $WordToComplete, $CommandAst, @@ -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. @@ -106,13 +107,22 @@ 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" # 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 @@ -137,7 +147,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" @@ -182,6 +192,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" @@ -264,10 +279,10 @@ 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, activeHelpEnvVar(name))) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name))) } func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { diff --git a/powershell_completions.md b/powershell_completions.md deleted file mode 100644 index c449f1e5c..000000000 --- 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/powershell_completions_test.go b/powershell_completions_test.go index 3d3d10333..603b50c97 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. @@ -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)) } diff --git a/shell_completions.go b/shell_completions.go index 126e83c30..b035742d3 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/active_help.md b/site/content/active_help.md similarity index 96% rename from active_help.md rename to site/content/active_help.md index 5e7f59af3..d72acc722 100644 --- a/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 diff --git a/shell_completions.md b/site/content/completions/_index.md similarity index 95% rename from shell_completions.md rename to site/content/completions/_index.md index 286c2525b..4efad2907 100644 --- a/shell_completions.md +++ b/site/content/completions/_index.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 @@ -237,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. @@ -385,6 +380,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 @@ -408,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 @@ -417,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 @@ -440,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`. @@ -474,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 @@ -527,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 52919b2fa..6838a3a6b 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 000000000..5253f7d49 --- /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 000000000..024b119a0 --- /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 7cff61787..3e27208b7 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 8e07baae3..eba2a5fce 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 95% rename from projects_using_cobra.md rename to site/content/projects_using_cobra.md index 934b04ab6..8a291eb20 100644 --- a/projects_using_cobra.md +++ b/site/content/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/) @@ -26,7 +27,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) @@ -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) diff --git a/user_guide.md b/site/content/user_guide.md similarity index 87% rename from user_guide.md rename to site/content/user_guide.md index fee9848dc..4116e8dc7 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) @@ -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. @@ -313,12 +344,21 @@ 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") +``` + +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 both of these cases: +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 @@ -556,9 +596,15 @@ 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: +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` @@ -641,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: @@ -684,12 +734,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). diff --git a/zsh_completions.go b/zsh_completions.go index 84cec76fd..1856e4c7f 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. @@ -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 -*- @@ -108,8 +109,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 +179,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 +229,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 +269,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 +303,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then fi `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpMarker)) } diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 258b1f71d..fe898b3da 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.