mirror of
https://github.com/spf13/cobra
synced 2024-12-29 15:57:10 +00:00
Changed the nushell completion implementation to be a nushell external completer
This commit is contained in:
parent
a1431b2c57
commit
594faef23f
3 changed files with 58 additions and 146 deletions
|
@ -16,109 +16,59 @@ package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var carrageReturnRE = regexp.MustCompile(`\r?\n`)
|
func (c *Command) GenNushellCompletion(w io.Writer) error {
|
||||||
|
|
||||||
func descriptionString(desc string) string {
|
|
||||||
// Remove any carriage returns, this will break the extern
|
|
||||||
desc = carrageReturnRE.ReplaceAllString(desc, " ")
|
|
||||||
|
|
||||||
// Lets keep the descriptions short-ish
|
|
||||||
if len(desc) > 100 {
|
|
||||||
desc = desc[0:97] + "..."
|
|
||||||
}
|
|
||||||
return desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenNushellComp(c *Command, buf io.StringWriter, nameBuilder *strings.Builder, isRoot bool, includeDesc bool) {
|
|
||||||
processFlags := func(flags *pflag.FlagSet) {
|
|
||||||
flags.VisitAll(func(f *pflag.Flag) {
|
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("\t--%[1]s", f.Name))
|
|
||||||
|
|
||||||
if f.Shorthand != "" {
|
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("(-%[1]s)", f.Shorthand))
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeDesc && f.Usage != "" {
|
|
||||||
desc := descriptionString(f.Usage)
|
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("\t# %[1]s", desc))
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteStringAndCheck(buf, "\n")
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdName := c.Name()
|
|
||||||
// commands after root name will be like "git pull"
|
|
||||||
if !isRoot {
|
|
||||||
nameBuilder.WriteString(" ")
|
|
||||||
}
|
|
||||||
nameBuilder.WriteString(cmdName)
|
|
||||||
|
|
||||||
// only create an extern block if there is something to put in it
|
|
||||||
if len(c.ValidArgs) > 0 || c.HasAvailableFlags() {
|
|
||||||
builderString := nameBuilder.String()
|
|
||||||
|
|
||||||
// ensure there is a space before any previous content
|
|
||||||
// otherwise it will break descriptions
|
|
||||||
WriteStringAndCheck(buf, "\n")
|
|
||||||
|
|
||||||
funcName := builderString
|
|
||||||
if !isRoot {
|
|
||||||
funcName = fmt.Sprintf("\"%[1]s\"", builderString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeDesc && c.Short != "" {
|
|
||||||
desc := descriptionString(c.Short)
|
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("# %[1]s\n", desc))
|
|
||||||
}
|
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("export extern %[1]s [\n", funcName))
|
|
||||||
|
|
||||||
// valid args
|
|
||||||
for _, arg := range c.ValidArgs {
|
|
||||||
WriteStringAndCheck(buf, fmt.Sprintf("\t%[1]s?\n", arg))
|
|
||||||
}
|
|
||||||
|
|
||||||
processFlags(c.InheritedFlags())
|
|
||||||
processFlags(c.LocalFlags())
|
|
||||||
|
|
||||||
// End extern statement
|
|
||||||
WriteStringAndCheck(buf, "]\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// process sub commands
|
|
||||||
for _, child := range c.Commands() {
|
|
||||||
childBuilder := strings.Builder{}
|
|
||||||
childBuilder.WriteString(nameBuilder.String())
|
|
||||||
GenNushellComp(child, buf, &childBuilder, false, includeDesc)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) GenNushellCompletion(w io.Writer, includeDesc bool) error {
|
|
||||||
var nameBuilder strings.Builder
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
GenNushellComp(c, buf, &nameBuilder, true, includeDesc)
|
WriteStringAndCheck(buf, `
|
||||||
|
# An external configurator that works with any cobra based
|
||||||
|
# command line application (e.g. kubectl, minikube)
|
||||||
|
let cobra_configurator = {|spans|
|
||||||
|
|
||||||
|
let cmd = $spans.0
|
||||||
|
|
||||||
|
# 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 ($spans | last | str trim | is-empty) {
|
||||||
|
$'($cmd_args) ""'
|
||||||
|
} else {
|
||||||
|
$cmd_args
|
||||||
|
}
|
||||||
|
|
||||||
|
# The full command to be executed
|
||||||
|
let full_cmd = $'($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 $completions = ($stdout_lines | drop | parse -r '([\w\-\.:\+]*)\t?(.*)' | rename value description)
|
||||||
|
|
||||||
|
let result = ({
|
||||||
|
completions: $completions
|
||||||
|
directive_str: ($result.stderr)
|
||||||
|
directive: ($stdout_lines | last)
|
||||||
|
})
|
||||||
|
|
||||||
|
$result.completions
|
||||||
|
}`)
|
||||||
|
|
||||||
_, err := buf.WriteTo(w)
|
_, err := buf.WriteTo(w)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) GenNushellCompletionFile(filename string, includeDesc bool) error {
|
func (c *Command) GenNushellCompletionFile(filename string) error {
|
||||||
outFile, err := os.Create(filename)
|
outFile, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer outFile.Close()
|
||||||
|
|
||||||
return c.GenNushellCompletion(outFile, includeDesc)
|
return c.GenNushellCompletion(outFile)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenNushellCompletion(t *testing.T) {
|
func TestGenNushellCompletion(t *testing.T) {
|
||||||
rootCmd := &Command{
|
rootCmd := &Command{Use: "kubectl", Run: emptyRun}
|
||||||
Use: "kubectl",
|
|
||||||
Run: emptyRun,
|
|
||||||
}
|
|
||||||
rootCmd.PersistentFlags().String("server", "s", "The address and port of the Kubernetes API server")
|
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")
|
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{
|
getCmd := &Command{
|
||||||
Use: "get",
|
Use: "get",
|
||||||
Short: "Display one or many resources",
|
Short: "Display one or many resources",
|
||||||
|
@ -36,58 +32,17 @@ func TestGenNushellCompletion(t *testing.T) {
|
||||||
ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
|
ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(getCmd)
|
rootCmd.AddCommand(getCmd)
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
assertNoErr(t, rootCmd.GenNushellCompletion(buf, true))
|
assertNoErr(t, rootCmd.GenNushellCompletion(buf))
|
||||||
output := buf.String()
|
output := buf.String()
|
||||||
|
|
||||||
// root command has no local options, it should not be displayed
|
check(t, output, "let full_cmd = $'($cmd) __complete ($cmd_args)'")
|
||||||
checkOmit(t, output, "export extern kubectl")
|
|
||||||
|
|
||||||
check(t, output, "export extern \"kubectl get\"")
|
|
||||||
check(t, output, "--server")
|
|
||||||
check(t, output, "--skip-headers")
|
|
||||||
check(t, output, "pod?")
|
|
||||||
check(t, output, "node?")
|
|
||||||
check(t, output, "service?")
|
|
||||||
check(t, output, "replicationcontroller?")
|
|
||||||
|
|
||||||
check(t, output, "The address and port of the Kubernetes API serverIf true, avoid header prefixes in the log messages")
|
|
||||||
check(t, output, "The address and port of the Kubernetes API server")
|
|
||||||
check(t, output, "Display one or many resources")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenNushellCompletionWithoutDesc(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, false))
|
|
||||||
output := buf.String()
|
|
||||||
|
|
||||||
checkOmit(t, output, "The address and port of the Kubernetes API server")
|
|
||||||
checkOmit(t, output, "The address and port of the Kubernetes API serverIf true, avoid header prefixes in the log messages")
|
|
||||||
checkOmit(t, output, "Display one or many resources")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenNushellCompletionFile(t *testing.T) {
|
func TestGenNushellCompletionFile(t *testing.T) {
|
||||||
err := os.Mkdir("./tmp", 0755)
|
err := os.Mkdir("./tmp", 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -102,18 +57,18 @@ func TestGenNushellCompletionFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
rootCmd.AddCommand(child)
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
assertNoErr(t, rootCmd.GenNushellCompletionFile("./tmp/test", false))
|
assertNoErr(t, rootCmd.GenNushellCompletionFile("./tmp/test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailGenNushellCompletionFile(t *testing.T) {
|
func TestFailGenNushellCompletionFile(t *testing.T) {
|
||||||
err := os.Mkdir("./tmp", 0755)
|
err := os.Mkdir("./tmp", 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
defer os.RemoveAll("./tmp")
|
defer os.RemoveAll("./tmp")
|
||||||
|
|
||||||
f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0400)
|
f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0o400)
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
@ -124,7 +79,7 @@ func TestFailGenNushellCompletionFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
rootCmd.AddCommand(child)
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
got := rootCmd.GenNushellCompletionFile("./tmp/test", false)
|
got := rootCmd.GenNushellCompletionFile("./tmp/test")
|
||||||
if got == nil {
|
if got == nil {
|
||||||
t.Error("should raise permission denied error")
|
t.Error("should raise permission denied error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,11 +72,18 @@ PowerShell:
|
||||||
|
|
||||||
Nushell:
|
Nushell:
|
||||||
|
|
||||||
# To generate completions (replace YOUR_COMPLETION_DIR with actual path to save)
|
# 1. Copy the output of the command below:
|
||||||
> %[1]s completion nushell | save /YOUR_COMPLETION_DIR/%[1]s-completions.nu
|
> %[1]s completion nushell
|
||||||
|
|
||||||
# To load completions for each session, execute once (replace YOUR_COMPLETION_DIR with actual path):
|
# 2. Edit the nushell config file:
|
||||||
> echo "use /YOUR_COMPLETION_DIR/%[1]s-completions.nu *" | save --append $nu.config-path
|
> 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 for this setup to take effect.
|
||||||
|
|
||||||
`,cmd.Root().Name()),
|
`,cmd.Root().Name()),
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
|
|
Loading…
Reference in a new issue