spf13--cobra/fish_completions.go

163 lines
4.7 KiB
Go
Raw Normal View History

2018-09-25 10:22:55 +02:00
package cobra
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/spf13/pflag"
)
// GenFishCompletion generates fish completion and writes to the passed writer.
func (c *Command) GenFishCompletion(w io.Writer) error {
buf := new(bytes.Buffer)
writeFishPreamble(c, buf)
writeFishCommandCompletion(c, c, buf)
_, err := buf.WriteTo(w)
return err
}
func writeFishPreamble(cmd *Command, buf *bytes.Buffer) {
subCommandNames := []string{}
rangeCommands(cmd, func(subCmd *Command) {
subCommandNames = append(subCommandNames, subCmd.Name())
})
buf.WriteString(fmt.Sprintf(`
2018-09-25 13:42:36 +02:00
function __fish_%s_no_subcommand --description 'Test if %s has yet to be given the subcommand'
2018-09-25 10:22:55 +02:00
for i in (commandline -opc)
if contains -- $i %s
return 1
end
end
return 0
end
function __fish_%s_seen_subcommand_path --description 'Test whether the full path of subcommands is the current path'
set -l cmd (commandline -opc)
set -e cmd[1]
return (test (string trim -- "$argv") = (string trim -- "$cmd"))
end
2018-09-25 15:56:37 +02:00
# borrowed from current fish-shell master, since it is not in current 2.7.1 release
function __fish_seen_argument
argparse 's/short=+' 'l/long=+' -- $argv
set cmd (commandline -co)
set -e cmd[1]
for t in $cmd
for s in $_flag_s
if string match -qr "^-[A-z0-9]*"$s"[A-z0-9]*\$" -- $t
return 0
end
end
for l in $_flag_l
if string match -q -- "--$l" $t
return 0
end
end
end
return 1
end
`, cmd.Name(), cmd.Name(), strings.Join(subCommandNames, " "), cmd.Name()))
2018-09-25 10:22:55 +02:00
}
func writeFishCommandCompletion(rootCmd, cmd *Command, buf *bytes.Buffer) {
rangeCommands(cmd, func(subCmd *Command) {
condition := commandCompletionCondition(rootCmd, cmd)
buf.WriteString(fmt.Sprintf("complete -c %s -f %s -a %s -d '%s'\n", rootCmd.Name(), condition, subCmd.Name(), subCmd.Short))
})
for _, validArg := range append(cmd.ValidArgs, cmd.ArgAliases...) {
condition := commandCompletionCondition(rootCmd, cmd)
buf.WriteString(
fmt.Sprintf("complete -c %s -f %s -a %s -d '%s'\n",
rootCmd.Name(), condition, validArg, fmt.Sprintf("Positional Argument to %s", cmd.Name())))
}
2018-09-25 10:22:55 +02:00
writeCommandFlagsCompletion(rootCmd, cmd, buf)
rangeCommands(cmd, func(subCmd *Command) {
writeFishCommandCompletion(rootCmd, subCmd, buf)
})
}
func writeCommandFlagsCompletion(rootCmd, cmd *Command, buf *bytes.Buffer) {
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if nonCompletableFlag(flag) {
return
}
writeCommandFlagCompletion(rootCmd, cmd, buf, flag)
})
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
if nonCompletableFlag(flag) {
return
}
writeCommandFlagCompletion(rootCmd, cmd, buf, flag)
})
}
func writeCommandFlagCompletion(rootCmd, cmd *Command, buf *bytes.Buffer, flag *pflag.Flag) {
shortHandPortion := ""
if len(flag.Shorthand) > 0 {
shortHandPortion = fmt.Sprintf("-s %s", flag.Shorthand)
}
condition := completionCondition(rootCmd, cmd)
buf.WriteString(fmt.Sprintf("complete -c %s -f %s %s %s -l %s -d '%s'\n",
rootCmd.Name(), condition, flagRequiresArgumentCompletion(flag), shortHandPortion, flag.Name, flag.Usage))
}
func flagRequiresArgumentCompletion(flag *pflag.Flag) string {
if flag.Value.Type() != "bool" {
return "-r"
}
return ""
}
func subCommandPath(rootCmd *Command, cmd *Command) string {
path := []string{}
currentCmd := cmd
for {
path = append([]string{currentCmd.Name()}, path...)
if currentCmd.Parent() == rootCmd {
return strings.Join(path, " ")
}
currentCmd = currentCmd.Parent()
}
return ""
}
func rangeCommands(cmd *Command, callback func(subCmd *Command)) {
for _, subCmd := range cmd.Commands() {
if !subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand {
2018-09-25 10:22:55 +02:00
continue
}
callback(subCmd)
}
}
func commandCompletionCondition(rootCmd, cmd *Command) string {
localNonPersistentFlags := cmd.LocalNonPersistentFlags()
bareConditions := []string{}
if rootCmd != cmd {
bareConditions = append(bareConditions, fmt.Sprintf("__fish_%s_seen_subcommand_path %s", rootCmd.Name(), subCommandPath(rootCmd, cmd)))
2018-09-25 10:22:55 +02:00
} else {
bareConditions = append(bareConditions, fmt.Sprintf("__fish_%s_no_subcommand", rootCmd.Name()))
}
localNonPersistentFlags.VisitAll(func(flag *pflag.Flag) {
flagSelector := fmt.Sprintf("-l %s", flag.Name)
if len(flag.Shorthand) > 0 {
flagSelector = fmt.Sprintf("-s %s %s", flag.Shorthand, flagSelector)
}
bareConditions = append(bareConditions, fmt.Sprintf("not __fish_seen_argument %s", flagSelector))
2018-09-25 10:22:55 +02:00
})
return fmt.Sprintf("-n '%s'", strings.Join(bareConditions, "; and "))
}
func completionCondition(rootCmd, cmd *Command) string {
condition := fmt.Sprintf("-n '__fish_%s_no_subcommand'", rootCmd.Name())
if rootCmd != cmd {
condition = fmt.Sprintf("-n '__fish_%s_seen_subcommand_path %s'", rootCmd.Name(), subCommandPath(rootCmd, cmd))
2018-09-25 10:22:55 +02:00
}
return condition
}