mirror of
https://github.com/spf13/cobra
synced 2024-11-24 14:47:12 +00:00
Fish completion using Go completion (#1048)
Signed-off-by: Marc Khouzam <marc.khouzam@montreal.ca>
This commit is contained in:
parent
7fead4bf3b
commit
a684a6d7f5
7 changed files with 640 additions and 115 deletions
10
args.go
10
args.go
|
@ -2,6 +2,7 @@ package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PositionalArgs func(cmd *Command, args []string) error
|
type PositionalArgs func(cmd *Command, args []string) error
|
||||||
|
@ -34,8 +35,15 @@ func NoArgs(cmd *Command, args []string) error {
|
||||||
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
|
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
|
||||||
func OnlyValidArgs(cmd *Command, args []string) error {
|
func OnlyValidArgs(cmd *Command, args []string) error {
|
||||||
if len(cmd.ValidArgs) > 0 {
|
if len(cmd.ValidArgs) > 0 {
|
||||||
|
// Remove any description that may be included in ValidArgs.
|
||||||
|
// A description is following a tab character.
|
||||||
|
var validArgs []string
|
||||||
|
for _, v := range cmd.ValidArgs {
|
||||||
|
validArgs = append(validArgs, strings.Split(v, "\t")[0])
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range args {
|
for _, v := range args {
|
||||||
if !stringInSlice(v, cmd.ValidArgs) {
|
if !stringInSlice(v, validArgs) {
|
||||||
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -344,7 +344,7 @@ __%[1]s_handle_word()
|
||||||
__%[1]s_handle_word
|
__%[1]s_handle_word
|
||||||
}
|
}
|
||||||
|
|
||||||
`, name, ShellCompRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
|
`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePostscript(buf *bytes.Buffer, name string) {
|
func writePostscript(buf *bytes.Buffer, name string) {
|
||||||
|
@ -548,6 +548,9 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
|
||||||
buf.WriteString(" must_have_one_noun=()\n")
|
buf.WriteString(" must_have_one_noun=()\n")
|
||||||
sort.Sort(sort.StringSlice(cmd.ValidArgs))
|
sort.Sort(sort.StringSlice(cmd.ValidArgs))
|
||||||
for _, value := range cmd.ValidArgs {
|
for _, value := range cmd.ValidArgs {
|
||||||
|
// Remove any description that may be included following a tab character.
|
||||||
|
// Descriptions are not supported by bash completion.
|
||||||
|
value = strings.Split(value, "\t")[0]
|
||||||
buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
|
buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
|
||||||
}
|
}
|
||||||
if cmd.ValidArgsFunction != nil {
|
if cmd.ValidArgsFunction != nil {
|
||||||
|
|
|
@ -115,6 +115,8 @@ in this example again instead of the replication controllers.
|
||||||
|
|
||||||
In some cases it is not possible to provide a list of possible completions in advance. Instead, the list of completions must be determined at execution-time. Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command.
|
In some cases it is not possible to provide a list of possible completions in advance. Instead, the list of completions must be determined at execution-time. Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command.
|
||||||
|
|
||||||
|
**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*.
|
||||||
|
|
||||||
#### 1. Custom completions of nouns written in Go
|
#### 1. Custom completions of nouns written in Go
|
||||||
|
|
||||||
In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both.
|
In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both.
|
||||||
|
@ -301,6 +303,8 @@ So while there are many other files in the CWD it only shows me subdirs and thos
|
||||||
|
|
||||||
As for nouns, Cobra provides two ways of defining dynamic completion of flags. Note that both these methods can be used along-side each other as long as they are not both used for the same flag.
|
As for nouns, Cobra provides two ways of defining dynamic completion of flags. Note that both these methods can be used along-side each other as long as they are not both used for the same flag.
|
||||||
|
|
||||||
|
**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*.
|
||||||
|
|
||||||
## 1. Custom completions of flags written in Go
|
## 1. Custom completions of flags written in Go
|
||||||
|
|
||||||
To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner:
|
To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner:
|
||||||
|
|
|
@ -9,9 +9,14 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
// ShellCompRequestCmd is the name of the hidden command that is used to request
|
// ShellCompRequestCmd is the name of the hidden command that is used to request
|
||||||
// completion results from the program. It is used by the shell completion script.
|
// completion results from the program. It is used by the shell completion scripts.
|
||||||
const ShellCompRequestCmd = "__complete"
|
ShellCompRequestCmd = "__complete"
|
||||||
|
// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
|
||||||
|
// completion results without their description. It is used by the shell completion scripts.
|
||||||
|
ShellCompNoDescRequestCmd = "__completeNoDesc"
|
||||||
|
)
|
||||||
|
|
||||||
// Global map of flag completion functions.
|
// Global map of flag completion functions.
|
||||||
var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
|
var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
|
||||||
|
@ -77,6 +82,7 @@ func (d ShellCompDirective) string() string {
|
||||||
func (c *Command) initCompleteCmd(args []string) {
|
func (c *Command) initCompleteCmd(args []string) {
|
||||||
completeCmd := &Command{
|
completeCmd := &Command{
|
||||||
Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
|
Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
|
||||||
|
Aliases: []string{ShellCompNoDescRequestCmd},
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
DisableFlagParsing: true,
|
DisableFlagParsing: true,
|
||||||
|
@ -93,7 +99,12 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
// 2- Even without completions, we need to print the directive
|
// 2- Even without completions, we need to print the directive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
|
||||||
for _, comp := range completions {
|
for _, comp := range completions {
|
||||||
|
if noDescriptions {
|
||||||
|
// Remove any description that may be included following a tab character.
|
||||||
|
comp = strings.Split(comp, "\t")[0]
|
||||||
|
}
|
||||||
// Print each possible completion to stdout for the completion script to consume.
|
// Print each possible completion to stdout for the completion script to consume.
|
||||||
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
|
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
|
||||||
}
|
}
|
||||||
|
@ -139,6 +150,27 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
|
return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When doing completion of a flag name, as soon as an argument starts with
|
||||||
|
// a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
|
||||||
|
// the flag to be complete
|
||||||
|
if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
|
||||||
|
// We are completing a flag name
|
||||||
|
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
|
||||||
|
})
|
||||||
|
finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
|
||||||
|
})
|
||||||
|
|
||||||
|
directive := ShellCompDirectiveDefault
|
||||||
|
if len(completions) > 0 {
|
||||||
|
if strings.HasSuffix(completions[0], "=") {
|
||||||
|
directive = ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finalCmd, completions, directive, nil
|
||||||
|
}
|
||||||
|
|
||||||
var flag *pflag.Flag
|
var flag *pflag.Flag
|
||||||
if !finalCmd.DisableFlagParsing {
|
if !finalCmd.DisableFlagParsing {
|
||||||
// We only do flag completion if we are allowed to parse flags
|
// We only do flag completion if we are allowed to parse flags
|
||||||
|
@ -150,6 +182,33 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flag == nil {
|
||||||
|
// Complete subcommand names
|
||||||
|
for _, subCmd := range finalCmd.Commands() {
|
||||||
|
if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) {
|
||||||
|
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(finalCmd.ValidArgs) > 0 {
|
||||||
|
// Always complete ValidArgs, even if we are completing a subcommand name.
|
||||||
|
// This is for commands that have both subcommands and ValidArgs.
|
||||||
|
for _, validArg := range finalCmd.ValidArgs {
|
||||||
|
if strings.HasPrefix(validArg, toComplete) {
|
||||||
|
completions = append(completions, validArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are ValidArgs specified (even if they don't match), we stop completion.
|
||||||
|
// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
|
||||||
|
return finalCmd, completions, ShellCompDirectiveNoFileComp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always let the logic continue so as to add any ValidArgsFunction completions,
|
||||||
|
// even if we already found sub-commands.
|
||||||
|
// This is for commands that have subcommands but also specify a ValidArgsFunction.
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the flags and extract the arguments to prepare for calling the completion function
|
// Parse the flags and extract the arguments to prepare for calling the completion function
|
||||||
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
||||||
return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
||||||
|
@ -179,6 +238,32 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
return finalCmd, completions, directive, nil
|
return finalCmd, completions, directive, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
||||||
|
if nonCompletableFlag(flag) {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var completions []string
|
||||||
|
flagName := "--" + flag.Name
|
||||||
|
if strings.HasPrefix(flagName, toComplete) {
|
||||||
|
// Flag without the =
|
||||||
|
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
||||||
|
|
||||||
|
if len(flag.NoOptDefVal) == 0 {
|
||||||
|
// Flag requires a value, so it can be suffixed with =
|
||||||
|
flagName += "="
|
||||||
|
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flagName = "-" + flag.Shorthand
|
||||||
|
if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
|
||||||
|
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
|
||||||
func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
|
func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
|
||||||
var flagName string
|
var flagName string
|
||||||
trimmedArgs := args
|
trimmedArgs := args
|
||||||
|
|
|
@ -12,7 +12,7 @@ func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, Sh
|
||||||
}
|
}
|
||||||
|
|
||||||
var completions []string
|
var completions []string
|
||||||
for _, comp := range []string{"one", "two"} {
|
for _, comp := range []string{"one\tThe first", "two\tThe second"} {
|
||||||
if strings.HasPrefix(comp, toComplete) {
|
if strings.HasPrefix(comp, toComplete) {
|
||||||
completions = append(completions, comp)
|
completions = append(completions, comp)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, S
|
||||||
}
|
}
|
||||||
|
|
||||||
var completions []string
|
var completions []string
|
||||||
for _, comp := range []string{"three", "four"} {
|
for _, comp := range []string{"three\tThe third", "four\tThe fourth"} {
|
||||||
if strings.HasPrefix(comp, toComplete) {
|
if strings.HasPrefix(comp, toComplete) {
|
||||||
completions = append(completions, comp)
|
completions = append(completions, comp)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test completing an empty string
|
// Test completing an empty string
|
||||||
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "")
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check completing with a prefix
|
// Check completing with a prefix
|
||||||
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t")
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check completing with wrong number of args
|
// Check completing with wrong number of args
|
||||||
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "unexpectedArg", "t")
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "unexpectedArg", "t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
|
||||||
rootCmd.AddCommand(child1Cmd, child2Cmd)
|
rootCmd.AddCommand(child1Cmd, child2Cmd)
|
||||||
|
|
||||||
// Test completion of first sub-command with empty argument
|
// Test completion of first sub-command with empty argument
|
||||||
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "")
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test completion of first sub-command with a prefix to complete
|
// Test completion of first sub-command with a prefix to complete
|
||||||
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t")
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,339 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
|
||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check completing with wrong number of args
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "unexpectedArg", "t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test completion of second sub-command with empty argument
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"three",
|
||||||
|
"four",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"three",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check completing with wrong number of args
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "unexpectedArg", "t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidArgsFuncAliases(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
Aliases: []string{"son", "daughter"},
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
// Test completion of first sub-command with empty argument
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test completion of first sub-command with a prefix to complete
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "daughter", "t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"two",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check completing with wrong number of args
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "unexpectedArg", "t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidArgsFuncInBashScript(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
rootCmd.GenBashCompletion(buf)
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
check(t, output, "has_completion_function=1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoValidArgsFuncInBashScript(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
rootCmd.GenBashCompletion(buf)
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
checkOmit(t, output, "has_completion_function=1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteCmdInBashScript(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
rootCmd.GenBashCompletion(buf)
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
check(t, output, ShellCompNoDescRequestCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteNoDesCmdInFishScript(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
rootCmd.GenFishCompletion(buf, false)
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
check(t, output, ShellCompNoDescRequestCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteCmdInFishScript(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
rootCmd.GenFishCompletion(buf, true)
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
check(t, output, ShellCompRequestCmd)
|
||||||
|
checkOmit(t, output, ShellCompNoDescRequestCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagCompletionInGo(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
|
||||||
|
rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
completions := []string{}
|
||||||
|
for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
|
||||||
|
if strings.HasPrefix(comp, toComplete) {
|
||||||
|
completions = append(completions, comp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions, ShellCompDirectiveDefault
|
||||||
|
})
|
||||||
|
rootCmd.Flags().String("filename", "", "Enter a filename")
|
||||||
|
rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
completions := []string{}
|
||||||
|
for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
|
||||||
|
if strings.HasPrefix(comp, toComplete) {
|
||||||
|
completions = append(completions, comp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test completing an empty string
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"10",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check completing with a prefix
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "1")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"1",
|
||||||
|
"10",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test completing an empty string
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"file.yaml",
|
||||||
|
"myfile.json",
|
||||||
|
"file.xml",
|
||||||
|
":6",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check completing with a prefix
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "f")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"file.yaml",
|
||||||
|
"file.xml",
|
||||||
|
":6",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
child1Cmd := &Command{
|
||||||
|
Use: "child1",
|
||||||
|
ValidArgsFunction: validArgsFunc,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
child2Cmd := &Command{
|
||||||
|
Use: "child2",
|
||||||
|
ValidArgsFunction: validArgsFunc2,
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(child1Cmd, child2Cmd)
|
||||||
|
|
||||||
|
// Test completion of first sub-command with empty argument
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"one\tThe first",
|
||||||
|
"two\tThe second",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test completion of first sub-command with a prefix to complete
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"two\tThe second",
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
// Check completing with wrong number of args
|
// Check completing with wrong number of args
|
||||||
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t")
|
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -166,8 +499,8 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"three",
|
"three\tThe third",
|
||||||
"four",
|
"four\tThe fourth",
|
||||||
":0",
|
":0",
|
||||||
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
@ -181,7 +514,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"three",
|
"three\tThe third",
|
||||||
":0",
|
":0",
|
||||||
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
@ -204,94 +537,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidArgsFuncAliases(t *testing.T) {
|
func TestFlagCompletionInGoWithDesc(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
|
||||||
child := &Command{
|
|
||||||
Use: "child",
|
|
||||||
Aliases: []string{"son", "daughter"},
|
|
||||||
ValidArgsFunction: validArgsFunc,
|
|
||||||
Run: emptyRun,
|
|
||||||
}
|
|
||||||
rootCmd.AddCommand(child)
|
|
||||||
|
|
||||||
// Test completion of first sub-command with empty argument
|
|
||||||
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "son", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := strings.Join([]string{
|
|
||||||
"one",
|
|
||||||
"two",
|
|
||||||
":0",
|
|
||||||
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
|
||||||
|
|
||||||
if output != expected {
|
|
||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test completion of first sub-command with a prefix to complete
|
|
||||||
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "daughter", "t")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
|
||||||
"two",
|
|
||||||
":0",
|
|
||||||
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
|
||||||
|
|
||||||
if output != expected {
|
|
||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check completing with wrong number of args
|
|
||||||
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "son", "unexpectedArg", "t")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
|
||||||
":4",
|
|
||||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
|
||||||
|
|
||||||
if output != expected {
|
|
||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidArgsFuncInScript(t *testing.T) {
|
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
|
||||||
child := &Command{
|
|
||||||
Use: "child",
|
|
||||||
ValidArgsFunction: validArgsFunc,
|
|
||||||
Run: emptyRun,
|
|
||||||
}
|
|
||||||
rootCmd.AddCommand(child)
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
rootCmd.GenBashCompletion(buf)
|
|
||||||
output := buf.String()
|
|
||||||
|
|
||||||
check(t, output, "has_completion_function=1")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoValidArgsFuncInScript(t *testing.T) {
|
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
|
||||||
child := &Command{
|
|
||||||
Use: "child",
|
|
||||||
Run: emptyRun,
|
|
||||||
}
|
|
||||||
rootCmd.AddCommand(child)
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
rootCmd.GenBashCompletion(buf)
|
|
||||||
output := buf.String()
|
|
||||||
|
|
||||||
checkOmit(t, output, "has_completion_function=1")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFlagCompletionInGo(t *testing.T) {
|
|
||||||
rootCmd := &Command{
|
rootCmd := &Command{
|
||||||
Use: "root",
|
Use: "root",
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
|
@ -299,7 +545,7 @@ func TestFlagCompletionInGo(t *testing.T) {
|
||||||
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
|
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
|
||||||
rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
completions := []string{}
|
completions := []string{}
|
||||||
for _, comp := range []string{"1", "2", "10"} {
|
for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
|
||||||
if strings.HasPrefix(comp, toComplete) {
|
if strings.HasPrefix(comp, toComplete) {
|
||||||
completions = append(completions, comp)
|
completions = append(completions, comp)
|
||||||
}
|
}
|
||||||
|
@ -309,7 +555,7 @@ func TestFlagCompletionInGo(t *testing.T) {
|
||||||
rootCmd.Flags().String("filename", "", "Enter a filename")
|
rootCmd.Flags().String("filename", "", "Enter a filename")
|
||||||
rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
completions := []string{}
|
completions := []string{}
|
||||||
for _, comp := range []string{"file.yaml", "myfile.json", "file.xml"} {
|
for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
|
||||||
if strings.HasPrefix(comp, toComplete) {
|
if strings.HasPrefix(comp, toComplete) {
|
||||||
completions = append(completions, comp)
|
completions = append(completions, comp)
|
||||||
}
|
}
|
||||||
|
@ -324,9 +570,9 @@ func TestFlagCompletionInGo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := strings.Join([]string{
|
expected := strings.Join([]string{
|
||||||
"1",
|
"1\tThe first",
|
||||||
"2",
|
"2\tThe second",
|
||||||
"10",
|
"10\tThe tenth",
|
||||||
":0",
|
":0",
|
||||||
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
@ -341,8 +587,8 @@ func TestFlagCompletionInGo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"1",
|
"1\tThe first",
|
||||||
"10",
|
"10\tThe tenth",
|
||||||
":0",
|
":0",
|
||||||
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
@ -357,9 +603,9 @@ func TestFlagCompletionInGo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"file.yaml",
|
"file.yaml\tYAML format",
|
||||||
"myfile.json",
|
"myfile.json\tJSON format",
|
||||||
"file.xml",
|
"file.xml\tXML format",
|
||||||
":6",
|
":6",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
@ -374,8 +620,8 @@ func TestFlagCompletionInGo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"file.yaml",
|
"file.yaml\tYAML format",
|
||||||
"file.xml",
|
"file.xml\tXML format",
|
||||||
":6",
|
":6",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
|
172
fish_completions.go
Normal file
172
fish_completions.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) {
|
||||||
|
compCmd := ShellCompRequestCmd
|
||||||
|
if !includeDesc {
|
||||||
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name))
|
||||||
|
buf.WriteString(fmt.Sprintf(`
|
||||||
|
function __%[1]s_debug
|
||||||
|
set file "$BASH_COMP_DEBUG_FILE"
|
||||||
|
if test -n "$file"
|
||||||
|
echo "$argv" >> $file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function __%[1]s_perform_completion
|
||||||
|
__%[1]s_debug "Starting __%[1]s_perform_completion with: $argv"
|
||||||
|
|
||||||
|
set args (string split -- " " "$argv")
|
||||||
|
set lastArg "$args[-1]"
|
||||||
|
|
||||||
|
__%[1]s_debug "args: $args"
|
||||||
|
__%[1]s_debug "last arg: $lastArg"
|
||||||
|
|
||||||
|
set emptyArg ""
|
||||||
|
if test -z "$lastArg"
|
||||||
|
__%[1]s_debug "Setting emptyArg"
|
||||||
|
set emptyArg \"\"
|
||||||
|
end
|
||||||
|
__%[1]s_debug "emptyArg: $emptyArg"
|
||||||
|
|
||||||
|
set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg"
|
||||||
|
__%[1]s_debug "Calling $requestComp"
|
||||||
|
|
||||||
|
set results (eval $requestComp 2> /dev/null)
|
||||||
|
set comps $results[1..-2]
|
||||||
|
set directiveLine $results[-1]
|
||||||
|
|
||||||
|
# For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
|
||||||
|
# completions must be prefixed with the flag
|
||||||
|
set flagPrefix (string match -r -- '-.*=' "$lastArg")
|
||||||
|
|
||||||
|
__%[1]s_debug "Comps: $comps"
|
||||||
|
__%[1]s_debug "DirectiveLine: $directiveLine"
|
||||||
|
__%[1]s_debug "flagPrefix: $flagPrefix"
|
||||||
|
|
||||||
|
for comp in $comps
|
||||||
|
printf "%%s%%s\n" "$flagPrefix" "$comp"
|
||||||
|
end
|
||||||
|
|
||||||
|
printf "%%s\n" "$directiveLine"
|
||||||
|
end
|
||||||
|
|
||||||
|
# This function does three things:
|
||||||
|
# 1- Obtain the completions and store them in the global __%[1]s_comp_results
|
||||||
|
# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed
|
||||||
|
# and unset it otherwise
|
||||||
|
# 3- Return true if the completion results are not empty
|
||||||
|
function __%[1]s_prepare_completions
|
||||||
|
# Start fresh
|
||||||
|
set --erase __%[1]s_comp_do_file_comp
|
||||||
|
set --erase __%[1]s_comp_results
|
||||||
|
|
||||||
|
# Check if the command-line is already provided. This is useful for testing.
|
||||||
|
if not set --query __%[1]s_comp_commandLine
|
||||||
|
set __%[1]s_comp_commandLine (commandline)
|
||||||
|
end
|
||||||
|
__%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
|
||||||
|
|
||||||
|
set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine")
|
||||||
|
set --erase __%[1]s_comp_commandLine
|
||||||
|
__%[1]s_debug "Completion results: $results"
|
||||||
|
|
||||||
|
if test -z "$results"
|
||||||
|
__%[1]s_debug "No completion, probably due to a failure"
|
||||||
|
# Might as well do file completion, in case it helps
|
||||||
|
set --global __%[1]s_comp_do_file_comp 1
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set directive (string sub --start 2 $results[-1])
|
||||||
|
set --global __%[1]s_comp_results $results[1..-2]
|
||||||
|
|
||||||
|
__%[1]s_debug "Completions are: $__%[1]s_comp_results"
|
||||||
|
__%[1]s_debug "Directive is: $directive"
|
||||||
|
|
||||||
|
if test -z "$directive"
|
||||||
|
set directive 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set compErr (math (math --scale 0 $directive / %[3]d) %% 2)
|
||||||
|
if test $compErr -eq 1
|
||||||
|
__%[1]s_debug "Received error directive: aborting."
|
||||||
|
# Might as well do file completion, in case it helps
|
||||||
|
set --global __%[1]s_comp_do_file_comp 1
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set nospace (math (math --scale 0 $directive / %[4]d) %% 2)
|
||||||
|
set nofiles (math (math --scale 0 $directive / %[5]d) %% 2)
|
||||||
|
|
||||||
|
__%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
|
||||||
|
|
||||||
|
# Important not to quote the variable for count to work
|
||||||
|
set numComps (count $__%[1]s_comp_results)
|
||||||
|
__%[1]s_debug "numComps: $numComps"
|
||||||
|
|
||||||
|
if test $numComps -eq 1; and test $nospace -ne 0
|
||||||
|
# To support the "nospace" directive we trick the shell
|
||||||
|
# by outputting an extra, longer completion.
|
||||||
|
__%[1]s_debug "Adding second completion to perform nospace directive"
|
||||||
|
set --append __%[1]s_comp_results $__%[1]s_comp_results[1].
|
||||||
|
end
|
||||||
|
|
||||||
|
if test $numComps -eq 0; and test $nofiles -eq 0
|
||||||
|
__%[1]s_debug "Requesting file completion"
|
||||||
|
set --global __%[1]s_comp_do_file_comp 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we don't want file completion, we must return true even if there
|
||||||
|
# are no completions found. This is because fish will perform the last
|
||||||
|
# completion command, even if its condition is false, if no other
|
||||||
|
# completion command was triggered
|
||||||
|
return (not set --query __%[1]s_comp_do_file_comp)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove any pre-existing completions for the program since we will be handling all of them
|
||||||
|
# TODO this cleanup is not sufficient. Fish completions are only loaded once the user triggers
|
||||||
|
# them, so the below deletion will not work as it is run too early. What else can we do?
|
||||||
|
complete -c %[1]s -e
|
||||||
|
|
||||||
|
# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
|
||||||
|
# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
|
||||||
|
#
|
||||||
|
# This completion will be run second as complete commands are added FILO.
|
||||||
|
# It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
|
||||||
|
complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp'
|
||||||
|
|
||||||
|
# This completion will be run first as complete commands are added FILO.
|
||||||
|
# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp.
|
||||||
|
# It provides the program's completion choices.
|
||||||
|
complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
|
||||||
|
|
||||||
|
`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenFishCompletion generates fish completion file and writes to the passed writer.
|
||||||
|
func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
genFishComp(buf, c.Name(), includeDesc)
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenFishCompletionFile generates fish completion file.
|
||||||
|
func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.GenFishCompletion(outFile, includeDesc)
|
||||||
|
}
|
7
fish_completions.md
Normal file
7
fish_completions.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## Generating Fish Completions for your own cobra.Command
|
||||||
|
|
||||||
|
Cobra supports native Fish completions generated from the root `cobra.Command`. You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users.
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
* Custom completions implemented using the `ValidArgsFunction` and `RegisterFlagCompletionFunc()` are supported automatically but the ones implemented in Bash scripting are not.
|
Loading…
Reference in a new issue