Primary Question
My question is whether it is possible to optimize the creation of functions and subsequent autocomplete settings with a hash table.
The Functions I want to optimize e.g. with a ForEach
over the hash table below:
Function wu([string]$PackageName) {winget upgrade --include-unknown $PackageName}
Function wui([string]$PackageName) {winget upgrade -i $PackageName}
Function wi([string]$PackageName) {winget install $PackageName}
Function wii([string]$PackageName) {winget install -i -e $PackageName}
Function ws([string]$PackageName) {winget search $PackageName}
The subsequent autocomplete settings where I have adjusted -CommandName
and "$Local:ast".Replace('wu ', 'winget upgrade ')"
for the wu
function I created, need to be adjusted for every other Function
above. Source for original autocomplete is https://github.com/microsoft/winget-cli/blob/master/doc/Completion.md
As far as I understand I would need to create this for every single Function
. I'd like to optimize that e.g. with a ForEach
call as well:
Register-ArgumentCompleter -Native -CommandName wu -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
[Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new()
$Local:word = $wordToComplete.Replace('"', '""')
$Local:ast = $commandAst.ToString().Replace('"', '""')
winget complete --word="$Local:word" --commandline "$Local:ast".Replace('wu ', 'winget upgrade ') --position $cursorPosition | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
This is the hash table I created where I'm not able to properly use them for the creation of the Function
s and autocomplete configurations:
$functions = @{
"wu" = "winget upgrade --include-unknown";
"wui" = "winget upgrade -i -e";
"wi" = "winget install -i";
"wii" = "winget install -i -e";
"ws" = "winget search"
}
I tried something like this for creating functions, but it didn't evaluate the expressions:
ForEach($item in $functions.keys) {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("Function $item ([string]$PackageName) {$($functions[$item])}")
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}
Secondary question (Answered)
I still have two code blocks for the autocomplete section. One for the original winget
command (with -CommandName winget
and --commandline "$Local:ast"
) and one for my hash table commands (with -CommandName $funcName
and --commandline "$Local:new_ast"
. Can I neatly combine them into one code blocks:
# Hashtable autocomplete configuration
foreach($funcName in $functions.PSBase.Keys){
Register-ArgumentCompleter -Native -CommandName $funcName -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
[Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new()
$Local:word = $wordToComplete.Replace('"', '""')
$Local:ast = $commandAst.ToString().Replace('"', '""')
$Local:new_ast = $Local:ast -replace '^w[uis]{1,2}\b', {
if($functions.ContainsKey($_.Value)){ return $functions[$_.Value] } return $_.Value
}
winget complete --word="$Local:word" --commandline "$Local:new_ast" --position $cursorPosition | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
}
# Original autocomplete configuration
Register-ArgumentCompleter -Native -CommandName winget -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
[Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new()
$Local:word = $wordToComplete.Replace('"', '""')
$Local:ast = $commandAst.ToString().Replace('"', '""')
winget complete --word="$Local:word" --commandline "$Local:ast" --position $cursorPosition | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
I think something like this might work, because winget
would be ignored by the regex since it's not in the functions hash table. But I can't figure out the syntax because the type is KeyCollection
:
foreach($funcName in $($functions.PSBase.Keys @{'winget'}))
Answer
The finished result in my $PROFILE
thanks to @mathias-r-jessen:
$functions = @{
"wu" = "winget upgrade --include-unknown";
"wui" = "winget upgrade -i -e";
"wi" = "winget install -i";
"wii" = "winget install -i -e";
"ws" = "winget search"
}
foreach($funcName in $functions.PSBase.Keys){
New-Item function:\ -Name $funcName -Value $([scriptblock]::Create($functions[$funcName])) | Out-Null
}
foreach($funcName in @($functions.PSBase.Keys; 'winget')){
Register-ArgumentCompleter -Native -CommandName $funcName -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
[Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new()
$Local:word = $wordToComplete.Replace('"', '""')
$Local:ast = $commandAst.ToString().Replace('"', '""')
$Local:new_ast = $Local:ast -replace '^w[uis]{1,2}\b', {
if($functions.ContainsKey($_.Value)){ return $functions[$_.Value] } return $_.Value
}
winget complete --word="$Local:word" --commandline "$Local:new_ast" --position $cursorPosition | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
}
CodePudding user response:
Registering functions programmatically is as straightforward as creating new items in the functions:
drive:
$functions = @{
"wu" = "winget upgrade --include-unknown";
"wui" = "winget upgrade -i -e";
"wi" = "winget install -i";
"wii" = "winget install -i -e";
"ws" = "winget search"
}
foreach($funcName in $functions.PSBase.Keys){
New-Item function:\ -Name $funcName -Value $functions[$funcName]
}
This will register all key-value entries from the table using the key as then function name, and the value as the source code for the function body.
Make sure you execute this code in the global scope (ie. by calling the registration code from a profile script or by explicitly exporting the resulting functions if part of a module).
For the argument completer, use the -replace
to search for any 2- or 3-letter word beginning with w
, then use a scriptblock as a match evaluator to lookup the correct translation:
$functions = @{
"wu" = "winget upgrade --include-unknown";
"wui" = "winget upgrade -i -e";
"wi" = "winget install -i";
"wii" = "winget install -i -e";
"ws" = "winget search"
}
$commandLine = 'wu somePackage'
$commandLine -replace '^w[uis]{1,2}\b', {
if($functions.ContainsKey($_.Value)){
return $functions[$_.Value]
}
return $_.Value
}
$translated
now holds the string value "winget upgrade --include-unknown somePackage"
In PowerShell versions prior to 6.1, you can achieve the same by directly invoking [regex]::Replace()
:
$functions = @{
"wu" = "winget upgrade --include-unknown";
"wui" = "winget upgrade -i -e";
"wi" = "winget install -i";
"wii" = "winget install -i -e";
"ws" = "winget search"
}
$commandLine = 'wu somePackage'
$translated = [regex]::Replace($commandLine, '^w[uis]{1,2}\b', {
param($match)
if($functions.ContainsKey($match.Value)){
return $functions[$match.Value]
}
return $match.Value
})