Skip to content

Instantly share code, notes, and snippets.

@jhoneill
Last active October 6, 2023 20:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jhoneill/47f5151b22a1dabb4ddc79c083162f77 to your computer and use it in GitHub Desktop.
Save jhoneill/47f5151b22a1dabb4ddc79c083162f77 to your computer and use it in GitHub Desktop.
PowerShell to make a nicer CD.. Just add to profile.
#Profile addition to redefine cd as a proxy for push location (and cd- for Pop).
# cd ... is transformed into into cd ..\.. (extra dot is an extra level),
# cd ^ is transformed into cd <<script i.e. profile>> directory
# cd \*doc is transformed into cd \*\Doc*\
# cd = is transformed into an item from the stack. = is the first each extra = goes one deeper in the stack
# cd - will not tab expand but will pop an item from the location stack. 3 or more - will do an extra pop.
# -- means "all the rest are a strings", so two levels needs -- --
# cd ~ Now supports tab expansion
# cd ~~ Tab completes "Special" folders (e.g. MyDocuments, Desktop, ProgramFiles)
# cd ~~Name\ Transorms to or tab completes the special folder
# cd\ & cd.. (without space) do push instead of set and cd~ (no space) has been added
# cd HK[tab] will expand to HKCU: and HKLM: and similarly for other drives.
Remove-Item -Path Alias:\cd -ErrorAction SilentlyContinue
class PathTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute {
[object] Transform([System.Management.Automation.EngineIntrinsics]$EngineIntrinsics, [object] $InputData) {
switch -regex ($InputData) {
"^=+" {return ($inputData -replace "^=+", (Get-Location -Stack).ToArray()[$Matches[0].Length -1].Path); break}
"^\^" {return ($inputData -replace "^\^", $PSScriptRoot) ; break }
"^\\\*|^/\*" {return ($pwd.path -replace "^(.*$($InputData.substring(2)).*?)[/\\].*$",'$1') ; break }
"^\.{3}" {return ($InputData -replace "(?<=^\.[.\\]*)(?=\.{2,}(\\|$))", ".\") ; break}
"^~~[a-z]+" {try {return ($InputData -replace "~~\w+", ( [System.Environment]::GetFolderPath(($InputData -replace "~~([a-z]+).*$",'$1'))) )} catch {};break}
}
return ($InputData)
}
}
class ValidatePathAttribute : System.Management.Automation.ValidateArgumentsAttribute {
[string]$Exemption = "" #"^-+$"
[boolean]$ContainersOnly = $false
[int]$MaxItems = -1
[void] Validate([object] $arguments , [System.Management.Automation.EngineIntrinsics]$EngineIntrinsics) {
if ($this.Exemption -and $arguments -match $this.Exemption) {return} #Exempt some things eg "-"" or "----""
elseif ($arguments -match "^(\w+):\\?" -and (Get-PSDrive $Matches[1] -ErrorAction SilentlyContinue) ) {return} #Allow drives
else {
if ($this.ContainersOnly) {$count = (Get-Item -Path $arguments -ErrorAction SilentlyContinue).where({$_.psIscontainer}).count}
else {$count = (Get-Item -Path $arguments -ErrorAction SilentlyContinue).count }
if ($count -eq 0 -and $this.maxitems -ge 0) {
throw [System.Management.Automation.ValidationMetadataException]::new("'$arguments' does not exist.")
}
elseif ($this.Maxitems -ge 0 -and $count -gt $this.maxitems) {
throw [System.Management.Automation.ValidationMetadataException]::new("'$arguments' resolved to multiple $count items. Maximum allowed is $($this.Maxitems)")
}
}
return
}
}
class PathCompleter : System.Management.Automation.IArgumentCompleter {
[System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument(
[string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
[System.Management.Automation.Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
)
{
$results = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()
$dots = [regex]"^\.\.(\.*)(\\|$|/)" #find two dots, any more dots (captured), followed by / \ or end of string
$sep = [system.io.path]::DirectorySeparatorChar
$wtc = ""
switch -regex ($wordToComplete) {
#.. alone doesn't expand, expand .. followed by n dots (and possibly \ or /) to ..\ n+1 times
$dots {$newPath = "..$Sep" * (1 + $dots.Matches($wordToComplete)[0].Groups[1].Length)
$wtc = $dots.Replace($wordtocomplete,$newPath) ; break }
"^\^$" {$wtc = $PSScriptRoot ; break } # ^ [tab] ==> PS profile dir
"^\.$" {$wtc = "" ; break } # . and ~ alone don't expand.
"^~$" {$wtc = $env:USERPROFILE ; break }
#for 1 = sign tab through the location stack.
"^=$" { foreach ($stackPath in (Get-Location -Stack).ToArray().Path) {
if ($stackpath -match "[ ']") {$stackpath = '"' + $stackPath + '"'}
$results.Add([System.Management.Automation.CompletionResult]::new($stackPath))
}
return $results ; continue
}
#replace string of = signs with the item that many up the location stack
"^=+$" {$wtc = (Get-Location -Stack).ToArray()[$wordToComplete.Length -1].Path ; continue }
#if path is c:\here\there\everywhere\stuff convert "\*the" to "c:\here\there"
"^\\\*|^/\*" {$wtc = $pwd.path -replace "^(.*$($WordToComplete.substring(2)).*?)[/\\].*$",'$1' ; continue }
"^~~[a-z]+[\\/]" {
try {$wtc = [System.Environment]::GetFolderPath(($wordToComplete -replace "~~([a-z]+).*$",'$1')) + ($wordToComplete -replace "~~[a-z]+(.*$)",'$1') }
catch {}
break
}
"^~~" { [enum]::GetNames([System.Environment+SpecialFolder]) |
Where-Object {$_ -match "^\w+$" -and [System.Environment]::GetFolderPath($_) -and $_ -like "$($wordToComplete.substring(2))*" } |
Sort-Object |
ForEach-Object {$results.Add([System.Management.Automation.CompletionResult]::new("~~$_")) }
return $results ; continue
}
default {$wtc = $wordToComplete}
}
foreach ($result in [System.Management.Automation.CompletionCompleters]::CompleteFilename($wtc) ) {
if ($result.resultType -eq "ProviderContainer" -or $CommandName -notin @("cd","dir")) {$results.Add($result)}
}
foreach ($result in $Global:ExecutionContext.SessionState.Drive.GetAll().name -like "$wordTocomplete*") {
$results.Add([System.Management.Automation.CompletionResult]::new("$result`:"))
}
return $results
}
}
function cd {
<#
.ForwardHelpTargetName Microsoft.PowerShell.Management\Push-Location
.ForwardHelpCategory Cmdlet
#>
[CmdletBinding(DefaultParameterSetName='Path')]
param(
[Parameter(ParameterSetName='Path', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[PathTransform()]
[ArgumentCompleter([PathCompleter])]
[ValidatePath(Exemption="^-+$",ContainersOnly=$true,MaxItems=1)]
[string]$Path,
[Parameter(ParameterSetName='LiteralPath', ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath','LP')]
[string]$LiteralPath,
[switch]$PassThru,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[string]$StackName
)
process {
if ($Path -match "^-+$") {foreach ($i in (1..$Path.Length)) {Pop-Location }}
elseif ($Path -or $LiteralPath) {Push-Location @PSBoundParameters }
}
}
Set-Alias -Name "cd-" -Value Pop-Location
Function cd.. {Push-Location -Path ..}
Function cd\ {Push-Location -Path \ }
Function cd~ {Push-Location -Path ~}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment