Merge branch 'spf13:main' into main

This commit is contained in:
Dean Eigenmann 2023-10-13 14:47:58 -05:00 committed by GitHub
commit de76854134
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 575 additions and 142 deletions

View file

@ -25,7 +25,7 @@ jobs:
-v $(pwd):/wrk -w /wrk
ghcr.io/google/addlicense
-c 'The Cobra Authors'
-y '2013-2022'
-y '2013-2023'
-l apache
-ignore '.github/**'
-check
@ -43,13 +43,13 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: '^1.19'
go-version: '^1.21'
check-latest: true
cache: true
- uses: actions/checkout@v3
- uses: golangci/golangci-lint-action@v3.4.0
- uses: golangci/golangci-lint-action@v3.7.0
with:
version: latest
args: --verbose
@ -63,10 +63,11 @@ jobs:
- ubuntu
- macOS
go:
- 16
- 17
- 18
- 19
- 20
- 21
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
runs-on: ${{ matrix.platform }}-latest
steps:

View file

@ -1,4 +1,4 @@
# Copyright 2013-2022 The Cobra Authors
# Copyright 2013-2023 The Cobra Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -19,7 +19,7 @@ linters:
disable-all: true
enable:
#- bodyclose
- deadcode
# - deadcode ! deprecated since v1.49.0; replaced by 'unused'
#- depguard
#- dogsled
#- dupl
@ -51,12 +51,12 @@ linters:
#- rowserrcheck
#- scopelint
#- staticcheck
- structcheck
#- structcheck ! deprecated since v1.49.0; replaced by 'unused'
#- stylecheck
#- typecheck
- unconvert
#- unparam
#- unused
- varcheck
- unused
# - varcheck ! deprecated since v1.49.0; replaced by 'unused'
#- whitespace
fast: false

View file

@ -1,10 +1,10 @@
![cobra logo](https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png)
![cobra logo](assets/CobraMain.png)
Cobra is a library for creating powerful modern CLI applications.
Cobra is used in many Go projects such as [Kubernetes](https://kubernetes.io/),
[Hugo](https://gohugo.io), and [GitHub CLI](https://github.com/cli/cli) to
name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra.
name a few. [This list](site/content/projects_using_cobra.md) contains a more extensive list of projects using Cobra.
[![](https://img.shields.io/github/actions/workflow/status/spf13/cobra/test.yml?branch=main&longCache=true&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest)
[![Go Reference](https://pkg.go.dev/badge/github.com/spf13/cobra.svg)](https://pkg.go.dev/github.com/spf13/cobra)
@ -80,7 +80,7 @@ which maintains the same interface while adding POSIX compliance.
# Installing
Using Cobra is easy. First, use `go get` to install the latest version
of the library.
of the library.
```
go get -u github.com/spf13/cobra@latest
@ -105,8 +105,8 @@ go install github.com/spf13/cobra-cli@latest
For complete details on using the Cobra-CLI generator, please read [The Cobra Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md)
For complete details on using the Cobra library, please read the [The Cobra User Guide](user_guide.md).
For complete details on using the Cobra library, please read the [The Cobra User Guide](site/content/user_guide.md).
# License
Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt)
Cobra is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt)

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

BIN
assets/CobraMain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -85,7 +85,7 @@ __%[1]s_handle_go_custom_completion()
local out requestComp lastParam lastChar comp directive args
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly %[1]s allows to handle aliases
# Calling ${words[0]} instead of directly %[1]s allows handling aliases
args=("${words[@]:1}")
# Disable ActiveHelp which is not supported for bash completion v1
requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}"

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -57,7 +57,7 @@ __%[1]s_get_completion_results() {
local requestComp lastParam lastChar args
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly %[1]s allows to handle aliases
# Calling ${words[0]} instead of directly %[1]s allows handling aliases
args=("${words[@]:1}")
requestComp="${words[0]} %[2]s ${args[*]}"
@ -101,6 +101,7 @@ __%[1]s_process_completion_results() {
local shellCompDirectiveNoFileComp=%[5]d
local shellCompDirectiveFilterFileExt=%[6]d
local shellCompDirectiveFilterDirs=%[7]d
local shellCompDirectiveKeepOrder=%[8]d
if (((directive & shellCompDirectiveError) != 0)); then
# Error code. No completion.
@ -115,6 +116,19 @@ __%[1]s_process_completion_results() {
__%[1]s_debug "No space directive not supported in this version of bash"
fi
fi
if (((directive & shellCompDirectiveKeepOrder) != 0)); then
if [[ $(type -t compopt) == builtin ]]; then
# no sort isn't supported for bash less than < 4.4
if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then
__%[1]s_debug "No sort directive not supported in this version of bash"
else
__%[1]s_debug "Activating keep order"
compopt -o nosort
fi
else
__%[1]s_debug "No sort directive not supported in this version of bash"
fi
fi
if (((directive & shellCompDirectiveNoFileComp) != 0)); then
if [[ $(type -t compopt) == builtin ]]; then
__%[1]s_debug "Activating no file completion"
@ -183,7 +197,7 @@ __%[1]s_process_completion_results() {
# Separate activeHelp lines from real completions.
# Fills the $activeHelp and $completions arrays.
__%[1]s_extract_activeHelp() {
local activeHelpMarker="%[8]s"
local activeHelpMarker="%[9]s"
local endIndex=${#activeHelpMarker}
while IFS='' read -r comp; do
@ -360,7 +374,7 @@ fi
# ex: ts=4 sw=4 et filetype=sh
`, name, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
activeHelpMarker))
}

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -48,7 +48,7 @@ const (
defaultCaseInsensitive = false
)
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
// EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing
// to automatically enable in CLI tools.
// Set this to true to enable it.
var EnablePrefixMatching = defaultPrefixMatching

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -117,6 +117,8 @@ type Command struct {
// * PostRun()
// * PersistentPostRun()
// All functions get the same args, the arguments after the command name.
// The *PreRun and *PostRun functions will only be executed if the Run function of the current
// command has been declared.
//
// PersistentPreRun: children of this command will inherit and execute.
PersistentPreRun func(cmd *Command, args []string)
@ -185,6 +187,9 @@ type Command struct {
// versionTemplate is the version template defined by user.
versionTemplate string
// errPrefix is the error message prefix defined by user.
errPrefix string
// inReader is a reader defined by the user that replaces stdin
inReader io.Reader
// outWriter is a writer defined by the user that replaces stdout
@ -350,6 +355,11 @@ func (c *Command) SetVersionTemplate(s string) {
c.versionTemplate = s
}
// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix.
func (c *Command) SetErrPrefix(s string) {
c.errPrefix = s
}
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
// The user should not have a cyclic dependency on commands.
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
@ -599,6 +609,18 @@ func (c *Command) VersionTemplate() string {
`
}
// ErrPrefix return error message prefix for the command
func (c *Command) ErrPrefix() string {
if c.errPrefix != "" {
return c.errPrefix
}
if c.HasParent() {
return c.parent.ErrPrefix()
}
return "Error:"
}
func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
flag := fs.Lookup(name)
if flag == nil {
@ -756,7 +778,9 @@ func (c *Command) findNext(next string) *Command {
}
if len(matches) == 1 {
return matches[0]
// Temporarily disable gosec G602, which produces a false positive.
// See https://github.com/securego/gosec/issues/1005.
return matches[0] // #nosec G602
}
return nil
@ -1069,7 +1093,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
c = cmd
}
if !c.SilenceErrors {
c.PrintErrln("Error:", err.Error())
c.PrintErrln(c.ErrPrefix(), err.Error())
c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
}
return c, err
@ -1098,7 +1122,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
// If root command has SilenceErrors flagged,
// all subcommands should respect it
if !cmd.SilenceErrors && !c.SilenceErrors {
c.PrintErrln("Error:", err.Error())
c.PrintErrln(cmd.ErrPrefix(), err.Error())
}
// If root command has SilenceUsage flagged,

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -438,7 +438,7 @@ func TestFlagLong(t *testing.T) {
output, err := executeCommand(c, "--intf=7", "--sf=abc", "one", "--", "two")
if output != "" {
t.Errorf("Unexpected output: %v", err)
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
@ -475,7 +475,7 @@ func TestFlagShort(t *testing.T) {
output, err := executeCommand(c, "-i", "7", "-sabc", "one", "two")
if output != "" {
t.Errorf("Unexpected output: %v", err)
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
@ -504,7 +504,7 @@ func TestChildFlag(t *testing.T) {
output, err := executeCommand(rootCmd, "child", "-i7")
if output != "" {
t.Errorf("Unexpected output: %v", err)
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
@ -1099,6 +1099,39 @@ func TestShorthandVersionTemplate(t *testing.T) {
checkStringContains(t, output, "customized version: 1.0.0")
}
func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
rootCmd.SetErrPrefix("root error prefix:")
rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})
output, err := executeCommand(rootCmd, "sub", "--unknown-flag")
if err == nil {
t.Errorf("Expected error")
}
checkStringContains(t, output, "root error prefix: unknown flag: --unknown-flag")
}
func TestRootAndSubErrPrefix(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
subCmd := &Command{Use: "sub", Run: emptyRun}
rootCmd.AddCommand(subCmd)
rootCmd.SetErrPrefix("root error prefix:")
subCmd.SetErrPrefix("sub error prefix:")
if output, err := executeCommand(rootCmd, "--unknown-root-flag"); err == nil {
t.Errorf("Expected error")
} else {
checkStringContains(t, output, "root error prefix: unknown flag: --unknown-root-flag")
}
if output, err := executeCommand(rootCmd, "sub", "--unknown-sub-flag"); err == nil {
t.Errorf("Expected error")
} else {
checkStringContains(t, output, "sub error prefix: unknown flag: --unknown-sub-flag")
}
}
func TestVersionFlagExecutedOnSubcommand(t *testing.T) {
rootCmd := &Command{Use: "root", Version: "1.0.0"}
rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -77,6 +77,10 @@ const (
// obtain the same behavior but only for flags.
ShellCompDirectiveFilterDirs
// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
// in which the completions are provided
ShellCompDirectiveKeepOrder
// ===========================================================================
// All directives using iota should be above this one.
@ -141,6 +145,25 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman
return nil
}
// GetFlagCompletion returns the completion function for the given flag, if available.
func GetFlagCompletion(flag *pflag.Flag) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) {
flagCompletionMutex.RLock()
defer flagCompletionMutex.RUnlock()
completionFunc, exists := flagCompletionFunctions[flag]
return completionFunc, exists
}
// GetFlagCompletionByName returns the completion function for the given flag in the command by name, if available.
func (c *Command) GetFlagCompletionByName(flagName string) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) {
flag := c.Flags().Lookup(flagName)
if flag == nil {
return nil, false
}
return GetFlagCompletion(flag)
}
// Returns a string listing the different directive enabled in the specified parameter
func (d ShellCompDirective) string() string {
var directives []string
@ -159,6 +182,9 @@ func (d ShellCompDirective) string() string {
if d&ShellCompDirectiveFilterDirs != 0 {
directives = append(directives, "ShellCompDirectiveFilterDirs")
}
if d&ShellCompDirectiveKeepOrder != 0 {
directives = append(directives, "ShellCompDirectiveKeepOrder")
}
if len(directives) == 0 {
directives = append(directives, "ShellCompDirectiveDefault")
}
@ -727,7 +753,7 @@ to enable it. You can execute the following once:
To load completions in your current shell session:
source <(%[1]s completion zsh); compdef _%[1]s %[1]s
source <(%[1]s completion zsh)
To load completions for every new session, execute once:

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -2830,6 +2830,104 @@ func TestCompletionForGroupedFlags(t *testing.T) {
}
}
func TestCompletionForOneRequiredGroupFlags(t *testing.T) {
getCmd := func() *Command {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
childCmd := &Command{
Use: "child",
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"subArg"}, ShellCompDirectiveNoFileComp
},
Run: emptyRun,
}
rootCmd.AddCommand(childCmd)
rootCmd.PersistentFlags().Int("ingroup1", -1, "ingroup1")
rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2")
childCmd.Flags().Bool("ingroup3", false, "ingroup3")
childCmd.Flags().Bool("nogroup", false, "nogroup")
// Add flags to a group
childCmd.MarkFlagsOneRequired("ingroup1", "ingroup2", "ingroup3")
return rootCmd
}
// Each test case uses a unique command from the function above.
testcases := []struct {
desc string
args []string
expectedOutput string
}{
{
desc: "flags in group suggested without - prefix",
args: []string{"child", ""},
expectedOutput: strings.Join([]string{
"--ingroup1",
"--ingroup2",
"--ingroup3",
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "flags in group suggested with - prefix",
args: []string{"child", "-"},
expectedOutput: strings.Join([]string{
"--ingroup1",
"--ingroup2",
"--ingroup3",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "when any flag in group present, other flags in group not suggested without - prefix",
args: []string{"child", "--ingroup2", "value", ""},
expectedOutput: strings.Join([]string{
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "when all flags in group present, flags not suggested without - prefix",
args: []string{"child", "--ingroup1", "8", "--ingroup2", "value2", "--ingroup3", ""},
expectedOutput: strings.Join([]string{
"subArg",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "group ignored if some flags not applicable",
args: []string{"--ingroup2", "value", ""},
expectedOutput: strings.Join([]string{
"child",
"completion",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
c := getCmd()
args := []string{ShellCompNoDescRequestCmd}
args = append(args, tc.args...)
output, err := executeCommand(c, args...)
switch {
case err == nil && output != tc.expectedOutput:
t.Errorf("expected: %q, got: %q", tc.expectedOutput, output)
case err != nil:
t.Errorf("Unexpected error %q", err)
}
})
}
}
func TestCompletionForMutuallyExclusiveFlags(t *testing.T) {
getCmd := func() *Command {
rootCmd := &Command{

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -150,7 +150,7 @@ func TestGenManSeeAlso(t *testing.T) {
}
}
func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
func TestManPrintFlagsHidesShortDeprecated(t *testing.T) {
c := &cobra.Command{}
c.Flags().StringP("foo", "f", "default", "Foo flag")
assertNoErr(t, c.Flags().MarkShorthandDeprecated("foo", "don't use it no more"))

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -53,7 +53,7 @@ function __%[1]s_perform_completion
__%[1]s_debug "last arg: $lastArg"
# Disable ActiveHelp which is not supported for fish shell
set -l requestComp "%[9]s=0 $args[1] %[3]s $args[2..-1] $lastArg"
set -l requestComp "%[10]s=0 $args[1] %[3]s $args[2..-1] $lastArg"
__%[1]s_debug "Calling $requestComp"
set -l results (eval $requestComp 2> /dev/null)
@ -89,6 +89,60 @@ function __%[1]s_perform_completion
printf "%%s\n" "$directiveLine"
end
# this function limits calls to __%[1]s_perform_completion, by caching the result behind $__%[1]s_perform_completion_once_result
function __%[1]s_perform_completion_once
__%[1]s_debug "Starting __%[1]s_perform_completion_once"
if test -n "$__%[1]s_perform_completion_once_result"
__%[1]s_debug "Seems like a valid result already exists, skipping __%[1]s_perform_completion"
return 0
end
set --global __%[1]s_perform_completion_once_result (__%[1]s_perform_completion)
if test -z "$__%[1]s_perform_completion_once_result"
__%[1]s_debug "No completions, probably due to a failure"
return 1
end
__%[1]s_debug "Performed completions and set __%[1]s_perform_completion_once_result"
return 0
end
# this function is used to clear the $__%[1]s_perform_completion_once_result variable after completions are run
function __%[1]s_clear_perform_completion_once_result
__%[1]s_debug ""
__%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable =========="
set --erase __%[1]s_perform_completion_once_result
__%[1]s_debug "Successfully erased the variable __%[1]s_perform_completion_once_result"
end
function __%[1]s_requires_order_preservation
__%[1]s_debug ""
__%[1]s_debug "========= checking if order preservation is required =========="
__%[1]s_perform_completion_once
if test -z "$__%[1]s_perform_completion_once_result"
__%[1]s_debug "Error determining if order preservation is required"
return 1
end
set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
__%[1]s_debug "Directive is: $directive"
set -l shellCompDirectiveKeepOrder %[9]d
set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) %% 2)
__%[1]s_debug "Keeporder is: $keeporder"
if test $keeporder -ne 0
__%[1]s_debug "This does require order preservation"
return 0
end
__%[1]s_debug "This doesn't require order preservation"
return 1
end
# This function does two things:
# - Obtain the completions and store them in the global __%[1]s_comp_results
# - Return false if file completion should be performed
@ -99,17 +153,17 @@ function __%[1]s_prepare_completions
# Start fresh
set --erase __%[1]s_comp_results
set -l results (__%[1]s_perform_completion)
__%[1]s_debug "Completion results: $results"
__%[1]s_perform_completion_once
__%[1]s_debug "Completion results: $__%[1]s_perform_completion_once_result"
if test -z "$results"
if test -z "$__%[1]s_perform_completion_once_result"
__%[1]s_debug "No completion, probably due to a failure"
# Might as well do file completion, in case it helps
return 1
end
set -l directive (string sub --start 2 $results[-1])
set --global __%[1]s_comp_results $results[1..-2]
set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
set --global __%[1]s_comp_results $__%[1]s_perform_completion_once_result[1..-2]
__%[1]s_debug "Completions are: $__%[1]s_comp_results"
__%[1]s_debug "Directive is: $directive"
@ -205,13 +259,17 @@ end
# Remove any pre-existing completions for the program since we will be handling all of them.
complete -c %[2]s -e
# this will get called after the two calls below and clear the $__%[1]s_perform_completion_once_result global
complete -c %[2]s -n '__%[1]s_clear_perform_completion_once_result'
# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
# which provides the program's completion choices.
complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
# If this doesn't require order preservation, we don't use the -k flag
complete -c %[2]s -n 'not __%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
# otherwise we use the -k flag
complete -k -c %[2]s -n '__%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
`, nameForVar, name, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
}
// GenFishCompletion generates fish completion file and writes to the passed writer.

View file

@ -1,4 +0,0 @@
## Generating Fish Completions For Your cobra.Command
Please refer to [Shell Completions](shell_completions.md) for details.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -16,9 +16,10 @@ package cobra
import (
"bytes"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"testing"
)
@ -98,12 +99,12 @@ func TestFishCompletionNoActiveHelp(t *testing.T) {
}
func TestGenFishCompletionFile(t *testing.T) {
err := os.Mkdir("./tmp", 0755)
tmpFile, err := os.CreateTemp("", "cobra-test")
if err != nil {
log.Fatal(err.Error())
t.Fatal(err.Error())
}
defer os.RemoveAll("./tmp")
defer os.Remove(tmpFile.Name())
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child := &Command{
@ -113,18 +114,18 @@ func TestGenFishCompletionFile(t *testing.T) {
}
rootCmd.AddCommand(child)
assertNoErr(t, rootCmd.GenFishCompletionFile("./tmp/test", false))
assertNoErr(t, rootCmd.GenFishCompletionFile(tmpFile.Name(), false))
}
func TestFailGenFishCompletionFile(t *testing.T) {
err := os.Mkdir("./tmp", 0755)
tmpDir, err := os.MkdirTemp("", "cobra-test")
if err != nil {
log.Fatal(err.Error())
t.Fatal(err.Error())
}
defer os.RemoveAll("./tmp")
defer os.RemoveAll(tmpDir)
f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0400)
f, _ := os.OpenFile(filepath.Join(tmpDir, "test"), os.O_CREATE, 0400)
defer f.Close()
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
@ -135,18 +136,8 @@ func TestFailGenFishCompletionFile(t *testing.T) {
}
rootCmd.AddCommand(child)
got := rootCmd.GenFishCompletionFile("./tmp/test", false)
if got == nil {
t.Error("should raise permission denied error")
}
if os.Getenv("MSYSTEM") == "MINGW64" {
if got.Error() != "open ./tmp/test: Access is denied." {
t.Errorf("got: %s, want: %s", got.Error(), "open ./tmp/test: Access is denied.")
}
} else {
if got.Error() != "open ./tmp/test: permission denied" {
t.Errorf("got: %s, want: %s", got.Error(), "open ./tmp/test: permission denied")
}
got := rootCmd.GenFishCompletionFile(f.Name(), false)
if !errors.Is(got, os.ErrPermission) {
t.Errorf("got: %s, want: %s", got.Error(), os.ErrPermission.Error())
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import (
const (
requiredAsGroup = "cobra_annotation_required_if_others_set"
oneRequired = "cobra_annotation_one_required"
mutuallyExclusive = "cobra_annotation_mutually_exclusive"
)
@ -43,6 +44,22 @@ func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) {
}
}
// MarkFlagsOneRequired marks the given flags with annotations so that Cobra errors
// if the command is invoked without at least one flag from the given set of flags.
func (c *Command) MarkFlagsOneRequired(flagNames ...string) {
c.mergePersistentFlags()
for _, v := range flagNames {
f := c.Flags().Lookup(v)
if f == nil {
panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a one-required flag group", v))
}
if err := c.Flags().SetAnnotation(v, oneRequired, append(f.Annotations[oneRequired], strings.Join(flagNames, " "))); err != nil {
// Only errs if the flag isn't found.
panic(err)
}
}
}
// MarkFlagsMutuallyExclusive marks the given flags with annotations so that Cobra errors
// if the command is invoked with more than one flag from the given set of flags.
func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
@ -59,7 +76,7 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
}
}
// ValidateFlagGroups validates the mutuallyExclusive/requiredAsGroup logic and returns the
// ValidateFlagGroups validates the mutuallyExclusive/oneRequired/requiredAsGroup logic and returns the
// first error encountered.
func (c *Command) ValidateFlagGroups() error {
if c.DisableFlagParsing {
@ -71,15 +88,20 @@ func (c *Command) ValidateFlagGroups() error {
// groupStatus format is the list of flags as a unique ID,
// then a map of each flag name and whether it is set or not.
groupStatus := map[string]map[string]bool{}
oneRequiredGroupStatus := map[string]map[string]bool{}
mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
flags.VisitAll(func(pflag *flag.Flag) {
processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus)
processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus)
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus)
})
if err := validateRequiredFlagGroups(groupStatus); err != nil {
return err
}
if err := validateOneRequiredFlagGroups(oneRequiredGroupStatus); err != nil {
return err
}
if err := validateExclusiveFlagGroups(mutuallyExclusiveGroupStatus); err != nil {
return err
}
@ -142,6 +164,27 @@ func validateRequiredFlagGroups(data map[string]map[string]bool) error {
return nil
}
func validateOneRequiredFlagGroups(data map[string]map[string]bool) error {
keys := sortedKeys(data)
for _, flagList := range keys {
flagnameAndStatus := data[flagList]
var set []string
for flagname, isSet := range flagnameAndStatus {
if isSet {
set = append(set, flagname)
}
}
if len(set) >= 1 {
continue
}
// Sort values, so they can be tested/scripted against consistently.
sort.Strings(set)
return fmt.Errorf("at least one of the flags in the group [%v] is required", flagList)
}
return nil
}
func validateExclusiveFlagGroups(data map[string]map[string]bool) error {
keys := sortedKeys(data)
for _, flagList := range keys {
@ -176,6 +219,7 @@ func sortedKeys(m map[string]map[string]bool) []string {
// enforceFlagGroupsForCompletion will do the following:
// - when a flag in a group is present, other flags in the group will be marked required
// - when none of the flags in a one-required group are present, all flags in the group will be marked required
// - when a flag in a mutually exclusive group is present, other flags in the group will be marked as hidden
// This allows the standard completion logic to behave appropriately for flag groups
func (c *Command) enforceFlagGroupsForCompletion() {
@ -185,9 +229,11 @@ func (c *Command) enforceFlagGroupsForCompletion() {
flags := c.Flags()
groupStatus := map[string]map[string]bool{}
oneRequiredGroupStatus := map[string]map[string]bool{}
mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
c.Flags().VisitAll(func(pflag *flag.Flag) {
processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus)
processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus)
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus)
})
@ -204,6 +250,26 @@ func (c *Command) enforceFlagGroupsForCompletion() {
}
}
// If none of the flags of a one-required group are present, we make all the flags
// of that group required so that the shell completion suggests them automatically
for flagList, flagnameAndStatus := range oneRequiredGroupStatus {
set := 0
for _, isSet := range flagnameAndStatus {
if isSet {
set++
}
}
// None of the flags of the group are set, mark all flags in the group
// as required
if set == 0 {
for _, fName := range strings.Split(flagList, " ") {
_ = c.MarkFlagRequired(fName)
}
}
}
// If a flag that is mutually exclusive to others is present, we hide the other
// flags of that group so the shell completion does not suggest them
for flagList, flagnameAndStatus := range mutuallyExclusiveGroupStatus {

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -43,13 +43,15 @@ func TestValidateFlagGroups(t *testing.T) {
// Each test case uses a unique command from the function above.
testcases := []struct {
desc string
flagGroupsRequired []string
flagGroupsExclusive []string
subCmdFlagGroupsRequired []string
subCmdFlagGroupsExclusive []string
args []string
expectErr string
desc string
flagGroupsRequired []string
flagGroupsOneRequired []string
flagGroupsExclusive []string
subCmdFlagGroupsRequired []string
subCmdFlagGroupsOneRequired []string
subCmdFlagGroupsExclusive []string
args []string
expectErr string
}{
{
desc: "No flags no problem",
@ -62,6 +64,11 @@ func TestValidateFlagGroups(t *testing.T) {
flagGroupsRequired: []string{"a b c"},
args: []string{"--a=foo"},
expectErr: "if any flags in the group [a b c] are set they must all be set; missing [b c]",
}, {
desc: "One-required flag group not satisfied",
flagGroupsOneRequired: []string{"a b"},
args: []string{"--c=foo"},
expectErr: "at least one of the flags in the group [a b] is required",
}, {
desc: "Exclusive flag group not satisfied",
flagGroupsExclusive: []string{"a b c"},
@ -72,6 +79,11 @@ func TestValidateFlagGroups(t *testing.T) {
flagGroupsRequired: []string{"a b c", "a d"},
args: []string{"--c=foo", "--d=foo"},
expectErr: `if any flags in the group [a b c] are set they must all be set; missing [a b]`,
}, {
desc: "Multiple one-required flag group not satisfied returns first error",
flagGroupsOneRequired: []string{"a b", "d e"},
args: []string{"--c=foo", "--f=foo"},
expectErr: `at least one of the flags in the group [a b] is required`,
}, {
desc: "Multiple exclusive flag group not satisfied returns first error",
flagGroupsExclusive: []string{"a b c", "a d"},
@ -82,32 +94,57 @@ func TestValidateFlagGroups(t *testing.T) {
flagGroupsRequired: []string{"a d", "a b", "a c"},
args: []string{"--a=foo"},
expectErr: `if any flags in the group [a b] are set they must all be set; missing [b]`,
}, {
desc: "Validation of one-required groups occurs on groups in sorted order",
flagGroupsOneRequired: []string{"d e", "a b", "f g"},
args: []string{"--c=foo"},
expectErr: `at least one of the flags in the group [a b] is required`,
}, {
desc: "Validation of exclusive groups occurs on groups in sorted order",
flagGroupsExclusive: []string{"a d", "a b", "a c"},
args: []string{"--a=foo", "--b=foo", "--c=foo"},
expectErr: `if any flags in the group [a b] are set none of the others can be; [a b] were all set`,
}, {
desc: "Persistent flags utilize both features and can fail required groups",
desc: "Persistent flags utilize required and exclusive groups and can fail required groups",
flagGroupsRequired: []string{"a e", "e f"},
flagGroupsExclusive: []string{"f g"},
args: []string{"--a=foo", "--f=foo", "--g=foo"},
expectErr: `if any flags in the group [a e] are set they must all be set; missing [e]`,
}, {
desc: "Persistent flags utilize both features and can fail mutually exclusive groups",
desc: "Persistent flags utilize one-required and exclusive groups and can fail one-required groups",
flagGroupsOneRequired: []string{"a b", "e f"},
flagGroupsExclusive: []string{"e f"},
args: []string{"--e=foo"},
expectErr: `at least one of the flags in the group [a b] is required`,
}, {
desc: "Persistent flags utilize required and exclusive groups and can fail mutually exclusive groups",
flagGroupsRequired: []string{"a e", "e f"},
flagGroupsExclusive: []string{"f g"},
args: []string{"--a=foo", "--e=foo", "--f=foo", "--g=foo"},
expectErr: `if any flags in the group [f g] are set none of the others can be; [f g] were all set`,
}, {
desc: "Persistent flags utilize both features and can pass",
desc: "Persistent flags utilize required and exclusive groups and can pass",
flagGroupsRequired: []string{"a e", "e f"},
flagGroupsExclusive: []string{"f g"},
args: []string{"--a=foo", "--e=foo", "--f=foo"},
}, {
desc: "Persistent flags utilize one-required and exclusive groups and can pass",
flagGroupsOneRequired: []string{"a e", "e f"},
flagGroupsExclusive: []string{"f g"},
args: []string{"--a=foo", "--e=foo", "--f=foo"},
}, {
desc: "Subcmds can use required groups using inherited flags",
subCmdFlagGroupsRequired: []string{"e subonly"},
args: []string{"subcmd", "--e=foo", "--subonly=foo"},
}, {
desc: "Subcmds can use one-required groups using inherited flags",
subCmdFlagGroupsOneRequired: []string{"e subonly"},
args: []string{"subcmd", "--e=foo", "--subonly=foo"},
}, {
desc: "Subcmds can use one-required groups using inherited flags and fail one-required groups",
subCmdFlagGroupsOneRequired: []string{"e subonly"},
args: []string{"subcmd"},
expectErr: "at least one of the flags in the group [e subonly] is required",
}, {
desc: "Subcmds can use exclusive groups using inherited flags",
subCmdFlagGroupsExclusive: []string{"e subonly"},
@ -130,12 +167,18 @@ func TestValidateFlagGroups(t *testing.T) {
for _, flagGroup := range tc.flagGroupsRequired {
c.MarkFlagsRequiredTogether(strings.Split(flagGroup, " ")...)
}
for _, flagGroup := range tc.flagGroupsOneRequired {
c.MarkFlagsOneRequired(strings.Split(flagGroup, " ")...)
}
for _, flagGroup := range tc.flagGroupsExclusive {
c.MarkFlagsMutuallyExclusive(strings.Split(flagGroup, " ")...)
}
for _, flagGroup := range tc.subCmdFlagGroupsRequired {
sub.MarkFlagsRequiredTogether(strings.Split(flagGroup, " ")...)
}
for _, flagGroup := range tc.subCmdFlagGroupsOneRequired {
sub.MarkFlagsOneRequired(strings.Split(flagGroup, " ")...)
}
for _, flagGroup := range tc.subCmdFlagGroupsExclusive {
sub.MarkFlagsMutuallyExclusive(strings.Split(flagGroup, " ")...)
}

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -47,7 +47,7 @@ filter __%[1]s_escapeStringWithSpecialChars {
`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
}
[scriptblock]$__%[2]sCompleterBlock = {
[scriptblock]${__%[2]sCompleterBlock} = {
param(
$WordToComplete,
$CommandAst,
@ -77,6 +77,7 @@ filter __%[1]s_escapeStringWithSpecialChars {
$ShellCompDirectiveNoFileComp=%[6]d
$ShellCompDirectiveFilterFileExt=%[7]d
$ShellCompDirectiveFilterDirs=%[8]d
$ShellCompDirectiveKeepOrder=%[9]d
# Prepare the command to request completions for the program.
# Split the command at the first space to separate the program and arguments.
@ -106,13 +107,22 @@ filter __%[1]s_escapeStringWithSpecialChars {
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
__%[1]s_debug "Adding extra empty parameter"
`+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+`
`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+`
# PowerShell 7.2+ changed the way how the arguments are passed to executables,
# so for pre-7.2 or when Legacy argument passing is enabled we need to use
`+" # `\"`\" to pass an empty argument, a \"\" or '' does not work!!!"+`
if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or
($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or
(($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and
$PSNativeCommandArgumentPassing -eq 'Legacy')) {
`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+`
} else {
$RequestComp="$RequestComp" + ' ""'
}
}
__%[1]s_debug "Calling $RequestComp"
# First disable ActiveHelp which is not supported for Powershell
$env:%[9]s=0
${env:%[10]s}=0
#call the command store the output in $out and redirect stderr and stdout to null
# $Out is an array contains each line per element
@ -182,6 +192,11 @@ filter __%[1]s_escapeStringWithSpecialChars {
}
}
# we sort the values in ascending order by name if keep order isn't passed
if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) {
$Values = $Values | Sort-Object -Property Name
}
if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
__%[1]s_debug "ShellCompDirectiveNoFileComp is called"
@ -264,10 +279,10 @@ filter __%[1]s_escapeStringWithSpecialChars {
}
}
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock $__%[2]sCompleterBlock
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock ${__%[2]sCompleterBlock}
`, name, nameForVar, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
}
func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {

View file

@ -1,3 +0,0 @@
# Generating PowerShell Completions For Your Own cobra.Command
Please refer to [Shell Completions](shell_completions.md#powershell-completions) for details.

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -29,5 +29,5 @@ func TestPwshCompletionNoActiveHelp(t *testing.T) {
// check that active help is being disabled
activeHelpVar := activeHelpEnvVar(c.Name())
check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
check(t, output, fmt.Sprintf("${env:%s}=0", activeHelpVar))
}

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View file

@ -228,6 +228,10 @@ ShellCompDirectiveFilterFileExt
// return []string{"themes"}, ShellCompDirectiveFilterDirs
//
ShellCompDirectiveFilterDirs
// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
// in which the completions are provided
ShellCompDirectiveKeepOrder
```
***Note***: When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function.
@ -412,7 +416,7 @@ completion firstcommand secondcommand
### Bash legacy dynamic completions
For backward compatibility, Cobra still supports its bash legacy dynamic completion solution.
Please refer to [Bash Completions](bash_completions.md) for details.
Please refer to [Bash Completions](bash.md) for details.
### Bash completion V2
@ -421,13 +425,13 @@ Cobra provides two versions for bash completion. The original bash completion (
A new V2 bash completion version is also available. This version can be used by calling `GenBashCompletionV2()` or
`GenBashCompletionFileV2()`. The V2 version does **not** support the legacy dynamic completion
(see [Bash Completions](bash_completions.md)) but instead works only with the Go dynamic completion
(see [Bash Completions](bash.md)) but instead works only with the Go dynamic completion
solution described in this document.
Unless your program already uses the legacy dynamic completion solution, it is recommended that you use the bash
completion V2 solution which provides the following extra features:
- Supports completion descriptions (like the other shells)
- Small completion script of less than 300 lines (v1 generates scripts of thousands of lines; `kubectl` for example has a bash v1 completion script of over 13K lines)
- Streamlined user experience thanks to a completion behavior aligned with the other shells
- Streamlined user experience thanks to a completion behavior aligned with the other shells
`Bash` completion V2 supports descriptions for completions. When calling `GenBashCompletionV2()` or `GenBashCompletionFileV2()`
you must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra
@ -444,7 +448,7 @@ show (show information of a chart)
$ helm s[tab][tab]
search show status
```
**Note**: Cobra's default `completion` command uses bash completion V2. If for some reason you need to use bash completion V1, you will need to implement your own `completion` command.
**Note**: Cobra's default `completion` command uses bash completion V2. If for some reason you need to use bash completion V1, you will need to implement your own `completion` command.
## Zsh completions
Cobra supports native zsh completion generated from the root `cobra.Command`.
@ -478,7 +482,7 @@ search show status
### Zsh completions standardization
Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backward-compatible, some small changes in behavior were introduced.
Please refer to [Zsh Completions](zsh_completions.md) for details.
Please refer to [Zsh Completions](zsh.md) for details.
## fish completions
@ -531,7 +535,7 @@ search (search for a keyword in charts) show (show information of a chart) s
# With descriptions and Mode 'MenuComplete' The description of the current selected value will be displayed below the suggestions.
$ helm s[tab]
search show status
search show status
search for a keyword in charts

View file

@ -1,6 +1,6 @@
# Generating Bash Completions For Your cobra.Command
Please refer to [Shell Completions](shell_completions.md) for details.
Please refer to [Shell Completions](_index.md) for details.
## Bash legacy dynamic completions

View file

@ -0,0 +1,4 @@
## Generating Fish Completions For Your cobra.Command
Please refer to [Shell Completions](_index.md) for details.

View file

@ -0,0 +1,3 @@
# Generating PowerShell Completions For Your Own cobra.Command
Please refer to [Shell Completions](_index.md#powershell-completions) for details.

View file

@ -1,6 +1,6 @@
## Generating Zsh Completion For Your cobra.Command
Please refer to [Shell Completions](shell_completions.md) for details.
Please refer to [Shell Completions](_index.md) for details.
## Zsh completions standardization

View file

@ -1,9 +1,9 @@
# Documentation generation
- [Man page docs](./man_docs.md)
- [Markdown docs](./md_docs.md)
- [Rest docs](./rest_docs.md)
- [Yaml docs](./yaml_docs.md)
- [Man page docs](man.md)
- [Markdown docs](md.md)
- [Rest docs](rest.md)
- [Yaml docs](yaml.md)
## Options
### `DisableAutoGenTag`

View file

@ -1,6 +1,7 @@
## Projects using Cobra
- [Allero](https://github.com/allero-io/allero)
- [Arewefastyet](https://benchmark.vitess.io)
- [Arduino CLI](https://github.com/arduino/arduino-cli)
- [Bleve](https://blevesearch.com/)
- [Cilium](https://cilium.io/)
@ -57,6 +58,7 @@
- [Tendermint](https://github.com/tendermint/tendermint)
- [Twitch CLI](https://github.com/twitchdev/twitch-cli)
- [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli)
- [Vitess](https://vitess.io)
- VMware's [Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) & [Tanzu Framework](https://github.com/vmware-tanzu/tanzu-framework)
- [Werf](https://werf.io/)
- [ZITADEL](https://github.com/zitadel/zitadel)

View file

@ -29,8 +29,8 @@ func main() {
## Using the Cobra Generator
Cobra-CLI is its own program that will create your application and add any
commands you want. It's the easiest way to incorporate Cobra into your application.
Cobra-CLI is its own program that will create your application and add any commands you want.
It's the easiest way to incorporate Cobra into your application.
For complete details on using the Cobra generator, please refer to [The Cobra-CLI Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md)
@ -188,6 +188,37 @@ var versionCmd = &cobra.Command{
}
```
### Organizing subcommands
A command may have subcommands which in turn may have other subcommands. This is achieved by using
`AddCommand`. In some cases, especially in larger applications, each subcommand may be defined in
its own go package.
The suggested approach is for the parent command to use `AddCommand` to add its most immediate
subcommands. For example, consider the following directory structure:
```text
├── cmd
│   ├── root.go
│   └── sub1
│   ├── sub1.go
│   └── sub2
│   ├── leafA.go
│   ├── leafB.go
│   └── sub2.go
└── main.go
```
In this case:
* The `init` function of `root.go` adds the command defined in `sub1.go` to the root command.
* The `init` function of `sub1.go` adds the command defined in `sub2.go` to the sub1 command.
* The `init` function of `sub2.go` adds the commands defined in `leafA.go` and `leafB.go` to the
sub2 command.
This approach ensures the subcommands are always included at compile time while avoiding cyclic
references.
### Returning and handling errors
If you wish to return an error to the caller of a command, `RunE` can be used.
@ -318,7 +349,16 @@ rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML")
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
```
In both of these cases:
If you want to require at least one flag from a group to be present, you can use `MarkFlagsOneRequired`.
This can be combined with `MarkFlagsMutuallyExclusive` to enforce exactly one flag from a given group:
```go
rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON")
rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML")
rootCmd.MarkFlagsOneRequired("json", "yaml")
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
```
In these cases:
- both local and persistent flags can be used
- **NOTE:** the group is only enforced on commands where every flag is defined
- a flag may appear in multiple groups
@ -556,9 +596,15 @@ Running an application with the '--version' flag will print the version to stdou
the version template. The template can be customized using the
`cmd.SetVersionTemplate(s string)` function.
## Error Message Prefix
Cobra prints an error message when receiving a non-nil error value.
The default error message is `Error: <error contents>`.
The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` function.
## PreRun and PostRun Hooks
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order:
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. The `*PreRun` and `*PostRun` functions will only be executed if the `Run` function of the current command has been declared. These functions are run in the following order:
- `PersistentPreRun`
- `PreRun`
@ -684,12 +730,17 @@ Run 'kubectl help' for usage.
## Generating documentation for your command
Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md).
Cobra can generate documentation based on subcommands, flags, etc.
Read more about it in the [docs generation documentation](docgen/_index.md).
## Generating shell completions
Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md).
Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell.
If you add more information to your commands, these completions can be amazingly powerful and flexible.
Read more about it in [Shell Completions](completions/_index.md).
## Providing Active Help
Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users. Active Help are messages (hints, warnings, etc) printed as the program is being used. Read more about it in [Active Help](active_help.md).
Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users.
Active Help are messages (hints, warnings, etc) printed as the program is being used.
Read more about it in [Active Help](active_help.md).

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -90,6 +90,7 @@ func genZshComp(buf io.StringWriter, name string, includeDesc bool) {
compCmd = ShellCompNoDescRequestCmd
}
WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s
compdef _%[1]s %[1]s
# zsh completion for %-36[1]s -*- shell-script -*-
@ -108,8 +109,9 @@ _%[1]s()
local shellCompDirectiveNoFileComp=%[5]d
local shellCompDirectiveFilterFileExt=%[6]d
local shellCompDirectiveFilterDirs=%[7]d
local shellCompDirectiveKeepOrder=%[8]d
local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace
local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder
local -a completions
__%[1]s_debug "\n========= starting completion logic =========="
@ -177,7 +179,7 @@ _%[1]s()
return
fi
local activeHelpMarker="%[8]s"
local activeHelpMarker="%[9]s"
local endIndex=${#activeHelpMarker}
local startIndex=$((${#activeHelpMarker}+1))
local hasActiveHelp=0
@ -227,6 +229,11 @@ _%[1]s()
noSpace="-S ''"
fi
if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then
__%[1]s_debug "Activating keep order."
keepOrder="-V"
fi
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
# File extension filtering
local filteringCmd
@ -262,7 +269,7 @@ _%[1]s()
return $result
else
__%[1]s_debug "Calling _describe"
if eval _describe "completions" completions $flagPrefix $noSpace; then
if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then
__%[1]s_debug "_describe found some completions"
# Return the success of having called _describe
@ -296,6 +303,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then
fi
`, name, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
activeHelpMarker))
}

View file

@ -1,4 +1,4 @@
// Copyright 2013-2022 The Cobra Authors
// Copyright 2013-2023 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.