mirror of
https://github.com/spf13/cobra
synced 2024-11-16 18:57:08 +00:00
Merge aaf5cf8177
into 5a1acea321
This commit is contained in:
commit
daff378c57
7 changed files with 336 additions and 6 deletions
|
@ -25,7 +25,7 @@ Cobra provides:
|
|||
* Automatic help generation for commands and flags
|
||||
* Grouping help for subcommands
|
||||
* 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
|
||||
* Command aliases so you can change things without breaking them
|
||||
* 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().GenPowerShellCompletionWithDesc(out)
|
||||
|
||||
},
|
||||
}
|
||||
if haveNoDescFlag {
|
||||
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 {
|
||||
|
@ -875,7 +905,7 @@ func CompDebug(msg string, printToStdErr bool) {
|
|||
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
|
||||
if path := os.Getenv("BASH_COMP_DEBUG_FILE"); 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 {
|
||||
defer f.Close()
|
||||
WriteStringAndCheck(f, msg)
|
||||
|
|
|
@ -2577,6 +2577,7 @@ func TestCompleteCompletion(t *testing.T) {
|
|||
expected := strings.Join([]string{
|
||||
"bash",
|
||||
"fish",
|
||||
"nushell",
|
||||
"powershell",
|
||||
"zsh",
|
||||
":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
|
||||
- fish
|
||||
- PowerShell
|
||||
- Nushell
|
||||
|
||||
Cobra will automatically provide your program with a fully functional `completion` 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
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Use: "completion [bash|zsh|fish|powershell|nushell]",
|
||||
Short: "Generate completion script",
|
||||
Long: fmt.Sprintf(`To load completions:
|
||||
|
||||
|
@ -68,9 +69,29 @@ PowerShell:
|
|||
# To load completions for every new session, run:
|
||||
PS> %[1]s completion powershell > %[1]s.ps1
|
||||
# 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()),
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell", "nushell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch args[0] {
|
||||
|
@ -82,6 +103,8 @@ PowerShell:
|
|||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
case "nushell":
|
||||
cmd.Root().GenNushellCompletion(os.Stdout, true)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue