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}