Skip to content

Commit 7b640ff

Browse files
committed
powershell
1 parent 3a591bf commit 7b640ff

File tree

4 files changed

+86
-18
lines changed

4 files changed

+86
-18
lines changed

completion/all.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@ import (
1212
)
1313

1414
const (
15-
BashShell string = "bash"
16-
FishShell string = "fish"
17-
ZShell string = "zsh"
15+
BashShell string = "bash"
16+
FishShell string = "fish"
17+
ZShell string = "zsh"
18+
Powershell string = "powershell"
1819
)
1920

2021
var shellCompletionByName = map[string]func(io.Writer, string) error{
21-
BashShell: GenerateBashCompletion,
22-
FishShell: GenerateFishCompletion,
23-
ZShell: GenerateZshCompletion,
22+
BashShell: GenerateBashCompletion,
23+
FishShell: GenerateFishCompletion,
24+
ZShell: GenerateZshCompletion,
25+
Powershell: GeneratePowershellCompletion,
2426
}
2527

2628
func ShellOptions(choice *string) *serpent.Enum {
27-
return serpent.EnumOf(choice, BashShell, FishShell, ZShell)
29+
return serpent.EnumOf(choice, BashShell, FishShell, ZShell, Powershell)
2830
}
2931

3032
func ShellHandler() serpent.CompletionHandlerFunc {
31-
return EnumHandler(BashShell, FishShell, ZShell)
33+
return EnumHandler(BashShell, FishShell, ZShell, Powershell)
3234
}
3335

3436
func GetCompletion(writer io.Writer, shell string, cmdName string) error {

completion/handlers.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func ListFiles(word string, filter func(info os.FileInfo) bool) []string {
6868

6969
var cur string
7070
if info.IsDir() {
71-
cur = fmt.Sprintf("%s%s/", dir, info.Name())
71+
cur = fmt.Sprintf("%s%s%c", dir, info.Name(), os.PathSeparator)
7272
} else {
7373
cur = fmt.Sprintf("%s%s", dir, info.Name())
7474
}

completion/powershell.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package completion
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"text/template"
7+
)
8+
9+
const pshCompletionTemplate = `
10+
11+
# Escaping output sourced from:
12+
# https://github.com/spf13/cobra/blob/e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec/powershell_completions.go#L47
13+
filter _{{.Name}}_escapeStringWithSpecialChars {
14+
` + " $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'" + `
15+
}
16+
17+
$_{{.Name}}_completions = {
18+
param(
19+
$wordToComplete,
20+
$commandAst,
21+
$cursorPosition
22+
)
23+
# Legacy space handling sourced from:
24+
# https://github.com/spf13/cobra/blob/e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec/powershell_completions.go#L107
25+
if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or
26+
($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or
27+
(($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and
28+
$PSNativeCommandArgumentPassing -eq 'Legacy')) {
29+
$Space =` + "' `\"`\"'" + `
30+
} else {
31+
$Space = ' ""'
32+
}
33+
$Command = $commandAst.ToString().Substring(0, $cursorPosition - 1)
34+
if ($wordToComplete -ne "" ) {
35+
$wordToComplete = $Command.Split(" ")[-1]
36+
} else {
37+
$Command = $Command + $Space
38+
}
39+
# Get completions by calling the command with the COMPLETION_MODE environment variable set to 1
40+
"$Command" | Out-File -Append -FilePath "out.log"
41+
$env:COMPLETION_MODE = 1
42+
Invoke-Expression $Command | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
43+
"$_" | _{{.Name}}_escapeStringWithSpecialChars
44+
}
45+
rm env:COMPLETION_MODE
46+
}
47+
48+
Register-ArgumentCompleter -CommandName {{.Name}} -ScriptBlock $_{{.Name}}_completions
49+
`
50+
51+
func GeneratePowershellCompletion(
52+
w io.Writer,
53+
rootCmdName string,
54+
) error {
55+
tmpl, err := template.New("powershell").Parse(pshCompletionTemplate)
56+
if err != nil {
57+
return fmt.Errorf("parse template: %w", err)
58+
}
59+
60+
err = tmpl.Execute(
61+
w,
62+
map[string]string{
63+
"Name": rootCmdName,
64+
},
65+
)
66+
if err != nil {
67+
return fmt.Errorf("execute template: %w", err)
68+
}
69+
70+
return nil
71+
}

completion_test.go

+4-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package serpent_test
33
import (
44
"fmt"
55
"os"
6-
"runtime"
76
"strings"
87
"testing"
98

@@ -80,10 +79,6 @@ func TestCompletion(t *testing.T) {
8079
func TestFileCompletion(t *testing.T) {
8180
t.Parallel()
8281

83-
if runtime.GOOS == "windows" {
84-
t.Skip("Skipping test on Windows")
85-
}
86-
8782
cmd := func() *serpent.Command { return SampleCommand(t) }
8883

8984
t.Run("DirOK", func(t *testing.T) {
@@ -94,12 +89,12 @@ func TestFileCompletion(t *testing.T) {
9489
io := fakeIO(i)
9590
err := i.Run()
9691
require.NoError(t, err)
97-
require.Equal(t, tempDir+"/\n", io.Stdout.String())
92+
require.Equal(t, fmt.Sprintf("%s%c\n", tempDir, os.PathSeparator), io.Stdout.String())
9893
})
9994

10095
t.Run("EmptyDirOK", func(t *testing.T) {
10196
t.Parallel()
102-
tempDir := t.TempDir() + "/"
97+
tempDir := t.TempDir() + string(os.PathSeparator)
10398
i := cmd().Invoke("file", tempDir)
10499
i.Environ.Set(serpent.CompletionModeEnv, "1")
105100
io := fakeIO(i)
@@ -142,7 +137,7 @@ func TestFileCompletion(t *testing.T) {
142137
output := strings.Split(io.Stdout.String(), "\n")
143138
output = output[:len(output)-1]
144139
for _, str := range output {
145-
if strings.HasSuffix(str, "/") {
140+
if strings.HasSuffix(str, string(os.PathSeparator)) {
146141
require.DirExists(t, str)
147142
} else {
148143
require.FileExists(t, str)
@@ -168,7 +163,7 @@ func TestFileCompletion(t *testing.T) {
168163
require.Len(t, parts, 2)
169164
require.Equal(t, parts[0], "example.go")
170165
fileComp := parts[1]
171-
if strings.HasSuffix(fileComp, "/") {
166+
if strings.HasSuffix(fileComp, string(os.PathSeparator)) {
172167
require.DirExists(t, fileComp)
173168
} else {
174169
require.FileExists(t, fileComp)

0 commit comments

Comments
 (0)