Private/Template.ps1
|
Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # The init script, mirroring zoxide 0.10.0 templates/powershell.txt as askama renders it from # shell::Opts (cmd, hook, echo, resolve_symlinks; the PowerShell template does not read # resolve_symlinks). The template text below is the rendered default output with three markers # on lines of their own: @@ECHO@@ (the _ZO_ECHO line in __zoxide_cd), @@HOOK@@ (the hook # section body) and @@CMD@@ (the z and zi aliases). tests/fixtures/init_powershell*.ps1 hold the # binary's output for every variant; Template.Tests.ps1 diffs them byte for byte. $script:ZoInitTemplate = @' # ============================================================================= # # Utility functions for zoxide. # # Call zoxide binary, returning the output as UTF-8. function global:__zoxide_bin { $encoding = [Console]::OutputEncoding try { [Console]::OutputEncoding = [System.Text.Utf8Encoding]::new() $result = zoxide @args return $result } finally { [Console]::OutputEncoding = $encoding } } # pwd based on zoxide's format. function global:__zoxide_pwd { $cwd = Microsoft.PowerShell.Management\Get-Location if ($cwd.Provider.Name -eq "FileSystem") { $cwd.ProviderPath } } # cd + custom logic based on the value of _ZO_ECHO. function global:__zoxide_cd($dir, $literal) { $dir = if ($literal) { if ($null -eq $dir) { Microsoft.PowerShell.Management\Set-Location } else { Microsoft.PowerShell.Management\Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop } } else { if ($dir -eq '-' -and ($PSVersionTable.PSVersion -lt 6.1)) { Microsoft.PowerShell.Utility\Write-Error "cd - is not supported below PowerShell 6.1. Please upgrade your version of PowerShell." } elseif ($dir -eq '+' -and ($PSVersionTable.PSVersion -lt 6.2)) { Microsoft.PowerShell.Utility\Write-Error "cd + is not supported below PowerShell 6.2. Please upgrade your version of PowerShell." } else { Microsoft.PowerShell.Management\Set-Location -Path $dir -Passthru -ErrorAction Stop } } @@ECHO@@ } # ============================================================================= # # Hook configuration for zoxide. # @@HOOK@@ # ============================================================================= # # When using zoxide with --no-cmd, alias these internal functions as desired. # # Jump to a directory using only keywords. function global:__zoxide_z { if ($args.Length -eq 0) { __zoxide_cd $null $true } elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) { __zoxide_cd $args[0] $false } elseif ($args.Length -eq 1 -and (Microsoft.PowerShell.Management\Test-Path -PathType Container -LiteralPath $args[0])) { __zoxide_cd $args[0] $true } elseif ($args.Length -eq 1 -and (Microsoft.PowerShell.Management\Test-Path -PathType Container -Path $args[0] )) { __zoxide_cd $args[0] $false } else { $result = __zoxide_pwd if ($null -ne $result) { $result = __zoxide_bin query --exclude $result "--" @args } else { $result = __zoxide_bin query "--" @args } if ($LASTEXITCODE -eq 0) { __zoxide_cd $result $true } } } # Jump to a directory using interactive search. function global:__zoxide_zi { $result = __zoxide_bin query -i "--" @args if ($LASTEXITCODE -eq 0) { __zoxide_cd $result $true } } # ============================================================================= # # Commands for zoxide. Disable these using --no-cmd. # @@CMD@@ # ============================================================================= # # To initialize zoxide, add this to your configuration (find it by running # `echo $profile` in PowerShell): # # Invoke-Expression (& { (zoxide init powershell | Out-String) }) '@ $script:ZoInitEcho = ' Microsoft.PowerShell.Utility\Write-Output $dir.Path' $script:ZoInitNotConfigured = '# -- not configured --' $script:ZoInitHookPrompt = @' # Hook to add new entries to the database. function global:__zoxide_hook { $result = __zoxide_pwd if ($null -ne $result) { zoxide add "--" $result } } '@ $script:ZoInitHookPwd = @' # Hook to add new entries to the database. $global:__zoxide_oldpwd = __zoxide_pwd function global:__zoxide_hook { $result = __zoxide_pwd if ($result -ne $global:__zoxide_oldpwd) { if ($null -ne $result) { zoxide add "--" $result } $global:__zoxide_oldpwd = $result } } '@ $script:ZoInitHookInstall = @' # Initialize hook. $global:__zoxide_hooked = (Microsoft.PowerShell.Utility\Get-Variable __zoxide_hooked -ErrorAction Ignore -ValueOnly) if ($global:__zoxide_hooked -ne 1) { $global:__zoxide_hooked = 1 $global:__zoxide_prompt_old = $function:prompt function global:prompt { if ($null -ne $__zoxide_prompt_old) { & $__zoxide_prompt_old } $null = __zoxide_hook } } '@ function Get-ZoInitScript { <# .SYNOPSIS The PowerShell init script that `zoxide init powershell` prints, without the final newline. .DESCRIPTION Mirrors the Powershell(opts).render() call in cmd/init.rs over templates/powershell.txt. -Cmd is the alias prefix (Opts.cmd: Some unless --no-cmd), -Hook is Opts.hook and -Echo is Opts.echo (config::echo, _ZO_ECHO=1). -ResolveSymlinks is accepted because Opts carries it, but the PowerShell template never reads it. Lines are joined with LF, as the binary writes them; the caller adds the newline that writeln! appends. .PARAMETER Cmd The prefix for the z and zi aliases, or $null (or empty) for --no-cmd. .PARAMETER Hook none, prompt or pwd. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'ResolveSymlinks', Justification = 'Part of shell::Opts; templates/powershell.txt does not use it.')] [CmdletBinding()] [OutputType([string])] param( [AllowNull()] [AllowEmptyString()] [string]$Cmd = 'z', [ValidateSet('none', 'prompt', 'pwd')] [string]$Hook = 'pwd', [bool]$Echo = $false, [bool]$ResolveSymlinks = $false ) $hookText = switch ($Hook) { 'none' { $script:ZoInitNotConfigured } 'prompt' { $script:ZoInitHookPrompt + "`n" + $script:ZoInitHookInstall } 'pwd' { $script:ZoInitHookPwd + "`n" + $script:ZoInitHookInstall } } $cmdText = if ($Cmd) { "Microsoft.PowerShell.Utility\Set-Alias -Name $Cmd -Value __zoxide_z -Option AllScope -Scope Global -Force`n" + "Microsoft.PowerShell.Utility\Set-Alias -Name ${Cmd}i -Value __zoxide_zi -Option AllScope -Scope Global -Force" } else { $script:ZoInitNotConfigured } # The source file may be checked out with CRLF; the binary prints LF only. $text = $script:ZoInitTemplate.Replace("`r`n", "`n") $text = if ($Echo) { $text.Replace('@@ECHO@@', $script:ZoInitEcho) } else { $text.Replace("@@ECHO@@`n", '') } $text.Replace('@@HOOK@@', $hookText.Replace("`r`n", "`n")).Replace('@@CMD@@', $cmdText) } function Get-ZoInitFunction { <# .SYNOPSIS The functions a rendered init script defines, by name, as unbound script blocks. .DESCRIPTION Parses the output of Get-ZoInitScript and returns an ordered dictionary from each function name (without the 'global:' prefix: __zoxide_bin, __zoxide_pwd, __zoxide_cd, __zoxide_hook when hooked, prompt when hooked, __zoxide_z, __zoxide_zi) to a script block with the function's text, its parameters turned into a param() block. The script blocks are created with [scriptblock]::Create, so they are not bound to this module and run in the session state of whoever calls them, as the template's functions do when the init output is evaluated (Install-ZoxideHook explains why that matters). Only those seven names are returned, each from its first definition, so text appended to the script can never add or replace a function (defence in depth; Install-ZoxideHook renders without user data). #> [CmdletBinding()] [OutputType([Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory)] [string]$Script ) $errors = $null $ast = [Management.Automation.Language.Parser]::ParseInput($Script, [ref]$null, [ref]$errors) if ($errors) { throw "the init script does not parse: $($errors[0].Message)" } $functions = [ordered]@{} $definitions = $ast.FindAll({ param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] }, $true) $known = @('__zoxide_bin', '__zoxide_pwd', '__zoxide_cd', '__zoxide_hook', 'prompt', '__zoxide_z', '__zoxide_zi') foreach ($definition in $definitions) { $name = $definition.Name -replace '^global:', '' if ($known -cnotcontains $name -or $functions.Contains($name)) { continue } $params = if ($definition.Parameters) { 'param(' + (($definition.Parameters | ForEach-Object { $_.Extent.Text }) -join ', ') + ")`n" } else { '' } $body = $definition.Body.Extent.Text $functions[$name] = [scriptblock]::Create($params + $body.Substring(1, $body.Length - 2)) } $functions } |