Home > Net >  Optimize function creation and subsequent autocomplete configuration with a hash table
Optimize function creation and subsequent autocomplete configuration with a hash table

Time:05-20

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 ForEachcall 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 Functions 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
})
  • Related