powershell_completions.go

  1// PowerShell completions are based on the amazing work from clap:
  2// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs
  3//
  4// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
  5// can be downloaded separately for windows 7 or 8.1).
  6
  7package cobra
  8
  9import (
 10	"bytes"
 11	"fmt"
 12	"io"
 13	"os"
 14	"strings"
 15
 16	"github.com/spf13/pflag"
 17)
 18
 19var powerShellCompletionTemplate = `using namespace System.Management.Automation
 20using namespace System.Management.Automation.Language
 21Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
 22    param($wordToComplete, $commandAst, $cursorPosition)
 23    $commandElements = $commandAst.CommandElements
 24    $command = @(
 25        '%s'
 26        for ($i = 1; $i -lt $commandElements.Count; $i++) {
 27            $element = $commandElements[$i]
 28            if ($element -isnot [StringConstantExpressionAst] -or
 29                $element.StringConstantType -ne [StringConstantType]::BareWord -or
 30                $element.Value.StartsWith('-')) {
 31                break
 32            }
 33            $element.Value
 34        }
 35    ) -join ';'
 36    $completions = @(switch ($command) {%s
 37    })
 38    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
 39        Sort-Object -Property ListItemText
 40}`
 41
 42func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) {
 43	var cmdName string
 44	if previousCommandName == "" {
 45		cmdName = cmd.Name()
 46	} else {
 47		cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name())
 48	}
 49
 50	fmt.Fprintf(out, "\n        '%s' {", cmdName)
 51
 52	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
 53		if nonCompletableFlag(flag) {
 54			return
 55		}
 56		usage := escapeStringForPowerShell(flag.Usage)
 57		if len(flag.Shorthand) > 0 {
 58			fmt.Fprintf(out, "\n            [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage)
 59		}
 60		fmt.Fprintf(out, "\n            [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage)
 61	})
 62
 63	for _, subCmd := range cmd.Commands() {
 64		usage := escapeStringForPowerShell(subCmd.Short)
 65		fmt.Fprintf(out, "\n            [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage)
 66	}
 67
 68	fmt.Fprint(out, "\n            break\n        }")
 69
 70	for _, subCmd := range cmd.Commands() {
 71		generatePowerShellSubcommandCases(out, subCmd, cmdName)
 72	}
 73}
 74
 75func escapeStringForPowerShell(s string) string {
 76	return strings.Replace(s, "'", "''", -1)
 77}
 78
 79// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer.
 80func (c *Command) GenPowerShellCompletion(w io.Writer) error {
 81	buf := new(bytes.Buffer)
 82
 83	var subCommandCases bytes.Buffer
 84	generatePowerShellSubcommandCases(&subCommandCases, c, "")
 85	fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String())
 86
 87	_, err := buf.WriteTo(w)
 88	return err
 89}
 90
 91// GenPowerShellCompletionFile generates PowerShell completion file.
 92func (c *Command) GenPowerShellCompletionFile(filename string) error {
 93	outFile, err := os.Create(filename)
 94	if err != nil {
 95		return err
 96	}
 97	defer outFile.Close()
 98
 99	return c.GenPowerShellCompletion(outFile)
100}