mirror of
https://github.com/spf13/cobra
synced 2024-06-26 05:39:09 +00:00
Merge aaf5cf8177
into 5a1acea321
This commit is contained in:
commit
daff378c57
|
@ -25,7 +25,7 @@ Cobra provides:
|
||||||
* Automatic help generation for commands and flags
|
* Automatic help generation for commands and flags
|
||||||
* Grouping help for subcommands
|
* Grouping help for subcommands
|
||||||
* Automatic help flag recognition of `-h`, `--help`, etc.
|
* Automatic help flag recognition of `-h`, `--help`, etc.
|
||||||
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
|
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell, nushell)
|
||||||
* Automatically generated man pages for your application
|
* Automatically generated man pages for your application
|
||||||
* Command aliases so you can change things without breaking them
|
* Command aliases so you can change things without breaking them
|
||||||
* The flexibility to define your own help, usage, etc.
|
* The flexibility to define your own help, usage, etc.
|
||||||
|
|
|
@ -835,14 +835,44 @@ to your powershell profile.
|
||||||
return cmd.Root().GenPowerShellCompletion(out)
|
return cmd.Root().GenPowerShellCompletion(out)
|
||||||
}
|
}
|
||||||
return cmd.Root().GenPowerShellCompletionWithDesc(out)
|
return cmd.Root().GenPowerShellCompletionWithDesc(out)
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if haveNoDescFlag {
|
if haveNoDescFlag {
|
||||||
powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
completionCmd.AddCommand(bash, zsh, fish, powershell)
|
nushell := &Command{
|
||||||
|
Use: "nushell",
|
||||||
|
Short: fmt.Sprintf(shortDesc, "nushell"),
|
||||||
|
Long: fmt.Sprintf(`Generate the autocompletion script for nushell.
|
||||||
|
|
||||||
|
To configure the Nushell cobra external completer for the first time:
|
||||||
|
# 1. Copy the output of the command below:
|
||||||
|
> %[1]s completion nushell
|
||||||
|
# 2. Edit the nushell config file:
|
||||||
|
> config nu
|
||||||
|
# 3. Paste above the "let-env config" line.
|
||||||
|
# 4. Change the config block's external_completer line to be external_completer: $cobra_completer
|
||||||
|
# 5. You will need to start a new shel for this setup to take effect.
|
||||||
|
|
||||||
|
If you have already setup the cobra external completer for other Cobra-based applications:
|
||||||
|
# 1. Edit the nushell config file:
|
||||||
|
> config nu
|
||||||
|
# 2. Modify the cobra_apps variable to contain this new application:
|
||||||
|
> let cobra_apps = [ "othercobraapp", "%[1]s" ]
|
||||||
|
# 3. You will need to start a new shell for this setup to take effect.
|
||||||
|
`, c.Root().Name()),
|
||||||
|
Args: NoArgs,
|
||||||
|
ValidArgsFunction: NoFileCompletions,
|
||||||
|
RunE: func(cmd *Command, args []string) error {
|
||||||
|
return cmd.Root().GenNushellCompletion(out, !noDesc)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if haveNoDescFlag {
|
||||||
|
nushell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
completionCmd.AddCommand(bash, zsh, fish, powershell, nushell)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFlag(cmd *Command, name string) *pflag.Flag {
|
func findFlag(cmd *Command, name string) *pflag.Flag {
|
||||||
|
@ -875,7 +905,7 @@ func CompDebug(msg string, printToStdErr bool) {
|
||||||
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
|
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
|
||||||
if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
|
if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
|
||||||
f, err := os.OpenFile(path,
|
f, err := os.OpenFile(path,
|
||||||
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
WriteStringAndCheck(f, msg)
|
WriteStringAndCheck(f, msg)
|
||||||
|
|
|
@ -2577,6 +2577,7 @@ func TestCompleteCompletion(t *testing.T) {
|
||||||
expected := strings.Join([]string{
|
expected := strings.Join([]string{
|
||||||
"bash",
|
"bash",
|
||||||
"fish",
|
"fish",
|
||||||
|
"nushell",
|
||||||
"powershell",
|
"powershell",
|
||||||
"zsh",
|
"zsh",
|
||||||
":4",
|
":4",
|
||||||
|
|
175
nushell_completions.go
Normal file
175
nushell_completions.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Command) GenNushellCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
name := c.Name()
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`
|
||||||
|
# A list of cobra apps that completion will be attempted for.
|
||||||
|
# Add new apps to this list to enable completion for them.
|
||||||
|
let cobra_apps = ["%[1]s"]
|
||||||
|
|
||||||
|
# An external completer that works with any cobra based
|
||||||
|
# command line application (e.g. kubectl, minikube)
|
||||||
|
let cobra_completer = {|spans|
|
||||||
|
let cmd = $spans.0
|
||||||
|
|
||||||
|
if not ($cobra_apps | where $cmd =~ $it | is-empty) {
|
||||||
|
let ShellCompDirectiveError = %[2]d
|
||||||
|
let ShellCompDirectiveNoSpace = %[3]d
|
||||||
|
let ShellCompDirectiveNoFileComp = %[4]d
|
||||||
|
let ShellCompDirectiveFilterFileExt = %[5]d
|
||||||
|
let ShellCompDirectiveFilterDirs = %[6]d
|
||||||
|
let last_span = ($spans | last | str trim)
|
||||||
|
|
||||||
|
def exec_complete [
|
||||||
|
--fuzzy,
|
||||||
|
spans: list
|
||||||
|
] {
|
||||||
|
let params = {
|
||||||
|
last_span: ($spans | last | str trim),
|
||||||
|
spans: $spans
|
||||||
|
}
|
||||||
|
# If there is an equals in the last span
|
||||||
|
# parse the span into two
|
||||||
|
let params = if $last_span =~ '=' {
|
||||||
|
let split = ($last_span | split row '=')
|
||||||
|
if ($split | length) > 1 {
|
||||||
|
{
|
||||||
|
last_span: ($split | last),
|
||||||
|
spans: ($spans | drop | append ($split | first) | append ($split | last))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
last_span: '',
|
||||||
|
spans: ($spans | drop | append ($split | first) | append '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$params
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_span = $params.last_span
|
||||||
|
let spans = $params.spans
|
||||||
|
|
||||||
|
# Drop the last param so we can fuzzy search on it
|
||||||
|
let spans = if $fuzzy {
|
||||||
|
$spans | drop
|
||||||
|
} else {
|
||||||
|
$spans
|
||||||
|
}
|
||||||
|
|
||||||
|
# skip the first entry in the span (the command) and join the rest of the span to create __complete args
|
||||||
|
let cmd_args = ($spans | skip 1 | str join ' ')
|
||||||
|
|
||||||
|
# If the last span entry was empty add "" to the end of the command args
|
||||||
|
let cmd_args = if ($last_span | is-empty) or $fuzzy {
|
||||||
|
$'($cmd_args) ""'
|
||||||
|
} else {
|
||||||
|
$cmd_args
|
||||||
|
}
|
||||||
|
|
||||||
|
# The full command to be executed with active help disable (Nushell does not support active help)
|
||||||
|
let full_cmd = $'COBRA_ACTIVE_HELP=0 ($cmd) __complete ($cmd_args)'
|
||||||
|
|
||||||
|
# Since nushell doesn't have anything like eval, execute in a subshell
|
||||||
|
let result = (do -i { nu -c $"'($full_cmd)'" } | complete)
|
||||||
|
|
||||||
|
# Create a record with all completion related info.
|
||||||
|
# directive and directive_str are for posterity
|
||||||
|
let stdout_lines = ($result.stdout | lines)
|
||||||
|
let directive = ($stdout_lines | last | str trim | str replace ":" "" | into int)
|
||||||
|
let completions = ($stdout_lines | drop | parse -r '([\w\-\.:\+\=\/]*)\t?(.*)' | rename value description)
|
||||||
|
let completions = if $fuzzy {
|
||||||
|
$completions | where $it.value =~ $last_span
|
||||||
|
|
||||||
|
} else {
|
||||||
|
($completions | where {|it| $it.value | str starts-with $last_span })
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
directive: $directive,
|
||||||
|
completions: $completions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = (exec_complete $spans)
|
||||||
|
let result = if (not ($last_span | is-empty)) and ($result.completions | is-empty) {
|
||||||
|
exec_complete --fuzzy $spans
|
||||||
|
} else {
|
||||||
|
$result
|
||||||
|
}
|
||||||
|
|
||||||
|
let directive = $result.directive
|
||||||
|
let completions = $result.completions
|
||||||
|
|
||||||
|
# Add space at the end of each completion
|
||||||
|
let completions = if $directive != $ShellCompDirectiveNoSpace {
|
||||||
|
$completions | each {|it| {value: $"($it.value) ", description: $it.description}}
|
||||||
|
} else {
|
||||||
|
$completions
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cobra returns a list of completions that are supported with this directive
|
||||||
|
# There is no way to currently support this in a nushell external completer
|
||||||
|
let completions = if $directive == $ShellCompDirectiveFilterFileExt {
|
||||||
|
[]
|
||||||
|
} else {
|
||||||
|
$completions
|
||||||
|
}
|
||||||
|
|
||||||
|
let return_val = if $last_span =~ '=' {
|
||||||
|
# if the completion is of the form -n= return flag as part of the completion so that it doesn't get replaced
|
||||||
|
$completions | each {|it| $"($last_span | split row '=' | first)=($it.value)" }
|
||||||
|
} else if $directive == $ShellCompDirectiveNoFileComp {
|
||||||
|
# Allow empty results as this will stop file completion
|
||||||
|
$completions
|
||||||
|
} else if ($completions | is-empty) or $directive == $ShellCompDirectiveError {
|
||||||
|
# Not returning null causes file completions to break
|
||||||
|
# Return null if there are no completions or ShellCompDirectiveError
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
$completions
|
||||||
|
}
|
||||||
|
|
||||||
|
$return_val
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, name, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
||||||
|
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) GenNushellCompletionFile(filename string, includeDesc bool) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.GenNushellCompletion(outFile, includeDesc)
|
||||||
|
}
|
4
nushell_completions.md
Normal file
4
nushell_completions.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
## Generating Nushell Completions For Your cobra.Command
|
||||||
|
|
||||||
|
Please refer to [Shell Completions](shell_completions.md) for details.
|
||||||
|
|
97
nushell_completions_test.go
Normal file
97
nushell_completions_test.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2013-2022 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenNushellCompletion(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "kubectl", Run: emptyRun}
|
||||||
|
rootCmd.PersistentFlags().String("server", "s", "The address and port of the Kubernetes API server")
|
||||||
|
rootCmd.PersistentFlags().BoolP("skip-headers", "", false, "The address and port of the Kubernetes API serverIf true, avoid header prefixes in the log messages")
|
||||||
|
getCmd := &Command{
|
||||||
|
Use: "get",
|
||||||
|
Short: "Display one or many resources",
|
||||||
|
ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"},
|
||||||
|
ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(getCmd)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assertNoErr(t, rootCmd.GenNushellCompletion(buf, true))
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
check(t, output, fmt.Sprintf("let cobra_apps = [\"%[1]s\"]", rootCmd.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenNushellCompletionFile(t *testing.T) {
|
||||||
|
err := os.Mkdir("./tmp", 0o755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll("./tmp")
|
||||||
|
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
assertNoErr(t, rootCmd.GenNushellCompletionFile("./tmp/test", true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailGenNushellCompletionFile(t *testing.T) {
|
||||||
|
err := os.Mkdir("./tmp", 0o755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll("./tmp")
|
||||||
|
|
||||||
|
f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0o400)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
got := rootCmd.GenNushellCompletionFile("./tmp/test", true)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ The currently supported shells are:
|
||||||
- Zsh
|
- Zsh
|
||||||
- fish
|
- fish
|
||||||
- PowerShell
|
- PowerShell
|
||||||
|
- Nushell
|
||||||
|
|
||||||
Cobra will automatically provide your program with a fully functional `completion` command,
|
Cobra will automatically provide your program with a fully functional `completion` command,
|
||||||
similarly to how it provides the `help` command.
|
similarly to how it provides the `help` command.
|
||||||
|
@ -28,7 +29,7 @@ and then modifying the generated `cmd/completion.go` file to look something like
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var completionCmd = &cobra.Command{
|
var completionCmd = &cobra.Command{
|
||||||
Use: "completion [bash|zsh|fish|powershell]",
|
Use: "completion [bash|zsh|fish|powershell|nushell]",
|
||||||
Short: "Generate completion script",
|
Short: "Generate completion script",
|
||||||
Long: fmt.Sprintf(`To load completions:
|
Long: fmt.Sprintf(`To load completions:
|
||||||
|
|
||||||
|
@ -68,9 +69,29 @@ PowerShell:
|
||||||
# To load completions for every new session, run:
|
# To load completions for every new session, run:
|
||||||
PS> %[1]s completion powershell > %[1]s.ps1
|
PS> %[1]s completion powershell > %[1]s.ps1
|
||||||
# and source this file from your PowerShell profile.
|
# and source this file from your PowerShell profile.
|
||||||
|
|
||||||
|
Nushell:
|
||||||
|
|
||||||
|
# To configure the Nushell cobra external completer for the first time:
|
||||||
|
# 1. Copy the output of the command below:
|
||||||
|
> %[1]s completion nushell
|
||||||
|
# 2. Edit the nushell config file:
|
||||||
|
> config nu
|
||||||
|
# 3. Paste above the "let-env config" line.
|
||||||
|
# 4. Change the config block's external_completer line to be
|
||||||
|
external_completer: $cobra_completer
|
||||||
|
# 5. You will need to start a new shell or for this setup to take effect.
|
||||||
|
|
||||||
|
# If you have already setup the cobra external completer:
|
||||||
|
# 1. Edit the nushell config file:
|
||||||
|
> config nu
|
||||||
|
# 2. Modify the cobra_apps varible to contain this application:
|
||||||
|
> let cobra_apps = [ "othercobraapp", "%[1]s" ]
|
||||||
|
# 3. You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
`,cmd.Root().Name()),
|
`,cmd.Root().Name()),
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell", "nushell"},
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
|
@ -82,6 +103,8 @@ PowerShell:
|
||||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||||
case "powershell":
|
case "powershell":
|
||||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||||
|
case "nushell":
|
||||||
|
cmd.Root().GenNushellCompletion(os.Stdout, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue