ZLocation.psm1
Set-StrictMode -Version Latest # Listing nested modules in .psd1 creates additional scopes and Pester cannot mock cmdlets in those scopes. # Instead we import them here which works. Import-Module "$PSScriptRoot\ZLocation.Service.psd1" Import-Module "$PSScriptRoot\ZLocation.Search.psm1" Import-Module "$PSScriptRoot\ZLocation.Storage.psm1" # I currently consider number of commands executed in directory to be a better metric, than total time spent in a directory. # See [corresponding issue](https://github.com/vors/ZLocation/issues/6) for details. # If you prefer the old behavior, uncomment this code. <# # # Weight function. # function Update-ZLocation([string]$path) { $now = [datetime]::Now if (Test-Path variable:global:__zlocation_current) { $prev = $global:__zlocation_current $weight = $now.Subtract($prev.Time).TotalSeconds Add-ZWeight ($prev.Location) $weight } $global:__zlocation_current = @{ Location = $path Time = [datetime]::Now } # populate folder immediately after the first cd Add-ZWeight $path 0 } # this approach hurts `cd` performance (0.0008 sec vs 0.025 sec). # Consider replacing it with OnIdle Event. (Get-Variable pwd).attributes.Add((new-object ValidateScript { Update-ZLocation $_.Path; return $true })) #> function Update-ZLocation([string]$path) { Add-ZWeight $path 1.0 } function Register-PromptHook { param() # Insert a call to Update-Zlocation in the prompt function but only once. if (-not (Test-Path function:\global:ZlocationOrigPrompt)) { Copy-Item function:\prompt function:\global:ZlocationOrigPrompt $global:ZLocationPromptScriptBlock = { Update-ZLocation $pwd ZLocationOrigPrompt } Set-Content -Path function:\prompt -Value $global:ZLocationPromptScriptBlock -Force } } # On removal/unload of the module, restore original prompt or LocationChangedAction event handler. $ExecutionContext.SessionState.Module.OnRemove = { Copy-Item function:\global:ZlocationOrigPrompt function:\global:prompt Remove-Item function:\ZlocationOrigPrompt Remove-Variable ZLocationPromptScriptBlock -Scope Global } # # End of weight function. # # # Tab completion. # if(Get-Command -Name Register-ArgumentCompleter -ErrorAction Ignore) { 'Set-ZLocation', 'Invoke-ZLocation' | % { Register-ArgumentCompleter -CommandName $_ -ParameterName match -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) # Omit first item (command name) and empty strings $i = $commandAst.CommandElements.Count [string[]]$query = if($i -gt 1) { $commandAst.CommandElements[1..($i-1)] | ForEach-Object { $_.toString()} } Find-Matches (Get-ZLocation) $query | Get-EscapedPath } } } function Get-EscapedPath { param( [Parameter( Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true) ] [string]$path ) process { if ($path.Contains(' ')) { return '"' + $path + '"' } return $path } } # # End of tab completion. # # Default location stack is local for module. Users cannot use 'Pop-Location' directly, so we need to provide a command inside the module for that. function Pop-ZLocation { Pop-Location } function Set-ZLocation([Parameter(ValueFromRemainingArguments)][string[]]$match) { Register-PromptHook if (-not $match) { $match= @() } # Special case to enable Pop-Location. if (($match.Count -eq 1) -and ($match[0] -eq '-')) { Pop-ZLocation return } $matches = Find-Matches (Get-ZLocation) $match $pushDone = $false foreach ($m in $matches) { if (Test-path $m) { Push-Location $m $pushDone = $true break } else { Write-Warning "There is no path $m on the file system. Removing obsolete data from database." Remove-ZLocation $m } } if (-not $pushDone) { if (($match.Count -eq 1) -and (Test-Path "$match")) { Write-Debug "No matches for $match, attempting Push-Location" Push-Location "$match" } else { Write-Warning "Cannot find matching location" } } } <# This is the main entry point in the interactive usage of ZLocation. It's intended to be used as an alias z Usage: z - prints available directories z -l foo - prints available directories scoped to foo query z foo - jumps into the location that matches foo #> function Invoke-ZLocation { param( [Parameter(ValueFromRemainingArguments)][string[]]$match ) $sortProperty = "Path" $sortDescending = $false $locations = $null if ($null -eq $match) { $locations = Get-ZLocation } elseif (($match.Length -gt 0) -and ($match[0] -eq '-l')) { $locations = Get-ZLocation ($match | Select-Object -Skip 1) $sortProperty = "Weight" $sortDescending = $true } if ($locations) { $locations | ForEach-Object {$_.GetEnumerator()} | ForEach-Object {[PSCustomObject]@{Weight = $_.Value; Path = $_.Name}} | Sort-Object -Property $sortProperty -Descending:$sortDescending return } Set-ZLocation -match $match } Register-PromptHook Set-Alias -Name z -Value Invoke-ZLocation Export-ModuleMember -Function @('Invoke-ZLocation', 'Set-ZLocation', 'Get-ZLocation', 'Pop-ZLocation', 'Remove-ZLocation') -Alias z # export this function to make it accessible from prompt Export-ModuleMember -Function Update-ZLocation |