mirror of
https://github.com/spf13/cobra
synced 2025-05-05 04:47:22 +00:00
Merge branch 'main' into oninitializee
This commit is contained in:
commit
28c7b5ca41
53 changed files with 3184 additions and 338 deletions
6
.github/workflows/labeler.yml
vendored
6
.github/workflows/labeler.yml
vendored
|
@ -2,8 +2,14 @@ name: "Pull Request Labeler"
|
||||||
on:
|
on:
|
||||||
- pull_request_target
|
- pull_request_target
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
|
permissions:
|
||||||
|
contents: read # for actions/labeler to determine modified files
|
||||||
|
pull-requests: write # for actions/labeler to add labels to PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v4
|
- uses: actions/labeler@v4
|
||||||
|
|
5
.github/workflows/size-labeler.yml
vendored
5
.github/workflows/size-labeler.yml
vendored
|
@ -4,8 +4,13 @@ name: size-labeler
|
||||||
|
|
||||||
on: [pull_request_target]
|
on: [pull_request_target]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
size-labeler:
|
size-labeler:
|
||||||
|
permissions:
|
||||||
|
pull-requests: write # for codelytv/pr-size-labeler to add labels & comment on PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Label the PR size
|
name: Label the PR size
|
||||||
steps:
|
steps:
|
||||||
|
|
50
.github/workflows/stale.yml
vendored
50
.github/workflows/stale.yml
vendored
|
@ -1,50 +0,0 @@
|
||||||
name: Mark stale issues and pull requests
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v5
|
|
||||||
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-label: 'lifecycle/frozen'
|
|
||||||
exempt-pr-label: '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'
|
|
||||||
|
|
68
.github/workflows/test.yml
vendored
68
.github/workflows/test.yml
vendored
|
@ -3,27 +3,58 @@ name: Test
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
golangci-lint:
|
|
||||||
|
lic-headers:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
go-version: '1.17'
|
- run: >-
|
||||||
|
docker run
|
||||||
|
-v $(pwd):/wrk -w /wrk
|
||||||
|
ghcr.io/google/addlicense
|
||||||
|
-c 'The Cobra Authors'
|
||||||
|
-y '2013-2022'
|
||||||
|
-l apache
|
||||||
|
-ignore '.github/**'
|
||||||
|
-check
|
||||||
|
.
|
||||||
|
|
||||||
|
|
||||||
|
golangci-lint:
|
||||||
|
permissions:
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: golangci/golangci-lint-action@v3.1.0
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '^1.19'
|
||||||
|
check-latest: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: golangci/golangci-lint-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --verbose
|
args: --verbose
|
||||||
|
|
||||||
|
|
||||||
test-unix:
|
test-unix:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -32,37 +63,28 @@ jobs:
|
||||||
- ubuntu
|
- ubuntu
|
||||||
- macOS
|
- macOS
|
||||||
go:
|
go:
|
||||||
- 14
|
|
||||||
- 15
|
|
||||||
- 16
|
- 16
|
||||||
- 17
|
- 17
|
||||||
- 18
|
- 18
|
||||||
|
- 19
|
||||||
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
|
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
|
||||||
runs-on: ${{ matrix.platform }}-latest
|
runs-on: ${{ matrix.platform }}-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: 1.${{ matrix.go }}.x
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
go-version: 1.${{ matrix.go }}.x
|
||||||
key: ${{ runner.os }}-1.${{ matrix.go }}.x-${{ hashFiles('**/go.sum') }}
|
cache: true
|
||||||
restore-keys: ${{ runner.os }}-1.${{ matrix.go }}.x-
|
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
export GOBIN=$HOME/go/bin
|
export GOBIN=$HOME/go/bin
|
||||||
case "${{ matrix.go }}" in
|
go install github.com/kyoh86/richgo@latest
|
||||||
16|17|18) _version='@latest';;
|
go install github.com/mitchellh/gox@latest
|
||||||
*) _version='';;
|
|
||||||
esac
|
- run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin/:$PATH make richtest
|
||||||
go install github.com/kyoh86/richgo"${_version}"
|
|
||||||
go install github.com/mitchellh/gox"${_version}"
|
|
||||||
|
|
||||||
- run: RICHGO_FORCE_COLOR=1 PATH=$HOME/go/bin/:$PATH make test
|
|
||||||
|
|
||||||
test-win:
|
test-win:
|
||||||
name: MINGW64
|
name: MINGW64
|
||||||
|
@ -98,4 +120,4 @@ jobs:
|
||||||
go install github.com/kyoh86/richgo@latest
|
go install github.com/kyoh86/richgo@latest
|
||||||
go install github.com/mitchellh/gox@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
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2013-2022 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.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
run:
|
run:
|
||||||
deadline: 5m
|
deadline: 5m
|
||||||
|
|
||||||
|
|
8
Makefile
8
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")
|
$(warning "could not find golangci-lint in $(PATH), run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh")
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq (, $(shell which richgo))
|
|
||||||
$(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo")
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: fmt lint test install_deps clean
|
.PHONY: fmt lint test install_deps clean
|
||||||
|
|
||||||
default: all
|
default: all
|
||||||
|
@ -25,6 +21,10 @@ lint:
|
||||||
|
|
||||||
test: install_deps
|
test: install_deps
|
||||||
$(info ******************** running tests ********************)
|
$(info ******************** running tests ********************)
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
|
richtest: install_deps
|
||||||
|
$(info ******************** running tests with kyoh86/richgo ********************)
|
||||||
richgo test -v ./...
|
richgo test -v ./...
|
||||||
|
|
||||||
install_deps:
|
install_deps:
|
||||||
|
|
13
README.md
13
README.md
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
Cobra is a library for creating powerful modern CLI applications.
|
Cobra is a library for creating powerful modern CLI applications.
|
||||||
|
|
||||||
Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/),
|
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
|
[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](./projects_using_cobra.md) contains a more extensive list of projects using Cobra.
|
||||||
|
|
||||||
[](https://github.com/spf13/cobra/actions?query=workflow%3ATest)
|
[](https://github.com/spf13/cobra/actions?query=workflow%3ATest)
|
||||||
[](https://pkg.go.dev/github.com/spf13/cobra)
|
[](https://pkg.go.dev/github.com/spf13/cobra)
|
||||||
[](https://goreportcard.com/report/github.com/spf13/cobra)
|
[](https://goreportcard.com/report/github.com/spf13/cobra)
|
||||||
[](https://gophers.slack.com/archives/CD3LP1199)
|
[](https://gophers.slack.com/archives/CD3LP1199)
|
||||||
|
@ -23,12 +23,13 @@ Cobra provides:
|
||||||
* Global, local and cascading flags
|
* Global, local and cascading flags
|
||||||
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
||||||
* Automatic help generation for commands and flags
|
* Automatic help generation for commands and flags
|
||||||
|
* Grouping help for subcommands
|
||||||
* Automatic help flag recognition of `-h`, `--help`, etc.
|
* Automatic help flag recognition of `-h`, `--help`, etc.
|
||||||
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
|
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
|
||||||
* Automatically generated man pages for your application
|
* Automatically generated man pages for your application
|
||||||
* Command aliases so you can change things without breaking them
|
* Command aliases so you can change things without breaking them
|
||||||
* The flexibility to define your own help, usage, etc.
|
* The flexibility to define your own help, usage, etc.
|
||||||
* Optional seamless integration with [viper](http://github.com/spf13/viper) for 12-factor apps
|
* Optional seamless integration with [viper](https://github.com/spf13/viper) for 12-factor apps
|
||||||
|
|
||||||
# Concepts
|
# Concepts
|
||||||
|
|
||||||
|
@ -40,9 +41,9 @@ The best applications read like sentences when used, and as a result, users
|
||||||
intuitively know how to interact with them.
|
intuitively know how to interact with them.
|
||||||
|
|
||||||
The pattern to follow is
|
The pattern to follow is
|
||||||
`APPNAME VERB NOUN --ADJECTIVE.`
|
`APPNAME VERB NOUN --ADJECTIVE`
|
||||||
or
|
or
|
||||||
`APPNAME COMMAND ARG --FLAG`
|
`APPNAME COMMAND ARG --FLAG`.
|
||||||
|
|
||||||
A few good real world examples may better illustrate this point.
|
A few good real world examples may better illustrate this point.
|
||||||
|
|
||||||
|
|
63
active_help.go
Normal file
63
active_help.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
activeHelpMarker = "_activeHelp_ "
|
||||||
|
// The below values should not be changed: programs will be using them explicitly
|
||||||
|
// in their user documentation, and users will be using them explicitly.
|
||||||
|
activeHelpEnvVarSuffix = "_ACTIVE_HELP"
|
||||||
|
activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP"
|
||||||
|
activeHelpGlobalDisable = "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// The array parameter should be the array that will contain the completions.
|
||||||
|
// This function can be called multiple times before and/or after completions are added to
|
||||||
|
// the array. Each time this function is called with the same array, the new
|
||||||
|
// ActiveHelp line will be shown below the previous ones when completion is triggered.
|
||||||
|
func AppendActiveHelp(compArray []string, activeHelpStr string) []string {
|
||||||
|
return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveHelpConfig returns the value of the ActiveHelp environment variable
|
||||||
|
// <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the root command in upper
|
||||||
|
// case, with all - replaced by _.
|
||||||
|
// It will always return "0" if the global environment variable COBRA_ACTIVE_HELP
|
||||||
|
// is set to "0".
|
||||||
|
func GetActiveHelpConfig(cmd *Command) string {
|
||||||
|
activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar)
|
||||||
|
if activeHelpCfg != activeHelpGlobalDisable {
|
||||||
|
activeHelpCfg = os.Getenv(activeHelpEnvVar(cmd.Root().Name()))
|
||||||
|
}
|
||||||
|
return activeHelpCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment
|
||||||
|
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
|
||||||
|
// root command in upper case, with all - 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, "-", "_")
|
||||||
|
}
|
157
active_help.md
Normal file
157
active_help.md
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
# Active Help
|
||||||
|
|
||||||
|
Active Help is a framework provided by Cobra which allows a program to define messages (hints, warnings, etc) that will be printed during program usage. It aims to make it easier for your users to learn how to use your program. If configured by the program, Active Help is printed when the user triggers shell completion.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
```
|
||||||
|
bash-5.1$ helm repo add [tab]
|
||||||
|
You must choose a name for the repo you are adding.
|
||||||
|
|
||||||
|
bash-5.1$ bin/helm package [tab]
|
||||||
|
Please specify the path to the chart to package
|
||||||
|
|
||||||
|
bash-5.1$ bin/helm package [tab][tab]
|
||||||
|
bin/ internal/ scripts/ pkg/ testdata/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hint**: A good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions to guide the user in knowing what is expected by the program.
|
||||||
|
## Supported shells
|
||||||
|
|
||||||
|
Active Help is currently only supported for the following shells:
|
||||||
|
- Bash (using [bash completion V2](shell_completions.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed.
|
||||||
|
- Zsh
|
||||||
|
|
||||||
|
## Adding Active Help messages
|
||||||
|
|
||||||
|
As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](shell_completions.md).
|
||||||
|
|
||||||
|
Adding Active Help is done through the use of the `cobra.AppendActiveHelp(...)` function, where the program repeatedly adds Active Help messages to the list of completions. Keep reading for details.
|
||||||
|
|
||||||
|
### Active Help for nouns
|
||||||
|
|
||||||
|
Adding Active Help when completing a noun is done within the `ValidArgsFunction(...)` of a command. Please notice the use of `cobra.AppendActiveHelp(...)` in the following example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "add [NAME] [URL]",
|
||||||
|
Short: "add a chart repository",
|
||||||
|
Args: require.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return addRepo(args)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
var comps []string
|
||||||
|
if len(args) == 0 {
|
||||||
|
comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
|
||||||
|
} else if len(args) == 1 {
|
||||||
|
comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding")
|
||||||
|
} else {
|
||||||
|
comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments")
|
||||||
|
}
|
||||||
|
return comps, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The example above defines the completions (none, in this specific example) as well as the Active Help messages for the `helm repo add` command. It yields the following behavior:
|
||||||
|
```
|
||||||
|
bash-5.1$ helm repo add [tab]
|
||||||
|
You must choose a name for the repo you are adding
|
||||||
|
|
||||||
|
bash-5.1$ helm repo add grafana [tab]
|
||||||
|
You must specify the URL for the repo you are adding
|
||||||
|
|
||||||
|
bash-5.1$ helm repo add grafana https://grafana.github.io/helm-charts [tab]
|
||||||
|
This command does not take any more arguments
|
||||||
|
```
|
||||||
|
**Hint**: As can be seen in the above example, a good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions.
|
||||||
|
|
||||||
|
### Active Help for flags
|
||||||
|
|
||||||
|
Providing Active Help for flags is done in the same fashion as for nouns, but using the completion function registered for the flag. For example:
|
||||||
|
```go
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
return cobra.AppendActiveHelp(nil, "You must first specify the chart to install before the --version flag can be completed"), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return compVersionFlag(args[1], toComplete)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
The example above prints an Active Help message when not enough information was given by the user to complete the `--version` flag.
|
||||||
|
```
|
||||||
|
bash-5.1$ bin/helm install myrelease --version 2.0.[tab]
|
||||||
|
You must first specify the chart to install before the --version flag can be completed
|
||||||
|
|
||||||
|
bash-5.1$ bin/helm install myrelease bitnami/solr --version 2.0.[tab][tab]
|
||||||
|
2.0.1 2.0.2 2.0.3
|
||||||
|
```
|
||||||
|
|
||||||
|
## User control of Active Help
|
||||||
|
|
||||||
|
You may want to allow your users to disable Active Help or choose between different levels of Active Help. It is entirely up to the program to define the type of configurability of Active Help that it wants to offer, if any.
|
||||||
|
Allowing to configure Active Help is entirely optional; you can use Active Help in your program without doing anything about Active Help configuration.
|
||||||
|
|
||||||
|
The way to configure Active Help is to use the program's Active Help environment
|
||||||
|
variable. That variable is named `<PROGRAM>_ACTIVE_HELP` where `<PROGRAM>` is the name of your
|
||||||
|
program in uppercase with any `-` 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
|
||||||
|
would set the desired behavior to `local` by doing `export HELM_ACTIVE_HELP=local` in their shell.
|
||||||
|
|
||||||
|
For simplicity, when in `cmd.ValidArgsFunction(...)` or a flag's completion function, the program should read the
|
||||||
|
Active Help configuration using the `cobra.GetActiveHelpConfig(cmd)` function and select what Active Help messages
|
||||||
|
should or should not be added (instead of reading the environment variable directly).
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```go
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
activeHelpLevel := cobra.GetActiveHelpConfig(cmd)
|
||||||
|
|
||||||
|
var comps []string
|
||||||
|
if len(args) == 0 {
|
||||||
|
if activeHelpLevel != "off" {
|
||||||
|
comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
|
||||||
|
}
|
||||||
|
} else if len(args) == 1 {
|
||||||
|
if activeHelpLevel != "off" {
|
||||||
|
comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if activeHelpLevel == "local" {
|
||||||
|
comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comps, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
```
|
||||||
|
**Note 1**: If the `<PROGRAM>_ACTIVE_HELP` environment variable is set to the string "0", Cobra will automatically disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help without having to call `cobra.GetActiveHelpConfig(cmd)` explicitly.
|
||||||
|
|
||||||
|
**Note 2**: If a user wants to disable Active Help for every single program based on Cobra, she can set the environment variable `COBRA_ACTIVE_HELP` to "0". In this case `cobra.GetActiveHelpConfig(cmd)` will return "0" no matter what the variable `<PROGRAM>_ACTIVE_HELP` is set to.
|
||||||
|
|
||||||
|
**Note 3**: If the user does not set `<PROGRAM>_ACTIVE_HELP` or `COBRA_ACTIVE_HELP` (which will be a common case), the default value for the Active Help configuration returned by `cobra.GetActiveHelpConfig(cmd)` will be the empty string.
|
||||||
|
## Active Help with Cobra's default completion command
|
||||||
|
|
||||||
|
Cobra provides a default `completion` command for programs that wish to use it.
|
||||||
|
When using the default `completion` command, Active Help is configurable in the same
|
||||||
|
fashion as described above using environment variables. You may wish to document this in more
|
||||||
|
details for your users.
|
||||||
|
|
||||||
|
## Debugging Active Help
|
||||||
|
|
||||||
|
Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details.
|
||||||
|
|
||||||
|
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 `<PROGRAM>_ACTIVE_HELP` where any `-` is 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<ENTER>
|
||||||
|
bitnami/haproxy
|
||||||
|
bitnami/harbor
|
||||||
|
_activeHelp_ WARNING: cannot re-use a name that is still in use
|
||||||
|
:0
|
||||||
|
Completion ended with directive: ShellCompDirectiveDefault
|
||||||
|
|
||||||
|
$ HELM_ACTIVE_HELP=0 bin/helm __complete install wordpress bitnami/h<ENTER>
|
||||||
|
bitnami/haproxy
|
||||||
|
bitnami/harbor
|
||||||
|
:0
|
||||||
|
Completion ended with directive: ShellCompDirectiveDefault
|
||||||
|
```
|
400
active_help_test.go
Normal file
400
active_help_test.go
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
activeHelpMessage = "This is an activeHelp message"
|
||||||
|
activeHelpMessage2 = "This is the rest of the activeHelp message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActiveHelpAlone(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
activeHelpFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := AppendActiveHelp(nil, activeHelpMessage)
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that activeHelp can be added to a root command
|
||||||
|
rootCmd.ValidArgsFunction = activeHelpFunc
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.ValidArgsFunction = nil
|
||||||
|
|
||||||
|
// Test that activeHelp can be added to a child command
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
childCmd.ValidArgsFunction = activeHelpFunc
|
||||||
|
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActiveHelpWithComps(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
// Test that activeHelp can be added following other completions
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{"first", "second"}
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage)
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"first",
|
||||||
|
"second",
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that activeHelp can be added preceding other completions
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
var comps []string
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage)
|
||||||
|
comps = append(comps, []string{"first", "second"}...)
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
"first",
|
||||||
|
"second",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that activeHelp can be added interleaved with other completions
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{"first"}
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage)
|
||||||
|
comps = append(comps, "second")
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"first",
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
"second",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiActiveHelp(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
// Test that multiple activeHelp message can be added
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := AppendActiveHelp(nil, activeHelpMessage)
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage2)
|
||||||
|
return comps, ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that multiple activeHelp messages can be used along with completions
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{"first"}
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage)
|
||||||
|
comps = append(comps, "second")
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage2)
|
||||||
|
return comps, ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"first",
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
"second",
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActiveHelpForFlag(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
flagname := "flag"
|
||||||
|
rootCmd.Flags().String(flagname, "", "A flag")
|
||||||
|
|
||||||
|
// Test that multiple activeHelp message can be added
|
||||||
|
_ = rootCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{"first"}
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage)
|
||||||
|
comps = append(comps, "second")
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage2)
|
||||||
|
return comps, ShellCompDirectiveNoFileComp
|
||||||
|
})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--flag", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"first",
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
|
||||||
|
"second",
|
||||||
|
fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigActiveHelp(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
activeHelpCfg := "someconfig,anotherconfig"
|
||||||
|
// Set the variable that the user would be setting
|
||||||
|
os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)
|
||||||
|
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
|
||||||
|
if receivedActiveHelpCfg != activeHelpCfg {
|
||||||
|
t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
|
||||||
|
}
|
||||||
|
return nil, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test active help config for a flag
|
||||||
|
activeHelpCfg = "a config for a flag"
|
||||||
|
// Set the variable that the completions scripts will be setting
|
||||||
|
os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)
|
||||||
|
|
||||||
|
flagname := "flag"
|
||||||
|
childCmd.Flags().String(flagname, "", "A flag")
|
||||||
|
|
||||||
|
// Test that multiple activeHelp message can be added
|
||||||
|
_ = childCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
|
||||||
|
if receivedActiveHelpCfg != activeHelpCfg {
|
||||||
|
t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
|
||||||
|
}
|
||||||
|
return nil, ShellCompDirectiveDefault
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "--flag", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisableActiveHelp(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
// Test the disabling of activeHelp using the specific program
|
||||||
|
// environment variable that the completions scripts will be setting.
|
||||||
|
// Make sure the disabling value is "0" by hard-coding it in the tests;
|
||||||
|
// this is for backwards-compatibility as programs will be using this value.
|
||||||
|
os.Setenv(activeHelpEnvVar(rootCmd.Name()), "0")
|
||||||
|
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{"first"}
|
||||||
|
comps = AppendActiveHelp(comps, activeHelpMessage)
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
os.Unsetenv(activeHelpEnvVar(rootCmd.Name()))
|
||||||
|
|
||||||
|
// Make sure there is no ActiveHelp in the output
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"first",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now test the global disabling of ActiveHelp
|
||||||
|
os.Setenv(activeHelpGlobalEnvVar, "0")
|
||||||
|
// Set the specific variable, to make sure it is ignored when the global env
|
||||||
|
// var is set properly
|
||||||
|
os.Setenv(activeHelpEnvVar(rootCmd.Name()), "1")
|
||||||
|
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there is no ActiveHelp in the output
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"first",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that if the global env variable is set to anything else than
|
||||||
|
// the disable value it is ignored
|
||||||
|
os.Setenv(activeHelpGlobalEnvVar, "on")
|
||||||
|
// Set the specific variable, to make sure it is used (while ignoring the global env var)
|
||||||
|
activeHelpCfg := "1"
|
||||||
|
os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)
|
||||||
|
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
|
||||||
|
if receivedActiveHelpCfg != activeHelpCfg {
|
||||||
|
t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
|
||||||
|
}
|
||||||
|
return nil, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
40
args.go
40
args.go
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,7 +21,7 @@ import (
|
||||||
|
|
||||||
type PositionalArgs func(cmd *Command, args []string) error
|
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 no subcommands can take arbitrary arguments
|
||||||
// - root commands with subcommands will do subcommand validity checking
|
// - root commands with subcommands will do subcommand validity checking
|
||||||
// - subcommands will always accept arbitrary arguments
|
// - subcommands will always accept arbitrary arguments
|
||||||
|
@ -32,7 +46,8 @@ func NoArgs(cmd *Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
|
// OnlyValidArgs returns an error if there are any positional args that are not in
|
||||||
|
// the `ValidArgs` field of `Command`
|
||||||
func OnlyValidArgs(cmd *Command, args []string) error {
|
func OnlyValidArgs(cmd *Command, args []string) error {
|
||||||
if len(cmd.ValidArgs) > 0 {
|
if len(cmd.ValidArgs) > 0 {
|
||||||
// Remove any description that may be included in ValidArgs.
|
// Remove any description that may be included in ValidArgs.
|
||||||
|
@ -41,7 +56,6 @@ func OnlyValidArgs(cmd *Command, args []string) error {
|
||||||
for _, v := range cmd.ValidArgs {
|
for _, v := range cmd.ValidArgs {
|
||||||
validArgs = append(validArgs, strings.Split(v, "\t")[0])
|
validArgs = append(validArgs, strings.Split(v, "\t")[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range args {
|
for _, v := range args {
|
||||||
if !stringInSlice(v, validArgs) {
|
if !stringInSlice(v, validArgs) {
|
||||||
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
||||||
|
@ -86,18 +100,6 @@ func ExactArgs(n int) PositionalArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExactValidArgs returns an error if
|
|
||||||
// there are not exactly N positional args OR
|
|
||||||
// there are any positional args that are not in the `ValidArgs` field of `Command`
|
|
||||||
func ExactValidArgs(n int) PositionalArgs {
|
|
||||||
return func(cmd *Command, args []string) error {
|
|
||||||
if err := ExactArgs(n)(cmd, args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return OnlyValidArgs(cmd, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeArgs returns an error if the number of args is not within the expected range.
|
// RangeArgs returns an error if the number of args is not within the expected range.
|
||||||
func RangeArgs(min int, max int) PositionalArgs {
|
func RangeArgs(min int, max int) PositionalArgs {
|
||||||
return func(cmd *Command, args []string) error {
|
return func(cmd *Command, args []string) error {
|
||||||
|
@ -119,3 +121,11 @@ func MatchAll(pargs ...PositionalArgs) PositionalArgs {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExactValidArgs returns an error if there are not exactly N positional args OR
|
||||||
|
// there are any positional args that are not in the `ValidArgs` field of `Command`
|
||||||
|
//
|
||||||
|
// Deprecated: use MatchAll(ExactArgs(n), OnlyValidArgs) instead
|
||||||
|
func ExactValidArgs(n int) PositionalArgs {
|
||||||
|
return MatchAll(ExactArgs(n), OnlyValidArgs)
|
||||||
|
}
|
||||||
|
|
264
args_test.go
264
args_test.go
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -27,7 +41,7 @@ func expectSuccess(output string, err error, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validWithInvalidArgs(err error, t *testing.T) {
|
func validOnlyWithInvalidArgs(err error, t *testing.T) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected an error")
|
t.Fatal("Expected an error")
|
||||||
}
|
}
|
||||||
|
@ -38,12 +52,12 @@ func validWithInvalidArgs(err error, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func noArgsWithArgs(err error, t *testing.T) {
|
func noArgsWithArgs(err error, t *testing.T, arg string) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected an error")
|
t.Fatal("Expected an error")
|
||||||
}
|
}
|
||||||
got := err.Error()
|
got := err.Error()
|
||||||
expected := `unknown command "illegal" for "c"`
|
expected := `unknown command "` + arg + `" for "c"`
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("Expected: %q, got: %q", expected, got)
|
t.Errorf("Expected: %q, got: %q", expected, got)
|
||||||
}
|
}
|
||||||
|
@ -93,102 +107,280 @@ func rangeArgsWithInvalidCount(err error, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoArgs
|
||||||
|
|
||||||
func TestNoArgs(t *testing.T) {
|
func TestNoArgs(t *testing.T) {
|
||||||
c := getCommand(NoArgs, false)
|
c := getCommand(NoArgs, false)
|
||||||
output, err := executeCommand(c)
|
output, err := executeCommand(c)
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoArgsWithArgs(t *testing.T) {
|
func TestNoArgs_WithArgs(t *testing.T) {
|
||||||
c := getCommand(NoArgs, false)
|
c := getCommand(NoArgs, false)
|
||||||
_, err := executeCommand(c, "illegal")
|
_, err := executeCommand(c, "one")
|
||||||
noArgsWithArgs(err, t)
|
noArgsWithArgs(err, t, "one")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoArgs_WithValid_WithArgs(t *testing.T) {
|
||||||
|
c := getCommand(NoArgs, true)
|
||||||
|
_, err := executeCommand(c, "one")
|
||||||
|
noArgsWithArgs(err, t, "one")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(NoArgs, true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
noArgsWithArgs(err, t, "a")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, NoArgs), true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyValidArgs
|
||||||
|
|
||||||
func TestOnlyValidArgs(t *testing.T) {
|
func TestOnlyValidArgs(t *testing.T) {
|
||||||
c := getCommand(OnlyValidArgs, true)
|
c := getCommand(OnlyValidArgs, true)
|
||||||
output, err := executeCommand(c, "one", "two")
|
output, err := executeCommand(c, "one", "two")
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnlyValidArgsWithInvalidArgs(t *testing.T) {
|
func TestOnlyValidArgs_WithInvalidArgs(t *testing.T) {
|
||||||
c := getCommand(OnlyValidArgs, true)
|
c := getCommand(OnlyValidArgs, true)
|
||||||
_, err := executeCommand(c, "a")
|
_, err := executeCommand(c, "a")
|
||||||
validWithInvalidArgs(err, t)
|
validOnlyWithInvalidArgs(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArbitraryArgs
|
||||||
|
|
||||||
func TestArbitraryArgs(t *testing.T) {
|
func TestArbitraryArgs(t *testing.T) {
|
||||||
c := getCommand(ArbitraryArgs, false)
|
c := getCommand(ArbitraryArgs, false)
|
||||||
output, err := executeCommand(c, "a", "b")
|
output, err := executeCommand(c, "a", "b")
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArbitraryArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(ArbitraryArgs, true)
|
||||||
|
output, err := executeCommand(c, "one", "two")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArbitraryArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(ArbitraryArgs, true)
|
||||||
|
output, err := executeCommand(c, "a")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArbitraryArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, ArbitraryArgs), true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimumNArgs
|
||||||
|
|
||||||
func TestMinimumNArgs(t *testing.T) {
|
func TestMinimumNArgs(t *testing.T) {
|
||||||
c := getCommand(MinimumNArgs(2), false)
|
c := getCommand(MinimumNArgs(2), false)
|
||||||
output, err := executeCommand(c, "a", "b", "c")
|
output, err := executeCommand(c, "a", "b", "c")
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMinimumNArgsWithLessArgs(t *testing.T) {
|
func TestMinimumNArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(MinimumNArgs(2), true)
|
||||||
|
output, err := executeCommand(c, "one", "three")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimumNArgs_WithValid__WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MinimumNArgs(2), true)
|
||||||
|
output, err := executeCommand(c, "a", "b")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimumNArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, MinimumNArgs(2)), true)
|
||||||
|
_, err := executeCommand(c, "a", "b")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimumNArgs_WithLessArgs(t *testing.T) {
|
||||||
c := getCommand(MinimumNArgs(2), false)
|
c := getCommand(MinimumNArgs(2), false)
|
||||||
_, err := executeCommand(c, "a")
|
_, err := executeCommand(c, "a")
|
||||||
minimumNArgsWithLessArgs(err, t)
|
minimumNArgsWithLessArgs(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMinimumNArgs_WithLessArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(MinimumNArgs(2), true)
|
||||||
|
_, err := executeCommand(c, "one")
|
||||||
|
minimumNArgsWithLessArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimumNArgs_WithLessArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MinimumNArgs(2), true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
minimumNArgsWithLessArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimumNArgs_WithLessArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, MinimumNArgs(2)), true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaximumNArgs
|
||||||
|
|
||||||
func TestMaximumNArgs(t *testing.T) {
|
func TestMaximumNArgs(t *testing.T) {
|
||||||
c := getCommand(MaximumNArgs(3), false)
|
c := getCommand(MaximumNArgs(3), false)
|
||||||
output, err := executeCommand(c, "a", "b")
|
output, err := executeCommand(c, "a", "b")
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaximumNArgsWithMoreArgs(t *testing.T) {
|
func TestMaximumNArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(MaximumNArgs(2), true)
|
||||||
|
output, err := executeCommand(c, "one", "three")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaximumNArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MaximumNArgs(2), true)
|
||||||
|
output, err := executeCommand(c, "a", "b")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaximumNArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, MaximumNArgs(2)), true)
|
||||||
|
_, err := executeCommand(c, "a", "b")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaximumNArgs_WithMoreArgs(t *testing.T) {
|
||||||
c := getCommand(MaximumNArgs(2), false)
|
c := getCommand(MaximumNArgs(2), false)
|
||||||
_, err := executeCommand(c, "a", "b", "c")
|
_, err := executeCommand(c, "a", "b", "c")
|
||||||
maximumNArgsWithMoreArgs(err, t)
|
maximumNArgsWithMoreArgs(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaximumNArgs_WithMoreArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(MaximumNArgs(2), true)
|
||||||
|
_, err := executeCommand(c, "one", "three", "two")
|
||||||
|
maximumNArgsWithMoreArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaximumNArgs_WithMoreArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MaximumNArgs(2), true)
|
||||||
|
_, err := executeCommand(c, "a", "b", "c")
|
||||||
|
maximumNArgsWithMoreArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaximumNArgs_WithMoreArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, MaximumNArgs(2)), true)
|
||||||
|
_, err := executeCommand(c, "a", "b", "c")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExactArgs
|
||||||
|
|
||||||
func TestExactArgs(t *testing.T) {
|
func TestExactArgs(t *testing.T) {
|
||||||
c := getCommand(ExactArgs(3), false)
|
c := getCommand(ExactArgs(3), false)
|
||||||
output, err := executeCommand(c, "a", "b", "c")
|
output, err := executeCommand(c, "a", "b", "c")
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExactArgsWithInvalidCount(t *testing.T) {
|
func TestExactArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(ExactArgs(3), true)
|
||||||
|
output, err := executeCommand(c, "three", "one", "two")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExactArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(ExactArgs(3), true)
|
||||||
|
output, err := executeCommand(c, "three", "a", "two")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExactArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, ExactArgs(3)), true)
|
||||||
|
_, err := executeCommand(c, "three", "a", "two")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExactArgs_WithInvalidCount(t *testing.T) {
|
||||||
c := getCommand(ExactArgs(2), false)
|
c := getCommand(ExactArgs(2), false)
|
||||||
_, err := executeCommand(c, "a", "b", "c")
|
_, err := executeCommand(c, "a", "b", "c")
|
||||||
exactArgsWithInvalidCount(err, t)
|
exactArgsWithInvalidCount(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExactValidArgs(t *testing.T) {
|
func TestExactArgs_WithInvalidCount_WithValid(t *testing.T) {
|
||||||
c := getCommand(ExactValidArgs(3), true)
|
c := getCommand(ExactArgs(2), true)
|
||||||
output, err := executeCommand(c, "three", "one", "two")
|
|
||||||
expectSuccess(output, err, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExactValidArgsWithInvalidCount(t *testing.T) {
|
|
||||||
c := getCommand(ExactValidArgs(2), false)
|
|
||||||
_, err := executeCommand(c, "three", "one", "two")
|
_, err := executeCommand(c, "three", "one", "two")
|
||||||
exactArgsWithInvalidCount(err, t)
|
exactArgsWithInvalidCount(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExactValidArgsWithInvalidArgs(t *testing.T) {
|
func TestExactArgs_WithInvalidCount_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
c := getCommand(ExactValidArgs(3), true)
|
c := getCommand(ExactArgs(2), true)
|
||||||
_, err := executeCommand(c, "three", "a", "two")
|
_, err := executeCommand(c, "three", "a", "two")
|
||||||
validWithInvalidArgs(err, t)
|
exactArgsWithInvalidCount(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExactArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, ExactArgs(2)), true)
|
||||||
|
_, err := executeCommand(c, "three", "a", "two")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeArgs
|
||||||
|
|
||||||
func TestRangeArgs(t *testing.T) {
|
func TestRangeArgs(t *testing.T) {
|
||||||
c := getCommand(RangeArgs(2, 4), false)
|
c := getCommand(RangeArgs(2, 4), false)
|
||||||
output, err := executeCommand(c, "a", "b", "c")
|
output, err := executeCommand(c, "a", "b", "c")
|
||||||
expectSuccess(output, err, t)
|
expectSuccess(output, err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRangeArgsWithInvalidCount(t *testing.T) {
|
func TestRangeArgs_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(RangeArgs(2, 4), true)
|
||||||
|
output, err := executeCommand(c, "three", "one", "two")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeArgs_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(RangeArgs(2, 4), true)
|
||||||
|
output, err := executeCommand(c, "three", "a", "two")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeArgs_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, RangeArgs(2, 4)), true)
|
||||||
|
_, err := executeCommand(c, "three", "a", "two")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeArgs_WithInvalidCount(t *testing.T) {
|
||||||
c := getCommand(RangeArgs(2, 4), false)
|
c := getCommand(RangeArgs(2, 4), false)
|
||||||
_, err := executeCommand(c, "a")
|
_, err := executeCommand(c, "a")
|
||||||
rangeArgsWithInvalidCount(err, t)
|
rangeArgsWithInvalidCount(err, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRangeArgs_WithInvalidCount_WithValid(t *testing.T) {
|
||||||
|
c := getCommand(RangeArgs(2, 4), true)
|
||||||
|
_, err := executeCommand(c, "two")
|
||||||
|
rangeArgsWithInvalidCount(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeArgs_WithInvalidCount_WithValid_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(RangeArgs(2, 4), true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
rangeArgsWithInvalidCount(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(MatchAll(OnlyValidArgs, RangeArgs(2, 4)), true)
|
||||||
|
_, err := executeCommand(c, "a")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes(No)Args
|
||||||
|
|
||||||
func TestRootTakesNoArgs(t *testing.T) {
|
func TestRootTakesNoArgs(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Run: emptyRun}
|
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||||
childCmd := &Command{Use: "child", Run: emptyRun}
|
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||||
|
@ -293,6 +485,32 @@ func TestMatchAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
|
|
||||||
|
func TestExactValidArgs(t *testing.T) {
|
||||||
|
c := getCommand(ExactValidArgs(3), true)
|
||||||
|
output, err := executeCommand(c, "three", "one", "two")
|
||||||
|
expectSuccess(output, err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExactValidArgs_WithInvalidCount(t *testing.T) {
|
||||||
|
c := getCommand(ExactValidArgs(2), false)
|
||||||
|
_, err := executeCommand(c, "three", "one", "two")
|
||||||
|
exactArgsWithInvalidCount(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExactValidArgs_WithInvalidCount_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(ExactValidArgs(2), true)
|
||||||
|
_, err := executeCommand(c, "three", "a", "two")
|
||||||
|
exactArgsWithInvalidCount(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExactValidArgs_WithInvalidArgs(t *testing.T) {
|
||||||
|
c := getCommand(ExactValidArgs(2), true)
|
||||||
|
_, err := executeCommand(c, "three", "a")
|
||||||
|
validOnlyWithInvalidArgs(err, t)
|
||||||
|
}
|
||||||
|
|
||||||
// This test make sure we keep backwards-compatibility with respect
|
// This test make sure we keep backwards-compatibility with respect
|
||||||
// to the legacyArgs() function.
|
// to the legacyArgs() function.
|
||||||
// It makes sure the root command accepts arguments if it does not have
|
// It makes sure the root command accepts arguments if it does not have
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -73,7 +87,8 @@ __%[1]s_handle_go_custom_completion()
|
||||||
# Prepare the command to request completions for the program.
|
# Prepare the command to request completions for the program.
|
||||||
# Calling ${words[0]} instead of directly %[1]s allows to handle aliases
|
# Calling ${words[0]} instead of directly %[1]s allows to handle aliases
|
||||||
args=("${words[@]:1}")
|
args=("${words[@]:1}")
|
||||||
requestComp="${words[0]} %[2]s ${args[*]}"
|
# Disable ActiveHelp which is not supported for bash completion v1
|
||||||
|
requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}"
|
||||||
|
|
||||||
lastParam=${words[$((${#words[@]}-1))]}
|
lastParam=${words[$((${#words[@]}-1))]}
|
||||||
lastChar=${lastParam:$((${#lastParam}-1)):1}
|
lastChar=${lastParam:$((${#lastParam}-1)):1}
|
||||||
|
@ -383,11 +398,11 @@ __%[1]s_handle_word()
|
||||||
|
|
||||||
`, name, ShellCompNoDescRequestCmd,
|
`, name, ShellCompNoDescRequestCmd,
|
||||||
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePostscript(buf io.StringWriter, name string) {
|
func writePostscript(buf io.StringWriter, name string) {
|
||||||
name = strings.Replace(name, ":", "__", -1)
|
name = strings.ReplaceAll(name, ":", "__")
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
|
WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf(`{
|
WriteStringAndCheck(buf, fmt.Sprintf(`{
|
||||||
local cur prev words cword split
|
local cur prev words cword split
|
||||||
|
@ -517,7 +532,7 @@ func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup annotations for go completions for registered flags
|
// prepareCustomAnnotationsForFlags setup annotations for go completions for registered flags
|
||||||
func prepareCustomAnnotationsForFlags(cmd *Command) {
|
func prepareCustomAnnotationsForFlags(cmd *Command) {
|
||||||
flagCompletionMutex.RLock()
|
flagCompletionMutex.RLock()
|
||||||
defer flagCompletionMutex.RUnlock()
|
defer flagCompletionMutex.RUnlock()
|
||||||
|
@ -645,8 +660,8 @@ func gen(buf io.StringWriter, cmd *Command) {
|
||||||
gen(buf, c)
|
gen(buf, c)
|
||||||
}
|
}
|
||||||
commandName := cmd.CommandPath()
|
commandName := cmd.CommandPath()
|
||||||
commandName = strings.Replace(commandName, " ", "_", -1)
|
commandName = strings.ReplaceAll(commandName, " ", "_")
|
||||||
commandName = strings.Replace(commandName, ":", "__", -1)
|
commandName = strings.ReplaceAll(commandName, ":", "__")
|
||||||
|
|
||||||
if cmd.Root() == cmd {
|
if cmd.Root() == cmd {
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName))
|
WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName))
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -24,7 +38,7 @@ func genBashComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
|
|
||||||
__%[1]s_debug()
|
__%[1]s_debug()
|
||||||
{
|
{
|
||||||
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
|
if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then
|
||||||
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -51,7 +65,7 @@ __%[1]s_get_completion_results() {
|
||||||
lastChar=${lastParam:$((${#lastParam}-1)):1}
|
lastChar=${lastParam:$((${#lastParam}-1)):1}
|
||||||
__%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"
|
__%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"
|
||||||
|
|
||||||
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
|
if [[ -z ${cur} && ${lastChar} != = ]]; then
|
||||||
# If the last parameter is complete (there is a space following it)
|
# 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.
|
# We add an extra empty parameter so we can indicate this to the go method.
|
||||||
__%[1]s_debug "Adding extra empty parameter"
|
__%[1]s_debug "Adding extra empty parameter"
|
||||||
|
@ -61,7 +75,7 @@ __%[1]s_get_completion_results() {
|
||||||
# When completing a flag with an = (e.g., %[1]s -n=<TAB>)
|
# When completing a flag with an = (e.g., %[1]s -n=<TAB>)
|
||||||
# bash focuses on the part after the =, so we need to remove
|
# bash focuses on the part after the =, so we need to remove
|
||||||
# the flag part from $cur
|
# the flag part from $cur
|
||||||
if [[ "${cur}" == -*=* ]]; then
|
if [[ ${cur} == -*=* ]]; then
|
||||||
cur="${cur#*=}"
|
cur="${cur#*=}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -73,7 +87,7 @@ __%[1]s_get_completion_results() {
|
||||||
directive=${out##*:}
|
directive=${out##*:}
|
||||||
# Remove the directive
|
# Remove the directive
|
||||||
out=${out%%:*}
|
out=${out%%:*}
|
||||||
if [ "${directive}" = "${out}" ]; then
|
if [[ ${directive} == "${out}" ]]; then
|
||||||
# There is not directive specified
|
# There is not directive specified
|
||||||
directive=0
|
directive=0
|
||||||
fi
|
fi
|
||||||
|
@ -87,22 +101,36 @@ __%[1]s_process_completion_results() {
|
||||||
local shellCompDirectiveNoFileComp=%[5]d
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
local shellCompDirectiveFilterFileExt=%[6]d
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
local shellCompDirectiveFilterDirs=%[7]d
|
local shellCompDirectiveFilterDirs=%[7]d
|
||||||
|
local shellCompDirectiveKeepOrder=%[8]d
|
||||||
|
|
||||||
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
|
if (((directive & shellCompDirectiveError) != 0)); then
|
||||||
# Error code. No completion.
|
# Error code. No completion.
|
||||||
__%[1]s_debug "Received error from custom completion go code"
|
__%[1]s_debug "Received error from custom completion go code"
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
|
if (((directive & shellCompDirectiveNoSpace) != 0)); then
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
__%[1]s_debug "Activating no space"
|
__%[1]s_debug "Activating no space"
|
||||||
compopt -o nospace
|
compopt -o nospace
|
||||||
else
|
else
|
||||||
__%[1]s_debug "No space directive not supported in this version of bash"
|
__%[1]s_debug "No space directive not supported in this version of bash"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
|
if (((directive & shellCompDirectiveKeepOrder) != 0)); then
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
|
# no sort isn't supported for bash less than < 4.4
|
||||||
|
if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then
|
||||||
|
__%[1]s_debug "No sort directive not supported in this version of bash"
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Activating keep order"
|
||||||
|
compopt -o nosort
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
__%[1]s_debug "No sort directive not supported in this version of bash"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if (((directive & shellCompDirectiveNoFileComp) != 0)); then
|
||||||
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
__%[1]s_debug "Activating no file completion"
|
__%[1]s_debug "Activating no file completion"
|
||||||
compopt +o default
|
compopt +o default
|
||||||
else
|
else
|
||||||
|
@ -111,26 +139,30 @@ __%[1]s_process_completion_results() {
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
|
# Separate activeHelp from normal completions
|
||||||
|
local completions=()
|
||||||
|
local activeHelp=()
|
||||||
|
__%[1]s_extract_activeHelp
|
||||||
|
|
||||||
|
if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
|
||||||
# File extension filtering
|
# File extension filtering
|
||||||
local fullFilter filter filteringCmd
|
local fullFilter filter filteringCmd
|
||||||
|
|
||||||
# Do not use quotes around the $out variable or else newline
|
# Do not use quotes around the $completions variable or else newline
|
||||||
# characters will be kept.
|
# characters will be kept.
|
||||||
for filter in ${out}; do
|
for filter in ${completions[*]}; do
|
||||||
fullFilter+="$filter|"
|
fullFilter+="$filter|"
|
||||||
done
|
done
|
||||||
|
|
||||||
filteringCmd="_filedir $fullFilter"
|
filteringCmd="_filedir $fullFilter"
|
||||||
__%[1]s_debug "File filtering command: $filteringCmd"
|
__%[1]s_debug "File filtering command: $filteringCmd"
|
||||||
$filteringCmd
|
$filteringCmd
|
||||||
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
|
elif (((directive & shellCompDirectiveFilterDirs) != 0)); then
|
||||||
# File completion for directories only
|
# File completion for directories only
|
||||||
|
|
||||||
# Use printf to strip any trailing newline
|
|
||||||
local subdir
|
local subdir
|
||||||
subdir=$(printf "%%s" "${out}")
|
subdir=${completions[0]}
|
||||||
if [ -n "$subdir" ]; then
|
if [[ -n $subdir ]]; then
|
||||||
__%[1]s_debug "Listing directories in $subdir"
|
__%[1]s_debug "Listing directories in $subdir"
|
||||||
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
|
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
|
||||||
else
|
else
|
||||||
|
@ -143,6 +175,43 @@ __%[1]s_process_completion_results() {
|
||||||
|
|
||||||
__%[1]s_handle_special_char "$cur" :
|
__%[1]s_handle_special_char "$cur" :
|
||||||
__%[1]s_handle_special_char "$cur" =
|
__%[1]s_handle_special_char "$cur" =
|
||||||
|
|
||||||
|
# Print the activeHelp statements before we finish
|
||||||
|
if ((${#activeHelp[*]} != 0)); then
|
||||||
|
printf "\n";
|
||||||
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
# The prompt format is only available from bash 4.4.
|
||||||
|
# We test if it is available before using it.
|
||||||
|
if (x=${PS1@P}) 2> /dev/null; then
|
||||||
|
printf "%%s" "${PS1@P}${COMP_LINE[@]}"
|
||||||
|
else
|
||||||
|
# Can't print the prompt. Just print the
|
||||||
|
# text the user had typed, it is workable enough.
|
||||||
|
printf "%%s" "${COMP_LINE[@]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Separate activeHelp lines from real completions.
|
||||||
|
# Fills the $activeHelp and $completions arrays.
|
||||||
|
__%[1]s_extract_activeHelp() {
|
||||||
|
local activeHelpMarker="%[9]s"
|
||||||
|
local endIndex=${#activeHelpMarker}
|
||||||
|
|
||||||
|
while IFS='' read -r comp; do
|
||||||
|
if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
|
||||||
|
comp=${comp:endIndex}
|
||||||
|
__%[1]s_debug "ActiveHelp found: $comp"
|
||||||
|
if [[ -n $comp ]]; then
|
||||||
|
activeHelp+=("$comp")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Not an activeHelp line but a normal completion
|
||||||
|
completions+=("$comp")
|
||||||
|
fi
|
||||||
|
done <<<"${out}"
|
||||||
}
|
}
|
||||||
|
|
||||||
__%[1]s_handle_completion_types() {
|
__%[1]s_handle_completion_types() {
|
||||||
|
@ -163,7 +232,7 @@ __%[1]s_handle_completion_types() {
|
||||||
if [[ $comp == "$cur"* ]]; then
|
if [[ $comp == "$cur"* ]]; then
|
||||||
COMPREPLY+=("$comp")
|
COMPREPLY+=("$comp")
|
||||||
fi
|
fi
|
||||||
done < <(printf "%%s\n" "${out}")
|
done < <(printf "%%s\n" "${completions[@]}")
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
|
@ -177,10 +246,8 @@ __%[1]s_handle_standard_completion_case() {
|
||||||
local tab=$'\t' comp
|
local tab=$'\t' comp
|
||||||
|
|
||||||
# Short circuit to optimize if we don't have descriptions
|
# Short circuit to optimize if we don't have descriptions
|
||||||
if [[ $out != *$tab* ]]; then
|
if [[ "${completions[*]}" != *$tab* ]]; then
|
||||||
while IFS='' read -r comp; do
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
|
||||||
COMPREPLY+=("$comp")
|
|
||||||
done < <(IFS=$'\n' compgen -W "$out" -- "$cur")
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -188,6 +255,7 @@ __%[1]s_handle_standard_completion_case() {
|
||||||
local compline
|
local compline
|
||||||
# Look for the longest completion so that we can format things nicely
|
# Look for the longest completion so that we can format things nicely
|
||||||
while IFS='' read -r compline; do
|
while IFS='' read -r compline; do
|
||||||
|
[[ -z $compline ]] && continue
|
||||||
# Strip any description before checking the length
|
# Strip any description before checking the length
|
||||||
comp=${compline%%%%$tab*}
|
comp=${compline%%%%$tab*}
|
||||||
# Only consider the completions that match
|
# Only consider the completions that match
|
||||||
|
@ -196,10 +264,10 @@ __%[1]s_handle_standard_completion_case() {
|
||||||
if ((${#comp}>longest)); then
|
if ((${#comp}>longest)); then
|
||||||
longest=${#comp}
|
longest=${#comp}
|
||||||
fi
|
fi
|
||||||
done < <(printf "%%s\n" "${out}")
|
done < <(printf "%%s\n" "${completions[@]}")
|
||||||
|
|
||||||
# If there is a single completion left, remove the description text
|
# If there is a single completion left, remove the description text
|
||||||
if [ ${#COMPREPLY[*]} -eq 1 ]; then
|
if ((${#COMPREPLY[*]} == 1)); then
|
||||||
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
|
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
|
||||||
comp="${COMPREPLY[0]%%%%$tab*}"
|
comp="${COMPREPLY[0]%%%%$tab*}"
|
||||||
__%[1]s_debug "Removed description from single completion, which is now: ${comp}"
|
__%[1]s_debug "Removed description from single completion, which is now: ${comp}"
|
||||||
|
@ -216,8 +284,8 @@ __%[1]s_handle_special_char()
|
||||||
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
|
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
|
||||||
local word=${comp%%"${comp##*${char}}"}
|
local word=${comp%%"${comp##*${char}}"}
|
||||||
local idx=${#COMPREPLY[*]}
|
local idx=${#COMPREPLY[*]}
|
||||||
while [[ $((--idx)) -ge 0 ]]; do
|
while ((--idx >= 0)); do
|
||||||
COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
|
COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -243,7 +311,7 @@ __%[1]s_format_comp_descriptions()
|
||||||
|
|
||||||
# Make sure we can fit a description of at least 8 characters
|
# Make sure we can fit a description of at least 8 characters
|
||||||
# if we are to align the descriptions.
|
# if we are to align the descriptions.
|
||||||
if [[ $maxdesclength -gt 8 ]]; then
|
if ((maxdesclength > 8)); then
|
||||||
# Add the proper number of spaces to align the descriptions
|
# Add the proper number of spaces to align the descriptions
|
||||||
for ((i = ${#comp} ; i < longest ; i++)); do
|
for ((i = ${#comp} ; i < longest ; i++)); do
|
||||||
comp+=" "
|
comp+=" "
|
||||||
|
@ -255,8 +323,8 @@ __%[1]s_format_comp_descriptions()
|
||||||
|
|
||||||
# If there is enough space for any description text,
|
# If there is enough space for any description text,
|
||||||
# truncate the descriptions that are too long for the shell width
|
# truncate the descriptions that are too long for the shell width
|
||||||
if [ $maxdesclength -gt 0 ]; then
|
if ((maxdesclength > 0)); then
|
||||||
if [ ${#desc} -gt $maxdesclength ]; then
|
if ((${#desc} > maxdesclength)); then
|
||||||
desc=${desc:0:$(( maxdesclength - 1 ))}
|
desc=${desc:0:$(( maxdesclength - 1 ))}
|
||||||
desc+="…"
|
desc+="…"
|
||||||
fi
|
fi
|
||||||
|
@ -277,9 +345,9 @@ __start_%[1]s()
|
||||||
# Call _init_completion from the bash-completion package
|
# Call _init_completion from the bash-completion package
|
||||||
# to prepare the arguments properly
|
# to prepare the arguments properly
|
||||||
if declare -F _init_completion >/dev/null 2>&1; then
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
_init_completion -n "=:" || return
|
_init_completion -n =: || return
|
||||||
else
|
else
|
||||||
__%[1]s_init_completion -n "=:" || return
|
__%[1]s_init_completion -n =: || return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
__%[1]s_debug
|
__%[1]s_debug
|
||||||
|
@ -306,7 +374,8 @@ fi
|
||||||
# ex: ts=4 sw=4 et filetype=sh
|
# ex: ts=4 sw=4 et filetype=sh
|
||||||
`, name, compCmd,
|
`, name, compCmd,
|
||||||
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
|
||||||
|
activeHelpMarker))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenBashCompletionFileV2 generates Bash completion version 2.
|
// GenBashCompletionFileV2 generates Bash completion version 2.
|
||||||
|
|
33
bash_completionsV2_test.go
Normal file
33
bash_completionsV2_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBashCompletionV2WithActiveHelp(t *testing.T) {
|
||||||
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assertNoErr(t, c.GenBashCompletionV2(buf, true))
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
// check that active help is not being disabled
|
||||||
|
activeHelpVar := activeHelpEnvVar(c.Name())
|
||||||
|
checkOmit(t, output, fmt.Sprintf("%s=0", activeHelpVar))
|
||||||
|
}
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -261,3 +275,15 @@ func TestBashCompletionTraverseChildren(t *testing.T) {
|
||||||
checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`)
|
checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`)
|
||||||
checkOmit(t, output, `local_nonpersistent_flags+=("-b")`)
|
checkOmit(t, output, `local_nonpersistent_flags+=("-b")`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBashCompletionNoActiveHelp(t *testing.T) {
|
||||||
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assertNoErr(t, c.GenBashCompletion(buf))
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
// check that active help is being disabled
|
||||||
|
activeHelpVar := activeHelpEnvVar(c.Name())
|
||||||
|
check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
|
||||||
|
}
|
||||||
|
|
29
cobra.go
29
cobra.go
|
@ -1,9 +1,10 @@
|
||||||
// Copyright © 2013 Steve Francia <spf@spf13.com>.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -39,17 +40,27 @@ var templateFuncs = template.FuncMap{
|
||||||
}
|
}
|
||||||
|
|
||||||
var initializers []func()
|
var initializers []func()
|
||||||
|
var finalizers []func()
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPrefixMatching = false
|
||||||
|
defaultCommandSorting = true
|
||||||
|
defaultCaseInsensitive = false
|
||||||
|
)
|
||||||
|
|
||||||
var initializersE []func() error
|
var initializersE []func() error
|
||||||
|
|
||||||
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
|
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
|
||||||
// to automatically enable in CLI tools.
|
// to automatically enable in CLI tools.
|
||||||
// Set this to true to enable it.
|
// Set this to true to enable it.
|
||||||
var EnablePrefixMatching = false
|
var EnablePrefixMatching = defaultPrefixMatching
|
||||||
|
|
||||||
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
||||||
// To disable sorting, set it to false.
|
// To disable sorting, set it to false.
|
||||||
var EnableCommandSorting = true
|
var EnableCommandSorting = defaultCommandSorting
|
||||||
|
|
||||||
|
// EnableCaseInsensitive allows case-insensitive commands names. (case sensitive by default)
|
||||||
|
var EnableCaseInsensitive = defaultCaseInsensitive
|
||||||
|
|
||||||
// MousetrapHelpText enables an information splash screen on Windows
|
// MousetrapHelpText enables an information splash screen on Windows
|
||||||
// if the CLI is started from explorer.exe.
|
// if the CLI is started from explorer.exe.
|
||||||
|
@ -94,6 +105,12 @@ func OnInitializeE(y ...func() error) {
|
||||||
initializersE = append(initializersE, y...)
|
initializersE = append(initializersE, y...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnFinalize sets the passed functions to be run when each command's
|
||||||
|
// Execute method is terminated.
|
||||||
|
func OnFinalize(y ...func()) {
|
||||||
|
finalizers = append(finalizers, y...)
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
||||||
|
|
||||||
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
|
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
|
||||||
|
@ -160,8 +177,8 @@ func appendIfNotPresent(s, stringToAppend string) string {
|
||||||
|
|
||||||
// rpad adds padding to the right of a string.
|
// rpad adds padding to the right of a string.
|
||||||
func rpad(s string, padding int) string {
|
func rpad(s string, padding int) string {
|
||||||
template := fmt.Sprintf("%%-%ds", padding)
|
formattedString := fmt.Sprintf("%%-%ds", padding)
|
||||||
return fmt.Sprintf(template, s)
|
return fmt.Sprintf(formattedString, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmpl executes the given template text on data, writing the result to w.
|
// tmpl executes the given template text on data, writing the result to w.
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
193
command.go
193
command.go
|
@ -1,9 +1,10 @@
|
||||||
// Copyright © 2013 Steve Francia <spf@spf13.com>.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -18,6 +19,7 @@ package cobra
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -28,16 +30,24 @@ import (
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra"
|
||||||
|
|
||||||
// FParseErrWhitelist configures Flag parse errors to be ignored
|
// FParseErrWhitelist configures Flag parse errors to be ignored
|
||||||
type FParseErrWhitelist flag.ParseErrorsWhitelist
|
type FParseErrWhitelist flag.ParseErrorsWhitelist
|
||||||
|
|
||||||
|
// Group Structure to manage groups for commands
|
||||||
|
type Group struct {
|
||||||
|
ID string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
// Command is just that, a command for your application.
|
// Command is just that, a command for your application.
|
||||||
// E.g. 'go run ...' - 'run' is the command. Cobra requires
|
// E.g. 'go run ...' - 'run' is the command. Cobra requires
|
||||||
// you to define the usage and description as part of your command
|
// you to define the usage and description as part of your command
|
||||||
// definition to ensure usability.
|
// definition to ensure usability.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
// Use is the one-line usage message.
|
// Use is the one-line usage message.
|
||||||
// Recommended syntax is as follow:
|
// Recommended syntax is as follows:
|
||||||
// [ ] identifies an optional argument. Arguments that are not enclosed in brackets are required.
|
// [ ] identifies an optional argument. Arguments that are not enclosed in brackets are required.
|
||||||
// ... indicates that you can specify multiple values for the previous argument.
|
// ... indicates that you can specify multiple values for the previous argument.
|
||||||
// | indicates mutually exclusive information. You can use the argument to the left of the separator or the
|
// | indicates mutually exclusive information. You can use the argument to the left of the separator or the
|
||||||
|
@ -57,6 +67,9 @@ type Command struct {
|
||||||
// Short is the short description shown in the 'help' output.
|
// Short is the short description shown in the 'help' output.
|
||||||
Short string
|
Short string
|
||||||
|
|
||||||
|
// The group id under which this subcommand is grouped in the 'help' output of its parent.
|
||||||
|
GroupID string
|
||||||
|
|
||||||
// Long is the long message shown in the 'help <this-command>' output.
|
// Long is the long message shown in the 'help <this-command>' output.
|
||||||
Long string
|
Long string
|
||||||
|
|
||||||
|
@ -124,6 +137,9 @@ type Command struct {
|
||||||
// PersistentPostRunE: PersistentPostRun but returns an error.
|
// PersistentPostRunE: PersistentPostRun but returns an error.
|
||||||
PersistentPostRunE func(cmd *Command, args []string) error
|
PersistentPostRunE func(cmd *Command, args []string) error
|
||||||
|
|
||||||
|
// groups for subcommands
|
||||||
|
commandgroups []*Group
|
||||||
|
|
||||||
// args is actual args parsed from flags.
|
// args is actual args parsed from flags.
|
||||||
args []string
|
args []string
|
||||||
// flagErrorBuf contains all error messages from pflag.
|
// flagErrorBuf contains all error messages from pflag.
|
||||||
|
@ -156,6 +172,12 @@ type Command struct {
|
||||||
// helpCommand is command with usage 'help'. If it's not defined by user,
|
// helpCommand is command with usage 'help'. If it's not defined by user,
|
||||||
// cobra uses default help command.
|
// cobra uses default help command.
|
||||||
helpCommand *Command
|
helpCommand *Command
|
||||||
|
// helpCommandGroupID is the group id for the helpCommand
|
||||||
|
helpCommandGroupID string
|
||||||
|
|
||||||
|
// completionCommandGroupID is the group id for the completion command
|
||||||
|
completionCommandGroupID string
|
||||||
|
|
||||||
// versionTemplate is the version template defined by user.
|
// versionTemplate is the version template defined by user.
|
||||||
versionTemplate string
|
versionTemplate string
|
||||||
|
|
||||||
|
@ -235,8 +257,8 @@ func (c *Command) Context() context.Context {
|
||||||
return c.ctx
|
return c.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContext sets context for the command. It is set to context.Background by default and will be overwritten by
|
// SetContext sets context for the command. This context will be overwritten by
|
||||||
// Command.ExecuteContext or Command.ExecuteContextC
|
// Command.ExecuteContext or Command.ExecuteContextC.
|
||||||
func (c *Command) SetContext(ctx context.Context) {
|
func (c *Command) SetContext(ctx context.Context) {
|
||||||
c.ctx = ctx
|
c.ctx = ctx
|
||||||
}
|
}
|
||||||
|
@ -299,6 +321,21 @@ func (c *Command) SetHelpCommand(cmd *Command) {
|
||||||
c.helpCommand = cmd
|
c.helpCommand = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHelpCommandGroupID sets the group id of the help command.
|
||||||
|
func (c *Command) SetHelpCommandGroupID(groupID string) {
|
||||||
|
if c.helpCommand != nil {
|
||||||
|
c.helpCommand.GroupID = groupID
|
||||||
|
}
|
||||||
|
// helpCommandGroupID is used if no helpCommand is defined by the user
|
||||||
|
c.helpCommandGroupID = groupID
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCompletionCommandGroupID sets the group id of the completion command.
|
||||||
|
func (c *Command) SetCompletionCommandGroupID(groupID string) {
|
||||||
|
// completionCommandGroupID is used if no completion command is defined by the user
|
||||||
|
c.Root().completionCommandGroupID = groupID
|
||||||
|
}
|
||||||
|
|
||||||
// SetHelpTemplate sets help template to be used. Application can use it to set custom template.
|
// SetHelpTemplate sets help template to be used. Application can use it to set custom template.
|
||||||
func (c *Command) SetHelpTemplate(s string) {
|
func (c *Command) SetHelpTemplate(s string) {
|
||||||
c.helpTemplate = s
|
c.helpTemplate = s
|
||||||
|
@ -507,10 +544,16 @@ Aliases:
|
||||||
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
|
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}}
|
||||||
|
|
||||||
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
||||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}}
|
||||||
|
|
||||||
|
{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}}
|
||||||
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}}
|
||||||
|
|
||||||
|
Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}}
|
||||||
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
||||||
|
@ -612,20 +655,44 @@ Loop:
|
||||||
|
|
||||||
// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like
|
// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like
|
||||||
// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]).
|
// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]).
|
||||||
func argsMinusFirstX(args []string, x string) []string {
|
// Special care needs to be taken not to remove a flag value.
|
||||||
for i, y := range args {
|
func (c *Command) argsMinusFirstX(args []string, x string) []string {
|
||||||
if x == y {
|
if len(args) == 0 {
|
||||||
ret := []string{}
|
return args
|
||||||
ret = append(ret, args[:i]...)
|
}
|
||||||
ret = append(ret, args[i+1:]...)
|
c.mergePersistentFlags()
|
||||||
return ret
|
flags := c.Flags()
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for pos := 0; pos < len(args); pos++ {
|
||||||
|
s := args[pos]
|
||||||
|
switch {
|
||||||
|
case s == "--":
|
||||||
|
// -- means we have reached the end of the parseable args. Break out of the loop now.
|
||||||
|
break Loop
|
||||||
|
case strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags):
|
||||||
|
fallthrough
|
||||||
|
case strings.HasPrefix(s, "-") && !strings.Contains(s, "=") && len(s) == 2 && !shortHasNoOptDefVal(s[1:], flags):
|
||||||
|
// This is a flag without a default value, and an equal sign is not used. Increment pos in order to skip
|
||||||
|
// over the next arg, because that is the value of this flag.
|
||||||
|
pos++
|
||||||
|
continue
|
||||||
|
case !strings.HasPrefix(s, "-"):
|
||||||
|
// This is not a flag or a flag value. Check to see if it matches what we're looking for, and if so,
|
||||||
|
// return the args, excluding the one at this position.
|
||||||
|
if s == x {
|
||||||
|
ret := []string{}
|
||||||
|
ret = append(ret, args[:pos]...)
|
||||||
|
ret = append(ret, args[pos+1:]...)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func isFlagArg(arg string) bool {
|
func isFlagArg(arg string) bool {
|
||||||
return ((len(arg) >= 3 && arg[1] == '-') ||
|
return ((len(arg) >= 3 && arg[0:2] == "--") ||
|
||||||
(len(arg) >= 2 && arg[0] == '-' && arg[1] != '-'))
|
(len(arg) >= 2 && arg[0] == '-' && arg[1] != '-'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,7 +710,7 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
|
||||||
|
|
||||||
cmd := c.findNext(nextSubCmd)
|
cmd := c.findNext(nextSubCmd)
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
|
return innerfind(cmd, c.argsMinusFirstX(innerArgs, nextSubCmd))
|
||||||
}
|
}
|
||||||
return c, innerArgs
|
return c, innerArgs
|
||||||
}
|
}
|
||||||
|
@ -675,7 +742,7 @@ func (c *Command) findSuggestions(arg string) string {
|
||||||
func (c *Command) findNext(next string) *Command {
|
func (c *Command) findNext(next string) *Command {
|
||||||
matches := make([]*Command, 0)
|
matches := make([]*Command, 0)
|
||||||
for _, cmd := range c.commands {
|
for _, cmd := range c.commands {
|
||||||
if cmd.Name() == next || cmd.HasAlias(next) {
|
if commandNameMatches(cmd.Name(), next) || cmd.HasAlias(next) {
|
||||||
cmd.commandCalledAs.name = next
|
cmd.commandCalledAs.name = next
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -838,6 +905,8 @@ func (c *Command) execute(a []string) (err error) {
|
||||||
c.preRun()
|
c.preRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer c.postRun()
|
||||||
|
|
||||||
argWoFlags := c.Flags().Args()
|
argWoFlags := c.Flags().Args()
|
||||||
if c.DisableFlagParsing {
|
if c.DisableFlagParsing {
|
||||||
argWoFlags = a
|
argWoFlags = a
|
||||||
|
@ -866,10 +935,10 @@ func (c *Command) execute(a []string) (err error) {
|
||||||
c.PreRun(c, argWoFlags)
|
c.PreRun(c, argWoFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.validateRequiredFlags(); err != nil {
|
if err := c.ValidateRequiredFlags(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.validateFlagGroups(); err != nil {
|
if err := c.ValidateFlagGroups(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,6 +988,12 @@ func (c *Command) preRunE() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) postRun() {
|
||||||
|
for _, x := range finalizers {
|
||||||
|
x()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecuteContext is the same as Execute(), but sets the ctx on the command.
|
// ExecuteContext is the same as Execute(), but sets the ctx on the command.
|
||||||
// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs
|
// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs
|
||||||
// functions.
|
// functions.
|
||||||
|
@ -962,7 +1037,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
// initialize help at the last point to allow for user overriding
|
// initialize help at the last point to allow for user overriding
|
||||||
c.InitDefaultHelpCmd()
|
c.InitDefaultHelpCmd()
|
||||||
// initialize completion at the last point to allow for user overriding
|
// initialize completion at the last point to allow for user overriding
|
||||||
c.initDefaultCompletionCmd()
|
c.InitDefaultCompletionCmd()
|
||||||
|
|
||||||
|
// Now that all commands have been created, let's make sure all groups
|
||||||
|
// are properly created also
|
||||||
|
c.checkCommandGroups()
|
||||||
|
|
||||||
args := c.args
|
args := c.args
|
||||||
|
|
||||||
|
@ -1007,7 +1086,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Always show help if requested, even if SilenceErrors is in
|
// Always show help if requested, even if SilenceErrors is in
|
||||||
// effect
|
// effect
|
||||||
if err == flag.ErrHelp {
|
if errors.Is(err, flag.ErrHelp) {
|
||||||
cmd.HelpFunc()(cmd, args)
|
cmd.HelpFunc()(cmd, args)
|
||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
@ -1029,12 +1108,13 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
|
|
||||||
func (c *Command) ValidateArgs(args []string) error {
|
func (c *Command) ValidateArgs(args []string) error {
|
||||||
if c.Args == nil {
|
if c.Args == nil {
|
||||||
return nil
|
return ArbitraryArgs(c, args)
|
||||||
}
|
}
|
||||||
return c.Args(c, args)
|
return c.Args(c, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) validateRequiredFlags() error {
|
// ValidateRequiredFlags validates all required flags are present and returns an error otherwise
|
||||||
|
func (c *Command) ValidateRequiredFlags() error {
|
||||||
if c.DisableFlagParsing {
|
if c.DisableFlagParsing {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1057,6 +1137,19 @@ func (c *Command) validateRequiredFlags() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkCommandGroups checks if a command has been added to a group that does not exists.
|
||||||
|
// If so, we panic because it indicates a coding error that should be corrected.
|
||||||
|
func (c *Command) checkCommandGroups() {
|
||||||
|
for _, sub := range c.commands {
|
||||||
|
// if Group is not defined let the developer know right away
|
||||||
|
if sub.GroupID != "" && !c.ContainsGroup(sub.GroupID) {
|
||||||
|
panic(fmt.Sprintf("group id '%s' is not defined for subcommand '%s'", sub.GroupID, sub.CommandPath()))
|
||||||
|
}
|
||||||
|
|
||||||
|
sub.checkCommandGroups()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// InitDefaultHelpFlag adds default help flag to c.
|
// InitDefaultHelpFlag adds default help flag to c.
|
||||||
// It is called automatically by executing the c or by calling help and usage.
|
// It is called automatically by executing the c or by calling help and usage.
|
||||||
// If c already has help flag, it will do nothing.
|
// If c already has help flag, it will do nothing.
|
||||||
|
@ -1070,6 +1163,7 @@ func (c *Command) InitDefaultHelpFlag() {
|
||||||
usage += c.Name()
|
usage += c.Name()
|
||||||
}
|
}
|
||||||
c.Flags().BoolP("help", "h", false, usage)
|
c.Flags().BoolP("help", "h", false, usage)
|
||||||
|
_ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1095,6 +1189,7 @@ func (c *Command) InitDefaultVersionFlag() {
|
||||||
} else {
|
} else {
|
||||||
c.Flags().Bool("version", false, usage)
|
c.Flags().Bool("version", false, usage)
|
||||||
}
|
}
|
||||||
|
_ = c.Flags().SetAnnotation("version", FlagSetByCobraAnnotation, []string{"true"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1137,10 +1232,12 @@ Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
||||||
c.Printf("Unknown help topic %#q\n", args)
|
c.Printf("Unknown help topic %#q\n", args)
|
||||||
CheckErr(c.Root().Usage())
|
CheckErr(c.Root().Usage())
|
||||||
} else {
|
} else {
|
||||||
cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown
|
cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown
|
||||||
|
cmd.InitDefaultVersionFlag() // make possible 'version' flag to be shown
|
||||||
CheckErr(cmd.Help())
|
CheckErr(cmd.Help())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
GroupID: c.helpCommandGroupID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.RemoveCommand(c.helpCommand)
|
c.RemoveCommand(c.helpCommand)
|
||||||
|
@ -1201,6 +1298,36 @@ func (c *Command) AddCommand(cmds ...*Command) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Groups returns a slice of child command groups.
|
||||||
|
func (c *Command) Groups() []*Group {
|
||||||
|
return c.commandgroups
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllChildCommandsHaveGroup returns if all subcommands are assigned to a group
|
||||||
|
func (c *Command) AllChildCommandsHaveGroup() bool {
|
||||||
|
for _, sub := range c.commands {
|
||||||
|
if (sub.IsAvailableCommand() || sub == c.helpCommand) && sub.GroupID == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddGroup adds one or more command groups to this parent command.
|
||||||
|
func (c *Command) AddGroup(groups ...*Group) {
|
||||||
|
c.commandgroups = append(c.commandgroups, groups...)
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveCommand removes one or more commands from a parent command.
|
// RemoveCommand removes one or more commands from a parent command.
|
||||||
func (c *Command) RemoveCommand(cmds ...*Command) {
|
func (c *Command) RemoveCommand(cmds ...*Command) {
|
||||||
commands := []*Command{}
|
commands := []*Command{}
|
||||||
|
@ -1344,7 +1471,7 @@ func (c *Command) Name() string {
|
||||||
// HasAlias determines if a given string is an alias of the command.
|
// HasAlias determines if a given string is an alias of the command.
|
||||||
func (c *Command) HasAlias(s string) bool {
|
func (c *Command) HasAlias(s string) bool {
|
||||||
for _, a := range c.Aliases {
|
for _, a := range c.Aliases {
|
||||||
if a == s {
|
if commandNameMatches(a, s) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1521,7 +1648,8 @@ func (c *Command) LocalFlags() *flag.FlagSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
addToLocal := func(f *flag.Flag) {
|
addToLocal := func(f *flag.Flag) {
|
||||||
if c.lflags.Lookup(f.Name) == nil && c.parentsPflags.Lookup(f.Name) == nil {
|
// Add the flag if it is not a parent PFlag, or it shadows a parent PFlag
|
||||||
|
if c.lflags.Lookup(f.Name) == nil && f != c.parentsPflags.Lookup(f.Name) {
|
||||||
c.lflags.AddFlag(f)
|
c.lflags.AddFlag(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1710,3 +1838,14 @@ func (c *Command) updateParentsPflags() {
|
||||||
c.parentsPflags.AddFlagSet(parent.PersistentFlags())
|
c.parentsPflags.AddFlagSet(parent.PersistentFlags())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// commandNameMatches checks if two command names are equal
|
||||||
|
// taking into account case sensitivity according to
|
||||||
|
// EnableCaseInsensitive global configuration.
|
||||||
|
func commandNameMatches(s string, t string) bool {
|
||||||
|
if EnableCaseInsensitive {
|
||||||
|
return strings.EqualFold(s, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s == t
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
|
|
592
command_test.go
592
command_test.go
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -314,7 +328,7 @@ func TestEnablePrefixMatching(t *testing.T) {
|
||||||
t.Errorf("aCmdArgs expected: %q, got: %q", onetwo, got)
|
t.Errorf("aCmdArgs expected: %q, got: %q", onetwo, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnablePrefixMatching = false
|
EnablePrefixMatching = defaultPrefixMatching
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAliasPrefixMatching(t *testing.T) {
|
func TestAliasPrefixMatching(t *testing.T) {
|
||||||
|
@ -349,7 +363,7 @@ func TestAliasPrefixMatching(t *testing.T) {
|
||||||
t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got)
|
t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnablePrefixMatching = false
|
EnablePrefixMatching = defaultPrefixMatching
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestChildSameName checks the correct behaviour of cobra in cases,
|
// TestChildSameName checks the correct behaviour of cobra in cases,
|
||||||
|
@ -707,10 +721,7 @@ func TestEmptyInputs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverwrittenFlag(t *testing.T) {
|
func TestChildFlagShadowsParentPersistentFlag(t *testing.T) {
|
||||||
// TODO: This test fails, but should work.
|
|
||||||
t.Skip()
|
|
||||||
|
|
||||||
parent := &Command{Use: "parent", Run: emptyRun}
|
parent := &Command{Use: "parent", Run: emptyRun}
|
||||||
child := &Command{Use: "child", Run: emptyRun}
|
child := &Command{Use: "child", Run: emptyRun}
|
||||||
|
|
||||||
|
@ -732,7 +743,7 @@ func TestOverwrittenFlag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if childInherited.Lookup("intf") != nil {
|
if childInherited.Lookup("intf") != nil {
|
||||||
t.Errorf(`InheritedFlags should not contain overwritten flag "intf"`)
|
t.Errorf(`InheritedFlags should not contain shadowed flag "intf"`)
|
||||||
}
|
}
|
||||||
if childLocal.Lookup("intf") == nil {
|
if childLocal.Lookup("intf") == nil {
|
||||||
t.Error(`LocalFlags expected to contain "intf", got "nil"`)
|
t.Error(`LocalFlags expected to contain "intf", got "nil"`)
|
||||||
|
@ -887,6 +898,38 @@ func TestHelpCommandExecutedOnChild(t *testing.T) {
|
||||||
checkStringContains(t, output, childCmd.Long)
|
checkStringContains(t, output, childCmd.Long)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelpCommandExecutedOnChildWithFlagThatShadowsParentFlag(t *testing.T) {
|
||||||
|
parent := &Command{Use: "parent", Run: emptyRun}
|
||||||
|
child := &Command{Use: "child", Run: emptyRun}
|
||||||
|
parent.AddCommand(child)
|
||||||
|
|
||||||
|
parent.PersistentFlags().Bool("foo", false, "parent foo usage")
|
||||||
|
parent.PersistentFlags().Bool("bar", false, "parent bar usage")
|
||||||
|
child.Flags().Bool("foo", false, "child foo usage") // This shadows parent's foo flag
|
||||||
|
child.Flags().Bool("baz", false, "child baz usage")
|
||||||
|
|
||||||
|
got, err := executeCommand(parent, "help", "child")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `Usage:
|
||||||
|
parent child [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--baz child baz usage
|
||||||
|
--foo child foo usage
|
||||||
|
-h, --help help for child
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--bar parent bar usage
|
||||||
|
`
|
||||||
|
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("Help text mismatch.\nExpected:\n%s\n\nGot:\n%s\n", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSetHelpCommand(t *testing.T) {
|
func TestSetHelpCommand(t *testing.T) {
|
||||||
c := &Command{Use: "c", Run: emptyRun}
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
c.AddCommand(&Command{Use: "empty", Run: emptyRun})
|
c.AddCommand(&Command{Use: "empty", Run: emptyRun})
|
||||||
|
@ -1234,6 +1277,113 @@ func TestSuggestions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCaseInsensitive(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||||
|
childCmd := &Command{Use: "child", Run: emptyRun, Aliases: []string{"alternative"}}
|
||||||
|
granchildCmd := &Command{Use: "GRANDCHILD", Run: emptyRun, Aliases: []string{"ALIAS"}}
|
||||||
|
|
||||||
|
childCmd.AddCommand(granchildCmd)
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
failWithoutEnabling bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: []string{"child"},
|
||||||
|
failWithoutEnabling: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"CHILD"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"chILD"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"CHIld"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"alternative"},
|
||||||
|
failWithoutEnabling: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"ALTERNATIVE"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"ALTernatIVE"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"alternatiVE"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"child", "GRANDCHILD"},
|
||||||
|
failWithoutEnabling: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"child", "grandchild"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"CHIld", "GRANdchild"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"alternative", "ALIAS"},
|
||||||
|
failWithoutEnabling: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"alternative", "alias"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"CHILD", "alias"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"CHIld", "aliAS"},
|
||||||
|
failWithoutEnabling: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
for _, enableCaseInsensitivity := range []bool{true, false} {
|
||||||
|
EnableCaseInsensitive = enableCaseInsensitivity
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, test.args...)
|
||||||
|
expectedFailure := test.failWithoutEnabling && !enableCaseInsensitivity
|
||||||
|
|
||||||
|
if !expectedFailure && output != "" {
|
||||||
|
t.Errorf("Unexpected output: %v", output)
|
||||||
|
}
|
||||||
|
if !expectedFailure && err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableCaseInsensitive = defaultCaseInsensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test make sure we keep backwards-compatibility with respect
|
||||||
|
// to command names case sensitivity behavior.
|
||||||
|
func TestCaseSensitivityBackwardCompatibility(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||||
|
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||||
|
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
_, err := executeCommand(rootCmd, strings.ToUpper(childCmd.Use))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error on calling a command in upper case while command names are case sensitive. Got nil.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestRemoveCommand(t *testing.T) {
|
func TestRemoveCommand(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
childCmd := &Command{Use: "child", Run: emptyRun}
|
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||||
|
@ -1593,7 +1743,7 @@ func TestCommandsAreSorted(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableCommandSorting = true
|
EnableCommandSorting = defaultCommandSorting
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
||||||
|
@ -1614,7 +1764,180 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableCommandSorting = true
|
EnableCommandSorting = defaultCommandSorting
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageWithGroup(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group1", Title: "group1"})
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group2", Title: "group2"})
|
||||||
|
|
||||||
|
rootCmd.AddCommand(&Command{Use: "cmd1", GroupID: "group1", Run: emptyRun})
|
||||||
|
rootCmd.AddCommand(&Command{Use: "cmd2", GroupID: "group2", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// help should be ungrouped here
|
||||||
|
checkStringContains(t, output, "\nAdditional Commands:\n help")
|
||||||
|
checkStringContains(t, output, "\ngroup1\n cmd1")
|
||||||
|
checkStringContains(t, output, "\ngroup2\n cmd2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageHelpGroup(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "group"})
|
||||||
|
rootCmd.AddCommand(&Command{Use: "xxx", GroupID: "group", Run: emptyRun})
|
||||||
|
rootCmd.SetHelpCommandGroupID("group")
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now help should be grouped under "group"
|
||||||
|
checkStringOmits(t, output, "\nAdditional Commands:\n help")
|
||||||
|
checkStringContains(t, output, "\ngroup\n help")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageCompletionGroup(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "group"})
|
||||||
|
rootCmd.AddGroup(&Group{ID: "help", Title: "help"})
|
||||||
|
|
||||||
|
rootCmd.AddCommand(&Command{Use: "xxx", GroupID: "group", Run: emptyRun})
|
||||||
|
rootCmd.SetHelpCommandGroupID("help")
|
||||||
|
rootCmd.SetCompletionCommandGroupID("group")
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now completion should be grouped under "group"
|
||||||
|
checkStringOmits(t, output, "\nAdditional Commands:\n completion")
|
||||||
|
checkStringContains(t, output, "\ngroup\n completion")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUngroupedCommand(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "group"})
|
||||||
|
rootCmd.AddGroup(&Group{ID: "help", Title: "help"})
|
||||||
|
|
||||||
|
rootCmd.AddCommand(&Command{Use: "xxx", GroupID: "group", Run: emptyRun})
|
||||||
|
rootCmd.SetHelpCommandGroupID("help")
|
||||||
|
rootCmd.SetCompletionCommandGroupID("group")
|
||||||
|
|
||||||
|
// Add a command without a group
|
||||||
|
rootCmd.AddCommand(&Command{Use: "yyy", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The yyy command should be in the additional command "group"
|
||||||
|
checkStringContains(t, output, "\nAdditional Commands:\n yyy")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddGroup(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
|
||||||
|
rootCmd.AddCommand(&Command{Use: "cmd", GroupID: "group", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, "\nTest group\n cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrongGroupFirstLevel(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
|
||||||
|
// Use the wrong group ID
|
||||||
|
rootCmd.AddCommand(&Command{Use: "cmd", GroupID: "wrong", Run: emptyRun})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Errorf("The code should have panicked due to a missing group")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrongGroupNestedLevel(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
var childCmd = &Command{Use: "child", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
childCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
|
||||||
|
// Use the wrong group ID
|
||||||
|
childCmd.AddCommand(&Command{Use: "cmd", GroupID: "wrong", Run: emptyRun})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Errorf("The code should have panicked due to a missing group")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err := executeCommand(rootCmd, "child", "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrongGroupForHelp(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
var childCmd = &Command{Use: "child", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
|
||||||
|
// Use the wrong group ID
|
||||||
|
rootCmd.SetHelpCommandGroupID("wrong")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Errorf("The code should have panicked due to a missing group")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrongGroupForCompletion(t *testing.T) {
|
||||||
|
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
|
||||||
|
var childCmd = &Command{Use: "child", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
|
||||||
|
// Use the wrong group ID
|
||||||
|
rootCmd.SetCompletionCommandGroupID("wrong")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if recover() == nil {
|
||||||
|
t.Errorf("The code should have panicked due to a missing group")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetOutput(t *testing.T) {
|
func TestSetOutput(t *testing.T) {
|
||||||
|
@ -1723,6 +2046,38 @@ func TestFlagErrorFunc(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFlagErrorFuncHelp(t *testing.T) {
|
||||||
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
|
c.PersistentFlags().Bool("help", false, "help for c")
|
||||||
|
c.SetFlagErrorFunc(func(_ *Command, err error) error {
|
||||||
|
return fmt.Errorf("wrap error: %w", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := executeCommand(c, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("--help should not fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `Usage:
|
||||||
|
c [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--help help for c
|
||||||
|
`
|
||||||
|
if out != expected {
|
||||||
|
t.Errorf("Expected: %v, got: %v", expected, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err = executeCommand(c, "-h")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("-h should not fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out != expected {
|
||||||
|
t.Errorf("Expected: %v, got: %v", expected, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSortedFlags checks,
|
// TestSortedFlags checks,
|
||||||
// if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false.
|
// if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false.
|
||||||
// Related to https://github.com/spf13/cobra/issues/404.
|
// Related to https://github.com/spf13/cobra/issues/404.
|
||||||
|
@ -2161,3 +2516,222 @@ func TestSetContextPersistentPreRun(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VersionFlag = "--version"
|
||||||
|
const HelpFlag = "--help"
|
||||||
|
|
||||||
|
func TestNoRootRunCommandExecutedWithVersionSet(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Version: "1.0.0", Long: "Long description"}
|
||||||
|
rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, rootCmd.Long)
|
||||||
|
checkStringContains(t, output, HelpFlag)
|
||||||
|
checkStringContains(t, output, VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoRootRunCommandExecutedWithoutVersionSet(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Long: "Long description"}
|
||||||
|
rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, rootCmd.Long)
|
||||||
|
checkStringContains(t, output, HelpFlag)
|
||||||
|
checkStringOmits(t, output, VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpCommandExecutedWithVersionSet(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Version: "1.0.0", Long: "Long description", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, rootCmd.Long)
|
||||||
|
checkStringContains(t, output, HelpFlag)
|
||||||
|
checkStringContains(t, output, VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpCommandExecutedWithoutVersionSet(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, "help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, rootCmd.Long)
|
||||||
|
checkStringContains(t, output, HelpFlag)
|
||||||
|
checkStringOmits(t, output, VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpflagCommandExecutedWithVersionSet(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Version: "1.0.0", Long: "Long description", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, HelpFlag)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, rootCmd.Long)
|
||||||
|
checkStringContains(t, output, HelpFlag)
|
||||||
|
checkStringContains(t, output, VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpflagCommandExecutedWithoutVersionSet(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun})
|
||||||
|
|
||||||
|
output, err := executeCommand(rootCmd, HelpFlag)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, output, rootCmd.Long)
|
||||||
|
checkStringContains(t, output, HelpFlag)
|
||||||
|
checkStringOmits(t, output, VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFind(t *testing.T) {
|
||||||
|
var foo, bar string
|
||||||
|
root := &Command{
|
||||||
|
Use: "root",
|
||||||
|
}
|
||||||
|
root.PersistentFlags().StringVarP(&foo, "foo", "f", "", "")
|
||||||
|
root.PersistentFlags().StringVarP(&bar, "bar", "b", "something", "")
|
||||||
|
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
}
|
||||||
|
root.AddCommand(child)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
args []string
|
||||||
|
expectedFoundArgs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]string{"child"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"child", "child"},
|
||||||
|
[]string{"child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"child", "foo", "child", "bar", "child", "baz", "child"},
|
||||||
|
[]string{"foo", "child", "bar", "child", "baz", "child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-f", "child", "child"},
|
||||||
|
[]string{"-f", "child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"child", "-f", "child"},
|
||||||
|
[]string{"-f", "child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-b", "child", "child"},
|
||||||
|
[]string{"-b", "child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"child", "-b", "child"},
|
||||||
|
[]string{"-b", "child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"child", "-b"},
|
||||||
|
[]string{"-b"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-b", "-f", "child", "child"},
|
||||||
|
[]string{"-b", "-f", "child"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-f", "child", "-b", "something", "child"},
|
||||||
|
[]string{"-f", "child", "-b", "something"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-f", "child", "child", "-b"},
|
||||||
|
[]string{"-f", "child", "-b"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-f=child", "-b=something", "child"},
|
||||||
|
[]string{"-f=child", "-b=something"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"--foo", "child", "--bar", "something", "child"},
|
||||||
|
[]string{"--foo", "child", "--bar", "something"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("%v", tc.args), func(t *testing.T) {
|
||||||
|
cmd, foundArgs, err := root.Find(tc.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd != child {
|
||||||
|
t.Fatal("Expected cmd to be child, but it was not")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.expectedFoundArgs, foundArgs) {
|
||||||
|
t.Fatalf("Wrong args\nExpected: %v\nGot: %v", tc.expectedFoundArgs, foundArgs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -63,6 +77,10 @@ const (
|
||||||
// obtain the same behavior but only for flags.
|
// obtain the same behavior but only for flags.
|
||||||
ShellCompDirectiveFilterDirs
|
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.
|
// All directives using iota should be above this one.
|
||||||
|
@ -145,6 +163,9 @@ func (d ShellCompDirective) string() string {
|
||||||
if d&ShellCompDirectiveFilterDirs != 0 {
|
if d&ShellCompDirectiveFilterDirs != 0 {
|
||||||
directives = append(directives, "ShellCompDirectiveFilterDirs")
|
directives = append(directives, "ShellCompDirectiveFilterDirs")
|
||||||
}
|
}
|
||||||
|
if d&ShellCompDirectiveKeepOrder != 0 {
|
||||||
|
directives = append(directives, "ShellCompDirectiveKeepOrder")
|
||||||
|
}
|
||||||
if len(directives) == 0 {
|
if len(directives) == 0 {
|
||||||
directives = append(directives, "ShellCompDirectiveDefault")
|
directives = append(directives, "ShellCompDirectiveDefault")
|
||||||
}
|
}
|
||||||
|
@ -155,7 +176,7 @@ func (d ShellCompDirective) string() string {
|
||||||
return strings.Join(directives, ", ")
|
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) {
|
func (c *Command) initCompleteCmd(args []string) {
|
||||||
completeCmd := &Command{
|
completeCmd := &Command{
|
||||||
Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
|
Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
|
||||||
|
@ -178,6 +199,12 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
|
|
||||||
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
|
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
|
||||||
for _, comp := range completions {
|
for _, comp := range completions {
|
||||||
|
if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable {
|
||||||
|
// Remove all activeHelp entries in this case
|
||||||
|
if strings.HasPrefix(comp, activeHelpMarker) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
if noDescriptions {
|
if noDescriptions {
|
||||||
// Remove any description that may be included following a tab character.
|
// Remove any description that may be included following a tab character.
|
||||||
comp = strings.Split(comp, "\t")[0]
|
comp = strings.Split(comp, "\t")[0]
|
||||||
|
@ -254,6 +281,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
}
|
}
|
||||||
finalCmd.ctx = c.ctx
|
finalCmd.ctx = c.ctx
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
// Check if we are doing flag value completion before parsing the flags.
|
// 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
|
// This is important because if we are completing a flag value, we need to also
|
||||||
// remove the flag name argument from the list of finalArgs or else the parsing
|
// remove the flag name argument from the list of finalArgs or else the parsing
|
||||||
|
@ -286,6 +319,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look for the --help or --version flags. If they are present,
|
||||||
|
// there should be no further completions.
|
||||||
|
if helpOrVersionFlagPresent(finalCmd) {
|
||||||
|
return finalCmd, []string{}, ShellCompDirectiveNoFileComp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// We only remove the flags from the arguments if DisableFlagParsing is not set.
|
// We only remove the flags from the arguments if DisableFlagParsing is not set.
|
||||||
// This is important for commands which have requested to do their own flag completion.
|
// This is important for commands which have requested to do their own flag completion.
|
||||||
if !finalCmd.DisableFlagParsing {
|
if !finalCmd.DisableFlagParsing {
|
||||||
|
@ -319,6 +358,9 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
var completions []string
|
var completions []string
|
||||||
var directive ShellCompDirective
|
var directive ShellCompDirective
|
||||||
|
|
||||||
|
// Enforce flag groups before doing flag completions
|
||||||
|
finalCmd.enforceFlagGroupsForCompletion()
|
||||||
|
|
||||||
// Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;
|
// Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;
|
||||||
// doing this allows for completion of persistent flag names even for commands that disable flag parsing.
|
// doing this allows for completion of persistent flag names even for commands that disable flag parsing.
|
||||||
//
|
//
|
||||||
|
@ -454,6 +496,18 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
return finalCmd, completions, directive, nil
|
return finalCmd, completions, directive, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func helpOrVersionFlagPresent(cmd *Command) bool {
|
||||||
|
if versionFlag := cmd.Flags().Lookup("version"); versionFlag != nil &&
|
||||||
|
len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if helpFlag := cmd.Flags().Lookup("help"); helpFlag != nil &&
|
||||||
|
len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
||||||
if nonCompletableFlag(flag) {
|
if nonCompletableFlag(flag) {
|
||||||
return []string{}
|
return []string{}
|
||||||
|
@ -598,12 +652,12 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
||||||
return flag, trimmedArgs, lastArg, nil
|
return flag, trimmedArgs, lastArg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDefaultCompletionCmd adds a default 'completion' command to c.
|
// InitDefaultCompletionCmd adds a default 'completion' command to c.
|
||||||
// This function will do nothing if any of the following is true:
|
// This function will do nothing if any of the following is true:
|
||||||
// 1- the feature has been explicitly disabled by the program,
|
// 1- the feature has been explicitly disabled by the program,
|
||||||
// 2- c has no subcommands (to avoid creating one),
|
// 2- c has no subcommands (to avoid creating one),
|
||||||
// 3- c already has a 'completion' command provided by the program.
|
// 3- c already has a 'completion' command provided by the program.
|
||||||
func (c *Command) initDefaultCompletionCmd() {
|
func (c *Command) InitDefaultCompletionCmd() {
|
||||||
if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
|
if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -626,6 +680,7 @@ See each sub-command's help for details on how to use the generated script.
|
||||||
Args: NoArgs,
|
Args: NoArgs,
|
||||||
ValidArgsFunction: NoFileCompletions,
|
ValidArgsFunction: NoFileCompletions,
|
||||||
Hidden: c.CompletionOptions.HiddenDefaultCmd,
|
Hidden: c.CompletionOptions.HiddenDefaultCmd,
|
||||||
|
GroupID: c.completionCommandGroupID,
|
||||||
}
|
}
|
||||||
c.AddCommand(completionCmd)
|
c.AddCommand(completionCmd)
|
||||||
|
|
||||||
|
@ -652,7 +707,7 @@ To load completions for every new session, execute once:
|
||||||
|
|
||||||
#### macOS:
|
#### macOS:
|
||||||
|
|
||||||
%[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
|
%[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
||||||
|
|
||||||
You will need to start a new shell for this setup to take effect.
|
You will need to start a new shell for this setup to take effect.
|
||||||
`, c.Root().Name()),
|
`, c.Root().Name()),
|
||||||
|
@ -689,7 +744,7 @@ To load completions for every new session, execute once:
|
||||||
|
|
||||||
#### macOS:
|
#### macOS:
|
||||||
|
|
||||||
%[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s
|
%[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s
|
||||||
|
|
||||||
You will need to start a new shell for this setup to take effect.
|
You will need to start a new shell for this setup to take effect.
|
||||||
`, c.Root().Name()),
|
`, c.Root().Name()),
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -479,8 +493,9 @@ func TestFlagNameCompletionInGo(t *testing.T) {
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
}
|
}
|
||||||
childCmd := &Command{
|
childCmd := &Command{
|
||||||
Use: "childCmd",
|
Use: "childCmd",
|
||||||
Run: emptyRun,
|
Version: "1.2.3",
|
||||||
|
Run: emptyRun,
|
||||||
}
|
}
|
||||||
rootCmd.AddCommand(childCmd)
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
@ -514,6 +529,8 @@ func TestFlagNameCompletionInGo(t *testing.T) {
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"--first",
|
"--first",
|
||||||
"-f",
|
"-f",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
"--second",
|
"--second",
|
||||||
"-s",
|
"-s",
|
||||||
":4",
|
":4",
|
||||||
|
@ -547,7 +564,11 @@ func TestFlagNameCompletionInGo(t *testing.T) {
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"--second",
|
"--second",
|
||||||
"-s",
|
"-s",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
"--subFlag",
|
"--subFlag",
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
":4",
|
":4",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
@ -562,9 +583,10 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
}
|
}
|
||||||
childCmd := &Command{
|
childCmd := &Command{
|
||||||
Use: "childCmd",
|
Use: "childCmd",
|
||||||
Short: "first command",
|
Short: "first command",
|
||||||
Run: emptyRun,
|
Version: "1.2.3",
|
||||||
|
Run: emptyRun,
|
||||||
}
|
}
|
||||||
rootCmd.AddCommand(childCmd)
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
@ -598,6 +620,8 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"--first\tfirst flag",
|
"--first\tfirst flag",
|
||||||
"-f\tfirst flag",
|
"-f\tfirst flag",
|
||||||
|
"--help\thelp for root",
|
||||||
|
"-h\thelp for root",
|
||||||
"--second\tsecond flag",
|
"--second\tsecond flag",
|
||||||
"-s\tsecond flag",
|
"-s\tsecond flag",
|
||||||
":4",
|
":4",
|
||||||
|
@ -631,7 +655,11 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"--second\tsecond flag",
|
"--second\tsecond flag",
|
||||||
"-s\tsecond flag",
|
"-s\tsecond flag",
|
||||||
|
"--help\thelp for childCmd",
|
||||||
|
"-h\thelp for childCmd",
|
||||||
"--subFlag\tsub flag",
|
"--subFlag\tsub flag",
|
||||||
|
"--version\tversion for childCmd",
|
||||||
|
"-v\tversion for childCmd",
|
||||||
":4",
|
":4",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
@ -674,6 +702,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
|
||||||
expected := strings.Join([]string{
|
expected := strings.Join([]string{
|
||||||
"--array",
|
"--array",
|
||||||
"--bslice",
|
"--bslice",
|
||||||
|
"--help",
|
||||||
"--second",
|
"--second",
|
||||||
"--slice",
|
"--slice",
|
||||||
":4",
|
":4",
|
||||||
|
@ -695,6 +724,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"--array",
|
"--array",
|
||||||
"--bslice",
|
"--bslice",
|
||||||
|
"--help",
|
||||||
"--slice",
|
"--slice",
|
||||||
":4",
|
":4",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
@ -717,6 +747,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
|
||||||
"--array",
|
"--array",
|
||||||
"--bslice",
|
"--bslice",
|
||||||
"--first",
|
"--first",
|
||||||
|
"--help",
|
||||||
"--second",
|
"--second",
|
||||||
"--slice",
|
"--slice",
|
||||||
":4",
|
":4",
|
||||||
|
@ -742,6 +773,8 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
|
||||||
"-b",
|
"-b",
|
||||||
"--first",
|
"--first",
|
||||||
"-f",
|
"-f",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
"--second",
|
"--second",
|
||||||
"-s",
|
"-s",
|
||||||
"--slice",
|
"--slice",
|
||||||
|
@ -1778,6 +1811,7 @@ func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) {
|
||||||
|
|
||||||
expected := strings.Join([]string{
|
expected := strings.Join([]string{
|
||||||
"--bool\ttest bool flag",
|
"--bool\ttest bool flag",
|
||||||
|
"--help\thelp for child",
|
||||||
"--string\ttest string flag",
|
"--string\ttest string flag",
|
||||||
":4",
|
":4",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
@ -2588,6 +2622,8 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) {
|
||||||
expected := strings.Join([]string{
|
expected := strings.Join([]string{
|
||||||
"--persistent",
|
"--persistent",
|
||||||
"-p",
|
"-p",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
"--nonPersistent",
|
"--nonPersistent",
|
||||||
"-n",
|
"-n",
|
||||||
"--flag",
|
"--flag",
|
||||||
|
@ -2610,6 +2646,8 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) {
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"--persistent",
|
"--persistent",
|
||||||
"-p",
|
"-p",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
"--nonPersistent",
|
"--nonPersistent",
|
||||||
"-n",
|
"-n",
|
||||||
":4",
|
":4",
|
||||||
|
@ -2691,3 +2729,453 @@ func TestFixedCompletions(t *testing.T) {
|
||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompletionForGroupedFlags(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.MarkFlagsRequiredTogether("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 not suggested without - prefix",
|
||||||
|
args: []string{"child", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"subArg",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "flags in group suggested with - prefix",
|
||||||
|
args: []string{"child", "-"},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"--ingroup1",
|
||||||
|
"--ingroup2",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
|
"--ingroup3",
|
||||||
|
"--nogroup",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when flag in group present, other flags in group suggested even without - prefix",
|
||||||
|
args: []string{"child", "--ingroup2", "value", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"--ingroup1",
|
||||||
|
"--ingroup3",
|
||||||
|
"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{
|
||||||
|
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().IntSlice("ingroup1", []int{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.MarkFlagsMutuallyExclusive("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 mutually exclusive group not suggested without the - prefix",
|
||||||
|
args: []string{"child", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"subArg",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "flags in mutually exclusive group suggested with the - prefix",
|
||||||
|
args: []string{"child", "-"},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"--ingroup1",
|
||||||
|
"--ingroup2",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
|
"--ingroup3",
|
||||||
|
"--nogroup",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when flag in mutually exclusive group present, other flags in group not suggested even with the - prefix",
|
||||||
|
args: []string{"child", "--ingroup1", "8", "-"},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"--ingroup1", // Should be suggested again since it is a slice
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
|
"--nogroup",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "group ignored if some flags not applicable",
|
||||||
|
args: []string{"--ingroup1", "8", "-"},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
|
"--ingroup1",
|
||||||
|
"--ingroup2",
|
||||||
|
":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 TestCompletionCobraFlags(t *testing.T) {
|
||||||
|
getCmd := func() *Command {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Version: "1.1.1",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "child",
|
||||||
|
Version: "1.1.1",
|
||||||
|
Run: emptyRun,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{"extra"}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
childCmd2 := &Command{
|
||||||
|
Use: "child2",
|
||||||
|
Version: "1.1.1",
|
||||||
|
Run: emptyRun,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{"extra2"}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
childCmd3 := &Command{
|
||||||
|
Use: "child3",
|
||||||
|
Version: "1.1.1",
|
||||||
|
Run: emptyRun,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{"extra3"}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.AddCommand(childCmd, childCmd2, childCmd3)
|
||||||
|
|
||||||
|
_ = childCmd.Flags().Bool("bool", false, "A bool flag")
|
||||||
|
_ = childCmd.MarkFlagRequired("bool")
|
||||||
|
|
||||||
|
// Have a command that adds its own help and version flag
|
||||||
|
_ = childCmd2.Flags().BoolP("help", "h", false, "My own help")
|
||||||
|
_ = childCmd2.Flags().BoolP("version", "v", false, "My own version")
|
||||||
|
|
||||||
|
// Have a command that only adds its own -v flag
|
||||||
|
_ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag")
|
||||||
|
|
||||||
|
return rootCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each test case uses a unique command from the function above.
|
||||||
|
testcases := []struct {
|
||||||
|
desc string
|
||||||
|
args []string
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "completion of help and version flags",
|
||||||
|
args: []string{"-"},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after --help flag",
|
||||||
|
args: []string{"--help", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after -h flag",
|
||||||
|
args: []string{"-h", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after --version flag",
|
||||||
|
args: []string{"--version", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after -v flag",
|
||||||
|
args: []string{"-v", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after --help flag even with other completions",
|
||||||
|
args: []string{"child", "--help", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after -h flag even with other completions",
|
||||||
|
args: []string{"child", "-h", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after --version flag even with other completions",
|
||||||
|
args: []string{"child", "--version", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after -v flag even with other completions",
|
||||||
|
args: []string{"child", "-v", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no completion after -v flag even with other flag completions",
|
||||||
|
args: []string{"child", "-v", "-"},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "completion after --help flag when created by program",
|
||||||
|
args: []string{"child2", "--help", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"extra2",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "completion after -h flag when created by program",
|
||||||
|
args: []string{"child2", "-h", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"extra2",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "completion after --version flag when created by program",
|
||||||
|
args: []string{"child2", "--version", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"extra2",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "completion after -v flag when created by program",
|
||||||
|
args: []string{"child2", "-v", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"extra2",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "completion after --version when only -v flag was created by program",
|
||||||
|
args: []string{"child3", "--version", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "completion after -v flag when only -v flag was created by program",
|
||||||
|
args: []string{"child3", "-v", ""},
|
||||||
|
expectedOutput: strings.Join([]string{
|
||||||
|
"extra3",
|
||||||
|
":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 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,11 @@
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
### `DisableAutoGenTag`
|
### `DisableAutoGenTag`
|
||||||
|
|
||||||
You may set `cmd.DisableAutoGenTag = true`
|
You may set `cmd.DisableAutoGenTag = true`
|
||||||
to _entirely_ remove the auto generated string "Auto generated by spf13/cobra..."
|
to _entirely_ remove the auto generated string "Auto generated by spf13/cobra..."
|
||||||
from any documentation source.
|
from any documentation source.
|
||||||
|
|
||||||
|
### `InitDefaultCompletionCmd`
|
||||||
|
|
||||||
|
You may call `cmd.InitDefaultCompletionCmd()` to document the default autocompletion command.
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package doc
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright 2015 Red Hat Inc. All rights reserved.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -66,7 +67,7 @@ func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
|
||||||
if opts.CommandSeparator != "" {
|
if opts.CommandSeparator != "" {
|
||||||
separator = opts.CommandSeparator
|
separator = opts.CommandSeparator
|
||||||
}
|
}
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", separator, -1)
|
basename := strings.ReplaceAll(cmd.CommandPath(), " ", separator)
|
||||||
filename := filepath.Join(opts.Path, basename+"."+section)
|
filename := filepath.Join(opts.Path, basename+"."+section)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,7 +117,7 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
|
||||||
|
|
||||||
func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error {
|
func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error {
|
||||||
if header.Title == "" {
|
if header.Title == "" {
|
||||||
header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
|
header.Title = strings.ToUpper(strings.ReplaceAll(name, " ", "\\-"))
|
||||||
}
|
}
|
||||||
if header.Section == "" {
|
if header.Section == "" {
|
||||||
header.Section = "1"
|
header.Section = "1"
|
||||||
|
@ -203,7 +204,7 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
|
||||||
cmd.InitDefaultHelpFlag()
|
cmd.InitDefaultHelpFlag()
|
||||||
|
|
||||||
// something like `rootcmd-subcmd1-subcmd2`
|
// something like `rootcmd-subcmd1-subcmd2`
|
||||||
dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1)
|
dashCommandName := strings.ReplaceAll(cmd.CommandPath(), " ", "-")
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
@ -218,7 +219,7 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
|
||||||
seealsos := make([]string, 0)
|
seealsos := make([]string, 0)
|
||||||
if cmd.HasParent() {
|
if cmd.HasParent() {
|
||||||
parentPath := cmd.Parent().CommandPath()
|
parentPath := cmd.Parent().CommandPath()
|
||||||
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
dashParentPath := strings.ReplaceAll(parentPath, " ", "-")
|
||||||
seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
|
seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
|
||||||
seealsos = append(seealsos, seealso)
|
seealsos = append(seealsos, seealso)
|
||||||
cmd.VisitParents(func(c *cobra.Command) {
|
cmd.VisitParents(func(c *cobra.Command) {
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package doc
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -20,7 +34,7 @@ func assertNoErr(t *testing.T, e error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func translate(in string) string {
|
func translate(in string) string {
|
||||||
return strings.Replace(in, "-", "\\-", -1)
|
return strings.ReplaceAll(in, "-", "\\-")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenManDoc(t *testing.T) {
|
func TestGenManDoc(t *testing.T) {
|
||||||
|
@ -38,7 +52,7 @@ func TestGenManDoc(t *testing.T) {
|
||||||
|
|
||||||
// Make sure parent has - in CommandPath() in SEE ALSO:
|
// Make sure parent has - in CommandPath() in SEE ALSO:
|
||||||
parentPath := echoCmd.Parent().CommandPath()
|
parentPath := echoCmd.Parent().CommandPath()
|
||||||
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
dashParentPath := strings.ReplaceAll(parentPath, " ", "-")
|
||||||
expected := translate(dashParentPath)
|
expected := translate(dashParentPath)
|
||||||
expected = expected + "(" + header.Section + ")"
|
expected = expected + "(" + header.Section + ")"
|
||||||
checkStringContains(t, output, expected)
|
checkStringContains(t, output, expected)
|
||||||
|
@ -73,7 +87,7 @@ func TestGenManNoHiddenParents(t *testing.T) {
|
||||||
|
|
||||||
// Make sure parent has - in CommandPath() in SEE ALSO:
|
// Make sure parent has - in CommandPath() in SEE ALSO:
|
||||||
parentPath := echoCmd.Parent().CommandPath()
|
parentPath := echoCmd.Parent().CommandPath()
|
||||||
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
dashParentPath := strings.ReplaceAll(parentPath, " ", "-")
|
||||||
expected := translate(dashParentPath)
|
expected := translate(dashParentPath)
|
||||||
expected = expected + "(" + header.Section + ")"
|
expected = expected + "(" + header.Section + ")"
|
||||||
checkStringContains(t, output, expected)
|
checkStringContains(t, output, expected)
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package doc_test
|
package doc_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
//Copyright 2015 Red Hat Inc. All rights reserved.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -83,7 +84,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
|
||||||
parent := cmd.Parent()
|
parent := cmd.Parent()
|
||||||
pname := parent.CommandPath()
|
pname := parent.CommandPath()
|
||||||
link := pname + ".md"
|
link := pname + ".md"
|
||||||
link = strings.Replace(link, " ", "_", -1)
|
link = strings.ReplaceAll(link, " ", "_")
|
||||||
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short))
|
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short))
|
||||||
cmd.VisitParents(func(c *cobra.Command) {
|
cmd.VisitParents(func(c *cobra.Command) {
|
||||||
if c.DisableAutoGenTag {
|
if c.DisableAutoGenTag {
|
||||||
|
@ -101,7 +102,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
|
||||||
}
|
}
|
||||||
cname := name + " " + child.Name()
|
cname := name + " " + child.Name()
|
||||||
link := cname + ".md"
|
link := cname + ".md"
|
||||||
link = strings.Replace(link, " ", "_", -1)
|
link = strings.ReplaceAll(link, " ", "_")
|
||||||
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short))
|
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short))
|
||||||
}
|
}
|
||||||
buf.WriteString("\n")
|
buf.WriteString("\n")
|
||||||
|
@ -137,7 +138,7 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
|
basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + ".md"
|
||||||
filename := filepath.Join(dir, basename)
|
filename := filepath.Join(dir, basename)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -85,7 +85,7 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `filePrepender` will prepend the return value given the full filepath to the rendered Markdown file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
|
The `filePrepender` will prepend the return value given the full filepath to the rendered Markdown file. A common use case is to add front matter to use the generated documentation with [Hugo](https://gohugo.io/):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
const fmTemplate = `---
|
const fmTemplate = `---
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package doc
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
//Copyright 2015 Red Hat Inc. All rights reserved.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -47,7 +48,7 @@ func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkHandler for default ReST hyperlink markup
|
// defaultLinkHandler for default ReST hyperlink markup
|
||||||
func defaultLinkHandler(name, ref string) string {
|
func defaultLinkHandler(name, ref string) string {
|
||||||
return fmt.Sprintf("`%s <%s.rst>`_", name, ref)
|
return fmt.Sprintf("`%s <%s.rst>`_", name, ref)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +71,7 @@ func GenReSTCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string, str
|
||||||
if len(long) == 0 {
|
if len(long) == 0 {
|
||||||
long = short
|
long = short
|
||||||
}
|
}
|
||||||
ref := strings.Replace(name, " ", "_", -1)
|
ref := strings.ReplaceAll(name, " ", "_")
|
||||||
|
|
||||||
buf.WriteString(".. _" + ref + ":\n\n")
|
buf.WriteString(".. _" + ref + ":\n\n")
|
||||||
buf.WriteString(name + "\n")
|
buf.WriteString(name + "\n")
|
||||||
|
@ -99,7 +100,7 @@ func GenReSTCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string, str
|
||||||
if cmd.HasParent() {
|
if cmd.HasParent() {
|
||||||
parent := cmd.Parent()
|
parent := cmd.Parent()
|
||||||
pname := parent.CommandPath()
|
pname := parent.CommandPath()
|
||||||
ref = strings.Replace(pname, " ", "_", -1)
|
ref = strings.ReplaceAll(pname, " ", "_")
|
||||||
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(pname, ref), parent.Short))
|
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(pname, ref), parent.Short))
|
||||||
cmd.VisitParents(func(c *cobra.Command) {
|
cmd.VisitParents(func(c *cobra.Command) {
|
||||||
if c.DisableAutoGenTag {
|
if c.DisableAutoGenTag {
|
||||||
|
@ -116,7 +117,7 @@ func GenReSTCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string, str
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cname := name + " " + child.Name()
|
cname := name + " " + child.Name()
|
||||||
ref = strings.Replace(cname, " ", "_", -1)
|
ref = strings.ReplaceAll(cname, " ", "_")
|
||||||
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(cname, ref), child.Short))
|
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(cname, ref), child.Short))
|
||||||
}
|
}
|
||||||
buf.WriteString("\n")
|
buf.WriteString("\n")
|
||||||
|
@ -151,7 +152,7 @@ func GenReSTTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".rst"
|
basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + ".rst"
|
||||||
filename := filepath.Join(dir, basename)
|
filename := filepath.Join(dir, basename)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -168,7 +169,7 @@ func GenReSTTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string
|
||||||
return nil
|
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 {
|
func indentString(s, p string) string {
|
||||||
var res []byte
|
var res []byte
|
||||||
b := []byte(s)
|
b := []byte(s)
|
||||||
|
|
|
@ -85,7 +85,7 @@ func GenReSTCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string, str
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `filePrepender` will prepend the return value given the full filepath to the rendered ReST file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
|
The `filePrepender` will prepend the return value given the full filepath to the rendered ReST file. A common use case is to add front matter to use the generated documentation with [Hugo](https://gohugo.io/):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
const fmTemplate = `---
|
const fmTemplate = `---
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package doc
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright 2015 Red Hat Inc. All rights reserved.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright 2016 French Ben. All rights reserved.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -23,7 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cmdOption struct {
|
type cmdOption struct {
|
||||||
|
@ -66,7 +67,7 @@ func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml"
|
basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + ".yaml"
|
||||||
filename := filepath.Join(dir, basename)
|
filename := filepath.Join(dir, basename)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -128,7 +129,7 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) str
|
||||||
if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() {
|
if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, child.Name()+" - "+child.Short)
|
result = append(result, child.CommandPath()+" - "+child.Short)
|
||||||
}
|
}
|
||||||
yamlDoc.SeeAlso = result
|
yamlDoc.SeeAlso = result
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ func GenYamlCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) str
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `filePrepender` will prepend the return value given the full filepath to the rendered Yaml file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
|
The `filePrepender` will prepend the return value given the full filepath to the rendered Yaml file. A common use case is to add front matter to use the generated documentation with [Hugo](https://gohugo.io/):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
const fmTemplate = `---
|
const fmTemplate = `---
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package doc
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -24,6 +39,7 @@ func TestGenYamlDoc(t *testing.T) {
|
||||||
checkStringContains(t, output, "rootflag")
|
checkStringContains(t, output, "rootflag")
|
||||||
checkStringContains(t, output, rootCmd.Short)
|
checkStringContains(t, output, rootCmd.Short)
|
||||||
checkStringContains(t, output, echoSubCmd.Short)
|
checkStringContains(t, output, echoSubCmd.Short)
|
||||||
|
checkStringContains(t, output, fmt.Sprintf("- %s - %s", echoSubCmd.CommandPath(), echoSubCmd.Short))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenYamlNoTag(t *testing.T) {
|
func TestGenYamlNoTag(t *testing.T) {
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,8 +25,8 @@ import (
|
||||||
func genFishComp(buf io.StringWriter, name string, includeDesc bool) {
|
func genFishComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
// Variables should not contain a '-' or ':' character
|
// Variables should not contain a '-' or ':' character
|
||||||
nameForVar := name
|
nameForVar := name
|
||||||
nameForVar = strings.Replace(nameForVar, "-", "_", -1)
|
nameForVar = strings.ReplaceAll(nameForVar, "-", "_")
|
||||||
nameForVar = strings.Replace(nameForVar, ":", "_", -1)
|
nameForVar = strings.ReplaceAll(nameForVar, ":", "_")
|
||||||
|
|
||||||
compCmd := ShellCompRequestCmd
|
compCmd := ShellCompRequestCmd
|
||||||
if !includeDesc {
|
if !includeDesc {
|
||||||
|
@ -38,7 +52,8 @@ function __%[1]s_perform_completion
|
||||||
__%[1]s_debug "args: $args"
|
__%[1]s_debug "args: $args"
|
||||||
__%[1]s_debug "last arg: $lastArg"
|
__%[1]s_debug "last arg: $lastArg"
|
||||||
|
|
||||||
set -l requestComp "$args[1] %[3]s $args[2..-1] $lastArg"
|
# Disable ActiveHelp which is not supported for fish shell
|
||||||
|
set -l requestComp "%[10]s=0 $args[1] %[3]s $args[2..-1] $lastArg"
|
||||||
|
|
||||||
__%[1]s_debug "Calling $requestComp"
|
__%[1]s_debug "Calling $requestComp"
|
||||||
set -l results (eval $requestComp 2> /dev/null)
|
set -l results (eval $requestComp 2> /dev/null)
|
||||||
|
@ -74,6 +89,60 @@ function __%[1]s_perform_completion
|
||||||
printf "%%s\n" "$directiveLine"
|
printf "%%s\n" "$directiveLine"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# this function limits calls to __%[1]s_perform_completion, by caching the result behind $__%[1]s_perform_completion_once_result
|
||||||
|
function __%[1]s_perform_completion_once
|
||||||
|
__%[1]s_debug "Starting __%[1]s_perform_completion_once"
|
||||||
|
|
||||||
|
if test -n "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "Seems like a valid result already exists, skipping __%[1]s_perform_completion"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set --global __%[1]s_perform_completion_once_result (__%[1]s_perform_completion)
|
||||||
|
if test -z "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "No completions, probably due to a failure"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
__%[1]s_debug "Performed completions and set __%[1]s_perform_completion_once_result"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# this function is used to clear the $__%[1]s_perform_completion_once_result variable after completions are run
|
||||||
|
function __%[1]s_clear_perform_completion_once_result
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable =========="
|
||||||
|
set --erase __%[1]s_perform_completion_once_result
|
||||||
|
__%[1]s_debug "Succesfully erased the variable __%[1]s_perform_completion_once_result"
|
||||||
|
end
|
||||||
|
|
||||||
|
function __%[1]s_requires_order_preservation
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= checking if order preservation is required =========="
|
||||||
|
|
||||||
|
__%[1]s_perform_completion_once
|
||||||
|
if test -z "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "Error determining if order preservation is required"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
|
||||||
|
__%[1]s_debug "Directive is: $directive"
|
||||||
|
|
||||||
|
set -l shellCompDirectiveKeepOrder %[9]d
|
||||||
|
set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) %% 2)
|
||||||
|
__%[1]s_debug "Keeporder is: $keeporder"
|
||||||
|
|
||||||
|
if test $keeporder -ne 0
|
||||||
|
__%[1]s_debug "This does require order preservation"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
__%[1]s_debug "This doesn't require order preservation"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# This function does two things:
|
# This function does two things:
|
||||||
# - Obtain the completions and store them in the global __%[1]s_comp_results
|
# - Obtain the completions and store them in the global __%[1]s_comp_results
|
||||||
# - Return false if file completion should be performed
|
# - Return false if file completion should be performed
|
||||||
|
@ -84,17 +153,17 @@ function __%[1]s_prepare_completions
|
||||||
# Start fresh
|
# Start fresh
|
||||||
set --erase __%[1]s_comp_results
|
set --erase __%[1]s_comp_results
|
||||||
|
|
||||||
set -l results (__%[1]s_perform_completion)
|
__%[1]s_perform_completion_once
|
||||||
__%[1]s_debug "Completion results: $results"
|
__%[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"
|
__%[1]s_debug "No completion, probably due to a failure"
|
||||||
# Might as well do file completion, in case it helps
|
# Might as well do file completion, in case it helps
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
set -l directive (string sub --start 2 $results[-1])
|
set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
|
||||||
set --global __%[1]s_comp_results $results[1..-2]
|
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 "Completions are: $__%[1]s_comp_results"
|
||||||
__%[1]s_debug "Directive is: $directive"
|
__%[1]s_debug "Directive is: $directive"
|
||||||
|
@ -190,13 +259,17 @@ end
|
||||||
# Remove any pre-existing completions for the program since we will be handling all of them.
|
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||||
complete -c %[2]s -e
|
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
|
# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
|
||||||
# which provides the program's completion choices.
|
# 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,
|
`, nameForVar, name, compCmd,
|
||||||
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenFishCompletion generates fish completion file and writes to the passed writer.
|
// GenFishCompletion generates fish completion file and writes to the passed writer.
|
||||||
|
|
|
@ -1,7 +1,24 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,3 +84,69 @@ func TestProgWithColon(t *testing.T) {
|
||||||
check(t, output, "-c root:colon")
|
check(t, output, "-c root:colon")
|
||||||
checkOmit(t, output, "-c root_colon")
|
checkOmit(t, output, "-c root_colon")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFishCompletionNoActiveHelp(t *testing.T) {
|
||||||
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assertNoErr(t, c.GenFishCompletion(buf, true))
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
// check that active help is being disabled
|
||||||
|
activeHelpVar := activeHelpEnvVar(c.Name())
|
||||||
|
check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenFishCompletionFile(t *testing.T) {
|
||||||
|
err := os.Mkdir("./tmp", 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll("./tmp")
|
||||||
|
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
assertNoErr(t, rootCmd.GenFishCompletionFile("./tmp/test", false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailGenFishCompletionFile(t *testing.T) {
|
||||||
|
err := os.Mkdir("./tmp", 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll("./tmp")
|
||||||
|
|
||||||
|
f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0400)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright © 2022 Steve Francia <spf@spf13.com>.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -58,9 +59,9 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateFlagGroups validates the mutuallyExclusive/requiredAsGroup logic and returns the
|
// ValidateFlagGroups validates the mutuallyExclusive/requiredAsGroup logic and returns the
|
||||||
// first error encountered.
|
// first error encountered.
|
||||||
func (c *Command) validateFlagGroups() error {
|
func (c *Command) ValidateFlagGroups() error {
|
||||||
if c.DisableFlagParsing {
|
if c.DisableFlagParsing {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -172,3 +173,52 @@ func sortedKeys(m map[string]map[string]bool) []string {
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enforceFlagGroupsForCompletion will do the following:
|
||||||
|
// - when a flag in a group is present, other 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() {
|
||||||
|
if c.DisableFlagParsing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := c.Flags()
|
||||||
|
groupStatus := 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, mutuallyExclusive, mutuallyExclusiveGroupStatus)
|
||||||
|
})
|
||||||
|
|
||||||
|
// If a flag that is part of a group is present, we make all the other flags
|
||||||
|
// of that group required so that the shell completion suggests them automatically
|
||||||
|
for flagList, flagnameAndStatus := range groupStatus {
|
||||||
|
for _, isSet := range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
// One of the flags of the group is set, mark the other ones as required
|
||||||
|
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 {
|
||||||
|
for flagName, isSet := range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
// One of the flags of the mutually exclusive group is set, mark the other ones as hidden
|
||||||
|
// Don't mark the flag that is already set as hidden because it may be an
|
||||||
|
// array or slice flag and therefore must continue being suggested
|
||||||
|
for _, fName := range strings.Split(flagList, " ") {
|
||||||
|
if fName != flagName {
|
||||||
|
flag := c.Flags().Lookup(fName)
|
||||||
|
flag.Hidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright © 2022 Steve Francia <spf@spf13.com>.
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -3,8 +3,8 @@ module github.com/spf13/cobra
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
github.com/cpuguy83/go-md2man/v2 v2.0.2
|
||||||
github.com/inconshreveable/mousetrap v1.0.0
|
github.com/inconshreveable/mousetrap v1.1.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -1,12 +1,12 @@
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
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=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
|
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
|
||||||
// can be downloaded separately for windows 7 or 8.1).
|
// can be downloaded separately for windows 7 or 8.1).
|
||||||
|
|
||||||
|
@ -8,9 +22,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) {
|
func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
|
// Variables should not contain a '-' or ':' character
|
||||||
|
nameForVar := name
|
||||||
|
nameForVar = strings.Replace(nameForVar, "-", "_", -1)
|
||||||
|
nameForVar = strings.Replace(nameForVar, ":", "_", -1)
|
||||||
|
|
||||||
compCmd := ShellCompRequestCmd
|
compCmd := ShellCompRequestCmd
|
||||||
if !includeDesc {
|
if !includeDesc {
|
||||||
compCmd = ShellCompNoDescRequestCmd
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
@ -27,7 +47,7 @@ filter __%[1]s_escapeStringWithSpecialChars {
|
||||||
`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
|
`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
|
||||||
}
|
}
|
||||||
|
|
||||||
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
[scriptblock]$__%[2]sCompleterBlock = {
|
||||||
param(
|
param(
|
||||||
$WordToComplete,
|
$WordToComplete,
|
||||||
$CommandAst,
|
$CommandAst,
|
||||||
|
@ -52,16 +72,18 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
||||||
}
|
}
|
||||||
__%[1]s_debug "Truncated command: $Command"
|
__%[1]s_debug "Truncated command: $Command"
|
||||||
|
|
||||||
$ShellCompDirectiveError=%[3]d
|
$ShellCompDirectiveError=%[4]d
|
||||||
$ShellCompDirectiveNoSpace=%[4]d
|
$ShellCompDirectiveNoSpace=%[5]d
|
||||||
$ShellCompDirectiveNoFileComp=%[5]d
|
$ShellCompDirectiveNoFileComp=%[6]d
|
||||||
$ShellCompDirectiveFilterFileExt=%[6]d
|
$ShellCompDirectiveFilterFileExt=%[7]d
|
||||||
$ShellCompDirectiveFilterDirs=%[7]d
|
$ShellCompDirectiveFilterDirs=%[8]d
|
||||||
|
$ShellCompDirectiveKeepOrder=%[9]d
|
||||||
|
|
||||||
# Prepare the command to request completions for the program.
|
# Prepare the command to request completions for the program.
|
||||||
# Split the command at the first space to separate the program and arguments.
|
# Split the command at the first space to separate the program and arguments.
|
||||||
$Program,$Arguments = $Command.Split(" ",2)
|
$Program,$Arguments = $Command.Split(" ",2)
|
||||||
$RequestComp="$Program %[2]s $Arguments"
|
|
||||||
|
$RequestComp="$Program %[3]s $Arguments"
|
||||||
__%[1]s_debug "RequestComp: $RequestComp"
|
__%[1]s_debug "RequestComp: $RequestComp"
|
||||||
|
|
||||||
# we cannot use $WordToComplete because it
|
# we cannot use $WordToComplete because it
|
||||||
|
@ -85,16 +107,27 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
||||||
# If the last parameter is complete (there is a space following it)
|
# 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.
|
# We add an extra empty parameter so we can indicate this to the go method.
|
||||||
__%[1]s_debug "Adding extra empty parameter"
|
__%[1]s_debug "Adding extra empty parameter"
|
||||||
`+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+`
|
# PowerShell 7.2+ changed the way how the arguments are passed to executables,
|
||||||
`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+`
|
# 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"
|
__%[1]s_debug "Calling $RequestComp"
|
||||||
|
# First disable ActiveHelp which is not supported for Powershell
|
||||||
|
$env:%[10]s=0
|
||||||
|
|
||||||
#call the command store the output in $out and redirect stderr and stdout to null
|
#call the command store the output in $out and redirect stderr and stdout to null
|
||||||
# $Out is an array contains each line per element
|
# $Out is an array contains each line per element
|
||||||
Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
|
Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
|
||||||
|
|
||||||
|
|
||||||
# get directive from last line
|
# get directive from last line
|
||||||
[int]$Directive = $Out[-1].TrimStart(':')
|
[int]$Directive = $Out[-1].TrimStart(':')
|
||||||
if ($Directive -eq "") {
|
if ($Directive -eq "") {
|
||||||
|
@ -114,7 +147,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
$Longest = 0
|
$Longest = 0
|
||||||
$Values = $Out | ForEach-Object {
|
[Array]$Values = $Out | ForEach-Object {
|
||||||
#Split the output in name and description
|
#Split the output in name and description
|
||||||
`+" $Name, $Description = $_.Split(\"`t\",2)"+`
|
`+" $Name, $Description = $_.Split(\"`t\",2)"+`
|
||||||
__%[1]s_debug "Name: $Name Description: $Description"
|
__%[1]s_debug "Name: $Name Description: $Description"
|
||||||
|
@ -159,6 +192,11 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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 ) {
|
if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
|
||||||
__%[1]s_debug "ShellCompDirectiveNoFileComp is called"
|
__%[1]s_debug "ShellCompDirectiveNoFileComp is called"
|
||||||
|
|
||||||
|
@ -240,9 +278,11 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, name, compCmd,
|
|
||||||
|
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock $__%[2]sCompleterBlock
|
||||||
|
`, name, nameForVar, compCmd,
|
||||||
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
|
func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
|
33
powershell_completions_test.go
Normal file
33
powershell_completions_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPwshCompletionNoActiveHelp(t *testing.T) {
|
||||||
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assertNoErr(t, c.GenPowerShellCompletion(buf))
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
// check that active help is being disabled
|
||||||
|
activeHelpVar := activeHelpEnvVar(c.Name())
|
||||||
|
check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
## Projects using Cobra
|
## Projects using Cobra
|
||||||
|
|
||||||
|
- [Allero](https://github.com/allero-io/allero)
|
||||||
- [Arduino CLI](https://github.com/arduino/arduino-cli)
|
- [Arduino CLI](https://github.com/arduino/arduino-cli)
|
||||||
- [Bleve](http://www.blevesearch.com/)
|
- [Bleve](https://blevesearch.com/)
|
||||||
- [CockroachDB](http://www.cockroachlabs.com/)
|
- [Cilium](https://cilium.io/)
|
||||||
|
- [CloudQuery](https://github.com/cloudquery/cloudquery)
|
||||||
|
- [CockroachDB](https://www.cockroachlabs.com/)
|
||||||
|
- [Constellation](https://github.com/edgelesssys/constellation)
|
||||||
- [Cosmos SDK](https://github.com/cosmos/cosmos-sdk)
|
- [Cosmos SDK](https://github.com/cosmos/cosmos-sdk)
|
||||||
- [Datree](https://github.com/datreeio/datree)
|
- [Datree](https://github.com/datreeio/datree)
|
||||||
- [Delve](https://github.com/derekparker/delve)
|
- [Delve](https://github.com/derekparker/delve)
|
||||||
|
@ -11,18 +15,19 @@
|
||||||
- [Gardener](https://github.com/gardener/gardenctl)
|
- [Gardener](https://github.com/gardener/gardenctl)
|
||||||
- [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl)
|
- [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl)
|
||||||
- [Git Bump](https://github.com/erdaltsksn/git-bump)
|
- [Git Bump](https://github.com/erdaltsksn/git-bump)
|
||||||
- [Github CLI](https://github.com/cli/cli)
|
- [GitHub CLI](https://github.com/cli/cli)
|
||||||
- [GitHub Labeler](https://github.com/erdaltsksn/gh-label)
|
- [GitHub Labeler](https://github.com/erdaltsksn/gh-label)
|
||||||
- [Golangci-lint](https://golangci-lint.run)
|
- [Golangci-lint](https://golangci-lint.run)
|
||||||
- [GopherJS](http://www.gopherjs.org/)
|
- [GopherJS](https://github.com/gopherjs/gopherjs)
|
||||||
- [GoReleaser](https://goreleaser.com)
|
- [GoReleaser](https://goreleaser.com)
|
||||||
- [Helm](https://helm.sh)
|
- [Helm](https://helm.sh)
|
||||||
- [Hugo](https://gohugo.io)
|
- [Hugo](https://gohugo.io)
|
||||||
- [Infracost](https://github.com/infracost/infracost)
|
- [Infracost](https://github.com/infracost/infracost)
|
||||||
- [Istio](https://istio.io)
|
- [Istio](https://istio.io)
|
||||||
- [Kool](https://github.com/kool-dev/kool)
|
- [Kool](https://github.com/kool-dev/kool)
|
||||||
- [Kubernetes](http://kubernetes.io/)
|
- [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/)
|
- [Linkerd](https://linkerd.io/)
|
||||||
- [Mattermost-server](https://github.com/mattermost/mattermost-server)
|
- [Mattermost-server](https://github.com/mattermost/mattermost-server)
|
||||||
- [Mercure](https://mercure.rocks/)
|
- [Mercure](https://mercure.rocks/)
|
||||||
|
@ -33,21 +38,25 @@
|
||||||
- [Multi-gitter](https://github.com/lindell/multi-gitter)
|
- [Multi-gitter](https://github.com/lindell/multi-gitter)
|
||||||
- [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
|
- [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
|
||||||
- [nFPM](https://nfpm.goreleaser.com)
|
- [nFPM](https://nfpm.goreleaser.com)
|
||||||
|
- [Okteto](https://github.com/okteto/okteto)
|
||||||
- [OpenShift](https://www.openshift.com/)
|
- [OpenShift](https://www.openshift.com/)
|
||||||
- [Ory Hydra](https://github.com/ory/hydra)
|
- [Ory Hydra](https://github.com/ory/hydra)
|
||||||
- [Ory Kratos](https://github.com/ory/kratos)
|
- [Ory Kratos](https://github.com/ory/kratos)
|
||||||
- [Pixie](https://github.com/pixie-io/pixie)
|
- [Pixie](https://github.com/pixie-io/pixie)
|
||||||
- [Polygon Edge](https://github.com/0xPolygon/polygon-edge)
|
- [Polygon Edge](https://github.com/0xPolygon/polygon-edge)
|
||||||
- [Pouch](https://github.com/alibaba/pouch)
|
- [Pouch](https://github.com/alibaba/pouch)
|
||||||
- [ProjectAtomic (enterprise)](http://www.projectatomic.io/)
|
- [ProjectAtomic (enterprise)](https://www.projectatomic.io/)
|
||||||
- [Prototool](https://github.com/uber/prototool)
|
- [Prototool](https://github.com/uber/prototool)
|
||||||
|
- [Pulumi](https://www.pulumi.com)
|
||||||
- [QRcp](https://github.com/claudiodangelis/qrcp)
|
- [QRcp](https://github.com/claudiodangelis/qrcp)
|
||||||
- [Random](https://github.com/erdaltsksn/random)
|
- [Random](https://github.com/erdaltsksn/random)
|
||||||
- [Rclone](https://rclone.org/)
|
- [Rclone](https://rclone.org/)
|
||||||
- [Scaleway CLI](https://github.com/scaleway/scaleway-cli)
|
- [Scaleway CLI](https://github.com/scaleway/scaleway-cli)
|
||||||
|
- [Sia](https://github.com/SiaFoundation/siad)
|
||||||
- [Skaffold](https://skaffold.dev/)
|
- [Skaffold](https://skaffold.dev/)
|
||||||
- [Tendermint](https://github.com/tendermint/tendermint)
|
- [Tendermint](https://github.com/tendermint/tendermint)
|
||||||
- [Twitch CLI](https://github.com/twitchdev/twitch-cli)
|
- [Twitch CLI](https://github.com/twitchdev/twitch-cli)
|
||||||
- [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli)
|
- [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli)
|
||||||
- VMware's [Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) & [Tanzu Framework](https://github.com/vmware-tanzu/tanzu-framework)
|
- 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/)
|
- [Werf](https://werf.io/)
|
||||||
|
- [ZITADEL](https://github.com/zitadel/zitadel)
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -40,7 +40,7 @@ Bash:
|
||||||
# Linux:
|
# Linux:
|
||||||
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
||||||
# macOS:
|
# macOS:
|
||||||
$ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
|
$ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
||||||
|
|
||||||
Zsh:
|
Zsh:
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ PowerShell:
|
||||||
`,cmd.Root().Name()),
|
`,cmd.Root().Name()),
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
Args: cobra.ExactValidArgs(1),
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "bash":
|
case "bash":
|
||||||
|
@ -99,6 +99,11 @@ To tell Cobra *not* to provide the default `completion` command:
|
||||||
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To tell Cobra to mark the default `completion` command as *hidden*:
|
||||||
|
```
|
||||||
|
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
||||||
|
```
|
||||||
|
|
||||||
To tell Cobra *not* to provide the user with the `--no-descriptions` flag to the completion sub-commands:
|
To tell Cobra *not* to provide the user with the `--no-descriptions` flag to the completion sub-commands:
|
||||||
```
|
```
|
||||||
rootCmd.CompletionOptions.DisableNoDescFlag = true
|
rootCmd.CompletionOptions.DisableNoDescFlag = true
|
||||||
|
@ -157,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 aliases are shown to the user on tab completion only if no completions were found within sub-commands or `ValidArgs`.
|
||||||
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`.
|
|
||||||
|
|
||||||
### Dynamic completion of nouns
|
### Dynamic completion of nouns
|
||||||
|
|
||||||
|
@ -232,6 +228,10 @@ ShellCompDirectiveFilterFileExt
|
||||||
// return []string{"themes"}, ShellCompDirectiveFilterDirs
|
// return []string{"themes"}, ShellCompDirectiveFilterDirs
|
||||||
//
|
//
|
||||||
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.
|
***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.
|
||||||
|
@ -380,6 +380,19 @@ or
|
||||||
```go
|
```go
|
||||||
ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"}
|
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
|
## Bash completions
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
@ -530,6 +543,21 @@ search for a keyword in charts
|
||||||
$ helm s[tab]
|
$ helm s[tab]
|
||||||
search show status
|
search show status
|
||||||
```
|
```
|
||||||
|
### Aliases
|
||||||
|
|
||||||
|
You can also configure `powershell` aliases for your program and they will also support completions.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sal aliasname origcommand
|
||||||
|
$ Register-ArgumentCompleter -CommandName 'aliasname' -ScriptBlock $__origcommandCompleterBlock
|
||||||
|
|
||||||
|
# and now when you run `aliasname` completion will make
|
||||||
|
# suggestions as it did for `origcommand`.
|
||||||
|
|
||||||
|
$ aliasname <tab>
|
||||||
|
completion firstcommand secondcommand
|
||||||
|
```
|
||||||
|
The name of the completer block variable is of the form `$__<programName>CompleterBlock` where every `-` and `:` in the program name have been replaced with `_`, to respect powershell naming syntax.
|
||||||
|
|
||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
|
|
101
user_guide.md
101
user_guide.md
|
@ -51,7 +51,7 @@ var rootCmd = &cobra.Command{
|
||||||
Short: "Hugo is a very fast static site generator",
|
Short: "Hugo is a very fast static site generator",
|
||||||
Long: `A Fast and Flexible Static Site Generator built with
|
Long: `A Fast and Flexible Static Site Generator built with
|
||||||
love by spf13 and friends in Go.
|
love by spf13 and friends in Go.
|
||||||
Complete documentation is available at http://hugo.spf13.com`,
|
Complete documentation is available at https://gohugo.io/documentation/`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Do Stuff Here
|
// Do Stuff Here
|
||||||
},
|
},
|
||||||
|
@ -302,19 +302,19 @@ rootCmd.MarkPersistentFlagRequired("region")
|
||||||
|
|
||||||
### Flag Groups
|
### Flag Groups
|
||||||
|
|
||||||
If you have different flags that must be provided together (e.g. if they provide the `--username` flag they MUST provide the `--password` flag as well) then
|
If you have different flags that must be provided together (e.g. if they provide the `--username` flag they MUST provide the `--password` flag as well) then
|
||||||
Cobra can enforce that requirement:
|
Cobra can enforce that requirement:
|
||||||
```go
|
```go
|
||||||
rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)")
|
rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)")
|
||||||
rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)")
|
rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)")
|
||||||
rootCmd.MarkFlagsRequiredTogether("username", "password")
|
rootCmd.MarkFlagsRequiredTogether("username", "password")
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also prevent different flags from being provided together if they represent mutually
|
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:
|
exclusive options such as specifying an output format as either `--json` or `--yaml` but never both:
|
||||||
```go
|
```go
|
||||||
rootCmd.Flags().BoolVar(&u, "json", false, "Output in JSON")
|
rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON")
|
||||||
rootCmd.Flags().BoolVar(&pw, "yaml", false, "Output in YAML")
|
rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML")
|
||||||
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
|
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -326,30 +326,48 @@ In both of these cases:
|
||||||
|
|
||||||
## Positional and Custom Arguments
|
## Positional and Custom Arguments
|
||||||
|
|
||||||
Validation of positional arguments can be specified using the `Args` field
|
Validation of positional arguments can be specified using the `Args` field of `Command`.
|
||||||
of `Command`.
|
|
||||||
|
|
||||||
The following validators are built in:
|
The following validators are built in:
|
||||||
|
|
||||||
- `NoArgs` - the command will report an error if there are any positional args.
|
- Number of arguments:
|
||||||
- `ArbitraryArgs` - the command will accept any args.
|
- `NoArgs` - report an error if there are any positional args.
|
||||||
- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`.
|
- `ArbitraryArgs` - accept any number of args.
|
||||||
- `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args.
|
- `MinimumNArgs(int)` - report an error if less than N positional args are provided.
|
||||||
- `MaximumNArgs(int)` - the command will report an error if there are more than N positional args.
|
- `MaximumNArgs(int)` - report an error if more than N positional args are provided.
|
||||||
- `ExactArgs(int)` - the command will report an error if there are not exactly N positional args.
|
- `ExactArgs(int)` - report an error if there are not exactly N positional args.
|
||||||
- `ExactValidArgs(int)` - the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command`
|
- `RangeArgs(min, max)` - report an error if the number of args is not between `min` and `max`.
|
||||||
- `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args.
|
- Content of the arguments:
|
||||||
- `MatchAll(pargs ...PositionalArgs)` - enables combining existing checks with arbitrary other checks (e.g. you want to check the ExactArgs length along with other qualities).
|
- `OnlyValidArgs` - report an error if there are any positional args not specified in the `ValidArgs` field of `Command`, which can optionally be set to a list of valid values for positional args.
|
||||||
|
|
||||||
An example of setting the custom validator:
|
If `Args` is undefined or `nil`, it defaults to `ArbitraryArgs`.
|
||||||
|
|
||||||
|
Moreover, `MatchAll(pargs ...PositionalArgs)` enables combining existing checks with arbitrary other checks.
|
||||||
|
For instance, if you want to report an error if there are not exactly N positional args OR if there are any positional
|
||||||
|
args that are not in the `ValidArgs` field of `Command`, you can call `MatchAll` on `ExactArgs` and `OnlyValidArgs`, as
|
||||||
|
shown below:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Short: "hello",
|
||||||
|
Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("Hello, World!")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to set any custom validator that satisfies `func(cmd *cobra.Command, args []string) error`.
|
||||||
|
For example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var cmd = &cobra.Command{
|
var cmd = &cobra.Command{
|
||||||
Short: "hello",
|
Short: "hello",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
// Optionally run one of the validators provided by cobra
|
||||||
return errors.New("requires a color argument")
|
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
// Run the custom validation logic
|
||||||
if myapp.IsValidColor(args[0]) {
|
if myapp.IsValidColor(args[0]) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -429,7 +447,7 @@ a count and a string.`,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/).
|
For a more complete example of a larger application, please checkout [Hugo](https://gohugo.io/).
|
||||||
|
|
||||||
## Help Command
|
## Help Command
|
||||||
|
|
||||||
|
@ -444,37 +462,46 @@ create' is called. Every command will automatically have the '--help' flag adde
|
||||||
The following output is automatically generated by Cobra. Nothing beyond the
|
The following output is automatically generated by Cobra. Nothing beyond the
|
||||||
command and flag definitions are needed.
|
command and flag definitions are needed.
|
||||||
|
|
||||||
$ cobra help
|
$ cobra-cli help
|
||||||
|
|
||||||
Cobra is a CLI library for Go that empowers applications.
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
This application is a tool to generate the needed files
|
This application is a tool to generate the needed files
|
||||||
to quickly create a Cobra application.
|
to quickly create a Cobra application.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
cobra [command]
|
cobra-cli [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
add Add a command to a Cobra Application
|
add Add a command to a Cobra Application
|
||||||
|
completion Generate the autocompletion script for the specified shell
|
||||||
help Help about any command
|
help Help about any command
|
||||||
init Initialize a Cobra Application
|
init Initialize a Cobra Application
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-a, --author string author name for copyright attribution (default "YOUR NAME")
|
-a, --author string author name for copyright attribution (default "YOUR NAME")
|
||||||
--config string config file (default is $HOME/.cobra.yaml)
|
--config string config file (default is $HOME/.cobra.yaml)
|
||||||
-h, --help help for cobra
|
-h, --help help for cobra-cli
|
||||||
-l, --license string name of license for the project
|
-l, --license string name of license for the project
|
||||||
--viper use Viper for configuration (default true)
|
--viper use Viper for configuration
|
||||||
|
|
||||||
Use "cobra [command] --help" for more information about a command.
|
Use "cobra-cli [command] --help" for more information about a command.
|
||||||
|
|
||||||
|
|
||||||
Help is just a command like any other. There is no special logic or behavior
|
Help is just a command like any other. There is no special logic or behavior
|
||||||
around it. In fact, you can provide your own if you want.
|
around it. In fact, you can provide your own if you want.
|
||||||
|
|
||||||
|
### Grouping commands in help
|
||||||
|
|
||||||
|
Cobra supports grouping of available commands in the help output. To group commands, each group must be explicitly
|
||||||
|
defined using `AddGroup()` on the parent command. Then a subcommand can be added to a group using the `GroupID` element
|
||||||
|
of that subcommand. The groups will appear in the help output in the same order as they are defined using different
|
||||||
|
calls to `AddGroup()`. If you use the generated `help` or `completion` commands, you can set their group ids using
|
||||||
|
`SetHelpCommandGroupId()` and `SetCompletionCommandGroupId()` on the root command, respectively.
|
||||||
|
|
||||||
### Defining your own help
|
### Defining your own help
|
||||||
|
|
||||||
You can provide your own Help command or your own template for the default command to use
|
You can provide your own Help command or your own template for the default command to use
|
||||||
with following functions:
|
with the following functions:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cmd.SetHelpCommand(cmd *Command)
|
cmd.SetHelpCommand(cmd *Command)
|
||||||
|
@ -493,22 +520,23 @@ showing the user the 'usage'.
|
||||||
You may recognize this from the help above. That's because the default help
|
You may recognize this from the help above. That's because the default help
|
||||||
embeds the usage as part of its output.
|
embeds the usage as part of its output.
|
||||||
|
|
||||||
$ cobra --invalid
|
$ cobra-cli --invalid
|
||||||
Error: unknown flag: --invalid
|
Error: unknown flag: --invalid
|
||||||
Usage:
|
Usage:
|
||||||
cobra [command]
|
cobra-cli [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
add Add a command to a Cobra Application
|
add Add a command to a Cobra Application
|
||||||
|
completion Generate the autocompletion script for the specified shell
|
||||||
help Help about any command
|
help Help about any command
|
||||||
init Initialize a Cobra Application
|
init Initialize a Cobra Application
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-a, --author string author name for copyright attribution (default "YOUR NAME")
|
-a, --author string author name for copyright attribution (default "YOUR NAME")
|
||||||
--config string config file (default is $HOME/.cobra.yaml)
|
--config string config file (default is $HOME/.cobra.yaml)
|
||||||
-h, --help help for cobra
|
-h, --help help for cobra-cli
|
||||||
-l, --license string name of license for the project
|
-l, --license string name of license for the project
|
||||||
--viper use Viper for configuration (default true)
|
--viper use Viper for configuration
|
||||||
|
|
||||||
Use "cobra [command] --help" for more information about a command.
|
Use "cobra [command] --help" for more information about a command.
|
||||||
|
|
||||||
|
@ -627,7 +655,7 @@ Did you mean this?
|
||||||
Run 'hugo --help' for usage.
|
Run 'hugo --help' for usage.
|
||||||
```
|
```
|
||||||
|
|
||||||
Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
|
Suggestions are automatically generated based on existing subcommands and use an implementation of [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
|
||||||
|
|
||||||
If you need to disable suggestions or tweak the string distance in your command, use:
|
If you need to disable suggestions or tweak the string distance in your command, use:
|
||||||
|
|
||||||
|
@ -641,7 +669,8 @@ or
|
||||||
command.SuggestionsMinimumDistance = 1
|
command.SuggestionsMinimumDistance = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example:
|
You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but make sense in your set of commands but for which
|
||||||
|
you don't want aliases. Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl remove
|
$ kubectl remove
|
||||||
|
@ -660,3 +689,7 @@ Cobra can generate documentation based on subcommands, flags, etc. Read more abo
|
||||||
## Generating shell completions
|
## 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](shell_completions.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).
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -75,7 +89,7 @@ func genZshComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
if !includeDesc {
|
if !includeDesc {
|
||||||
compCmd = ShellCompNoDescRequestCmd
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
}
|
}
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf(`#compdef _%[1]s %[1]s
|
WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s
|
||||||
|
|
||||||
# zsh completion for %-36[1]s -*- shell-script -*-
|
# zsh completion for %-36[1]s -*- shell-script -*-
|
||||||
|
|
||||||
|
@ -94,8 +108,9 @@ _%[1]s()
|
||||||
local shellCompDirectiveNoFileComp=%[5]d
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
local shellCompDirectiveFilterFileExt=%[6]d
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
local shellCompDirectiveFilterDirs=%[7]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
|
local -a completions
|
||||||
|
|
||||||
__%[1]s_debug "\n========= starting completion logic =========="
|
__%[1]s_debug "\n========= starting completion logic =========="
|
||||||
|
@ -163,7 +178,24 @@ _%[1]s()
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
local activeHelpMarker="%[9]s"
|
||||||
|
local endIndex=${#activeHelpMarker}
|
||||||
|
local startIndex=$((${#activeHelpMarker}+1))
|
||||||
|
local hasActiveHelp=0
|
||||||
while IFS='\n' read -r comp; do
|
while IFS='\n' read -r comp; do
|
||||||
|
# Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker)
|
||||||
|
if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then
|
||||||
|
__%[1]s_debug "ActiveHelp found: $comp"
|
||||||
|
comp="${comp[$startIndex,-1]}"
|
||||||
|
if [ -n "$comp" ]; then
|
||||||
|
compadd -x "${comp}"
|
||||||
|
__%[1]s_debug "ActiveHelp will need delimiter"
|
||||||
|
hasActiveHelp=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$comp" ]; then
|
if [ -n "$comp" ]; then
|
||||||
# If requested, completions are returned with a description.
|
# If requested, completions are returned with a description.
|
||||||
# The description is preceded by a TAB character.
|
# The description is preceded by a TAB character.
|
||||||
|
@ -180,11 +212,27 @@ _%[1]s()
|
||||||
fi
|
fi
|
||||||
done < <(printf "%%s\n" "${out[@]}")
|
done < <(printf "%%s\n" "${out[@]}")
|
||||||
|
|
||||||
|
# Add a delimiter after the activeHelp statements, but only if:
|
||||||
|
# - there are completions following the activeHelp statements, or
|
||||||
|
# - file completion will be performed (so there will be choices after the activeHelp)
|
||||||
|
if [ $hasActiveHelp -eq 1 ]; then
|
||||||
|
if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then
|
||||||
|
__%[1]s_debug "Adding activeHelp delimiter"
|
||||||
|
compadd -x "--"
|
||||||
|
hasActiveHelp=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
|
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
|
||||||
__%[1]s_debug "Activating nospace."
|
__%[1]s_debug "Activating nospace."
|
||||||
noSpace="-S ''"
|
noSpace="-S ''"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then
|
||||||
|
__%[1]s_debug "Activating keep order."
|
||||||
|
keepOrder="-V"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
|
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
|
||||||
# File extension filtering
|
# File extension filtering
|
||||||
local filteringCmd
|
local filteringCmd
|
||||||
|
@ -220,7 +268,7 @@ _%[1]s()
|
||||||
return $result
|
return $result
|
||||||
else
|
else
|
||||||
__%[1]s_debug "Calling _describe"
|
__%[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"
|
__%[1]s_debug "_describe found some completions"
|
||||||
|
|
||||||
# Return the success of having called _describe
|
# Return the success of having called _describe
|
||||||
|
@ -254,5 +302,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then
|
||||||
fi
|
fi
|
||||||
`, name, compCmd,
|
`, name, compCmd,
|
||||||
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
|
||||||
|
activeHelpMarker))
|
||||||
}
|
}
|
||||||
|
|
33
zsh_completions_test.go
Normal file
33
zsh_completions_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2013-2022 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestZshCompletionWithActiveHelp(t *testing.T) {
|
||||||
|
c := &Command{Use: "c", Run: emptyRun}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assertNoErr(t, c.GenZshCompletion(buf))
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
// check that active help is not being disabled
|
||||||
|
activeHelpVar := activeHelpEnvVar(c.Name())
|
||||||
|
checkOmit(t, output, fmt.Sprintf("%s=0", activeHelpVar))
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue