Skip to content

Commit

Permalink
Merge branch 'spf13:main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
faizan-siddiqui authored Jan 16, 2025
2 parents dd7bc42 + 01ffff4 commit a9bed3a
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 21 deletions.
8 changes: 5 additions & 3 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"

Expand Down Expand Up @@ -1078,8 +1077,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {

args := c.args

// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
// If running unit tests, we don't want to take the os.Args, see #155 and #2173.
// For example, the following would fail:
// go test -c -o foo.test
// ./foo.test -test.run TestNoArgs
if c.args == nil && !isTesting() {
args = os.Args[1:]
}

Expand Down
33 changes: 33 additions & 0 deletions command_go120.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2013-2024 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !go1.21
// +build !go1.21

package cobra

import (
"os"
"strings"
)

// based on golang.org/x/mod/internal/lazyregexp: https://cs.opensource.google/go/x/mod/+/refs/tags/v0.19.0:internal/lazyregexp/lazyre.go;l=66
// For a non-go-test program which still has a name ending with ".test[.exe]", it will need to either:
// 1- Use go >= 1.21, or
// 2- call "rootCmd.SetArgs(os.Args[1:])" before calling "rootCmd.Execute()"
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")

func isTesting() bool {
return inTest
}
25 changes: 25 additions & 0 deletions command_go121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2013-2024 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build go1.21
// +build go1.21

package cobra

import "testing"

func isTesting() bool {
// Only available starting with go 1.21
return testing.Testing()
}
15 changes: 14 additions & 1 deletion command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ func TestPlugin(t *testing.T) {
checkStringContains(t, cmdHelp, "version for kubectl plugin")
}

// TestPlugin checks usage as plugin with sub commands.
// TestPluginWithSubCommands checks usage as plugin with sub commands.
func TestPluginWithSubCommands(t *testing.T) {
rootCmd := &Command{
Use: "kubectl-plugin",
Expand Down Expand Up @@ -2839,3 +2839,16 @@ func TestUnknownFlagShouldReturnSameErrorRegardlessOfArgPosition(t *testing.T) {
})
}
}

// This tests verifies that when running unit tests, os.Args are not used.
// This is because we don't want to process any arguments that are provided
// by "go test"; instead, unit tests must set the arguments they need using
// rootCmd.SetArgs().
func TestNoOSArgsWhenTesting(t *testing.T) {
root := &Command{Use: "root", Run: emptyRun}
os.Args = append(os.Args, "--unknown")

if _, err := root.ExecuteC(); err != nil {
t.Errorf("error: %v", err)
}
}
15 changes: 13 additions & 2 deletions completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@ func (c *Command) initCompleteCmd(args []string) {
}
}

// SliceValue is a reduced version of [pflag.SliceValue]. It is used to detect
// flags that accept multiple values and therefore can provide completion
// multiple times.
type SliceValue interface {
// GetSlice returns the flag value list as an array of strings.
GetSlice() []string
}

func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
// The last argument, which is not completely typed by the user,
// should not be part of the list of arguments
Expand Down Expand Up @@ -399,10 +407,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// If we have not found any required flags, only then can we show regular flags
if len(completions) == 0 {
doCompleteFlags := func(flag *pflag.Flag) {
if !flag.Changed ||
_, acceptsMultiple := flag.Value.(SliceValue)
acceptsMultiple = acceptsMultiple ||
strings.Contains(flag.Value.Type(), "Slice") ||
strings.Contains(flag.Value.Type(), "Array") ||
strings.HasPrefix(flag.Value.Type(), "stringTo") {
strings.HasPrefix(flag.Value.Type(), "stringTo")

if !flag.Changed || acceptsMultiple {
// If the flag is not already present, or if it can be specified multiple times (Array, Slice, or stringTo)
// we suggest it as a completion
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
Expand Down
35 changes: 34 additions & 1 deletion completions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,29 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
}
}

// customMultiString is a custom Value type that accepts multiple values,
// but does not include "Slice" or "Array" in its "Type" string.
type customMultiString []string

var _ SliceValue = (*customMultiString)(nil)

func (s *customMultiString) String() string {
return fmt.Sprintf("%v", *s)
}

func (s *customMultiString) Set(v string) error {
*s = append(*s, v)
return nil
}

func (s *customMultiString) Type() string {
return "multi string"
}

func (s *customMultiString) GetSlice() []string {
return *s
}

func TestFlagNameCompletionRepeat(t *testing.T) {
rootCmd := &Command{
Use: "root",
Expand All @@ -693,6 +716,8 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
sliceFlag := rootCmd.Flags().Lookup("slice")
rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
bsliceFlag := rootCmd.Flags().Lookup("bslice")
rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag")
multiFlag := rootCmd.Flags().Lookup("multi")

// Test that flag names are not repeated unless they are an array or slice
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
Expand All @@ -706,6 +731,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
"--array",
"--bslice",
"--help",
"--multi",
"--second",
"--slice",
":4",
Expand All @@ -728,6 +754,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
"--array",
"--bslice",
"--help",
"--multi",
"--slice",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
Expand All @@ -737,20 +764,22 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
}

// Test that flag names are not repeated unless they are an array or slice
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--")
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
bsliceFlag.Changed = false
multiFlag.Changed = false

expected = strings.Join([]string{
"--array",
"--bslice",
"--first",
"--help",
"--multi",
"--second",
"--slice",
":4",
Expand All @@ -768,6 +797,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
multiFlag.Changed = false

expected = strings.Join([]string{
"--array",
Expand All @@ -778,6 +808,8 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
"-f",
"--help",
"-h",
"--multi",
"-m",
"--second",
"-s",
"--slice",
Expand All @@ -797,6 +829,7 @@ func TestFlagNameCompletionRepeat(t *testing.T) {
// Reset the flag for the next command
sliceFlag.Changed = false
arrayFlag.Changed = false
multiFlag.Changed = false

expected = strings.Join([]string{
"-a",
Expand Down
3 changes: 0 additions & 3 deletions doc/man_docs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,6 @@ func TestGenManSeeAlso(t *testing.T) {
if err := assertLineFound(scanner, ".SH SEE ALSO"); err != nil {
t.Fatalf("Couldn't find SEE ALSO section header: %v", err)
}
if err := assertNextLineEquals(scanner, ".PP"); err != nil {
t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
}
if err := assertNextLineEquals(scanner, `\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP`); err != nil {
t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/spf13/cobra
go 1.15

require (
github.com/cpuguy83/go-md2man/v2 v2.0.4
github.com/cpuguy83/go-md2man/v2 v2.0.6
github.com/inconshreveable/mousetrap v1.1.0
github.com/spf13/pflag v1.0.5
gopkg.in/yaml.v3 v3.0.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
Expand Down
35 changes: 30 additions & 5 deletions powershell_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ filter __%[1]s_escapeStringWithSpecialChars {
if (-Not $Description) {
$Description = " "
}
@{Name="$Name";Description="$Description"}
New-Object -TypeName PSCustomObject -Property @{
Name = "$Name"
Description = "$Description"
}
}
Expand Down Expand Up @@ -240,7 +243,12 @@ filter __%[1]s_escapeStringWithSpecialChars {
__%[1]s_debug "Only one completion left"
# insert space after value
[System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
} else {
$CompletionText
}
} else {
# Add the proper number of spaces to align the descriptions
Expand All @@ -255,7 +263,12 @@ filter __%[1]s_escapeStringWithSpecialChars {
$Description = " ($($comp.Description))"
}
[System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
$CompletionText = "$($comp.Name)$Description"
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
} else {
$CompletionText
}
}
}
Expand All @@ -264,15 +277,27 @@ filter __%[1]s_escapeStringWithSpecialChars {
# insert space after value
# MenuComplete will automatically show the ToolTip of
# the highlighted value at the bottom of the suggestions.
[System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
} else {
$CompletionText
}
}
# TabCompleteNext and in case we get something unknown
Default {
# Like MenuComplete but we don't want to add a space here because
# the user need to press space anyway to get the completion.
# Description will not be shown because that's not possible with TabCompleteNext
[System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars)
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
} else {
$CompletionText
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions site/content/active_help.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ bin/ internal/ scripts/ pkg/ testdata/
## Supported shells

Active Help is currently only supported for the following shells:
- Bash (using [bash completion V2](shell_completions.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed.
- Bash (using [bash completion V2](completions/_index.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed.
- Zsh

## Adding Active Help messages

As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](shell_completions.md).
As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](completions/_index.md).

Adding Active Help is done through the use of the `cobra.AppendActiveHelp(...)` function, where the program repeatedly adds Active Help messages to the list of completions. Keep reading for details.

Expand Down Expand Up @@ -148,7 +148,7 @@ details for your users.

## Debugging Active Help

Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details.
Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](completions/_index.md#debugging) for details.

When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable. That variable is named `<PROGRAM>_ACTIVE_HELP` where any non-ASCII-alphanumeric characters are replaced by an `_`. For example, we can test deactivating some Active Help as shown below:

Expand Down

0 comments on commit a9bed3a

Please sign in to comment.