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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var carrageReturnRE = regexp.MustCompile(`\r?\n`)
|
||||
|
||||
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
|
||||
func (c *Command) GenNushellCompletion(w io.Writer) error {
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Command) GenNushellCompletionFile(filename string, includeDesc bool) error {
|
||||
func (c *Command) GenNushellCompletionFile(filename string) error {
|
||||
outFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
return c.GenNushellCompletion(outFile, includeDesc)
|
||||
return c.GenNushellCompletion(outFile)
|
||||
}
|
||||
|
|
|
@ -22,13 +22,9 @@ import (
|
|||
)
|
||||
|
||||
func TestGenNushellCompletion(t *testing.T) {
|
||||
rootCmd := &Command{
|
||||
Use: "kubectl",
|
||||
Run: emptyRun,
|
||||
}
|
||||
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",
|
||||
|
@ -36,58 +32,17 @@ func TestGenNushellCompletion(t *testing.T) {
|
|||
ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
|
||||
Run: emptyRun,
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(getCmd)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
assertNoErr(t, rootCmd.GenNushellCompletion(buf, true))
|
||||
assertNoErr(t, rootCmd.GenNushellCompletion(buf))
|
||||
output := buf.String()
|
||||
|
||||
// root command has no local options, it should not be displayed
|
||||
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")
|
||||
check(t, output, "let full_cmd = $'($cmd) __complete ($cmd_args)'")
|
||||
}
|
||||
|
||||
func TestGenNushellCompletionFile(t *testing.T) {
|
||||
err := os.Mkdir("./tmp", 0755)
|
||||
err := os.Mkdir("./tmp", 0o755)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
@ -102,18 +57,18 @@ func TestGenNushellCompletionFile(t *testing.T) {
|
|||
}
|
||||
rootCmd.AddCommand(child)
|
||||
|
||||
assertNoErr(t, rootCmd.GenNushellCompletionFile("./tmp/test", false))
|
||||
assertNoErr(t, rootCmd.GenNushellCompletionFile("./tmp/test"))
|
||||
}
|
||||
|
||||
func TestFailGenNushellCompletionFile(t *testing.T) {
|
||||
err := os.Mkdir("./tmp", 0755)
|
||||
err := os.Mkdir("./tmp", 0o755)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||
|
@ -124,7 +79,7 @@ func TestFailGenNushellCompletionFile(t *testing.T) {
|
|||
}
|
||||
rootCmd.AddCommand(child)
|
||||
|
||||
got := rootCmd.GenNushellCompletionFile("./tmp/test", false)
|
||||
got := rootCmd.GenNushellCompletionFile("./tmp/test")
|
||||
if got == nil {
|
||||
t.Error("should raise permission denied error")
|
||||
}
|
||||
|
|
|
@ -72,11 +72,18 @@ PowerShell:
|
|||
|
||||
Nushell:
|
||||
|
||||
# To generate completions (replace YOUR_COMPLETION_DIR with actual path to save)
|
||||
> %[1]s completion nushell | save /YOUR_COMPLETION_DIR/%[1]s-completions.nu
|
||||
# 1. Copy the output of the command below:
|
||||
> %[1]s completion nushell
|
||||
|
||||
# To load completions for each session, execute once (replace YOUR_COMPLETION_DIR with actual path):
|
||||
> echo "use /YOUR_COMPLETION_DIR/%[1]s-completions.nu *" | save --append $nu.config-path
|
||||
# 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 for this setup to take effect.
|
||||
|
||||
`,cmd.Root().Name()),
|
||||
DisableFlagsInUseLine: true,
|
||||
|
|
Loading…
Reference in a new issue