208 lines
6.1 KiB
Go
208 lines
6.1 KiB
Go
package command
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
func init() {
|
|
RootCmd.AddCommand(completionCmd)
|
|
completionCmd.Flags().StringP("shell", "s", "bash", "The type of shell")
|
|
}
|
|
|
|
var completionCmd = &cobra.Command{
|
|
Use: "completion",
|
|
Hidden: true,
|
|
Short: "Generates completion scripts",
|
|
Long: `To enable completion in your shell, run:
|
|
|
|
eval "$(gh completion)"
|
|
|
|
You can add that to your '~/.bash_profile' to enable completion whenever you
|
|
start a new shell.
|
|
|
|
When installing with Homebrew, see https://docs.brew.sh/Shell-Completion
|
|
`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
shellType, err := cmd.Flags().GetString("shell")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch shellType {
|
|
case "bash":
|
|
RootCmd.GenBashCompletion(cmd.OutOrStdout())
|
|
case "zsh":
|
|
RootCmd.GenZshCompletion(cmd.OutOrStdout())
|
|
case "fish":
|
|
genFishCompletion(RootCmd, cmd.OutOrStdout())
|
|
default:
|
|
return fmt.Errorf("unsupported shell type %s", shellType)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// imported from https://github.com/spf13/cobra/pull/754
|
|
// author: Tim Reddehase
|
|
func genFishCompletion(c *cobra.Command, w io.Writer) error {
|
|
buf := new(bytes.Buffer)
|
|
|
|
writeFishPreamble(c, buf)
|
|
writeFishCommandCompletion(c, c, buf)
|
|
|
|
_, err := buf.WriteTo(w)
|
|
return err
|
|
}
|
|
|
|
func writeFishPreamble(cmd *cobra.Command, buf *bytes.Buffer) {
|
|
subCommandNames := []string{}
|
|
rangeCommands(cmd, func(subCmd *cobra.Command) {
|
|
subCommandNames = append(subCommandNames, subCmd.Name())
|
|
})
|
|
buf.WriteString(fmt.Sprintf(`
|
|
function __fish_%s_no_subcommand --description 'Test if %s has yet to be given the subcommand'
|
|
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]
|
|
set -l pattern (string replace -a " " ".+" "$argv")
|
|
string match -r "$pattern" (string trim -- "$cmd")
|
|
end
|
|
# 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()))
|
|
}
|
|
|
|
func writeFishCommandCompletion(rootCmd, cmd *cobra.Command, buf *bytes.Buffer) {
|
|
rangeCommands(cmd, func(subCmd *cobra.Command) {
|
|
condition := commandCompletionCondition(rootCmd, cmd)
|
|
escapedDescription := strings.Replace(subCmd.Short, "'", "\\'", -1)
|
|
buf.WriteString(fmt.Sprintf("complete -c %s -f %s -a %s -d '%s'\n", rootCmd.Name(), condition, subCmd.Name(), escapedDescription))
|
|
})
|
|
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())))
|
|
}
|
|
writeCommandFlagsCompletion(rootCmd, cmd, buf)
|
|
rangeCommands(cmd, func(subCmd *cobra.Command) {
|
|
writeFishCommandCompletion(rootCmd, subCmd, buf)
|
|
})
|
|
}
|
|
|
|
func writeCommandFlagsCompletion(rootCmd, cmd *cobra.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 *cobra.Command, buf *bytes.Buffer, flag *pflag.Flag) {
|
|
shortHandPortion := ""
|
|
if len(flag.Shorthand) > 0 {
|
|
shortHandPortion = fmt.Sprintf("-s %s", flag.Shorthand)
|
|
}
|
|
condition := completionCondition(rootCmd, cmd)
|
|
escapedUsage := strings.Replace(flag.Usage, "'", "\\'", -1)
|
|
buf.WriteString(fmt.Sprintf("complete -c %s -f %s %s %s -l %s -d '%s'\n",
|
|
rootCmd.Name(), condition, flagRequiresArgumentCompletion(flag), shortHandPortion, flag.Name, escapedUsage))
|
|
}
|
|
|
|
func flagRequiresArgumentCompletion(flag *pflag.Flag) string {
|
|
if flag.Value.Type() != "bool" {
|
|
return "-r"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func subCommandPath(rootCmd *cobra.Command, cmd *cobra.Command) string {
|
|
path := []string{}
|
|
currentCmd := cmd
|
|
if rootCmd == cmd {
|
|
return ""
|
|
}
|
|
for {
|
|
path = append([]string{currentCmd.Name()}, path...)
|
|
if currentCmd.Parent() == rootCmd {
|
|
return strings.Join(path, " ")
|
|
}
|
|
currentCmd = currentCmd.Parent()
|
|
}
|
|
}
|
|
|
|
func rangeCommands(cmd *cobra.Command, callback func(subCmd *cobra.Command)) {
|
|
for _, subCmd := range cmd.Commands() {
|
|
if !subCmd.IsAvailableCommand() || strings.HasPrefix(subCmd.Use, "help") {
|
|
continue
|
|
}
|
|
callback(subCmd)
|
|
}
|
|
}
|
|
|
|
func commandCompletionCondition(rootCmd, cmd *cobra.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)))
|
|
} 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))
|
|
})
|
|
return fmt.Sprintf("-n '%s'", strings.Join(bareConditions, "; and "))
|
|
}
|
|
|
|
func completionCondition(rootCmd, cmd *cobra.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))
|
|
}
|
|
return condition
|
|
}
|
|
|
|
func nonCompletableFlag(flag *pflag.Flag) bool {
|
|
return flag.Hidden || len(flag.Deprecated) > 0
|
|
}
|