Add basic PowerShell completions
This commit is contained in:
parent
e2c45ac9eb
commit
21ccc7b307
100
powershell_completions.go
Normal file
100
powershell_completions.go
Normal file
@ -0,0 +1,100 @@
|
||||
// PowerShell completions are based on the amazing work from clap:
|
||||
// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs
|
||||
//
|
||||
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
|
||||
// can be downloaded separately for windows 7 or 8.1).
|
||||
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var powerShellCompletionTemplate = `using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
$commandElements = $commandAst.CommandElements
|
||||
$command = @(
|
||||
'%s'
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-')) {
|
||||
break
|
||||
}
|
||||
$element.Value
|
||||
}
|
||||
) -join ';'
|
||||
$completions = @(switch ($command) {%s
|
||||
})
|
||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText
|
||||
}`
|
||||
|
||||
func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) {
|
||||
var cmdName string
|
||||
if previousCommandName == "" {
|
||||
cmdName = cmd.Name()
|
||||
} else {
|
||||
cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name())
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "\n '%s' {", cmdName)
|
||||
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if nonCompletableFlag(flag) {
|
||||
return
|
||||
}
|
||||
usage := escapeStringForPowerShell(flag.Usage)
|
||||
if len(flag.Shorthand) > 0 {
|
||||
fmt.Fprintf(out, "\n [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage)
|
||||
}
|
||||
fmt.Fprintf(out, "\n [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage)
|
||||
})
|
||||
|
||||
for _, subCmd := range cmd.Commands() {
|
||||
usage := escapeStringForPowerShell(subCmd.Short)
|
||||
fmt.Fprintf(out, "\n [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage)
|
||||
}
|
||||
|
||||
fmt.Fprint(out, "\n break\n }")
|
||||
|
||||
for _, subCmd := range cmd.Commands() {
|
||||
generatePowerShellSubcommandCases(out, subCmd, cmdName)
|
||||
}
|
||||
}
|
||||
|
||||
func escapeStringForPowerShell(s string) string {
|
||||
return strings.Replace(s, "'", "''", -1)
|
||||
}
|
||||
|
||||
// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer.
|
||||
func (c *Command) GenPowerShellCompletion(w io.Writer) error {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
var subCommandCases bytes.Buffer
|
||||
generatePowerShellSubcommandCases(&subCommandCases, c, "")
|
||||
fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String())
|
||||
|
||||
_, err := buf.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// GenPowerShellCompletionFile generates PowerShell completion file.
|
||||
func (c *Command) GenPowerShellCompletionFile(filename string) error {
|
||||
outFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
return c.GenPowerShellCompletion(outFile)
|
||||
}
|
122
powershell_completions_test.go
Normal file
122
powershell_completions_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPowerShellCompletion(t *testing.T) {
|
||||
tcs := []struct {
|
||||
name string
|
||||
root *Command
|
||||
expectedExpressions []string
|
||||
}{
|
||||
{
|
||||
name: "trivial",
|
||||
root: &Command{Use: "trivialapp"},
|
||||
expectedExpressions: []string{
|
||||
"Register-ArgumentCompleter -Native -CommandName 'trivialapp' -ScriptBlock",
|
||||
"$command = @(\n 'trivialapp'\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree",
|
||||
root: func() *Command {
|
||||
r := &Command{Use: "tree"}
|
||||
|
||||
sub1 := &Command{Use: "sub1"}
|
||||
r.AddCommand(sub1)
|
||||
|
||||
sub11 := &Command{Use: "sub11"}
|
||||
sub12 := &Command{Use: "sub12"}
|
||||
|
||||
sub1.AddCommand(sub11)
|
||||
sub1.AddCommand(sub12)
|
||||
|
||||
sub2 := &Command{Use: "sub2"}
|
||||
r.AddCommand(sub2)
|
||||
|
||||
sub21 := &Command{Use: "sub21"}
|
||||
sub22 := &Command{Use: "sub22"}
|
||||
|
||||
sub2.AddCommand(sub21)
|
||||
sub2.AddCommand(sub22)
|
||||
|
||||
return r
|
||||
}(),
|
||||
expectedExpressions: []string{
|
||||
"'tree'",
|
||||
"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')",
|
||||
"[CompletionResult]::new('sub2', 'sub2', [CompletionResultType]::ParameterValue, '')",
|
||||
"'tree;sub1'",
|
||||
"[CompletionResult]::new('sub11', 'sub11', [CompletionResultType]::ParameterValue, '')",
|
||||
"[CompletionResult]::new('sub12', 'sub12', [CompletionResultType]::ParameterValue, '')",
|
||||
"'tree;sub1;sub11'",
|
||||
"'tree;sub1;sub12'",
|
||||
"'tree;sub2'",
|
||||
"[CompletionResult]::new('sub21', 'sub21', [CompletionResultType]::ParameterValue, '')",
|
||||
"[CompletionResult]::new('sub22', 'sub22', [CompletionResultType]::ParameterValue, '')",
|
||||
"'tree;sub2;sub21'",
|
||||
"'tree;sub2;sub22'",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "flags",
|
||||
root: func() *Command {
|
||||
r := &Command{Use: "flags"}
|
||||
r.Flags().StringP("flag1", "a", "", "")
|
||||
r.Flags().String("flag2", "", "")
|
||||
|
||||
sub1 := &Command{Use: "sub1"}
|
||||
sub1.Flags().StringP("flag3", "c", "", "")
|
||||
r.AddCommand(sub1)
|
||||
|
||||
return r
|
||||
}(),
|
||||
expectedExpressions: []string{
|
||||
"'flags'",
|
||||
"[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, '')",
|
||||
"[CompletionResult]::new('--flag1', 'flag1', [CompletionResultType]::ParameterName, '')",
|
||||
"[CompletionResult]::new('--flag2', 'flag2', [CompletionResultType]::ParameterName, '')",
|
||||
"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')",
|
||||
"'flags;sub1'",
|
||||
"[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, '')",
|
||||
"[CompletionResult]::new('--flag3', 'flag3', [CompletionResultType]::ParameterName, '')",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "usage",
|
||||
root: func() *Command {
|
||||
r := &Command{Use: "usage"}
|
||||
r.Flags().String("flag", "", "this describes the usage of the 'flag' flag")
|
||||
|
||||
sub1 := &Command{
|
||||
Use: "sub1",
|
||||
Short: "short describes 'sub1'",
|
||||
}
|
||||
r.AddCommand(sub1)
|
||||
|
||||
return r
|
||||
}(),
|
||||
expectedExpressions: []string{
|
||||
"[CompletionResult]::new('--flag', 'flag', [CompletionResultType]::ParameterName, 'this describes the usage of the ''flag'' flag')",
|
||||
"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, 'short describes ''sub1''')",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
tc.root.GenPowerShellCompletion(buf)
|
||||
output := buf.String()
|
||||
|
||||
for _, expectedExpression := range tc.expectedExpressions {
|
||||
if !strings.Contains(output, expectedExpression) {
|
||||
t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user