mirror of
https://github.com/spf13/cobra
synced 2025-04-27 17:17:20 +00:00
Merge branch 'spf13:main' into dm/local-flags-doc
This commit is contained in:
commit
d35fd5fad3
16 changed files with 566 additions and 61 deletions
19
.github/labeler.yml
vendored
19
.github/labeler.yml
vendored
|
@ -1,17 +1,24 @@
|
||||||
# changes to documentation generation
|
# changes to documentation generation
|
||||||
"area/docs-generation": doc/**/*
|
"area/docs-generation":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'doc/**'
|
||||||
|
|
||||||
# changes to the core cobra command
|
# changes to the core cobra command
|
||||||
"area/cobra-command":
|
"area/cobra-command":
|
||||||
- any: ['./cobra.go', './cobra_test.go', './*command*.go']
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['./cobra.go', './cobra_test.go', './*command*.go']
|
||||||
|
|
||||||
# changes made to command flags/args
|
# changes made to command flags/args
|
||||||
"area/flags": ./args*.go
|
"area/flags":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: './args*.go'
|
||||||
|
|
||||||
# changes to Github workflows
|
# changes to Github workflows
|
||||||
"area/github": .github/**/*
|
"area/github":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: '.github/**'
|
||||||
|
|
||||||
# changes to shell completions
|
# changes to shell completions
|
||||||
"area/shell-completion":
|
"area/shell-completion":
|
||||||
- ./*completions*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: './*completions*'
|
||||||
|
|
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
||||||
pull-requests: write # for actions/labeler to add labels to PRs
|
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@v5
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ github.token }}"
|
repo-token: "${{ github.token }}"
|
||||||
|
|
||||||
|
|
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
@ -41,14 +41,12 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '^1.21'
|
go-version: '^1.21'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: golangci/golangci-lint-action@v3.7.0
|
- uses: golangci/golangci-lint-action@v3.7.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
@ -74,7 +72,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.${{ matrix.go }}.x
|
go-version: 1.${{ matrix.go }}.x
|
||||||
cache: true
|
cache: true
|
||||||
|
|
|
@ -17,21 +17,17 @@ package cobra
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
activeHelpMarker = "_activeHelp_ "
|
activeHelpMarker = "_activeHelp_ "
|
||||||
// The below values should not be changed: programs will be using them explicitly
|
// The below values should not be changed: programs will be using them explicitly
|
||||||
// in their user documentation, and users will be using them explicitly.
|
// in their user documentation, and users will be using them explicitly.
|
||||||
activeHelpEnvVarSuffix = "_ACTIVE_HELP"
|
activeHelpEnvVarSuffix = "ACTIVE_HELP"
|
||||||
activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP"
|
activeHelpGlobalEnvVar = configEnvVarGlobalPrefix + "_" + activeHelpEnvVarSuffix
|
||||||
activeHelpGlobalDisable = "0"
|
activeHelpGlobalDisable = "0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var activeHelpEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
|
|
||||||
|
|
||||||
// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp.
|
// 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
|
// Such strings will be processed by the completion script and will be shown as ActiveHelp
|
||||||
// to the user.
|
// to the user.
|
||||||
|
@ -60,8 +56,5 @@ func GetActiveHelpConfig(cmd *Command) string {
|
||||||
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
|
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
|
||||||
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
func activeHelpEnvVar(name string) string {
|
func activeHelpEnvVar(name string) string {
|
||||||
// This format should not be changed: users will be using it explicitly.
|
return configEnvVar(name, activeHelpEnvVarSuffix)
|
||||||
activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix))
|
|
||||||
activeHelpEnvVar = activeHelpEnvVarPrefixSubstRegexp.ReplaceAllString(activeHelpEnvVar, "_")
|
|
||||||
return activeHelpEnvVar
|
|
||||||
}
|
}
|
||||||
|
|
4
args.go
4
args.go
|
@ -52,9 +52,9 @@ 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.
|
||||||
// A description is following a tab character.
|
// A description is following a tab character.
|
||||||
var validArgs []string
|
validArgs := make([]string, 0, len(cmd.ValidArgs))
|
||||||
for _, v := range cmd.ValidArgs {
|
for _, v := range cmd.ValidArgs {
|
||||||
validArgs = append(validArgs, strings.Split(v, "\t")[0])
|
validArgs = append(validArgs, strings.SplitN(v, "\t", 2)[0])
|
||||||
}
|
}
|
||||||
for _, v := range args {
|
for _, v := range args {
|
||||||
if !stringInSlice(v, validArgs) {
|
if !stringInSlice(v, validArgs) {
|
||||||
|
|
|
@ -621,7 +621,7 @@ func writeRequiredNouns(buf io.StringWriter, cmd *Command) {
|
||||||
for _, value := range cmd.ValidArgs {
|
for _, value := range cmd.ValidArgs {
|
||||||
// Remove any description that may be included following a tab character.
|
// Remove any description that may be included following a tab character.
|
||||||
// Descriptions are not supported by bash completion.
|
// Descriptions are not supported by bash completion.
|
||||||
value = strings.Split(value, "\t")[0]
|
value = strings.SplitN(value, "\t", 2)[0]
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
|
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
|
||||||
}
|
}
|
||||||
if cmd.ValidArgsFunction != nil {
|
if cmd.ValidArgsFunction != nil {
|
||||||
|
|
2
cobra.go
2
cobra.go
|
@ -193,8 +193,6 @@ func ld(s, t string, ignoreCase bool) int {
|
||||||
d := make([][]int, len(s)+1)
|
d := make([][]int, len(s)+1)
|
||||||
for i := range d {
|
for i := range d {
|
||||||
d[i] = make([]int, len(t)+1)
|
d[i] = make([]int, len(t)+1)
|
||||||
}
|
|
||||||
for i := range d {
|
|
||||||
d[i][0] = i
|
d[i][0] = i
|
||||||
}
|
}
|
||||||
for j := range d[0] {
|
for j := range d[0] {
|
||||||
|
|
182
cobra_test.go
182
cobra_test.go
|
@ -40,3 +40,185 @@ func TestAddTemplateFunctions(t *testing.T) {
|
||||||
t.Errorf("Expected UsageString: %v\nGot: %v", expected, got)
|
t.Errorf("Expected UsageString: %v\nGot: %v", expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLevenshteinDistance(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
t string
|
||||||
|
ignoreCase bool
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Equal strings (case-sensitive)",
|
||||||
|
s: "hello",
|
||||||
|
t: "hello",
|
||||||
|
ignoreCase: false,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Equal strings (case-insensitive)",
|
||||||
|
s: "Hello",
|
||||||
|
t: "hello",
|
||||||
|
ignoreCase: true,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Different strings (case-sensitive)",
|
||||||
|
s: "kitten",
|
||||||
|
t: "sitting",
|
||||||
|
ignoreCase: false,
|
||||||
|
expected: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Different strings (case-insensitive)",
|
||||||
|
s: "Kitten",
|
||||||
|
t: "Sitting",
|
||||||
|
ignoreCase: true,
|
||||||
|
expected: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty strings",
|
||||||
|
s: "",
|
||||||
|
t: "",
|
||||||
|
ignoreCase: false,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "One empty string",
|
||||||
|
s: "abc",
|
||||||
|
t: "",
|
||||||
|
ignoreCase: false,
|
||||||
|
expected: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Both empty strings",
|
||||||
|
s: "",
|
||||||
|
t: "",
|
||||||
|
ignoreCase: true,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Act
|
||||||
|
got := ld(tt.s, tt.t, tt.ignoreCase)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("Expected ld: %v\nGot: %v", tt.expected, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringInSlice(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
a string
|
||||||
|
list []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "String in slice (case-sensitive)",
|
||||||
|
a: "apple",
|
||||||
|
list: []string{"orange", "banana", "apple", "grape"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "String not in slice (case-sensitive)",
|
||||||
|
a: "pear",
|
||||||
|
list: []string{"orange", "banana", "apple", "grape"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "String in slice (case-insensitive)",
|
||||||
|
a: "APPLE",
|
||||||
|
list: []string{"orange", "banana", "apple", "grape"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty slice",
|
||||||
|
a: "apple",
|
||||||
|
list: []string{},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty string",
|
||||||
|
a: "",
|
||||||
|
list: []string{"orange", "banana", "apple", "grape"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty strings match",
|
||||||
|
a: "",
|
||||||
|
list: []string{"orange", ""},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty string in empty slice",
|
||||||
|
a: "",
|
||||||
|
list: []string{},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Act
|
||||||
|
got := stringInSlice(tt.a, tt.list)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("Expected stringInSlice: %v\nGot: %v", tt.expected, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRpad(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inputString string
|
||||||
|
padding int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Padding required",
|
||||||
|
inputString: "Hello",
|
||||||
|
padding: 10,
|
||||||
|
expected: "Hello ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No padding required",
|
||||||
|
inputString: "World",
|
||||||
|
padding: 5,
|
||||||
|
expected: "World",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty string",
|
||||||
|
inputString: "",
|
||||||
|
padding: 8,
|
||||||
|
expected: " ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Zero padding",
|
||||||
|
inputString: "cobra",
|
||||||
|
padding: 0,
|
||||||
|
expected: "cobra",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Act
|
||||||
|
got := rpad(tt.inputString, tt.padding)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("Expected rpad: %v\nGot: %v", tt.expected, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
42
command.go
42
command.go
|
@ -708,7 +708,7 @@ Loop:
|
||||||
// This is not a flag or a flag value. Check to see if it matches what we're looking for, and if so,
|
// 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.
|
// return the args, excluding the one at this position.
|
||||||
if s == x {
|
if s == x {
|
||||||
ret := []string{}
|
ret := make([]string, 0, len(args)-1)
|
||||||
ret = append(ret, args[:pos]...)
|
ret = append(ret, args[:pos]...)
|
||||||
ret = append(ret, args[pos+1:]...)
|
ret = append(ret, args[pos+1:]...)
|
||||||
return ret
|
return ret
|
||||||
|
@ -756,14 +756,14 @@ func (c *Command) findSuggestions(arg string) string {
|
||||||
if c.SuggestionsMinimumDistance <= 0 {
|
if c.SuggestionsMinimumDistance <= 0 {
|
||||||
c.SuggestionsMinimumDistance = 2
|
c.SuggestionsMinimumDistance = 2
|
||||||
}
|
}
|
||||||
suggestionsString := ""
|
var sb strings.Builder
|
||||||
if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 {
|
if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 {
|
||||||
suggestionsString += "\n\nDid you mean this?\n"
|
sb.WriteString("\n\nDid you mean this?\n")
|
||||||
for _, s := range suggestions {
|
for _, s := range suggestions {
|
||||||
suggestionsString += fmt.Sprintf("\t%v\n", s)
|
_, _ = fmt.Fprintf(&sb, "\t%v\n", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return suggestionsString
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) findNext(next string) *Command {
|
func (c *Command) findNext(next string) *Command {
|
||||||
|
@ -1189,10 +1189,11 @@ func (c *Command) InitDefaultHelpFlag() {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
if c.Flags().Lookup("help") == nil {
|
if c.Flags().Lookup("help") == nil {
|
||||||
usage := "help for "
|
usage := "help for "
|
||||||
if c.Name() == "" {
|
name := c.displayName()
|
||||||
|
if name == "" {
|
||||||
usage += "this command"
|
usage += "this command"
|
||||||
} else {
|
} else {
|
||||||
usage += c.Name()
|
usage += name
|
||||||
}
|
}
|
||||||
c.Flags().BoolP("help", "h", false, usage)
|
c.Flags().BoolP("help", "h", false, usage)
|
||||||
_ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"})
|
_ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"})
|
||||||
|
@ -1238,7 +1239,7 @@ func (c *Command) InitDefaultHelpCmd() {
|
||||||
Use: "help [command]",
|
Use: "help [command]",
|
||||||
Short: "Help about any command",
|
Short: "Help about any command",
|
||||||
Long: `Help provides help for any command in the application.
|
Long: `Help provides help for any command in the application.
|
||||||
Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
Simply type ` + c.displayName() + ` help [path to command] for full details.`,
|
||||||
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
var completions []string
|
var completions []string
|
||||||
cmd, _, e := c.Root().Find(args)
|
cmd, _, e := c.Root().Find(args)
|
||||||
|
@ -1429,6 +1430,10 @@ func (c *Command) CommandPath() string {
|
||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.Parent().CommandPath() + " " + c.Name()
|
return c.Parent().CommandPath() + " " + c.Name()
|
||||||
}
|
}
|
||||||
|
return c.displayName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) displayName() string {
|
||||||
if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok {
|
if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok {
|
||||||
return displayName
|
return displayName
|
||||||
}
|
}
|
||||||
|
@ -1438,10 +1443,11 @@ func (c *Command) CommandPath() string {
|
||||||
// UseLine puts out the full usage for a given command (including parents).
|
// UseLine puts out the full usage for a given command (including parents).
|
||||||
func (c *Command) UseLine() string {
|
func (c *Command) UseLine() string {
|
||||||
var useline string
|
var useline string
|
||||||
|
use := strings.Replace(c.Use, c.Name(), c.displayName(), 1)
|
||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
useline = c.parent.CommandPath() + " " + c.Use
|
useline = c.parent.CommandPath() + " " + use
|
||||||
} else {
|
} else {
|
||||||
useline = c.Use
|
useline = use
|
||||||
}
|
}
|
||||||
if c.DisableFlagsInUseLine {
|
if c.DisableFlagsInUseLine {
|
||||||
return useline
|
return useline
|
||||||
|
@ -1644,7 +1650,7 @@ func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) f
|
||||||
// to this command (local and persistent declared here and by all parents).
|
// to this command (local and persistent declared here and by all parents).
|
||||||
func (c *Command) Flags() *flag.FlagSet {
|
func (c *Command) Flags() *flag.FlagSet {
|
||||||
if c.flags == nil {
|
if c.flags == nil {
|
||||||
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
if c.flagErrorBuf == nil {
|
if c.flagErrorBuf == nil {
|
||||||
c.flagErrorBuf = new(bytes.Buffer)
|
c.flagErrorBuf = new(bytes.Buffer)
|
||||||
}
|
}
|
||||||
|
@ -1659,7 +1665,7 @@ func (c *Command) Flags() *flag.FlagSet {
|
||||||
func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
|
func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
|
||||||
persistentFlags := c.PersistentFlags()
|
persistentFlags := c.PersistentFlags()
|
||||||
|
|
||||||
out := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
out := flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
c.LocalFlags().VisitAll(func(f *flag.Flag) {
|
c.LocalFlags().VisitAll(func(f *flag.Flag) {
|
||||||
if persistentFlags.Lookup(f.Name) == nil {
|
if persistentFlags.Lookup(f.Name) == nil {
|
||||||
out.AddFlag(f)
|
out.AddFlag(f)
|
||||||
|
@ -1674,7 +1680,7 @@ func (c *Command) LocalFlags() *flag.FlagSet {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
|
|
||||||
if c.lflags == nil {
|
if c.lflags == nil {
|
||||||
c.lflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.lflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
if c.flagErrorBuf == nil {
|
if c.flagErrorBuf == nil {
|
||||||
c.flagErrorBuf = new(bytes.Buffer)
|
c.flagErrorBuf = new(bytes.Buffer)
|
||||||
}
|
}
|
||||||
|
@ -1702,7 +1708,7 @@ func (c *Command) InheritedFlags() *flag.FlagSet {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
|
|
||||||
if c.iflags == nil {
|
if c.iflags == nil {
|
||||||
c.iflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.iflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
if c.flagErrorBuf == nil {
|
if c.flagErrorBuf == nil {
|
||||||
c.flagErrorBuf = new(bytes.Buffer)
|
c.flagErrorBuf = new(bytes.Buffer)
|
||||||
}
|
}
|
||||||
|
@ -1731,7 +1737,7 @@ func (c *Command) NonInheritedFlags() *flag.FlagSet {
|
||||||
// PersistentFlags returns the persistent FlagSet specifically set in the current command.
|
// PersistentFlags returns the persistent FlagSet specifically set in the current command.
|
||||||
func (c *Command) PersistentFlags() *flag.FlagSet {
|
func (c *Command) PersistentFlags() *flag.FlagSet {
|
||||||
if c.pflags == nil {
|
if c.pflags == nil {
|
||||||
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
if c.flagErrorBuf == nil {
|
if c.flagErrorBuf == nil {
|
||||||
c.flagErrorBuf = new(bytes.Buffer)
|
c.flagErrorBuf = new(bytes.Buffer)
|
||||||
}
|
}
|
||||||
|
@ -1744,9 +1750,9 @@ func (c *Command) PersistentFlags() *flag.FlagSet {
|
||||||
func (c *Command) ResetFlags() {
|
func (c *Command) ResetFlags() {
|
||||||
c.flagErrorBuf = new(bytes.Buffer)
|
c.flagErrorBuf = new(bytes.Buffer)
|
||||||
c.flagErrorBuf.Reset()
|
c.flagErrorBuf.Reset()
|
||||||
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
c.flags.SetOutput(c.flagErrorBuf)
|
c.flags.SetOutput(c.flagErrorBuf)
|
||||||
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
c.pflags.SetOutput(c.flagErrorBuf)
|
c.pflags.SetOutput(c.flagErrorBuf)
|
||||||
|
|
||||||
c.lflags = nil
|
c.lflags = nil
|
||||||
|
@ -1863,7 +1869,7 @@ func (c *Command) mergePersistentFlags() {
|
||||||
// If c.parentsPflags == nil, it makes new.
|
// If c.parentsPflags == nil, it makes new.
|
||||||
func (c *Command) updateParentsPflags() {
|
func (c *Command) updateParentsPflags() {
|
||||||
if c.parentsPflags == nil {
|
if c.parentsPflags == nil {
|
||||||
c.parentsPflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
c.parentsPflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
|
||||||
c.parentsPflags.SetOutput(c.flagErrorBuf)
|
c.parentsPflags.SetOutput(c.flagErrorBuf)
|
||||||
c.parentsPflags.SortFlags = false
|
c.parentsPflags.SortFlags = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,8 +370,28 @@ func TestAliasPrefixMatching(t *testing.T) {
|
||||||
// executable is `kubectl-plugin`, but we run it as `kubectl plugin`. The help
|
// executable is `kubectl-plugin`, but we run it as `kubectl plugin`. The help
|
||||||
// text should reflect the way we run the command.
|
// text should reflect the way we run the command.
|
||||||
func TestPlugin(t *testing.T) {
|
func TestPlugin(t *testing.T) {
|
||||||
|
cmd := &Command{
|
||||||
|
Use: "kubectl-plugin",
|
||||||
|
Args: NoArgs,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
CommandDisplayNameAnnotation: "kubectl plugin",
|
||||||
|
},
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdHelp, err := executeCommand(cmd, "-h")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, cmdHelp, "kubectl plugin [flags]")
|
||||||
|
checkStringContains(t, cmdHelp, "help for kubectl plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPlugin checks usage as plugin with sub commands.
|
||||||
|
func TestPluginWithSubCommands(t *testing.T) {
|
||||||
rootCmd := &Command{
|
rootCmd := &Command{
|
||||||
Use: "plugin",
|
Use: "kubectl-plugin",
|
||||||
Args: NoArgs,
|
Args: NoArgs,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
CommandDisplayNameAnnotation: "kubectl plugin",
|
CommandDisplayNameAnnotation: "kubectl plugin",
|
||||||
|
@ -387,6 +407,8 @@ func TestPlugin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkStringContains(t, rootHelp, "kubectl plugin [command]")
|
checkStringContains(t, rootHelp, "kubectl plugin [command]")
|
||||||
|
checkStringContains(t, rootHelp, "help for kubectl plugin")
|
||||||
|
checkStringContains(t, rootHelp, "kubectl plugin [command] --help")
|
||||||
|
|
||||||
childHelp, err := executeCommand(rootCmd, "sub", "-h")
|
childHelp, err := executeCommand(rootCmd, "sub", "-h")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -394,6 +416,15 @@ func TestPlugin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkStringContains(t, childHelp, "kubectl plugin sub [flags]")
|
checkStringContains(t, childHelp, "kubectl plugin sub [flags]")
|
||||||
|
checkStringContains(t, childHelp, "help for sub")
|
||||||
|
|
||||||
|
helpHelp, err := executeCommand(rootCmd, "help", "-h")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStringContains(t, helpHelp, "kubectl plugin help [path to command]")
|
||||||
|
checkStringContains(t, helpHelp, "kubectl plugin help [command]")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestChildSameName checks the correct behaviour of cobra in cases,
|
// TestChildSameName checks the correct behaviour of cobra in cases,
|
||||||
|
|
|
@ -17,6 +17,8 @@ package cobra
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -211,24 +213,29 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
// 2- Even without completions, we need to print the directive
|
// 2- Even without completions, we need to print the directive
|
||||||
}
|
}
|
||||||
|
|
||||||
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
|
noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd
|
||||||
|
if !noDescriptions {
|
||||||
|
if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil {
|
||||||
|
noDescriptions = !doDescriptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable
|
||||||
|
out := finalCmd.OutOrStdout()
|
||||||
for _, comp := range completions {
|
for _, comp := range completions {
|
||||||
if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable {
|
if noActiveHelp && strings.HasPrefix(comp, activeHelpMarker) {
|
||||||
// Remove all activeHelp entries in this case
|
// Remove all activeHelp entries if it's disabled.
|
||||||
if strings.HasPrefix(comp, activeHelpMarker) {
|
continue
|
||||||
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.SplitN(comp, "\t", 2)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we only write the first line to the output.
|
// Make sure we only write the first line to the output.
|
||||||
// This is needed if a description contains a linebreak.
|
// This is needed if a description contains a linebreak.
|
||||||
// Otherwise the shell scripts will interpret the other lines as new flags
|
// Otherwise the shell scripts will interpret the other lines as new flags
|
||||||
// and could therefore provide a wrong completion.
|
// and could therefore provide a wrong completion.
|
||||||
comp = strings.Split(comp, "\n")[0]
|
comp = strings.SplitN(comp, "\n", 2)[0]
|
||||||
|
|
||||||
// Finally trim the completion. This is especially important to get rid
|
// Finally trim the completion. This is especially important to get rid
|
||||||
// of a trailing tab when there are no description following it.
|
// of a trailing tab when there are no description following it.
|
||||||
|
@ -237,14 +244,14 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
// although there is no description).
|
// although there is no description).
|
||||||
comp = strings.TrimSpace(comp)
|
comp = strings.TrimSpace(comp)
|
||||||
|
|
||||||
// Print each possible completion to stdout for the completion script to consume.
|
// Print each possible completion to the output for the completion script to consume.
|
||||||
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
|
fmt.Fprintln(out, comp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// As the last printout, print the completion directive for the completion script to parse.
|
// As the last printout, print the completion directive for the completion script to parse.
|
||||||
// The directive integer must be that last character following a single colon (:).
|
// The directive integer must be that last character following a single colon (:).
|
||||||
// The completion script expects :<directive>
|
// The completion script expects :<directive>
|
||||||
fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive)
|
fmt.Fprintf(out, ":%d\n", directive)
|
||||||
|
|
||||||
// Print some helpful info to stderr for the user to understand.
|
// Print some helpful info to stderr for the user to understand.
|
||||||
// Output from stderr must be ignored by the completion script.
|
// Output from stderr must be ignored by the completion script.
|
||||||
|
@ -899,3 +906,34 @@ func CompError(msg string) {
|
||||||
func CompErrorln(msg string) {
|
func CompErrorln(msg string) {
|
||||||
CompError(fmt.Sprintf("%s\n", msg))
|
CompError(fmt.Sprintf("%s\n", msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These values should not be changed: users will be using them explicitly.
|
||||||
|
const (
|
||||||
|
configEnvVarGlobalPrefix = "COBRA"
|
||||||
|
configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
|
||||||
|
|
||||||
|
// configEnvVar returns the name of the program-specific configuration environment
|
||||||
|
// variable. It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the
|
||||||
|
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
func configEnvVar(name, suffix string) string {
|
||||||
|
// This format should not be changed: users will be using it explicitly.
|
||||||
|
v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix))
|
||||||
|
v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnvConfig returns the value of the configuration environment variable
|
||||||
|
// <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper
|
||||||
|
// case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
// If the value is empty or not set, the value of the environment variable
|
||||||
|
// COBRA_<SUFFIX> is returned instead.
|
||||||
|
func getEnvConfig(cmd *Command, suffix string) string {
|
||||||
|
v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix))
|
||||||
|
if v == "" {
|
||||||
|
v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -3517,3 +3518,194 @@ func TestGetFlagCompletion(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetEnvConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
use string
|
||||||
|
suffix string
|
||||||
|
cmdVar string
|
||||||
|
globalVar string
|
||||||
|
cmdVal string
|
||||||
|
globalVal string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Command envvar overrides global",
|
||||||
|
use: "root",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "ROOT_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "cmd",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "cmd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Missing/empty command envvar falls back to global",
|
||||||
|
use: "root",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "ROOT_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "global",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Missing/empty command and global envvars fall back to empty",
|
||||||
|
use: "root",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "ROOT_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "",
|
||||||
|
globalVal: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Periods in command use transform to underscores in env var name",
|
||||||
|
use: "foo.bar",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "FOO_BAR_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "cmd",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "cmd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Dashes in command use transform to underscores in env var name",
|
||||||
|
use: "quux-BAZ",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "QUUX_BAZ_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "cmd",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "cmd",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
// Could make env handling cleaner with t.Setenv with Go >= 1.17
|
||||||
|
err := os.Setenv(tc.cmdVar, tc.cmdVal)
|
||||||
|
defer func() {
|
||||||
|
assertNoErr(t, os.Unsetenv(tc.cmdVar))
|
||||||
|
}()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
err = os.Setenv(tc.globalVar, tc.globalVal)
|
||||||
|
defer func() {
|
||||||
|
assertNoErr(t, os.Unsetenv(tc.globalVar))
|
||||||
|
}()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
cmd := &Command{Use: tc.use}
|
||||||
|
got := getEnvConfig(cmd, tc.suffix)
|
||||||
|
if got != tc.expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", tc.expected, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisableDescriptions(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
specificDescriptionsEnvVar := configEnvVar(rootCmd.Name(), configEnvVarSuffixDescriptions)
|
||||||
|
globalDescriptionsEnvVar := configEnvVar(configEnvVarGlobalPrefix, configEnvVarSuffixDescriptions)
|
||||||
|
|
||||||
|
const (
|
||||||
|
descLineWithDescription = "first\tdescription"
|
||||||
|
descLineWithoutDescription = "first"
|
||||||
|
)
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{descLineWithDescription}
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
globalEnvValue string
|
||||||
|
specificEnvValue string
|
||||||
|
expectedLine string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"No env variables set",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
descLineWithDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Global value false",
|
||||||
|
"false",
|
||||||
|
"",
|
||||||
|
descLineWithoutDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Specific value false",
|
||||||
|
"",
|
||||||
|
"false",
|
||||||
|
descLineWithoutDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Both values false",
|
||||||
|
"false",
|
||||||
|
"false",
|
||||||
|
descLineWithoutDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Both values true",
|
||||||
|
"true",
|
||||||
|
"true",
|
||||||
|
descLineWithDescription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
if err := os.Setenv(specificDescriptionsEnvVar, tc.specificEnvValue); err != nil {
|
||||||
|
t.Errorf("Unexpected error setting %s: %v", specificDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv(globalDescriptionsEnvVar, tc.globalEnvValue); err != nil {
|
||||||
|
t.Errorf("Unexpected error setting %s: %v", globalDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var run = func() {
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
tc.expectedLine,
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
|
|
||||||
|
// For empty cases, test also unset state
|
||||||
|
if tc.specificEnvValue == "" {
|
||||||
|
if err := os.Unsetenv(specificDescriptionsEnvVar); err != nil {
|
||||||
|
t.Errorf("Unexpected error unsetting %s: %v", specificDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
if tc.globalEnvValue == "" {
|
||||||
|
if err := os.Unsetenv(globalDescriptionsEnvVar); err != nil {
|
||||||
|
t.Errorf("Unexpected error unsetting %s: %v", globalDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annota
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
groupStatus[group] = map[string]bool{}
|
groupStatus[group] = make(map[string]bool, len(flagnames))
|
||||||
for _, name := range flagnames {
|
for _, name := range flagnames {
|
||||||
groupStatus[group][name] = false
|
groupStatus[group][name] = false
|
||||||
}
|
}
|
||||||
|
@ -253,17 +253,17 @@ func (c *Command) enforceFlagGroupsForCompletion() {
|
||||||
// If none of the flags of a one-required group are present, we make all the flags
|
// 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
|
// of that group required so that the shell completion suggests them automatically
|
||||||
for flagList, flagnameAndStatus := range oneRequiredGroupStatus {
|
for flagList, flagnameAndStatus := range oneRequiredGroupStatus {
|
||||||
set := 0
|
isSet := false
|
||||||
|
|
||||||
for _, isSet := range flagnameAndStatus {
|
for _, isSet = range flagnameAndStatus {
|
||||||
if isSet {
|
if isSet {
|
||||||
set++
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// None of the flags of the group are set, mark all flags in the group
|
// None of the flags of the group are set, mark all flags in the group
|
||||||
// as required
|
// as required
|
||||||
if set == 0 {
|
if !isSet {
|
||||||
for _, fName := range strings.Split(flagList, " ") {
|
for _, fName := range strings.Split(flagList, " ") {
|
||||||
_ = c.MarkFlagRequired(fName)
|
_ = c.MarkFlagRequired(fName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -393,6 +393,9 @@ $ source <(helm completion bash --no-descriptions)
|
||||||
$ helm completion [tab][tab]
|
$ helm completion [tab][tab]
|
||||||
bash fish powershell zsh
|
bash fish powershell zsh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Setting the `<PROGRAM>_COMPLETION_DESCRIPTIONS` environment variable (falling back to `COBRA_COMPLETION_DESCRIPTIONS` if empty or not set) to a [falsey value](https://pkg.go.dev/strconv#ParseBool) achieves the same. `<PROGRAM>` is the name of your program with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
|
||||||
## Bash completions
|
## Bash completions
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
- [Datree](https://github.com/datreeio/datree)
|
- [Datree](https://github.com/datreeio/datree)
|
||||||
- [Delve](https://github.com/derekparker/delve)
|
- [Delve](https://github.com/derekparker/delve)
|
||||||
- [Docker (distribution)](https://github.com/docker/distribution)
|
- [Docker (distribution)](https://github.com/docker/distribution)
|
||||||
|
- [Encore](https://encore.dev)
|
||||||
- [Etcd](https://etcd.io/)
|
- [Etcd](https://etcd.io/)
|
||||||
- [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)
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
- [Kubescape](https://github.com/kubescape/kubescape)
|
- [Kubescape](https://github.com/kubescape/kubescape)
|
||||||
- [KubeVirt](https://github.com/kubevirt/kubevirt)
|
- [KubeVirt](https://github.com/kubevirt/kubevirt)
|
||||||
- [Linkerd](https://linkerd.io/)
|
- [Linkerd](https://linkerd.io/)
|
||||||
|
- [LXC](https://github.com/canonical/lxd)
|
||||||
- [Mattermost-server](https://github.com/mattermost/mattermost-server)
|
- [Mattermost-server](https://github.com/mattermost/mattermost-server)
|
||||||
- [Mercure](https://mercure.rocks/)
|
- [Mercure](https://mercure.rocks/)
|
||||||
- [Meroxa CLI](https://github.com/meroxa/cli)
|
- [Meroxa CLI](https://github.com/meroxa/cli)
|
||||||
|
@ -61,4 +63,5 @@
|
||||||
- [Vitess](https://vitess.io)
|
- [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)
|
- 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/)
|
||||||
|
- [Zarf](https://github.com/defenseunicorns/zarf)
|
||||||
- [ZITADEL](https://github.com/zitadel/zitadel)
|
- [ZITADEL](https://github.com/zitadel/zitadel)
|
||||||
|
|
|
@ -748,3 +748,57 @@ Read more about it in [Shell Completions](completions/_index.md).
|
||||||
Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users.
|
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.
|
Active Help are messages (hints, warnings, etc) printed as the program is being used.
|
||||||
Read more about it in [Active Help](active_help.md).
|
Read more about it in [Active Help](active_help.md).
|
||||||
|
|
||||||
|
## Creating a plugin
|
||||||
|
|
||||||
|
When creating a plugin for tools like *kubectl*, the executable is named
|
||||||
|
`kubectl-myplugin`, but it is used as `kubectl myplugin`. To fix help
|
||||||
|
messages and completions, annotate the root command with the
|
||||||
|
`cobra.CommandDisplayNameAnnotation` annotation.
|
||||||
|
|
||||||
|
### Example kubectl plugin
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
rootCmd := &cobra.Command{
|
||||||
|
Use: "kubectl-myplugin",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
cobra.CommandDisplayNameAnnotation: "kubectl myplugin",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subCmd := &cobra.Command{
|
||||||
|
Use: "subcmd",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("kubectl myplugin subcmd")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(subCmd)
|
||||||
|
rootCmd.Execute()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example run as a kubectl plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ kubectl myplugin
|
||||||
|
Usage:
|
||||||
|
kubectl myplugin [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
completion Generate the autocompletion script for the specified shell
|
||||||
|
help Help about any command
|
||||||
|
subcmd
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for kubectl myplugin
|
||||||
|
|
||||||
|
Use "kubectl myplugin [command] --help" for more information about a command.
|
||||||
|
```
|
||||||
|
|
Loading…
Add table
Reference in a new issue