mirror of https://github.com/spf13/cobra.git
351 lines
13 KiB
Go
351 lines
13 KiB
Go
// Copyright 2013-2023 The Cobra Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// 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"
|
|
)
|
|
|
|
func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) {
|
|
// Variables should not contain a '-' or ':' character
|
|
nameForVar := name
|
|
nameForVar = strings.ReplaceAll(nameForVar, "-", "_")
|
|
nameForVar = strings.ReplaceAll(nameForVar, ":", "_")
|
|
|
|
compCmd := ShellCompRequestCmd
|
|
if !includeDesc {
|
|
compCmd = ShellCompNoDescRequestCmd
|
|
}
|
|
WriteStringAndCheck(buf, fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*-
|
|
|
|
function __%[1]s_debug {
|
|
if ($env:BASH_COMP_DEBUG_FILE) {
|
|
"$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE"
|
|
}
|
|
}
|
|
|
|
filter __%[1]s_escapeStringWithSpecialChars {
|
|
`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
|
|
}
|
|
|
|
[scriptblock]${__%[2]sCompleterBlock} = {
|
|
param(
|
|
$WordToComplete,
|
|
$CommandAst,
|
|
$CursorPosition
|
|
)
|
|
|
|
# Get the current command line and convert into a string
|
|
$Command = $CommandAst.CommandElements
|
|
$Command = "$Command"
|
|
|
|
__%[1]s_debug ""
|
|
__%[1]s_debug "========= starting completion logic =========="
|
|
__%[1]s_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)
|
|
}
|
|
__%[1]s_debug "Truncated command: $Command"
|
|
|
|
$ShellCompDirectiveError=%[4]d
|
|
$ShellCompDirectiveNoSpace=%[5]d
|
|
$ShellCompDirectiveNoFileComp=%[6]d
|
|
$ShellCompDirectiveFilterFileExt=%[7]d
|
|
$ShellCompDirectiveFilterDirs=%[8]d
|
|
$ShellCompDirectiveKeepOrder=%[9]d
|
|
|
|
# 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 %[3]s $Arguments"
|
|
__%[1]s_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]
|
|
}
|
|
__%[1]s_debug "New WordToComplete: $WordToComplete"
|
|
|
|
|
|
# Check for flag with equal sign
|
|
$IsEqualFlag = ($WordToComplete -Like "--*=*" )
|
|
if ( $IsEqualFlag ) {
|
|
__%[1]s_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.
|
|
__%[1]s_debug "Adding extra empty parameter"
|
|
# PowerShell 7.2+ changed the way how the arguments are passed to executables,
|
|
# so for pre-7.2 or when Legacy argument passing is enabled we need to use
|
|
`+" # `\"`\" to pass an empty argument, a \"\" or '' does not work!!!"+`
|
|
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')) {
|
|
`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+`
|
|
} else {
|
|
$RequestComp="$RequestComp" + ' ""'
|
|
}
|
|
}
|
|
|
|
__%[1]s_debug "Calling $RequestComp"
|
|
# First disable ActiveHelp which is not supported for Powershell
|
|
${env:%[10]s}=0
|
|
|
|
#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
|
|
}
|
|
__%[1]s_debug "The completion directive is: $Directive"
|
|
|
|
# remove directive (last element) from out
|
|
$Out = $Out | Where-Object { $_ -ne $Out[-1] }
|
|
__%[1]s_debug "The completions are: $Out"
|
|
|
|
if (($Directive -band $ShellCompDirectiveError) -ne 0 ) {
|
|
# Error code. No completion.
|
|
__%[1]s_debug "Received error from custom completion go code"
|
|
return
|
|
}
|
|
|
|
$Longest = 0
|
|
[Array]$Values = $Out | ForEach-Object {
|
|
#Split the output in name and description
|
|
`+" $Name, $Description = $_.Split(\"`t\",2)"+`
|
|
__%[1]s_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 = " "
|
|
}
|
|
New-Object -TypeName PSCustomObject -Property @{
|
|
Name = "$Name"
|
|
Description = "$Description"
|
|
}
|
|
}
|
|
|
|
|
|
$Space = " "
|
|
if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) {
|
|
# remove the space here
|
|
__%[1]s_debug "ShellCompDirectiveNoSpace is called"
|
|
$Space = ""
|
|
}
|
|
|
|
if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
|
|
(($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) {
|
|
__%[1]s_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 an equal sign flag
|
|
if ( $IsEqualFlag ) {
|
|
__%[1]s_debug "Join the equal sign flag back to the completion value"
|
|
$_.Name = $Flag + "=" + $_.Name
|
|
}
|
|
}
|
|
|
|
# we sort the values in ascending order by name if keep order isn't passed
|
|
if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) {
|
|
$Values = $Values | Sort-Object -Property Name
|
|
}
|
|
|
|
if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
|
|
__%[1]s_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
|
|
}
|
|
}
|
|
|
|
# Get the current mode
|
|
$Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
|
|
__%[1]s_debug "Mode: $Mode"
|
|
|
|
$Values | ForEach-Object {
|
|
|
|
# store temporary 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 <mode>
|
|
|
|
# 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) {
|
|
__%[1]s_debug "Only one completion left"
|
|
|
|
# insert space after value
|
|
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space
|
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
|
} else {
|
|
$CompletionText
|
|
}
|
|
|
|
} 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))"
|
|
}
|
|
|
|
$CompletionText = "$($comp.Name)$Description"
|
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
|
|
} else {
|
|
$CompletionText
|
|
}
|
|
}
|
|
}
|
|
|
|
# zsh like
|
|
"MenuComplete" {
|
|
# insert space after value
|
|
# MenuComplete will automatically show the ToolTip of
|
|
# the highlighted value at the bottom of the suggestions.
|
|
|
|
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space
|
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
|
} else {
|
|
$CompletionText
|
|
}
|
|
}
|
|
|
|
# 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 that's not possible with TabCompleteNext
|
|
|
|
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars)
|
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
|
} else {
|
|
$CompletionText
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock ${__%[2]sCompleterBlock}
|
|
`, name, nameForVar, compCmd,
|
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
|
|
}
|
|
|
|
func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
|
|
buf := new(bytes.Buffer)
|
|
genPowerShellComp(buf, c.Name(), includeDesc)
|
|
_, err := buf.WriteTo(w)
|
|
return err
|
|
}
|
|
|
|
func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) error {
|
|
outFile, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outFile.Close()
|
|
|
|
return c.genPowerShellCompletion(outFile, includeDesc)
|
|
}
|
|
|
|
// GenPowerShellCompletionFile generates powershell completion file without descriptions.
|
|
func (c *Command) GenPowerShellCompletionFile(filename string) error {
|
|
return c.genPowerShellCompletionFile(filename, false)
|
|
}
|
|
|
|
// GenPowerShellCompletion generates powershell completion file without descriptions
|
|
// and writes it to the passed writer.
|
|
func (c *Command) GenPowerShellCompletion(w io.Writer) error {
|
|
return c.genPowerShellCompletion(w, false)
|
|
}
|
|
|
|
// GenPowerShellCompletionFileWithDesc generates powershell completion file with descriptions.
|
|
func (c *Command) GenPowerShellCompletionFileWithDesc(filename string) error {
|
|
return c.genPowerShellCompletionFile(filename, true)
|
|
}
|
|
|
|
// GenPowerShellCompletionWithDesc generates powershell completion file with descriptions
|
|
// and writes it to the passed writer.
|
|
func (c *Command) GenPowerShellCompletionWithDesc(w io.Writer) error {
|
|
return c.genPowerShellCompletion(w, true)
|
|
}
|