'Completions for nushell'

This commit is contained in:
Jack Wright 2022-11-11 18:05:24 -08:00
parent 02326d52c0
commit a1431b2c57
4 changed files with 282 additions and 1 deletions

124
nushell_completions.go Normal file
View file

@ -0,0 +1,124 @@
// 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"
"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
buf := new(bytes.Buffer)
GenNushellComp(c, buf, &nameBuilder, true, includeDesc)
_, 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
View file

@ -0,0 +1,4 @@
## Generating Nushell Completions For Your cobra.Command
Please refer to [Shell Completions](shell_completions.md) for details.

141
nushell_completions_test.go Normal file
View file

@ -0,0 +1,141 @@
// 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"
"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()
// 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")
}
func TestGenNushellCompletionFile(t *testing.T) {
err := os.Mkdir("./tmp", 0755)
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", false))
}
func TestFailGenNushellCompletionFile(t *testing.T) {
err := os.Mkdir("./tmp", 0755)
if err != nil {
log.Fatal(err.Error())
}
defer os.RemoveAll("./tmp")
f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0400)
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", false)
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")
}
}
}

View file

@ -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.
@ -68,9 +69,18 @@ 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 generate completions (replace YOUR_COMPLETION_DIR with actual path to save)
> %[1]s completion nushell | save /YOUR_COMPLETION_DIR/%[1]s-completions.nu
# 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
`,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 +92,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)
} }
}, },
} }