Skip to content

Commit

Permalink
powershell
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson committed Jul 11, 2024
1 parent 3a591bf commit 7b640ff
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 18 deletions.
18 changes: 10 additions & 8 deletions completion/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ import (
)

const (
BashShell string = "bash"
FishShell string = "fish"
ZShell string = "zsh"
BashShell string = "bash"
FishShell string = "fish"
ZShell string = "zsh"
Powershell string = "powershell"
)

var shellCompletionByName = map[string]func(io.Writer, string) error{
BashShell: GenerateBashCompletion,
FishShell: GenerateFishCompletion,
ZShell: GenerateZshCompletion,
BashShell: GenerateBashCompletion,
FishShell: GenerateFishCompletion,
ZShell: GenerateZshCompletion,
Powershell: GeneratePowershellCompletion,
}

func ShellOptions(choice *string) *serpent.Enum {
return serpent.EnumOf(choice, BashShell, FishShell, ZShell)
return serpent.EnumOf(choice, BashShell, FishShell, ZShell, Powershell)
}

func ShellHandler() serpent.CompletionHandlerFunc {
return EnumHandler(BashShell, FishShell, ZShell)
return EnumHandler(BashShell, FishShell, ZShell, Powershell)
}

func GetCompletion(writer io.Writer, shell string, cmdName string) error {
Expand Down
2 changes: 1 addition & 1 deletion completion/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func ListFiles(word string, filter func(info os.FileInfo) bool) []string {

var cur string
if info.IsDir() {
cur = fmt.Sprintf("%s%s/", dir, info.Name())
cur = fmt.Sprintf("%s%s%c", dir, info.Name(), os.PathSeparator)
} else {
cur = fmt.Sprintf("%s%s", dir, info.Name())
}
Expand Down
71 changes: 71 additions & 0 deletions completion/powershell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package completion

import (
"fmt"
"io"
"text/template"
)

const pshCompletionTemplate = `
# Escaping output sourced from:
# https://github.com/spf13/cobra/blob/e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec/powershell_completions.go#L47
filter _{{.Name}}_escapeStringWithSpecialChars {
` + " $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'" + `
}
$_{{.Name}}_completions = {
param(
$wordToComplete,
$commandAst,
$cursorPosition
)
# Legacy space handling sourced from:
# https://github.com/spf13/cobra/blob/e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec/powershell_completions.go#L107
if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or
($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or
(($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and
$PSNativeCommandArgumentPassing -eq 'Legacy')) {
$Space =` + "' `\"`\"'" + `
} else {
$Space = ' ""'
}
$Command = $commandAst.ToString().Substring(0, $cursorPosition - 1)
if ($wordToComplete -ne "" ) {
$wordToComplete = $Command.Split(" ")[-1]
} else {
$Command = $Command + $Space
}
# Get completions by calling the command with the COMPLETION_MODE environment variable set to 1
"$Command" | Out-File -Append -FilePath "out.log"
$env:COMPLETION_MODE = 1
Invoke-Expression $Command | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
"$_" | _{{.Name}}_escapeStringWithSpecialChars
}
rm env:COMPLETION_MODE
}
Register-ArgumentCompleter -CommandName {{.Name}} -ScriptBlock $_{{.Name}}_completions
`

func GeneratePowershellCompletion(
w io.Writer,
rootCmdName string,
) error {
tmpl, err := template.New("powershell").Parse(pshCompletionTemplate)
if err != nil {
return fmt.Errorf("parse template: %w", err)
}

err = tmpl.Execute(
w,
map[string]string{
"Name": rootCmdName,
},
)
if err != nil {
return fmt.Errorf("execute template: %w", err)
}

return nil
}
13 changes: 4 additions & 9 deletions completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package serpent_test
import (
"fmt"
"os"
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -80,10 +79,6 @@ func TestCompletion(t *testing.T) {
func TestFileCompletion(t *testing.T) {
t.Parallel()

if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}

cmd := func() *serpent.Command { return SampleCommand(t) }

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

t.Run("EmptyDirOK", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir() + "/"
tempDir := t.TempDir() + string(os.PathSeparator)
i := cmd().Invoke("file", tempDir)
i.Environ.Set(serpent.CompletionModeEnv, "1")
io := fakeIO(i)
Expand Down Expand Up @@ -142,7 +137,7 @@ func TestFileCompletion(t *testing.T) {
output := strings.Split(io.Stdout.String(), "\n")
output = output[:len(output)-1]
for _, str := range output {
if strings.HasSuffix(str, "/") {
if strings.HasSuffix(str, string(os.PathSeparator)) {
require.DirExists(t, str)
} else {
require.FileExists(t, str)
Expand All @@ -168,7 +163,7 @@ func TestFileCompletion(t *testing.T) {
require.Len(t, parts, 2)
require.Equal(t, parts[0], "example.go")
fileComp := parts[1]
if strings.HasSuffix(fileComp, "/") {
if strings.HasSuffix(fileComp, string(os.PathSeparator)) {
require.DirExists(t, fileComp)
} else {
require.FileExists(t, fileComp)
Expand Down

0 comments on commit 7b640ff

Please # to comment.