From debbda42f9f6ed5c3933a6466aaaf8cb78c228a1 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 10 Feb 2021 17:57:11 +0100 Subject: [PATCH] Add powershell completions Add support for generating powershell completion files. This is especially useful for people using the podman remote client on windows. [NO TESTS NEEDED] Signed-off-by: Paul Holzinger --- Makefile | 3 +- cmd/podman/completion/completion.go | 8 +- cmd/podman/root.go | 4 +- completions/powershell/podman-remote.ps1 | 227 ++++++++++++++++++++ completions/powershell/podman.ps1 | 227 ++++++++++++++++++++ docs/source/markdown/podman-completion.1.md | 25 ++- 6 files changed, 481 insertions(+), 13 deletions(-) create mode 100644 completions/powershell/podman-remote.ps1 create mode 100644 completions/powershell/podman.ps1 diff --git a/Makefile b/Makefile index 4c2a4047bcd8..df2e6104bba0 100644 --- a/Makefile +++ b/Makefile @@ -536,6 +536,7 @@ install.completions: install ${SELINUXOPT} -d -m 755 ${DESTDIR}${FISHINSTALLDIR} install ${SELINUXOPT} -m 644 completions/fish/podman.fish ${DESTDIR}${FISHINSTALLDIR} install ${SELINUXOPT} -m 644 completions/fish/podman-remote.fish ${DESTDIR}${FISHINSTALLDIR} + # There is no common location for powershell files so do not install them. Users have to source the file from their powershell profile. .PHONY: install.cni install.cni: @@ -648,7 +649,7 @@ install.libseccomp.sudo: .PHONY: completions completions: podman podman-remote # key = shell, value = completion filename - declare -A outfiles=([bash]=%s [zsh]=_%s [fish]=%s.fish);\ + declare -A outfiles=([bash]=%s [zsh]=_%s [fish]=%s.fish [powershell]=%s.ps1);\ for shell in $${!outfiles[*]}; do \ for remote in "" "-remote"; do \ podman="podman$$remote"; \ diff --git a/cmd/podman/completion/completion.go b/cmd/podman/completion/completion.go index a4ceab4b05d0..472068130fc8 100644 --- a/cmd/podman/completion/completion.go +++ b/cmd/podman/completion/completion.go @@ -21,7 +21,7 @@ const ( var ( file string noDesc bool - shells = []string{"bash", "zsh", "fish"} + shells = []string{"bash", "zsh", "fish", "powershell"} completionCmd = &cobra.Command{ Use: fmt.Sprintf("completion [options] {%s}", strings.Join(shells, "|")), Short: "Generate shell autocompletions", @@ -76,6 +76,12 @@ func completion(cmd *cobra.Command, args []string) error { } case "fish": err = cmd.Root().GenFishCompletion(w, !noDesc) + case "powershell": + if noDesc { + err = cmd.Root().GenPowerShellCompletion(w) + } else { + err = cmd.Root().GenPowerShellCompletionWithDesc(w) + } } if err != nil { diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 874573bb92b8..805e267502e0 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -3,7 +3,7 @@ package main import ( "fmt" "os" - "path" + "path/filepath" "runtime" "runtime/pprof" "strings" @@ -59,7 +59,7 @@ Options: var ( rootCmd = &cobra.Command{ - Use: path.Base(os.Args[0]) + " [options]", + Use: filepath.Base(os.Args[0]) + " [options]", Long: "Manage pods, containers and images", SilenceUsage: true, SilenceErrors: true, diff --git a/completions/powershell/podman-remote.ps1 b/completions/powershell/podman-remote.ps1 new file mode 100644 index 000000000000..9cdbabc529fb --- /dev/null +++ b/completions/powershell/podman-remote.ps1 @@ -0,0 +1,227 @@ +# powershell completion for podman-remote -*- shell-script -*- + +function __podman-remote_debug { + if ($env:BASH_COMP_DEBUG_FILE) { + "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE" + } +} + +filter __podman-remote_escapeStringWithSpecialChars { + $_ -replace '\s|#|@|\$|;|,|''|\{|\}|\(|\)|"|`|\||<|>|&','`$&' +} + +Register-ArgumentCompleter -CommandName 'podman-remote' -ScriptBlock { + param( + $WordToComplete, + $CommandAst, + $CursorPosition + ) + + # Get the current command line and convert into a string + $Command = $CommandAst.CommandElements + $Command = "$Command" + + __podman-remote_debug "" + __podman-remote_debug "========= starting completion logic ==========" + __podman-remote_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CursorPosition location, so we need + # to truncate the command-line ($Command) up to the $CursorPosition location. + # Make sure the $Command is longer then the $CursorPosition before we truncate. + # This happens because the $Command does not include the last space. + if ($Command.Length -gt $CursorPosition) { + $Command=$Command.Substring(0,$CursorPosition) + } + __podman-remote_debug "Truncated command: $Command" + + $ShellCompDirectiveError=1 + $ShellCompDirectiveNoSpace=2 + $ShellCompDirectiveNoFileComp=4 + $ShellCompDirectiveFilterFileExt=8 + $ShellCompDirectiveFilterDirs=16 + + # Prepare the command to request completions for the program. + # Split the command at the first space to separate the program and arguments. + $Program,$Arguments = $Command.Split(" ",2) + $RequestComp="$Program __complete $Arguments" + __podman-remote_debug "RequestComp: $RequestComp" + + # we cannot use $WordToComplete because it + # has the wrong values if the cursor was moved + # so use the last argument + if ($WordToComplete -ne "" ) { + $WordToComplete = $Arguments.Split(" ")[-1] + } + __podman-remote_debug "New WordToComplete: $WordToComplete" + + + # Check for flag with equal sign + $IsEqualFlag = ($WordToComplete -Like "--*=*" ) + if ( $IsEqualFlag ) { + __podman-remote_debug "Completing equal sign flag" + # Remove the flag part + $Flag,$WordToComplete = $WordToComplete.Split("=",2) + } + + if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) { + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __podman-remote_debug "Adding extra empty parameter" + # We need to use `"`" to pass an empty argument a "" or '' does not work!!! + $RequestComp="$RequestComp" + ' `"`"' + } + + __podman-remote_debug "Calling $RequestComp" + #call the command store the output in $out and redirect stderr and stdout to null + # $Out is an array contains each line per element + Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null + + + # get directive from last line + [int]$Directive = $Out[-1].TrimStart(':') + if ($Directive -eq "") { + # There is no directive specified + $Directive = 0 + } + __podman-remote_debug "The completion directive is: $Directive" + + # remove directive (last element) from out + $Out = $Out | Where-Object { $_ -ne $Out[-1] } + __podman-remote_debug "The completions are: $Out" + + if (($Directive -band $ShellCompDirectiveError) -ne 0 ) { + # Error code. No completion. + __podman-remote_debug "Received error from custom completion go code" + return + } + + $Longest = 0 + $Values = $Out | ForEach-Object { + #Split the output in name and description + $Name, $Description = $_.Split("`t",2) + __podman-remote_debug "Name: $Name Description: $Description" + + # Look for the longest completion so that we can format things nicely + if ($Longest -lt $Name.Length) { + $Longest = $Name.Length + } + + # Set the description to a one space string if there is none set. + # This is needed because the CompletionResult does not accept an empty string as argument + if (-Not $Description) { + $Description = " " + } + @{Name="$Name";Description="$Description"} + } + + + $Space = " " + if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) { + # remove the space here + __podman-remote_debug "ShellCompDirectiveNoSpace is called" + $Space = "" + } + + if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { + __podman-remote_debug "ShellCompDirectiveNoFileComp is called" + + if ($Values.Length -eq 0) { + # Just print an empty string here so the + # shell does not start to complete paths. + # We cannot use CompletionResult here because + # it does not accept an empty string as argument. + "" + return + } + } + + if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or + (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { + __podman-remote_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported" + + # return here to prevent the completion of the extensions + return + } + + $Values = $Values | Where-Object { + # filter the result + $_.Name -like "$WordToComplete*" + + # Join the flag back if we have a equal sign flag + if ( $IsEqualFlag ) { + __podman-remote_debug "Join the equal sign flag back to the completion value" + $_.Name = $Flag + "=" + $_.Name + } + } + + # Get the current mode + $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function + __podman-remote_debug "Mode: $Mode" + + $Values | ForEach-Object { + + # store temporay because switch will overwrite $_ + $comp = $_ + + # PowerShell supports three different completion modes + # - TabCompleteNext (default windows style - on each key press the next option is displayed) + # - Complete (works like bash) + # - MenuComplete (works like zsh) + # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function + + # CompletionResult Arguments: + # 1) CompletionText text to be used as the auto completion result + # 2) ListItemText text to be displayed in the suggestion list + # 3) ResultType type of completion result + # 4) ToolTip text for the tooltip with details about the object + + switch ($Mode) { + + # bash like + "Complete" { + + if ($Values.Length -eq 1) { + __podman-remote_debug "Only one completion left" + + # insert space after value + [System.Management.Automation.CompletionResult]::new($($comp.Name | __podman-remote_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + + } else { + # Add the proper number of spaces to align the descriptions + while($comp.Name.Length -lt $Longest) { + $comp.Name = $comp.Name + " " + } + + # Check for empty description and only add parentheses if needed + if ($($comp.Description) -eq " " ) { + $Description = "" + } else { + $Description = " ($($comp.Description))" + } + + [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") + } + } + + # zsh like + "MenuComplete" { + # insert space after value + # MenuComplete will automatically show the ToolTip of + # the highlighted value at the bottom of the suggestions. + [System.Management.Automation.CompletionResult]::new($($comp.Name | __podman-remote_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + + # TabCompleteNext and in case we get something unknown + Default { + # Like MenuComplete but we don't want to add a space here because + # the user need to press space anyway to get the completion. + # Description will not be shown because thats not possible with TabCompleteNext + [System.Management.Automation.CompletionResult]::new($($comp.Name | __podman-remote_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + } + + } +} + +# This file is generated with "podman-remote completion"; see: podman-completion(1) diff --git a/completions/powershell/podman.ps1 b/completions/powershell/podman.ps1 new file mode 100644 index 000000000000..6b6f832d2575 --- /dev/null +++ b/completions/powershell/podman.ps1 @@ -0,0 +1,227 @@ +# powershell completion for podman -*- shell-script -*- + +function __podman_debug { + if ($env:BASH_COMP_DEBUG_FILE) { + "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE" + } +} + +filter __podman_escapeStringWithSpecialChars { + $_ -replace '\s|#|@|\$|;|,|''|\{|\}|\(|\)|"|`|\||<|>|&','`$&' +} + +Register-ArgumentCompleter -CommandName 'podman' -ScriptBlock { + param( + $WordToComplete, + $CommandAst, + $CursorPosition + ) + + # Get the current command line and convert into a string + $Command = $CommandAst.CommandElements + $Command = "$Command" + + __podman_debug "" + __podman_debug "========= starting completion logic ==========" + __podman_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CursorPosition location, so we need + # to truncate the command-line ($Command) up to the $CursorPosition location. + # Make sure the $Command is longer then the $CursorPosition before we truncate. + # This happens because the $Command does not include the last space. + if ($Command.Length -gt $CursorPosition) { + $Command=$Command.Substring(0,$CursorPosition) + } + __podman_debug "Truncated command: $Command" + + $ShellCompDirectiveError=1 + $ShellCompDirectiveNoSpace=2 + $ShellCompDirectiveNoFileComp=4 + $ShellCompDirectiveFilterFileExt=8 + $ShellCompDirectiveFilterDirs=16 + + # Prepare the command to request completions for the program. + # Split the command at the first space to separate the program and arguments. + $Program,$Arguments = $Command.Split(" ",2) + $RequestComp="$Program __complete $Arguments" + __podman_debug "RequestComp: $RequestComp" + + # we cannot use $WordToComplete because it + # has the wrong values if the cursor was moved + # so use the last argument + if ($WordToComplete -ne "" ) { + $WordToComplete = $Arguments.Split(" ")[-1] + } + __podman_debug "New WordToComplete: $WordToComplete" + + + # Check for flag with equal sign + $IsEqualFlag = ($WordToComplete -Like "--*=*" ) + if ( $IsEqualFlag ) { + __podman_debug "Completing equal sign flag" + # Remove the flag part + $Flag,$WordToComplete = $WordToComplete.Split("=",2) + } + + if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) { + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __podman_debug "Adding extra empty parameter" + # We need to use `"`" to pass an empty argument a "" or '' does not work!!! + $RequestComp="$RequestComp" + ' `"`"' + } + + __podman_debug "Calling $RequestComp" + #call the command store the output in $out and redirect stderr and stdout to null + # $Out is an array contains each line per element + Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null + + + # get directive from last line + [int]$Directive = $Out[-1].TrimStart(':') + if ($Directive -eq "") { + # There is no directive specified + $Directive = 0 + } + __podman_debug "The completion directive is: $Directive" + + # remove directive (last element) from out + $Out = $Out | Where-Object { $_ -ne $Out[-1] } + __podman_debug "The completions are: $Out" + + if (($Directive -band $ShellCompDirectiveError) -ne 0 ) { + # Error code. No completion. + __podman_debug "Received error from custom completion go code" + return + } + + $Longest = 0 + $Values = $Out | ForEach-Object { + #Split the output in name and description + $Name, $Description = $_.Split("`t",2) + __podman_debug "Name: $Name Description: $Description" + + # Look for the longest completion so that we can format things nicely + if ($Longest -lt $Name.Length) { + $Longest = $Name.Length + } + + # Set the description to a one space string if there is none set. + # This is needed because the CompletionResult does not accept an empty string as argument + if (-Not $Description) { + $Description = " " + } + @{Name="$Name";Description="$Description"} + } + + + $Space = " " + if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) { + # remove the space here + __podman_debug "ShellCompDirectiveNoSpace is called" + $Space = "" + } + + if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { + __podman_debug "ShellCompDirectiveNoFileComp is called" + + if ($Values.Length -eq 0) { + # Just print an empty string here so the + # shell does not start to complete paths. + # We cannot use CompletionResult here because + # it does not accept an empty string as argument. + "" + return + } + } + + if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or + (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { + __podman_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported" + + # return here to prevent the completion of the extensions + return + } + + $Values = $Values | Where-Object { + # filter the result + $_.Name -like "$WordToComplete*" + + # Join the flag back if we have a equal sign flag + if ( $IsEqualFlag ) { + __podman_debug "Join the equal sign flag back to the completion value" + $_.Name = $Flag + "=" + $_.Name + } + } + + # Get the current mode + $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function + __podman_debug "Mode: $Mode" + + $Values | ForEach-Object { + + # store temporay because switch will overwrite $_ + $comp = $_ + + # PowerShell supports three different completion modes + # - TabCompleteNext (default windows style - on each key press the next option is displayed) + # - Complete (works like bash) + # - MenuComplete (works like zsh) + # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function + + # CompletionResult Arguments: + # 1) CompletionText text to be used as the auto completion result + # 2) ListItemText text to be displayed in the suggestion list + # 3) ResultType type of completion result + # 4) ToolTip text for the tooltip with details about the object + + switch ($Mode) { + + # bash like + "Complete" { + + if ($Values.Length -eq 1) { + __podman_debug "Only one completion left" + + # insert space after value + [System.Management.Automation.CompletionResult]::new($($comp.Name | __podman_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + + } else { + # Add the proper number of spaces to align the descriptions + while($comp.Name.Length -lt $Longest) { + $comp.Name = $comp.Name + " " + } + + # Check for empty description and only add parentheses if needed + if ($($comp.Description) -eq " " ) { + $Description = "" + } else { + $Description = " ($($comp.Description))" + } + + [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") + } + } + + # zsh like + "MenuComplete" { + # insert space after value + # MenuComplete will automatically show the ToolTip of + # the highlighted value at the bottom of the suggestions. + [System.Management.Automation.CompletionResult]::new($($comp.Name | __podman_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + + # TabCompleteNext and in case we get something unknown + Default { + # Like MenuComplete but we don't want to add a space here because + # the user need to press space anyway to get the completion. + # Description will not be shown because thats not possible with TabCompleteNext + [System.Management.Automation.CompletionResult]::new($($comp.Name | __podman_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + } + + } +} + +# This file is generated with "podman completion"; see: podman-completion(1) diff --git a/docs/source/markdown/podman-completion.1.md b/docs/source/markdown/podman-completion.1.md index 50b0b6145365..59d88f699206 100644 --- a/docs/source/markdown/podman-completion.1.md +++ b/docs/source/markdown/podman-completion.1.md @@ -4,10 +4,10 @@ podman\-completion - Generate shell completion scripts ## SYNOPSIS -**podman completion** [*options*] *bash*|*zsh*|*fish* +**podman completion** [*options*] *bash*|*zsh*|*fish*|*powershell* ## DESCRIPTION -The completion command allows you to generate shell completion scripts. Supported shells are **bash**, **zsh** and **fish**. +The completion command generates shell completion scripts for a variety of shells. Supported shells are **bash**, **zsh**, **fish** and **powershell**. These script are used by the shell to provide suggestions and complete commands when you are typing the command and press [TAB]. @@ -25,32 +25,39 @@ Do not provide description in the completions. ## Installation ### BASH -Make sure you have `bash-completion` installed on your system. +Make sure you have `bash-completion` installed on the system. -To load the completion script into your current session run: +To load the completion script into the current session run: `source <(podman completion bash)` -To make it available in all your bash sessions run: +To make it available for all bash sessions run: `podman completion bash -f /etc/bash_completion.d/podman` ### ZSH -If shell completion is not already enabled in your environment you will need to enable it. You can execute the following once: +If shell completion is not already enabled in the environment you will need to enable it. You can execute the following once: `echo "autoload -U compinit; compinit" >> ~/.zshrc` -To make it available in all your zsh sessions run: +To make it available for all zsh sessions run: `podman completion zsh -f "${fpath[1]}/_podman"` Once you reload the shell the autocompletion should be working. ### FISH -To load the completion script into your current session run: +To load the completion script into the current session run: `podman completion fish | source` -To make it available in all your fish sessions run: +To make it available for all fish sessions run: `podman completion fish -f ~/.config/fish/completions/podman.fish` +### POWERSHELL +To load the completion script into the current session run: +`podman.exe completion powershell | Out-String | Invoke-Expression` + +To make it available in all powershell sessions that a user has, write the +completion output to a file and source that to the user's powershell profile. +More information about profiles is available with `Get-Help about_Profiles`. ## SEE ALSO [podman(1)](podman.1.md)