├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── ci.yml ├── src ├── Register-Completion.psm1 ├── Utils.ps1 ├── Register-Completion.psd1 └── Completion.ps1 ├── LICENSE ├── CHANGELOG.md ├── Publish.ps1 ├── README.md └── test └── Register-Completion.Tests.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | temp/* 2 | presets/* 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [aliuq] 2 | -------------------------------------------------------------------------------- /src/Register-Completion.psm1: -------------------------------------------------------------------------------- 1 | if (Get-Module New-Completion) { return } 2 | 3 | . $PSScriptRoot\Utils.ps1 4 | . $PSScriptRoot\Completion.ps1 5 | 6 | $exportModuleMemberParams = @{ 7 | Function = @('New-Completion', 'Get-CompletionKeys', 'ConvertTo-Hash', 'Remove-Completion', 'Register-Alias') 8 | Variable = @('CacheAllCompletions', 'CacheCommands') 9 | } 10 | Export-ModuleMember @exportModuleMemberParams 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish PowerShell Module 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | test: 10 | uses: ./.github/workflows/ci.yml 11 | publish-to-gallery: 12 | runs-on: ubuntu-latest 13 | needs: test 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Build and publish 20 | env: 21 | NUGET_KEY: ${{ secrets.NUGET_KEY }} 22 | shell: pwsh 23 | run: | 24 | mv ./src ./Register-Completion 25 | Publish-Module -Path ./Register-Completion -NuGetApiKey $env:NUGET_KEY -Verbose 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'src/**/*.psm?1' 9 | - 'test/**/*.Tests.ps1' 10 | pull_request: 11 | branches: 12 | - master 13 | workflow_call: 14 | 15 | jobs: 16 | ci: 17 | runs-on: ${{ matrix.os }} 18 | 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest, macos-latest, windows-latest] 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Run Pester test in PowerShell 7.x 26 | shell: pwsh 27 | run: | 28 | Invoke-Pester test -Output Detailed 29 | env: 30 | GITHUB_ACTIONS: true 31 | 32 | - name: Run Pester test in PowerShell 5.x 33 | if: matrix.os == 'windows-latest' 34 | shell: powershell 35 | run: | 36 | Invoke-Pester test -Output Detailed 37 | env: 38 | GITHUB_ACTIONS: true 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 liuq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Utils.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Register a new alias 4 | .DESCRIPTION 5 | It supports providing string statements, if a single string is provided, it will call `Set-Alias` directly, if a statement is provided, it will automatically construct a dynamic function with the name `AliasFunction` and then call `Set-Alias`, you can call `Set-Alias` with `Get-Command -CommandType Function -Name "*AliasFunction"` to see all the alias registered in this way. 6 | .PARAMETER Name 7 | The alias name. 8 | .PARAMETER Value 9 | The alias content, it can be a string or a statement. 10 | .EXAMPLE 11 | Register-Alias hello "echo 'Hello World!'" 12 | Typing "hello" to output a string "Hello World!". 13 | .EXAMPLE 14 | Register-Alias i "cd ~/Projects/$($args[0])" 15 | Recieve a parameter $args[0] from the command line, and then change the current directory to ~/Projects/$($args[0]). 16 | .INPUTS 17 | None. 18 | .OUTPUTS 19 | None. 20 | .LINK 21 | https://github.com/aliuq/Register-Completion 22 | #> 23 | function Register-Alias { 24 | Param([string]$Name, [string]$Value) 25 | if (($Value -Split ' ').Count -le 1) { 26 | Set-Alias $Name $Value -scope global 27 | } 28 | else { 29 | $fullName = $Name + "AliasFunction" 30 | Set-Item "Function:\global:$fullName" -Value ([scriptblock]::Create($Value)) 31 | Set-Alias $Name $fullName -scope global 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [](https://github.com/aliuq/Register-Completion/compare/v0.0.23-beta1...v) (2022-07-05) 2 | 3 | 4 | ### Features 5 | 6 | * add function `Register-Alias` ([2e5e0bf](https://github.com/aliuq/Register-Completion/commit/2e5e0bf870a6b5cb104920ad787bd761401a05c0)) 7 | 8 | 9 | 10 | # Change Log 11 | 12 | 13 | 14 | ## [0.0.23-beta1](https://github.com/aliuq/Register-Completion/compare/v0.0.22...v0.0.23-beta1) (2022-07-04) 15 | 16 | 17 | 18 | ## [0.0.22](https://github.com/aliuq/Register-Completion/compare/v0.0.21...v0.0.22) (2022-07-04) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * fixed wrong variable and export function`Remove-Completion` ([2ee98d5](https://github.com/aliuq/Register-Completion/commit/2ee98d5112efd45b3bd951772767d6f51328e389)) 24 | 25 | 26 | 27 | ## [0.0.21](https://github.com/aliuq/Register-Completion/compare/v0.0.20...v0.0.21) (2022-07-04) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * remove default sort control ([2be84e3](https://github.com/aliuq/Register-Completion/commit/2be84e39d8cbe958ccafe36c6ef5b8fbcae8a845)) 33 | 34 | 35 | ### Features 36 | 37 | * support completion tooltip ([4c4f7d8](https://github.com/aliuq/Register-Completion/commit/4c4f7d8b2c8934831a4ad65a33a79dda28555b5d)) 38 | 39 | 40 | 41 | ## [0.0.20](https://github.com/aliuq/Register-Completion/compare/v0.0.19...v0.0.20) (2022-06-29) 42 | 43 | 44 | ### Features 45 | 46 | * support custom filter keys function ([2675d81](https://github.com/aliuq/Register-Completion/commit/2675d815eb9b99ba38d232bed2ec4b0c2a6c2f77)) 47 | 48 | 49 | 50 | ## [0.0.19](https://github.com/aliuq/Register-Completion/compare/v0.0.18...v0.0.19) (2022-06-29) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * fixed wrong key in powershell 5 ([87c30c2](https://github.com/aliuq/Register-Completion/commit/87c30c22ab469a6d58a8c800dce15fb50785c1d9)) 56 | * remove unsupported example ([12af8d1](https://github.com/aliuq/Register-Completion/commit/12af8d15eaa316bbc37fb37e7e82b40e8791b036)) 57 | 58 | 59 | 60 | ## [0.0.18](https://github.com/aliuq/Register-Completion/compare/v0.0.17...v0.0.18) (2022-06-28) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * add command help, fixed variable wrong export ([6ef4bc1](https://github.com/aliuq/Register-Completion/commit/6ef4bc12026fa9356171522b6ff7900c9146b559)) 66 | 67 | 68 | ### Features 69 | 70 | * refactor convert function and rename it ([93a0270](https://github.com/aliuq/Register-Completion/commit/93a027016896ecb78b581bc94aa35aa6d0367c45)) 71 | 72 | 73 | 74 | ## [0.0.17](https://github.com/aliuq/Register-Completion/compare/v0.0.16...v0.0.17) (2022-06-27) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * fixed wrong import module ([a91501d](https://github.com/aliuq/Register-Completion/commit/a91501d01ab3582935a5e93d7cdba73ab1d9b4f1)) 80 | 81 | 82 | 83 | ## [0.0.16](https://github.com/aliuq/Register-Completion/compare/v0.0.15...v0.0.16) (2022-06-27) 84 | 85 | 86 | 87 | ## 0.0.15 (2022-06-26) 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /Publish.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Helper publish script 4 | .DESCRIPTION 5 | Helper script to publish or reset commits to the remote repository. 6 | #> 7 | 8 | Param( 9 | [Parameter(Mandatory, Position=0)] 10 | [ValidateSet('release', 'reset', 'help')] 11 | [string]$Action, 12 | [switch]$Patch, 13 | [switch]$Minor, 14 | [switch]$Major, 15 | [switch]$Commit, 16 | [switch]$Tag, 17 | [switch]$Beta 18 | ) 19 | 20 | $dir = ".\src" 21 | $currentVersion = (Test-ModuleManifest "$dir\Register-Completion.psd1").version 22 | 23 | @{ 24 | "PowerShell Version" = $PSVersionTable.PSVersion; 25 | "Module Version" = $currentVersion 26 | } | ForEach-Object { new-object PSObject -Property $_} | Format-List 27 | 28 | function Get-BetaVersion { 29 | $prerelease = (Test-ModuleManifest "$dir\Register-Completion.psd1").PrivateData.PSData.Prerelease 30 | if ($prerelease) { 31 | $betaVersion = $prerelease.Substring(4) 32 | } 33 | else { 34 | $betaVersion = 0 35 | } 36 | return [Int32]$betaVersion 37 | } 38 | function Set-SemverVersion { 39 | param( 40 | [PSCustomObject]$version, 41 | [switch]$patch, 42 | [switch]$minor, 43 | [switch]$major 44 | ) 45 | 46 | $ver = $version.ToString() 47 | 48 | if ($patch) { 49 | $p = $version.Build + 1 50 | $ver = [string]$version.Major + "." + [string]$version.Minor + "." + [string]$p 51 | } 52 | elseif ($minor) { 53 | $m = $version.Minor + 1 54 | $ver = [string]$version.Major + "." + [string]$m + ".0" 55 | } 56 | elseif ($major) { 57 | $m = $version.Major + 1 58 | $ver = [string]$m + ".0.0" 59 | } 60 | 61 | return $ver 62 | } 63 | 64 | if ($Action -eq 'help') { 65 | Write-Host "Helper script:" 66 | @{ 67 | "1" = ".\Publish.ps1 release -Patch -Commit -Tag" 68 | "2" = ".\Publish.ps1 reset -Tag" 69 | } | ForEach-Object { new-object PSObject -Property $_} | Format-List 70 | } 71 | elseif ($Action -eq 'release') { 72 | 73 | $betaVersionNum = Get-BetaVersion 74 | 75 | if (($Beta -And ($betaVersionNum -eq 0)) -Or !$Beta) { 76 | if ($Patch) { $newVersion = Set-SemverVersion $currentVersion -patch } 77 | elseif ($Minor) { $newVersion = Set-SemverVersion $currentVersion -minor } 78 | elseif ($Major) { $newVersion = Set-SemverVersion $currentVersion -major } 79 | else { $newVersion = Read-Host "Input a new version(v$currentVersion)" } 80 | } 81 | else { 82 | $newVersion = $currentVersion 83 | } 84 | 85 | if ($Beta) { 86 | $betaVersion = "beta$($betaVersionNum + 1)" 87 | $fullVersion = "$newVersion-$betaVersion" 88 | } 89 | else { 90 | $fullVersion = $newVersion 91 | } 92 | Write-Host "You are about to release a version($fullVersion)`n" -ForegroundColor Yellow 93 | 94 | $confirm = Read-Host "Confirm to release v$($fullVersion)(previous v$($currentVersion))?(y/n)" [Char] 95 | 96 | if ($confirm -eq 'y') { 97 | if ($Beta) { 98 | Update-ModuleManifest -Path "$dir\Register-Completion.psd1" -ModuleVersion $newVersion -Prerelease $betaVersion 99 | } 100 | else { 101 | $d = "$dir\Register-Completion.psd1" 102 | (Get-Content $d -Raw) -Replace "Prerelease = 'beta\d+'", "# Prerelease = ''" | Set-Content $d 103 | Update-ModuleManifest -Path "$dir\Register-Completion.psd1" -ModuleVersion $newVersion 104 | } 105 | 106 | npx standard-changelog 107 | 108 | if ($Commit) { 109 | git add "$dir\Register-Completion.psd1" CHANGELOG.md 110 | git commit -m "Release v$fullVersion" 111 | } 112 | if ($Tag) { 113 | git tag --annotate --message "v$fullVersion" v$fullVersion 114 | } 115 | $comfirmPush = Read-Host "Confirm to push?(y/n)" [Char] 116 | if ($comfirmPush -eq 'y') { 117 | if ($Commit) { 118 | git push 119 | } 120 | if ($Tag) { 121 | git push --tags 122 | } 123 | } 124 | } 125 | } 126 | elseif ($Action -eq 'reset') { 127 | $version = (Test-ModuleManifest "$dir\Register-Completion.psd1").version 128 | $betaVersionNum = Get-BetaVersion 129 | if ($Beta) { 130 | $betaVersion = "beta$betaVersionNum" 131 | $fullVersion = "$version-$betaVersion" 132 | } 133 | else { 134 | $fullVersion = $version 135 | } 136 | $comfirmPush = Read-Host "Are you sure reset the version($fullVersion)?(y/n)" [Char] 137 | if ($comfirmPush -eq 'y') { 138 | git reset --hard HEAD~1 139 | if ($Tag) { 140 | git tag -d "v$fullVersion" 141 | } 142 | $comfirmPushRemote = Read-Host "Are you sure reset the version($fullVersion) to remote?(y/n)" [Char] 143 | if ($comfirmPushRemote -eq 'y') { 144 | git push -f 145 | if ($Tag) { 146 | git push origin ":refs/tags/v$fullVersion" 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Register-Completion.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Register-Completion' 3 | # 4 | # Generated by: aliuq 5 | # 6 | # Generated on: 2022/7/5 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Register-Completion.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '0.0.24' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '9628389e-7e96-4fd0-94ed-004bdd2f3f95' 22 | 23 | # Author of this module 24 | Author = 'aliuq' 25 | 26 | # Company or vendor of this module 27 | CompanyName = '' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2022 aliuq. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Easy to register tab completions with data structures. Easy to customize.' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | PowerShellVersion = '5.0' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'New-Completion', 'Get-CompletionKeys', 'ConvertTo-Hash', 73 | 'Remove-Completion', 'Register-Alias' 74 | 75 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 76 | CmdletsToExport = @() 77 | 78 | # Variables to export from this module 79 | VariablesToExport = 'CacheAllCompletions', 'CacheCommands' 80 | 81 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 82 | AliasesToExport = @() 83 | 84 | # DSC resources to export from this module 85 | # DscResourcesToExport = @() 86 | 87 | # List of all modules packaged with this module 88 | # ModuleList = @() 89 | 90 | # List of all files packaged with this module 91 | # FileList = @() 92 | 93 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 94 | PrivateData = @{ 95 | 96 | PSData = @{ 97 | 98 | # Tags applied to this module. These help with module discovery in online galleries. 99 | Tags = 'register','completion','auto-completion','tab-completion','tab' 100 | 101 | # A URL to the license for this module. 102 | LicenseUri = 'https://github.com/aliuq/Register-Completion/blob/master/LICENSE' 103 | 104 | # A URL to the main website for this project. 105 | ProjectUri = 'https://github.com/aliuq/Register-Completion' 106 | 107 | # A URL to an icon representing this module. 108 | # IconUri = '' 109 | 110 | # ReleaseNotes of this module 111 | ReleaseNotes = 'https://github.com/aliuq/Register-Completion/blob/master/CHANGELOG.md' 112 | 113 | # Prerelease string of this module 114 | # Prerelease = '' 115 | 116 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 117 | # RequireLicenseAcceptance = $false 118 | 119 | # External dependent modules of this module 120 | # ExternalModuleDependencies = @() 121 | 122 | } # End of PSData hashtable 123 | 124 | } # End of PrivateData hashtable 125 | 126 | # HelpInfo URI of this module 127 | HelpInfoURI = 'https://github.com/aliuq/Register-Completion' 128 | 129 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 130 | # DefaultCommandPrefix = '' 131 | 132 | } 133 | 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Register-Completion 2 | 3 | ![GitHub](https://img.shields.io/github/license/aliuq/Register-Completion) 4 | ![Github Action](https://img.shields.io/github/actions/workflow/status/aliuq/Register-Completion/ci.yml) 5 | ![powershellgallery downloads](https://img.shields.io/powershellgallery/dt/Register-Completion) 6 | ![powershellgallery version](https://img.shields.io/powershellgallery/v/Register-Completion?include_prereleases) 7 | 8 | Easy to register tab completions with fixed data structures. Easy to customize. 9 | 10 | > **Note** 11 | > Recommeded Powershell version 7.0.0 or higher. 12 | 13 | - [Register-Completion](#register-completion) 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [New-Completion](#new-completion) 17 | - [Register-Alias](#register-alias) 18 | - [Global Variables](#global-variables) 19 | - [License](#license) 20 | 21 | ## Installation 22 | 23 | Install module 24 | 25 | ```Powershell 26 | # Install 27 | Install-Module Register-Completion -Scope CurrentUser 28 | # Import 29 | Import-Module Register-Completion 30 | ``` 31 | 32 | Open config file `$profile.ps1` 33 | 34 | ```Powershell 35 | # Powershell 7.x 36 | pwsh 37 | notepad $profile 38 | 39 | # Powershell 5.x 40 | powershell 41 | notepad $profile 42 | ``` 43 | 44 | Add in `$profile.ps1` 45 | 46 | ```Powershell 47 | # profile.ps1 48 | Import-Module Register-Completion 49 | 50 | # Set Tab to menu complement and intellisense 51 | Set-PSReadLineKeyHandler -Key "Tab" -Function MenuComplete 52 | ``` 53 | 54 | ## Usage 55 | 56 | ### New-Completion 57 | 58 | `New-Completion [[-Command] ] [[-HashList] ] [-Force]` 59 | 60 | + `-Command`: Command name 61 | + `-HashList`: Allows basic type number、string、array、hashtable、object or nested types. 62 | + `-Force`: Force a replacement when a completion exists 63 | + `-Filter`: Custom filter and sort function 64 | + `-Where`: Custom filter function 65 | + `-Sort`: Custom sort function 66 | 67 | `Register-Completion` is usually used for tab completion of cli commands, but it goes beyond that, there are two types of tab completions: known datas and dynamic datas. 68 | 69 | For known datas, use `New-Completion` can easily to register a completion. just need to construct the correct data format, example using part of the `npm` command: 70 | 71 | ```Powershell 72 | $npmCmds = " 73 | { 74 | 'login': ['--registry', '--scope', '--auth-type', '--always-auth', '--help'], 75 | 'cache': ['add', { 'clean': '--force', 'clear': '--force', 'rm': '--force' }, 'verify', '--help'], 76 | 'config': [{ 'set': ['--global'] }, 'get', 'delete', { list: ['-l', '--json'] }, 'edit', '--help'], 77 | 'init': ['--force', '--yes', '--scope', '--help', { '#tooltip': 'npm init <@scope> (same as ``npx <@scope>/create``) `nnpm init [<@scope>/] (same as ``npx [<@scope>/]create-``)' }], 78 | 'install': ['--save-prod', '--save-dev', '--save-optional', '--save-exact', '--no-save', '--help'], 79 | 'publish': ['--tag', { '--access': ['public', 'restricted'] }, '--dry-run', '--otp', '--help'], 80 | 'run': ['--silent', '--help'], 81 | 'uninstall': ['--save-prod', '--save-dev', '--save-optional', '--no-save', '--help'], 82 | 'version': ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', '--preid', 'from-git', '--help'], 83 | '--version': '', 84 | '--help': '' 85 | } 86 | " 87 | New-Completion npm $npmCmds 88 | ``` 89 | 90 | Then, restart the current pssession or open a new terminal, use `npm ` to complete the command. In the above example, there is a special key `#tooltip`, which is a reserved field, and it means that after starting `MenuComplete`, powershell will give a tooltip, by providing this key, we can know more about. 91 | 92 | For dynamic data, we need to do some additional processing, continuing with the above field `$npmCmds`, using the `package.json` script as an example: 93 | 94 |
95 | The code 96 | 97 | ```Powershell 98 | Register-ArgumentCompleter -Native -CommandName npm -ScriptBlock { 99 | param($wordToComplete, $commandAst, $cursorPosition) 100 | [Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new() 101 | # If provided input string data, and needed to edit it, 102 | # use `ConvertTo-Hash` to convert the string data to a hash table 103 | $commands = ConvertTo-Hash $npmCmds 104 | # Remove the cache of the same completion key, because if exists, it will not be dynamic updated. 105 | Remove-Completion "npm.run" 106 | # Get package.json script content and append the script to the hashtable 107 | if (Test-Path "$pwd\package.json") { 108 | $scripts = (Get-Content "$pwd\package.json" | ConvertFrom-JSON).scripts 109 | if ($null -ne $scripts) { 110 | $scriptNames = $scripts | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name 111 | $scriptNames | ForEach-Object { 112 | # Add script name to the completion key, the script command to the completion tooltip 113 | $commands.run[$_] = @{ '#tooltip' = $scripts.$_ } 114 | } 115 | } 116 | } 117 | # According to the $wordToComplete、$commandAst、$commands, get the avaliable completion data 118 | # See more at https://github.com/aliuq/Register-Completion/blob/master/src/Completion.ps1#L303 119 | Get-CompletionKeys $wordToComplete $commandAst $commands | ForEach-Object { 120 | $key = $_.Key 121 | $value = $_.Value 122 | $tooltip = $key 123 | 124 | if ($value) { 125 | $value.GetEnumerator() | ForEach-Object { 126 | $lowerKey = $_.Key.ToString().ToLower() 127 | if ($lowerKey -eq '#tooltip') { 128 | $tooltip = $_.Value 129 | } 130 | } 131 | } 132 | 133 | [System.Management.Automation.CompletionResult]::new($key, $key, "ParameterValue", $tooltip) 134 | } 135 | } 136 | ``` 137 | 138 |
139 | 140 | Then, enter a directory where the `package.json` file exists, use `npm run ` to complete the command. Except the script, we can also append dynamic dependencies to `npm uninstall `, the code to implement it is not given here. 141 | 142 | Other `HashList` types to see the below examples. `-Force` let us can force to replacement a exist command. 143 | 144 | ```Powershell 145 | New-Completion nc 100 146 | New-Completion nc "1001" -Force 147 | New-Completion nc "hello world" -Force 148 | New-Completion nc "[100]" -Force 149 | New-Completion nc "[100,101]" -Force 150 | New-Completion nc 'arg1','arg2','arg3' -Force 151 | New-Completion nc '["arg1","arg2","arg3"]' -Force 152 | New-Completion nc "[{arg: 'arg_1'}]" -Force 153 | New-Completion nc "[{arg: {arg_1: 'arg_1_1'}}]" -Force 154 | New-Completion nc "[{arg: {arg_1: {arg_1_1: ['arg_1_1_1', 'arg_1_1_2']}}}]" -Force 155 | New-Completion nc "[100, 'hello', {arg1: 'arg1_1'}, ['arg2', 'arg3']]" -Force 156 | New-Completion nc @{100 = ""; hello = ""; arg1 = @{arg1_1 = ""}; arg2 = ""; arg3 = ""} -Force 157 | New-Completion nc @("arg1", "arg2") -Force 158 | New-Completion nc @("arg1", @{arg2 = "arg2_1"; arg3 = @("arg3_1", "arg3_2")}) -Force 159 | New-Completion nc @{100 = ""; hello = ""; arg1 = @{arg1_1 = ""}; arg2 = ""; arg3 = @("arg3_1", "arg3_2")} -Force 160 | New-Completion nc "{a:1,b:2,c:['c1','c2',{c3:{c3_1:'c3_1_1',c3_2:['c3_2_1','c3_2_2']}}]}" -Force 161 | New-Completion nc "{a:1,b:2,c:['c1','c2',{c3:{c3_1:'c3_1_1',c3_2:['c3_2_1','c3_2_2']}}]}" -filter { 162 | Param($Keys, $Word) 163 | $Keys | Where-Object { $_ -Like "*$Word*" } | Sort-Object -Descending 164 | } -Force 165 | ``` 166 | 167 | ### Register-Alias 168 | 169 | `Register-Alias [[-Name] ] [[-Value] ]` 170 | 171 | Provide bash-like experiences, [see more](https://github.com/aliuq/Register-Completion/blob/master/src/Utils.ps1#L23) 172 | 173 | ```Powershell 174 | Register-Alias ll ls 175 | Register-Alias la ls 176 | Register-Alias swd "echo $pwd" 177 | Register-Alias apps "cd ~/Projects" 178 | Register-Alias i "cd ~/Projects/$($args[0])" 179 | Register-Alias which Get-Command 180 | ``` 181 | 182 | ## Global Variables 183 | 184 | + `$CacheAllCompletions`: Cached all you typed completions, e.g. `nc ` 185 | + `$CacheCommands`: Cached all commands and hashlists from `New-Completion` 186 | 187 | ## License 188 | 189 | [MIT](.\LICENSE) 190 | -------------------------------------------------------------------------------- /src/Completion.ps1: -------------------------------------------------------------------------------- 1 | [hashtable]$CacheAllCompletions = [ordered]@{} 2 | [hashtable]$CacheCommands = [ordered]@{} 3 | $PSVersion = $PSVersionTable.PSVersion 4 | $keywords = '#listitemtext', '#type','#tooltip' 5 | 6 | class CacheCommandData { 7 | $Commands = $null 8 | [ScriptBlock]$Filter = $null 9 | [ScriptBlock]$Sort = $null 10 | [ScriptBlock]$Where = $null 11 | 12 | CacheCommandData ($c, [ScriptBlock]$f, [ScriptBlock]$s, [ScriptBlock]$w) { 13 | $this.Commands = $c 14 | $this.Filter = $f 15 | $this.Sort = $s 16 | $this.Where = $w 17 | } 18 | } 19 | 20 | <# 21 | .SYNOPSIS 22 | Convert to hashtable format. 23 | .DESCRIPTION 24 | Recursive conversion of data to hashtable format, by default, hashtable value is emtpy string, but if this hashtable key in `@('#listitemtext', '#type','#tooltip')`, it will be reserved, because it is used by the construct `System.Management.Automation.CompletionResult` 25 | .PARAMETER InputObject 26 | Input data, support for basic data types 27 | .EXAMPLE 28 | ConvertTo-Hash 'hello','world' 29 | # output: @{'hello' = ''; 'world' = ''} 30 | Convert array to hashtable format 31 | .EXAMPLE 32 | ConvertTo-Hash 100 33 | # output: @{100 = ''} 34 | Convert number to hashtable format 35 | .EXAMPLE 36 | ConvertTo-Hash '{arg1: "hello", arg2: "world"}' 37 | # output: @{arg1 = @{'hello' = ''}; arg2 = @{'world' = ''}} 38 | Convert object to hashtable format 39 | .EXAMPLE 40 | ConvertTo-Hash '[{arg1: {"#tooltip": "arg1 tooltip"}, arg2: {"#tooltip": "arg2 tooltip"}}]' 41 | # output: @{arg1 = @{'#tooltip' = 'arg1 tooltip'}; arg2 = @{'#tooltip' = 'arg2 tooltip'}} 42 | Convert Javascript object to hashtable format width keywords 43 | .INPUTS 44 | None. 45 | .OUTPUTS 46 | System.Collections.Hashtable 47 | .LINK 48 | https://github.com/aliuq/Register-Completion 49 | #> 50 | function ConvertTo-Hash { 51 | Param($InputObject) 52 | 53 | if (!$InputObject) { 54 | return "" 55 | } 56 | 57 | [hashtable]$hash = [ordered]@{} 58 | $inputType = $InputObject.getType() 59 | 60 | if ($inputType -eq [hashtable]) { 61 | $InputObject.Keys | ForEach-Object { 62 | if ($_.ToString().ToLower() -in $keywords) { $hash[$_] = $InputObject[$_] } 63 | else { $hash[$_] = ConvertTo-Hash $InputObject[$_] } 64 | } 65 | } 66 | elseif ($inputType -eq [Object[]]) { 67 | $InputObject | ForEach-Object { $hash += ConvertTo-Hash $_ } 68 | } 69 | elseif ($inputType -eq [System.Management.Automation.PSCustomObject]) { 70 | $InputObject.psobject.Properties | ForEach-Object { 71 | if ($_.Name.ToString().ToLower() -in $keywords) { $hash[$_.Name] = $_.Value } 72 | else { $hash[$_.Name] = ConvertTo-Hash $_.Value } 73 | } 74 | } 75 | else { 76 | try { 77 | if ($PSVersion -lt "7.0") { 78 | $json = ConvertFrom-Json -InputObject $InputObject 79 | } 80 | else { 81 | $json = ConvertFrom-Json -InputObject $InputObject -AsHashtable 82 | } 83 | $jsonType = $json.getType() 84 | if ($jsonType -in [hashtable],[Object[]],[System.Management.Automation.PSCustomObject]) { 85 | $hash = ConvertTo-Hash $json 86 | } 87 | else { 88 | $hash.Add($json, "") 89 | } 90 | } 91 | catch { 92 | $hash.Add($InputObject, "") 93 | } 94 | } 95 | return $hash 96 | } 97 | 98 | <# 99 | .SYNOPSIS 100 | According to input datas, returns avaliable completion object keys. 101 | .DESCRIPTION 102 | According to input word and data, return the corresponding command keys. 103 | it usually used in the cmdlet `Register-ArgumentCompleter`, when provide datasets, it will return the avaliable completion keys. 104 | .PARAMETER Word 105 | The input word. From `$wordToComplete` 106 | .PARAMETER Ast 107 | The input data. From `$commandAst` 108 | .PARAMETER HashList 109 | The datasets, support basic data types. 110 | .PARAMETER Filter 111 | The filter function. if provided, it will be used to filter and sort the completion object keys. 112 | .PARAMETER Where 113 | The where function. if provided, it will be used to filter the completion object keys. 114 | .PARAMETER Sort 115 | The sort function. if provided, it will be used to sort the completion object keys. 116 | .EXAMPLE 117 | Get-CompletionKeys '' nc 'hello','world' 118 | # output: @(@{hello = ''}, @{world = ''}) 119 | Returns object array 120 | .INPUTS 121 | None. 122 | .OUTPUTS 123 | Object[] 124 | .LINK 125 | https://github.com/aliuq/Register-Completion 126 | #> 127 | function Get-CompletionKeys { 128 | Param( 129 | [string]$Word, 130 | $Ast, 131 | $HashList, 132 | [ScriptBlock]$Filter, 133 | [ScriptBlock]$Where, 134 | [ScriptBlock]$Sort 135 | ) 136 | 137 | if (!$HashList) { 138 | return @() 139 | } 140 | 141 | $arr = $Ast.ToString().Split().ToLower() | Where-Object { $null -ne $_ } 142 | 143 | # Empty, need to return children completion keys 144 | if (!$Word) { 145 | [string]$key = ($arr -join ".").trim(".") 146 | $keyLevel = $arr 147 | } 148 | # Character, need to return sibling completion keys 149 | else { 150 | [string]$key = (($arr | Select-Object -SkipLast 1) -join ".").trim(".") 151 | $keyLevel = $key | ForEach-Object { $_.split(".") } 152 | } 153 | 154 | if (!$CacheAllCompletions.ContainsKey($key)) { 155 | $map = ConvertTo-Hash $HashList 156 | 157 | $prefix = "" 158 | $keyLevel | ForEach-Object { 159 | if ($prefix) { 160 | $map = $map[$_] 161 | $prefix = "$prefix.$($_)" 162 | } 163 | else { 164 | $prefix = $_ 165 | } 166 | if (!$CacheAllCompletions.ContainsKey($prefix)) { 167 | if ($null -ne $map) { 168 | $CacheAllCompletions[$prefix] = $map 169 | } 170 | else { 171 | $CacheAllCompletions[$prefix] = @{} 172 | } 173 | } 174 | } 175 | } 176 | 177 | # Convert HashtableEnumerator to Object[] 178 | $keyArrs = $CacheAllCompletions[$key].GetEnumerator() | ForEach-Object { $_ } 179 | 180 | if ($Filter -is [scriptblock]) { 181 | & $Filter $keyArrs $Word 182 | } 183 | else { 184 | if ($Where -is [scriptblock]) { 185 | $keyArrs = & $Where $keyArrs $Word 186 | } 187 | else { 188 | $keyArrs = $keyArrs | Where-Object { $_.Key -Like "*$Word*" } 189 | } 190 | if ($Word) { 191 | $keyArrs = $keyArrs | Sort-Object -Property ` 192 | @{Expression = { $Word -And $_.Key.ToString().StartsWith($Word) }; Descending = $true }, ` 193 | @{Expression = { $Word -And $_.Key.ToString().indexOf($Word) }; Descending = $false } 194 | } 195 | 196 | $keyArrs = $keyArrs | Sort-Object -Property ` 197 | @{Expression = { $_.Key.ToString().StartsWith('-') }; Descending = $false }, ` 198 | @{Expression = { $_.Key }; Descending = $false } 199 | 200 | if ($Sort -is [scriptblock]) { 201 | $keyArrs = & $Sort $keyArrs 202 | } 203 | } 204 | $keyArrs | Where-Object { $_.Key.ToString().ToLower() -notin $keywords } 205 | } 206 | 207 | <# 208 | .SYNOPSIS 209 | Remove a completion. 210 | .DESCRIPTION 211 | According to input command, remove the completion. 212 | .PARAMETER Command 213 | The command name. use dot to separate the command name. 214 | .EXAMPLE 215 | Remove-Completion nc 216 | .EXAMPLE 217 | Remove-Completion 'nc.hello' 218 | .INPUTS 219 | None. 220 | .OUTPUTS 221 | None. 222 | .LINK 223 | https://github.com/aliuq/Register-Completion 224 | #> 225 | function Remove-Completion { 226 | Param([string]$Command) 227 | 228 | if ($CacheCommands.ContainsKey($Command)) { 229 | $CacheCommands.Remove($Command) 230 | } 231 | if ($CacheCommands.ContainsKey("$Command--filter")) { 232 | $CacheCommands.Remove("$Command--filter") 233 | } 234 | $CacheAllCompletions.Clone().Keys | 235 | Where-Object { $_.StartsWith("$Command.") -or ($_ -eq $Command) } | 236 | ForEach-Object { $CacheAllCompletions.Remove($_) } 237 | } 238 | 239 | <# 240 | .SYNOPSIS 241 | Register a completion. 242 | .DESCRIPTION 243 | Register a completion. provide the command name and the completion datasets. when type the command name, and press `Tab`, it will show the completion keys. 244 | .PARAMETER Command 245 | The command name. 246 | .PARAMETER HashList 247 | The datasets, support basic data types. 248 | .PARAMETER Force 249 | Enable replaced the existing completion. default is false. 250 | .PARAMETER Filter 251 | The filter function. if provided, it will be used to filter the completion keys. 252 | The function will be called with two parameters: $Keys and $Word, and the return value is the filtered and sorted keys. 253 | .PARAMETER Where 254 | The where function. if provided, it will be used to filter the completion object keys. 255 | .PARAMETER Sort 256 | The sort function. if provided, it will be used to sort the completion object keys. 257 | .EXAMPLE 258 | New-Completion demo "hello","world" 259 | Register a completion with command name `demo` and datasets `hello`、`world`. 260 | Press `demo ` will get `demo hello` 261 | .EXAMPLE 262 | New-Completion demo "100" -Force 263 | Replace the existing completion with command name `demo` and datasets `100`. 264 | Press `demo ` will get `demo 100` 265 | .EXAMPLE 266 | $cmds = "{ 267 | 'access': ['public', { grant: ['read-only', 'read-write'] }, 'revoke', 'edit', '--help'], 268 | '--help': '' 269 | }" 270 | New-Completion nc $cmds -filter { 271 | Param($Keys, $Word) 272 | $Keys | Where-Object { $_ -Like "*$Word*" } | Sort-Object -Descending 273 | } 274 | Replace the default filter function, and will returns the filtered completion keys with provided fitler function. 275 | .INPUTS 276 | None. 277 | .OUTPUTS 278 | None. 279 | .LINK 280 | https://github.com/aliuq/Register-Completion 281 | #> 282 | function New-Completion { 283 | Param( 284 | [string]$Command, 285 | $HashList, 286 | [switch]$Force = $false, 287 | [ScriptBlock]$Filter, 288 | [ScriptBlock]$Sort, 289 | [ScriptBlock]$Where 290 | ) 291 | 292 | if ($CacheCommands.ContainsKey($Command)) { 293 | if ($Force) { 294 | Remove-Completion $Command 295 | } 296 | else { 297 | return 298 | } 299 | } 300 | 301 | $CacheCommands.Add($Command, [CacheCommandData]::new($HashList, $Filter, $Sort, $Where)) 302 | 303 | Register-ArgumentCompleter -Native -CommandName $Command -ScriptBlock { 304 | param($wordToComplete, $commandAst, $cursorPosition) 305 | [Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new() 306 | 307 | $cmd = $commandAst.CommandElements[0].Value 308 | $data = $CacheCommands[$cmd] 309 | 310 | if ($data) { 311 | Get-CompletionKeys $wordToComplete $commandAst $data.Commands -Filter $data.Filter -Sort $data.Sort -Where $data.Where | 312 | ForEach-Object { 313 | $key = $_.Key 314 | $value = $_.Value 315 | $type = "ParameterValue" 316 | $listItemText = $key 317 | $tooltip = $key 318 | 319 | if ($value) { 320 | $value.GetEnumerator() | ForEach-Object { 321 | $lowerKey = $_.Key.ToString().ToLower() 322 | if ($lowerKey -eq '#tooltip') { 323 | $tooltip = $_.Value 324 | } 325 | elseif ($lowerKey -eq '#type') { 326 | $type = $_.Value 327 | } 328 | elseif ($lowerKey -eq '#listitemtext') { 329 | $listItemText = $_.Value 330 | } 331 | } 332 | } 333 | 334 | [System.Management.Automation.CompletionResult]::new($key, $listItemText, $type, $tooltip) 335 | } 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /test/Register-Completion.Tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | . "$pwd\src\Utils.ps1" 3 | . "$pwd\src\Completion.ps1" 4 | 5 | function Compare-Hashtable { 6 | Param( 7 | [object]$HashObjectOne, 8 | [object]$HashObjectTwo 9 | ) 10 | if ($HashObjectOne.Count -ne $HashObjectTwo.Count) { 11 | return $false 12 | } 13 | 14 | $flag = $true 15 | 16 | foreach ($key in $HashObjectOne.Keys) { 17 | # Check the key is valid 18 | # Write-Host "key: $key" 19 | 20 | if ($key -Match "[\[\]\{\}]") { 21 | $flag = $false 22 | break 23 | } 24 | 25 | $hashOne = $HashObjectOne[$key] 26 | $hashTwo = $HashObjectTwo[$key] 27 | 28 | if ($hashOne.getType() -in [object[]],[hashtable]) { 29 | $flag = Compare-Hashtable $hashOne $hashTwo 30 | } 31 | elseif ([string]::IsNullOrEmpty($hashOne) -And [string]::IsNullOrEmpty($hashTwo)) { 32 | continue 33 | } 34 | elseif ($hashOne -ne $hashTwo) { 35 | $flag = $false 36 | break 37 | } 38 | } 39 | 40 | return $flag 41 | } 42 | } 43 | 44 | Describe 'Test funtion ConvertTo-Hash' { 45 | It "Convert " -ForEach @( 46 | @{Type = 'number'; Src = 100; Expected = @{100 = ''}} 47 | @{Type = 'array number'; Src = '[100,101]'; Expected = @{100 = ''; 101 = ''}} 48 | @{Type = 'powershell array number'; Src = 100,101; Expected = @{100 = ''; 101 = ''}} 49 | @{Type = 'string'; Src = 'hello'; Expected = @{'hello' = ''}} 50 | @{Type = 'array string'; Src = '["hello","world"]'; Expected = @{'hello' = ''; 'world' = ''}} 51 | @{Type = 'powershell array string'; Src = 'hello','world'; Expected = @{'hello' = ''; 'world' = ''}} 52 | @{ 53 | Type = 'object'; 54 | Src = '{arg1: "hello", arg2: "world"}'; 55 | Expected = @{arg1 = @{'hello' = ''}; arg2 = @{'world' = ''}} 56 | } 57 | @{ 58 | Type = 'array object'; 59 | Src = '[{arg1: "hello", arg2: "world"}]'; 60 | Expected = @{arg1 = @{'hello' = ''}; arg2 = @{'world' = ''}} 61 | } 62 | @{ 63 | Type = 'array nested object'; 64 | Src = '[{arg: {arg_1: "arg_1_1"}}]'; 65 | Expected = @{arg = @{arg_1 = @{arg_1_1 = ''}}} 66 | } 67 | @{ 68 | Type = 'array nested object array'; 69 | Src = '[{arg: {arg_1: {arg_1_1: ["arg_1_1_1", "arg_1_1_2"]}}}]'; 70 | Expected = @{arg = @{arg_1 = @{arg_1_1 = @{arg_1_1_1 = ''; arg_1_1_2 = ''}}}} 71 | } 72 | @{ 73 | Type = 'mixed type 1'; 74 | Src = '[100, "hello", {arg1: "arg1_1"}, ["arg2", "arg3"]]'; 75 | Expected = @{100 = ''; hello = ''; arg1 = @{arg1_1 = ''}; arg2 = ''; arg3 = ''} 76 | } 77 | @{ 78 | Type = 'mixed type 2'; 79 | Src = '{a:1,b:2,c:["c1","c2",{c3:{c3_1:"c3_1_1",c3_2:["c3_2_1","c3_2_2"]}}]}'; 80 | Expected = @{a = @{1 = ''}; b = @{2 = ''}; c = @{c1 = ''; c2 = ''; c3 = @{c3_1 = @{c3_1_1 = ''}; c3_2 = @{c3_2_1 = ''; c3_2_2 = ''}}}} 81 | } 82 | @{ 83 | Type = 'powershell hashtable mixed type'; 84 | Src = @{100 = ''; hello = ''; arg1 = @{arg1_1 = ''}; arg2 = ''; arg3 = ''}; 85 | Expected = @{100 = ''; hello = ''; arg1 = @{arg1_1 = ''}; arg2 = ''; arg3 = ''} 86 | } 87 | @{ 88 | Type = 'powershell list mixed type'; 89 | Src = @('arg1', @{arg2 = 'arg2_1'; arg3 = @('arg3_1', 'arg3_2')}); 90 | Expected = @{arg1 = ''; arg2 = @{arg2_1 = ''}; arg3 = @{arg3_1 = ''; arg3_2 = ''}} 91 | } 92 | @{ 93 | Type = 'tooltip'; 94 | Src = '[{arg1: {"#tooltip": "arg1 tooltip"}, arg2: {"#tooltip": "arg2 tooltip"}}]'; 95 | Expected = @{arg1 = @{'#tooltip' = 'arg1 tooltip'}; arg2 = @{'#tooltip' = 'arg2 tooltip'}} 96 | } 97 | @{ 98 | Type = 'tooltip listitemtext'; 99 | Src = '[{arg1: {"#tooltip": "arg1 tooltip", "#listitemtext": "arg1 listitemtext"}}]'; 100 | Expected = @{arg1 = @{'#tooltip' = 'arg1 tooltip'; '#listitemtext' = 'arg1 listitemtext'}} 101 | } 102 | @{ 103 | Type = 'powershell tooltip 1'; 104 | Src = @{arg1 = @{'#tooltip' = 'arg1 tooltip'}; arg2 = @{'#tooltip' = 'arg2 tooltip'}}; 105 | Expected = @{arg1 = @{'#tooltip' = 'arg1 tooltip'}; arg2 = @{'#tooltip' = 'arg2 tooltip'}} 106 | } 107 | @{ 108 | Type = 'powershell tooltip 2'; 109 | Src = @(@{arg1 = @{'#tooltip' = 'arg1 tooltip'}; arg2 = @{'#tooltip' = 'arg2 tooltip'}}); 110 | Expected = @{arg1 = @{'#tooltip' = 'arg1 tooltip'}; arg2 = @{'#tooltip' = 'arg2 tooltip'}} 111 | } 112 | @{ 113 | Type = 'powershell tooltip listitemtext'; 114 | Src = @{arg1 = @{'#tooltip' = 'arg1 tooltip'; '#listitemtext' = 'arg1 listitemtext'}}; 115 | Expected = @{arg1 = @{'#tooltip' = 'arg1 tooltip'; '#listitemtext' = 'arg1 listitemtext'}} 116 | } 117 | ) { 118 | $hash = ConvertTo-Hash $src 119 | Compare-Hashtable $hash $expected | Should -Be $true 120 | } 121 | } 122 | 123 | Describe 'Test Cases' { 124 | It 'test number' { 125 | New-Completion case 100 126 | $CacheCommands.case.Commands | Should -Be 100 127 | 128 | (Get-CompletionKeys '' 'case' 100).key | Should -Be @(100) 129 | (Get-CompletionKeys '1' 'case 1' 100).key | Should -Be @(100) 130 | Get-CompletionKeys '' 'case 1' 100 | Should -Be @() 131 | Get-CompletionKeys '2' 'case 2' 100 | Should -Be @() 132 | Get-CompletionKeys '' 'case 2' 100 | Should -Be @() 133 | 134 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash 100) | Should -Be $true 135 | New-Completion case 1001 136 | $CacheCommands.case.Commands | Should -Be 100 137 | New-Completion case 1001 -Force 138 | $CacheCommands.case.Commands | Should -Be 1001 139 | Remove-Completion case 140 | $CacheCommands.case | Should -Be $null 141 | $CacheAllCompletions.case | Should -Be $null 142 | } 143 | 144 | It 'test number string' { 145 | New-Completion case '100' 146 | $CacheCommands.case.Commands | Should -Be '100' 147 | 148 | (Get-CompletionKeys '' 'case' '100').key | Should -Be @('100') 149 | (Get-CompletionKeys '1' 'case 1' '100').key | Should -Be @('100') 150 | Get-CompletionKeys '' 'case 1' '100' | Should -Be @() 151 | Get-CompletionKeys '2' 'case 2' '100' | Should -Be @() 152 | Get-CompletionKeys '' 'case 2' '100' | Should -Be @() 153 | 154 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash '100') | Should -Be $true 155 | New-Completion case '1001' 156 | $CacheCommands.case.Commands | Should -Be '100' 157 | New-Completion case '1001' -Force 158 | $CacheCommands.case.Commands | Should -Be '1001' 159 | Remove-Completion case 160 | $CacheCommands.case | Should -Be $null 161 | $CacheAllCompletions.case | Should -Be $null 162 | } 163 | 164 | It 'test string' { 165 | New-Completion case 'world' 166 | $CacheCommands.case.Commands | Should -Be 'world' 167 | 168 | (Get-CompletionKeys '' "case" 'world').key | Should -Be @('world') 169 | (Get-CompletionKeys 'w' 'case w' 'world').key | Should -Be @('world') 170 | Get-CompletionKeys '' 'case w' 'world' | Should -Be @() 171 | Get-CompletionKeys 'c' 'case c' 'world' | Should -Be @() 172 | Get-CompletionKeys '' 'case c' 'world' | Should -Be @() 173 | 174 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash 'world') | Should -Be $true 175 | New-Completion case 'world new' 176 | $CacheCommands.case.Commands | Should -Be 'world' 177 | New-Completion case 'world new' -Force 178 | $CacheCommands.case.Commands | Should -Be 'world new' 179 | Remove-Completion case 180 | $CacheCommands.case | Should -Be $null 181 | $CacheAllCompletions.case | Should -Be $null 182 | } 183 | 184 | It 'test ' -ForEach @( 185 | @{ 186 | Type = 'js array'; 187 | List = '["arg1","arg2","arg3"]'; 188 | ListNew = '["arg1_new","arg2_new","arg3_new"]' 189 | } 190 | @{ 191 | Type = 'powershell list'; 192 | List = 'arg1','arg2','arg3'; 193 | ListNew = 'arg1_new','arg2_new','arg3_new' 194 | } 195 | ) { 196 | New-Completion case $list 197 | $CacheCommands.case.Commands | Should -Be $list 198 | 199 | (Get-CompletionKeys '' 'case' $list).key | Should -Be @('arg1','arg2','arg3') 200 | (Get-CompletionKeys '2' 'case 2' $list).key | Should -Be @('arg2') 201 | Get-CompletionKeys '' 'case w' $list | Should -Be @() 202 | Get-CompletionKeys "c" 'case c' $list | Should -Be @() 203 | Get-CompletionKeys '' 'case c' $list | Should -Be @() 204 | (Get-CompletionKeys 'arg1' 'case arg1' $list).key | Should -Be @('arg1') 205 | Get-CompletionKeys '' 'case arg1' $list | Should -Be @() 206 | 207 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash $list) | Should -Be $true 208 | New-Completion case $listNew 209 | $CacheCommands.case.Commands | Should -Be $list 210 | New-Completion case $listNew -Force 211 | $CacheCommands.case.Commands | Should -Be $listNew 212 | Remove-Completion case 213 | $CacheCommands.case | Should -Be $null 214 | $CacheAllCompletions.case | Should -Be $null 215 | } 216 | 217 | It 'test ' -ForEach @( 218 | @{ 219 | Type = 'js object'; 220 | List = '{ 221 | arg1: "arg1_1", 222 | arg2: "arg2_2", 223 | arg3: { 224 | arg3_1: "arg3_1_1", 225 | arg3_2: "arg3_2_1" 226 | } 227 | }'; 228 | ListNew = '{arg1: "arg1_1_new"}' 229 | } 230 | @{ 231 | Type = 'powershell hashtable'; 232 | List = @{ 233 | arg1 = 'arg1_1'; 234 | arg2 = 'arg2_2'; 235 | arg3 = @{ 236 | arg3_1 = 'arg3_1_1'; 237 | arg3_2 = 'arg3_2_1' 238 | } 239 | }; 240 | ListNew = @{arg1 = 'arg1_1_new'} 241 | } 242 | ) { 243 | New-Completion case $list 244 | $CacheCommands.case.Commands | Should -Be $list 245 | 246 | (Get-CompletionKeys '' 'case' $list).key | Should -Be @('arg1','arg2','arg3') 247 | (Get-CompletionKeys '2' 'case 2' $list).key | Should -Be @('arg2') 248 | Get-CompletionKeys '' 'case 2' $list | Should -Be @() 249 | (Get-CompletionKeys '' 'case arg3' $list).key | Should -Be @('arg3_1', 'arg3_2') 250 | (Get-CompletionKeys '2' 'case arg3 2' $list).key | Should -Be @('arg3_2') 251 | (Get-CompletionKeys '1' 'case arg3 arg3_1 1' $list).key | Should -Be @('arg3_1_1') 252 | Get-CompletionKeys '' 'case arg3 arg3_1 1' $list | Should -Be @() 253 | 254 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash $list) | Should -Be $true 255 | New-Completion case $listNew 256 | $CacheCommands.case.Commands | Should -Be $list 257 | New-Completion case $listNew -Force 258 | $CacheCommands.case.Commands | Should -Be $listNew 259 | Remove-Completion case 260 | $CacheCommands.case | Should -Be $null 261 | $CacheAllCompletions.case | Should -Be $null 262 | } 263 | 264 | It 'test ' -ForEach @( 265 | @{ 266 | Type = 'js object array'; 267 | List = '{ 268 | arg1: [ 269 | "arg1_1", 270 | { arg1_2: "arg1_2_1" } 271 | ], 272 | arg2: "arg2_2", 273 | arg3: { 274 | arg3_1: ["arg3_1_1", "arg3_1_2"], 275 | arg3_2: "arg3_2_1" 276 | } 277 | }'; 278 | ListNew = '{arg2: "arg2_2_new"}' 279 | } 280 | @{ 281 | Type = 'powershell hashtable list'; 282 | List = @{ 283 | arg1 = 'arg1_1',@{arg1_2 = 'arg1_2_1'}; 284 | arg2 = 'arg2_2'; 285 | arg3 = @{ 286 | arg3_1 = 'arg3_1_1', 'arg3_1_2'; 287 | arg3_2 = 'arg3_2_1' 288 | } 289 | }; 290 | ListNew = @{arg2 = 'arg2_2_new'} 291 | } 292 | ) { 293 | New-Completion case $list 294 | $CacheCommands.case.Commands | Should -Be $list 295 | 296 | (Get-CompletionKeys '' 'case' $list).key | Should -Be @('arg1','arg2','arg3') 297 | (Get-CompletionKeys '2' 'case 2' $list).key | Should -Be @('arg2') 298 | Get-CompletionKeys '' 'case 2' $list | Should -Be @() 299 | (Get-CompletionKeys '' 'case arg1' $list).key | Should -Be @('arg1_1', 'arg1_2') 300 | (Get-CompletionKeys '2' 'case arg1 2' $list).key | Should -Be @('arg1_2') 301 | (Get-CompletionKeys '' 'case arg1 arg1_2' $list).key | Should -Be @('arg1_2_1') 302 | (Get-CompletionKeys '1' 'case arg1 arg1_2 1' $list).key | Should -Be @('arg1_2_1') 303 | Get-CompletionKeys '' 'case arg1 arg1_2 1' $list | Should -Be @() 304 | (Get-CompletionKeys '1' 'case arg3 1' $list).key | Should -Be @('arg3_1') 305 | Get-CompletionKeys '' 'case arg3 1' $list | Should -Be @() 306 | (Get-CompletionKeys '' 'case arg3 arg3_1' $list).key | Should -Be @('arg3_1_1', 'arg3_1_2') 307 | (Get-CompletionKeys '2' 'case arg3 arg3_1 2' $list).key | Should -Be @('arg3_1_2') 308 | Get-CompletionKeys '' 'case arg3 arg3_1 2' $list | Should -Be @() 309 | 310 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash $list) | Should -Be $true 311 | New-Completion case $listNew 312 | $CacheCommands.case.Commands | Should -Be $list 313 | New-Completion case $listNew -Force 314 | $CacheCommands.case.Commands | Should -Be $listNew 315 | Remove-Completion case 316 | $CacheCommands.case | Should -Be $null 317 | $CacheAllCompletions.case | Should -Be $null 318 | } 319 | 320 | It 'test ' -ForEach @( 321 | @{ 322 | Type = 'js array object'; 323 | List = '[ 324 | "arg1", 325 | { arg2: "arg2_2" }, 326 | ["arg3", {arg4: "arg4_1"}] 327 | ]'; 328 | ListNew = '["arg1"]' 329 | } 330 | @{ 331 | Type = 'powershell list hashtable'; 332 | List = @( 333 | 'arg1', 334 | @{ arg2 = 'arg2_2' }, 335 | @('arg3', @{arg4 = 'arg4_1'}) 336 | ); 337 | ListNew = @('arg1') 338 | } 339 | ) { 340 | New-Completion case $list 341 | $CacheCommands.case.Commands | Should -Be $list 342 | 343 | (Get-CompletionKeys '' 'case' $list).key | Should -Be @('arg1','arg2','arg3','arg4') 344 | (Get-CompletionKeys '2' 'case 2' $list).key | Should -Be @('arg2') 345 | Get-CompletionKeys '' 'case 2' $list | Should -Be @() 346 | (Get-CompletionKeys '' 'case arg2' $list).key | Should -Be @('arg2_2') 347 | (Get-CompletionKeys '2' 'case arg2 2' $list).key | Should -Be @('arg2_2') 348 | Get-CompletionKeys '' 'case arg2 2' $list | Should -Be @() 349 | (Get-CompletionKeys '' 'case arg4' $list).key | Should -Be @('arg4_1') 350 | Get-CompletionKeys '' 'case arg4 2' $list | Should -Be @() 351 | 352 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash $list) | Should -Be $true 353 | New-Completion case $listNew 354 | $CacheCommands.case.Commands | Should -Be $list 355 | New-Completion case $listNew -Force 356 | $CacheCommands.case.Commands | Should -Be $listNew 357 | Remove-Completion case 358 | $CacheCommands.case | Should -Be $null 359 | $CacheAllCompletions.case | Should -Be $null 360 | } 361 | 362 | It "custom filter function" { 363 | $list = '{ 364 | "access": ["public", { grant: ["read-only", "read-write"] }, "edit", "--help"], 365 | "--help": "", 366 | "add": ["--help"] 367 | }' 368 | New-Completion case $list -filter { 369 | Param($Keys, $Word) 370 | $Keys | Where-Object { $_ -Like "*$Word*" } | Sort-Object -Descending 371 | } 372 | $CacheCommands.case.Commands | Should -Be $list 373 | $CacheCommands.case.Filter.ToString() | Should -Be ({ 374 | Param($Keys, $Word) 375 | $Keys | Where-Object { $_ -Like "*$Word*" } | Sort-Object -Descending 376 | }).ToString() 377 | 378 | (Get-CompletionKeys '' 'case' $list).key | Should -Be @('access','add','--help') 379 | (Get-CompletionKeys 'g' 'case access g' $list).key | Should -Be @('grant') 380 | (Get-CompletionKeys '' 'case access grant' $list).key | Should -Be @('read-only','read-write') 381 | (Get-CompletionKeys 't' 'case access t' $list).key | Should -Be @('edit','grant') 382 | Get-CompletionKeys '' 'case access t' $list | Should -Be @() 383 | 384 | Compare-Hashtable $CacheAllCompletions.case (ConvertTo-Hash $list) | Should -Be $true 385 | Remove-Completion case 386 | $CacheCommands.case | Should -Be $null 387 | $CacheAllCompletions.case | Should -Be $null 388 | } 389 | 390 | It "custom where function" { 391 | $list = '{ 392 | "access": ["public", { grant: ["read-only", "read-write"] }, "edit", "--help"], 393 | "--help": "", 394 | "add": ["--help"] 395 | }' 396 | $where = { 397 | Param($Keys, $Word) 398 | $Keys | Where-Object { $_.Key -Like "*$Word*" } 399 | } 400 | New-Completion case $list -Where $where 401 | $CacheCommands.case.Commands | Should -Be $list 402 | $CacheCommands.case.Where.ToString() | Should -Be $where.ToString() 403 | 404 | (Get-CompletionKeys '' 'case' $list -Where $where).key | Should -Be @('access','add','--help') 405 | 406 | $where = { 407 | Param($Keys, $Word) 408 | $Keys | Where-Object { 409 | if ($Word) { 410 | $_.Key.StartsWith($Word) 411 | } else { 412 | -not $_.Key.StartsWith('-') 413 | } 414 | } 415 | } 416 | (Get-CompletionKeys '' 'case' $list -Where $where).key | Should -Be @('access', 'add') 417 | (Get-CompletionKeys 'c' 'case c' $list -Where $where).key | Should -Be @() 418 | (Get-CompletionKeys 'g' 'case access g' $list -Where $where).key | Should -Be @('grant') 419 | (Get-CompletionKeys '-' 'case -' $list -Where $where).key | Should -Be @('--help') 420 | 421 | Remove-Completion case 422 | $CacheCommands.case | Should -Be $null 423 | } 424 | 425 | It "custom sort function" { 426 | $list = '["arg1", "arg2", "--help", "--set", "arg3", "--get", "arg4"]' 427 | $sort = { 428 | Param($Keys) 429 | $Keys | Sort-Object -Property ` 430 | @{Expression = { $_.Key.ToString().StartsWith('-') }; Descending = $false }, ` 431 | @{Expression = { $_.Key }; Descending = $true} 432 | } 433 | New-Completion case $list 434 | $CacheCommands.case.Commands | Should -Be $list 435 | $CacheCommands.case.Sort | Should -Be $null 436 | 437 | (Get-CompletionKeys '' 'case' $list).key | Should -Be @('arg1', 'arg2', 'arg3', 'arg4', '--get', '--help', '--set') 438 | 439 | New-Completion case $list -Sort $sort -Force 440 | $CacheCommands.case.Sort.ToString() | Should -Be $sort.ToString() 441 | (Get-CompletionKeys '' 'case' $list -Sort $sort).key | Should -Be @('arg4', 'arg3', 'arg2', 'arg1', '--set', '--help', '--get') 442 | 443 | Remove-Completion case 444 | $CacheCommands.case | Should -Be $null 445 | } 446 | 447 | It "custom default sort" { 448 | $list = '["arg1", "arg2", "--help", "--set", "arg3", "--get", "arg4"]' 449 | New-Completion case $list -DefaultSort $false 450 | $CacheCommands.case.Commands | Should -Be $list 451 | $CacheCommands.case.Sort | Should -Be $null 452 | $arr = Get-CompletionKeys '' 'case' $list 453 | $arr.key | Should -Be @('arg1', 'arg2', 'arg3', 'arg4', '--get', '--help', '--set') 454 | 455 | $arr = Get-CompletionKeys 'g' 'case g' $list 456 | $arr.key | Should -Be @('arg1', 'arg2', 'arg3', 'arg4', '--get') 457 | 458 | Remove-Completion case 459 | $CacheCommands.case | Should -Be $null 460 | } 461 | } 462 | 463 | Describe 'Test Register-Alias' { 464 | It "Test Command" { 465 | Get-Command Register-Alias | Should -Be $true 466 | } 467 | It "Test Alias echo by string" { 468 | Register-Alias hello "Write-Output 'Hello World!'" 469 | hello | Should -Be 'Hello World!' 470 | } 471 | It "Test Alias echo by ScriptBlock" { 472 | Register-Alias hello { Write-Output 'Hello World!' } 473 | hello | Should -Be 'Hello World!' 474 | } 475 | It "Test Alias swd by string" { 476 | Register-Alias swd "Write-Output `$pwd" 477 | swd | Should -Be $pwd 478 | } 479 | It "Test Alias swd by ScriptBlock" { 480 | Register-Alias swd { Write-Output $pwd } 481 | swd | Should -Be $pwd 482 | } 483 | } 484 | --------------------------------------------------------------------------------