completers/cargo_binstall_completer/cargo_binstall_completer.ps1
|
Set-StrictMode -Version 2.0 if ($true) { function Get-CargoBinstallCompleterCache { if (Test-Path -LiteralPath variable:script:CargoBinstallCompleterCache) { return $script:CargoBinstallCompleterCache } $cache = @{ CommandPath = $null HelpDefinition = $null TargetTriples = $null } Set-Variable -Name CargoBinstallCompleterCache -Scope Script -Value $cache $cache } function Resolve-CargoBinstallCommandPath { $cache = Get-CargoBinstallCompleterCache if ($cache.CommandPath) { return $cache.CommandPath } $command = Get-Command -Name 'cargo-binstall.exe', 'cargo-binstall' -ErrorAction SilentlyContinue | Select-Object -First 1 if ($command) { $cache.CommandPath = if ($command.Source) { $command.Source } else { $command.Name } } $cache.CommandPath } function Invoke-CargoBinstallText { param([string[]]$Arguments) $commandPath = Resolve-CargoBinstallCommandPath if (-not $commandPath) { return @() } try { @(& $commandPath @Arguments 2>&1 | ForEach-Object { $_.ToString() -replace '\e\[[0-9;?]*[ -/]*[@-~]', '' }) } catch { @() } } function Get-CargoBinstallHelpDefinition { $cache = Get-CargoBinstallCompleterCache if ($cache.HelpDefinition) { return $cache.HelpDefinition } $definition = @{ Options = @() OptionMap = @{} PositionalPlaceholder = '<crate[@version]>' } $lines = @(Invoke-CargoBinstallText -Arguments @('--help')) $currentEntry = $null foreach ($line in $lines) { if ($line -match '^\s{2,}(?<spec>(?:(?:-[A-Za-z0-9?],\s*)?--[A-Za-z0-9][A-Za-z0-9\-]*|-[A-Za-z0-9?])(?:\s+<[^>]+>)?(?:\.\.\.)?)\s*$') { if ($currentEntry) { $definition.Options += $currentEntry foreach ($name in $currentEntry.Names) { $definition.OptionMap[$name] = $currentEntry } } $spec = $matches['spec'].Trim() $names = @([regex]::Matches($spec, '(?:^|,\s*)(--[A-Za-z0-9][A-Za-z0-9\-]*|-[A-Za-z0-9?])') | ForEach-Object { $_.Groups[1].Value }) $takesValue = $spec -match '<(?<placeholder>[^>]+)>' $placeholder = if ($takesValue) { '<' + $matches['placeholder'] + '>' } else { $null } $currentEntry = [pscustomobject]@{ Names = $names PrimaryName = $names[-1] Description = '' TakesValue = $takesValue ValuePlaceholder = $placeholder } continue } if ($line -match '^\s{2,}\[crate\[@version\]\]\.\.\.$') { $definition.PositionalPlaceholder = '<crate[@version]>' continue } if ($currentEntry) { if ([string]::IsNullOrWhiteSpace($line)) { $definition.Options += $currentEntry foreach ($name in $currentEntry.Names) { $definition.OptionMap[$name] = $currentEntry } $currentEntry = $null continue } if ($line -match '^\S') { $definition.Options += $currentEntry foreach ($name in $currentEntry.Names) { $definition.OptionMap[$name] = $currentEntry } $currentEntry = $null } elseif ((-not $currentEntry.Description) -and $line -match '^\s{2,}(?<description>\S.*)$') { $currentEntry = [pscustomobject]@{ Names = $currentEntry.Names PrimaryName = $currentEntry.PrimaryName Description = $matches['description'].Trim() TakesValue = $currentEntry.TakesValue ValuePlaceholder = $currentEntry.ValuePlaceholder } } } } if ($currentEntry) { $definition.Options += $currentEntry foreach ($name in $currentEntry.Names) { $definition.OptionMap[$name] = $currentEntry } } $cache.HelpDefinition = $definition $definition } function Get-CargoBinstallTargetTriples { $cache = Get-CargoBinstallCompleterCache if ($cache.TargetTriples) { return $cache.TargetTriples } $rustc = Get-Command -Name 'rustc.exe', 'rustc' -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $rustc) { $cache.TargetTriples = @() return $cache.TargetTriples } try { $cache.TargetTriples = @(& $rustc.Source '--print' 'target-list' 2>$null | Where-Object { $_ } | Sort-Object -Unique) } catch { $cache.TargetTriples = @() } $cache.TargetTriples } function New-CargoBinstallCompletionResult { param( [string]$CompletionText, [string]$ListItemText, [string]$ResultType, [string]$ToolTip ) if ([string]::IsNullOrWhiteSpace($ListItemText)) { $ListItemText = $CompletionText } if ([string]::IsNullOrWhiteSpace($ToolTip)) { $ToolTip = $CompletionText } [System.Management.Automation.CompletionResult]::new( $CompletionText, $ListItemText, $ResultType, $ToolTip ) } function Remove-CargoBinstallOuterQuotes { param([string]$Value) if ($null -eq $Value) { return '' } $Value.Trim([char[]]@([char]34, [char]39)) } function ConvertTo-CargoBinstallQuotedValue { param( [string]$Value, [bool]$AlwaysQuote = $false ) if ([string]::IsNullOrWhiteSpace($Value)) { return $Value } if (($AlwaysQuote -or $Value -match '\s') -and -not ($Value.StartsWith('"') -and $Value.EndsWith('"'))) { return '"' + $Value.Replace('`', '``').Replace('"', '`"') + '"' } $Value } function Get-CargoBinstallCurrentToken { param( [string]$Line, [int]$CursorPosition, [string]$Fallback ) if ([string]::IsNullOrWhiteSpace($Line)) { return $Fallback } $safeCursor = [Math]::Min([Math]::Max($CursorPosition, 0), $Line.Length) $prefix = $Line.Substring(0, $safeCursor) if ($prefix -match '\s$') { return '' } $parts = @([regex]::Matches($prefix, '"[^"]*"|''[^'']*''|\S+') | ForEach-Object { $_.Value }) if ($parts.Count -gt 0) { return $parts[-1] } $Fallback } function Get-CargoBinstallTokensBeforeCursor { param( [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) $tokens = New-Object System.Collections.Generic.List[string] foreach ($element in $CommandAst.CommandElements | Select-Object -Skip 1) { if ($element.Extent.EndOffset -lt $CursorPosition) { $tokens.Add($element.Extent.Text) } } @($tokens.ToArray()) } function Get-CargoBinstallPathCompletions { param([string]$InputPath) $cleanInput = Remove-CargoBinstallOuterQuotes -Value $InputPath $alwaysQuote = -not [string]::IsNullOrEmpty($InputPath) -and ($InputPath.StartsWith('"') -or $InputPath.StartsWith("'")) [System.Management.Automation.CompletionCompleters]::CompleteFilename($cleanInput) | ForEach-Object { # CompleteFilename already quotes a path containing spaces; wrapping # that again would produce a path with literal quotes inside it. $completionText = if ($_.CompletionText -match '^[''"]') { $_.CompletionText } else { ConvertTo-CargoBinstallQuotedValue -Value $_.CompletionText -AlwaysQuote $alwaysQuote } New-CargoBinstallCompletionResult -CompletionText $completionText -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip } } function Get-CargoBinstallPlaceholderCompletion { param( [string]$Placeholder, [string]$CurrentWord, [string]$ToolTip ) # The placeholder names an empty slot; once text is typed there is nothing # to add, and echoing it back would only hide the engine's own fallback. if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and -not $Placeholder.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) { return } New-CargoBinstallCompletionResult -CompletionText $Placeholder -ListItemText $Placeholder -ResultType 'ParameterValue' -ToolTip $ToolTip } function Get-CargoBinstallNamedValueCompletions { param( [string[]]$Values, [string]$CurrentWord, [string]$ToolTip ) $cleanCurrent = Remove-CargoBinstallOuterQuotes -Value $CurrentWord foreach ($value in $Values) { if ($value.StartsWith($cleanCurrent, [System.StringComparison]::OrdinalIgnoreCase)) { New-CargoBinstallCompletionResult -CompletionText $value -ListItemText $value -ResultType 'ParameterValue' -ToolTip $ToolTip } } } function Get-CargoBinstallDelimitedValueCompletions { param( [string[]]$Values, [string]$CurrentWord, [string]$ToolTip ) $cleanCurrent = Remove-CargoBinstallOuterQuotes -Value $CurrentWord $segments = @(if ([string]::IsNullOrWhiteSpace($cleanCurrent)) { '' } else { $cleanCurrent.Split(',') }) $selected = if ($segments.Count -gt 1) { @($segments[0..($segments.Count - 2)] | ForEach-Object { $_.Trim() } | Where-Object { $_ }) } else { @() } $fragment = $segments[-1].Trim() $prefix = if ($segments.Count -gt 1) { (($segments[0..($segments.Count - 2)] | ForEach-Object { $_.Trim() }) -join ',') + ',' } else { '' } foreach ($value in $Values) { if (($selected -contains $value) -or (-not $value.StartsWith($fragment, [System.StringComparison]::OrdinalIgnoreCase))) { continue } $completionText = $prefix + $value New-CargoBinstallCompletionResult -CompletionText $completionText -ListItemText $completionText -ResultType 'ParameterValue' -ToolTip $ToolTip } } function Get-CargoBinstallOptionValueCompletions { param( [string]$OptionName, [string]$CurrentWord ) switch ($OptionName) { '--manifest-path' { return @(Get-CargoBinstallPathCompletions -InputPath $CurrentWord) } '--bin-dir' { return @(Get-CargoBinstallPathCompletions -InputPath $CurrentWord) } '--install-path' { return @(Get-CargoBinstallPathCompletions -InputPath $CurrentWord) } '--root' { return @(Get-CargoBinstallPathCompletions -InputPath $CurrentWord) } '--root-certificates' { return @(Get-CargoBinstallPathCompletions -InputPath $CurrentWord) } '--settings' { return @(Get-CargoBinstallPathCompletions -InputPath $CurrentWord) } '--pkg-fmt' { return @(Get-CargoBinstallNamedValueCompletions -Values @('tar', 'tbz2', 'tgz', 'txz', 'tzstd', 'zip', 'bin') -CurrentWord $CurrentWord -ToolTip 'cargo-binstall package format') } '--strategies' { return @(Get-CargoBinstallDelimitedValueCompletions -Values @('crate-meta-data', 'quick-install', 'compile') -CurrentWord $CurrentWord -ToolTip 'cargo-binstall strategy list') } '--disable-strategies' { return @(Get-CargoBinstallDelimitedValueCompletions -Values @('crate-meta-data', 'quick-install', 'compile') -CurrentWord $CurrentWord -ToolTip 'cargo-binstall strategy list') } '--min-tls-version' { return @(Get-CargoBinstallNamedValueCompletions -Values @('1.2', '1.3') -CurrentWord $CurrentWord -ToolTip 'Minimum TLS version') } '--log-level' { return @(Get-CargoBinstallNamedValueCompletions -Values @('trace', 'debug', 'info', 'warn', 'error', 'off') -CurrentWord $CurrentWord -ToolTip 'Utility log level') } '--targets' { $targetTriples = @(Get-CargoBinstallTargetTriples) if ($targetTriples.Count -gt 0) { return @(Get-CargoBinstallDelimitedValueCompletions -Values $targetTriples -CurrentWord $CurrentWord -ToolTip 'Rust target triples') } } } $definition = Get-CargoBinstallHelpDefinition if ($definition.OptionMap.ContainsKey($OptionName)) { $entry = $definition.OptionMap[$OptionName] if ($entry.ValuePlaceholder) { return @( Get-CargoBinstallPlaceholderCompletion -Placeholder $entry.ValuePlaceholder -CurrentWord $CurrentWord -ToolTip ("cargo-binstall value for $OptionName") ) } } @() } function Get-CargoBinstallOptionCompletions { param([string]$CurrentWord) $definition = Get-CargoBinstallHelpDefinition $cleanCurrent = Remove-CargoBinstallOuterQuotes -Value $CurrentWord foreach ($entry in $definition.Options) { foreach ($name in $entry.Names) { if ($name.StartsWith($cleanCurrent, [System.StringComparison]::OrdinalIgnoreCase)) { New-CargoBinstallCompletionResult -CompletionText $name -ListItemText $name -ResultType 'ParameterName' -ToolTip $entry.Description } } } } function Get-CargoBinstallPositionalCompletions { param([string]$CurrentWord) $definition = Get-CargoBinstallHelpDefinition @( Get-CargoBinstallPlaceholderCompletion -Placeholder $definition.PositionalPlaceholder -CurrentWord $CurrentWord -ToolTip 'crate name or crate@version' ) } function Get-CargoBinstallState { param([string[]]$TokensBeforeCurrent) $definition = Get-CargoBinstallHelpDefinition $pendingOption = $null $positionals = New-Object System.Collections.Generic.List[string] $afterDoubleDash = $false foreach ($rawToken in $TokensBeforeCurrent) { $token = Remove-CargoBinstallOuterQuotes -Value $rawToken if ([string]::IsNullOrWhiteSpace($token)) { continue } if ($pendingOption) { $pendingOption = $null continue } if ($afterDoubleDash) { $positionals.Add($token) continue } if ($token -eq '--') { $afterDoubleDash = $true continue } if ($definition.OptionMap.ContainsKey($token)) { $entry = $definition.OptionMap[$token] if ($entry.TakesValue) { $pendingOption = $entry.PrimaryName } continue } if ($token.StartsWith('-')) { continue } $positionals.Add($token) } [pscustomobject]@{ PendingOption = $pendingOption Positionals = @($positionals.ToArray()) AfterDoubleDash = $afterDoubleDash } } function Complete-CargoBinstall { param( [string]$wordToComplete, [System.Management.Automation.Language.CommandAst]$commandAst, [int]$cursorPosition ) $currentWord = if ($cursorPosition -gt $commandAst.Extent.EndOffset) { '' } else { Get-CargoBinstallCurrentToken -Line $commandAst.ToString() -CursorPosition ($cursorPosition - $commandAst.Extent.StartOffset) -Fallback $wordToComplete } $tokensBeforeCurrent = @(Get-CargoBinstallTokensBeforeCursor -CommandAst $commandAst -CursorPosition $cursorPosition) if (($tokensBeforeCurrent.Count -eq 0) -and ($currentWord -match '^(?i)cargo-binstall(?:\.exe)?$')) { $currentWord = '' } $state = Get-CargoBinstallState -TokensBeforeCurrent $tokensBeforeCurrent if ($state.PendingOption) { return @(Get-CargoBinstallOptionValueCompletions -OptionName $state.PendingOption -CurrentWord $currentWord) } if ($state.AfterDoubleDash) { # Everything after '--' is a crate operand. return @(Get-CargoBinstallPositionalCompletions -CurrentWord $currentWord) } if (-not [string]::IsNullOrEmpty($currentWord) -and $currentWord.StartsWith('-')) { $attached = [regex]::Match((Remove-CargoBinstallOuterQuotes -Value $currentWord), '^(?<name>--[^=]+)=(?<value>.*)$') if ($attached.Success) { # Attached --opt=value form: complete the value, keeping the prefix. $definition = Get-CargoBinstallHelpDefinition $attachedName = $attached.Groups['name'].Value if (-not $definition.OptionMap.ContainsKey($attachedName) -or -not $definition.OptionMap[$attachedName].TakesValue) { return @() } $attachedPrefix = $attachedName + '=' return @(Get-CargoBinstallOptionValueCompletions -OptionName $definition.OptionMap[$attachedName].PrimaryName -CurrentWord $attached.Groups['value'].Value | ForEach-Object { New-CargoBinstallCompletionResult -CompletionText ($attachedPrefix + $_.CompletionText) -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip }) } return @(Get-CargoBinstallOptionCompletions -CurrentWord $currentWord) } $results = New-Object System.Collections.Generic.List[object] if ([string]::IsNullOrEmpty($currentWord)) { foreach ($item in (Get-CargoBinstallOptionCompletions -CurrentWord '')) { $results.Add($item) } } foreach ($item in (Get-CargoBinstallPositionalCompletions -CurrentWord $currentWord)) { $results.Add($item) } @($results.ToArray()) } } Register-ArgumentCompleter -Native -CommandName @('cargo-binstall', 'cargo-binstall.exe') -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) Complete-CargoBinstall -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition } |