Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.1.0 #26

Merged
merged 15 commits into from
May 6, 2024
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Cody Duong, and contributers
Copyright (c) 2023-2024 Cody Duong, and contributers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
35 changes: 25 additions & 10 deletions alias-tips/Private/Find-AliasCommand.ps1
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
# Attempts to find an alias for a singular command
function Find-AliasCommand {
param(
[Parameter(ValueFromPipeline = $true)]
[Parameter(Mandatory, ValueFromPipeline = $true)]
[string]$Command
)

process {
begin {
if ($AliasTipsHash -and $AliasTipsHash.Count -eq 0) {
$AliasTipsHash = ConvertFrom-StringData -StringData $([System.IO.File]::ReadAllText($AliasTipsHashFile)) -Delimiter "|"
}
}

process {
# If we can find the alias quickly, do so
$Alias = $AliasTipsHash[$Command.Trim()]
if ($Alias) {
if (-not [string]::IsNullOrEmpty($Alias)) {
Write-Verbose "Quickly found alias inside of AliasTipsHash"
return $Alias
return $Alias | Format-Command
}

# TODO check if it is an alias, expand it back out to check if there is a better alias

# We failed to find the alias in the hash, instead get the executed command, and attempt to generate a regex for it.

# First we need to ensure we have generated required regexes
Find-RegexThreadJob
# Generate a regex that searches through our alias hash, and checks if it matches as an alias for our command
$Regex = Get-CommandRegex $Command
# Write-Host $Regex
if ([string]::IsNullOrEmpty($Regex)) {
return ""
}
Expand All @@ -34,24 +41,32 @@ function Find-AliasCommand {
$AliasTipsHash.GetEnumerator() | ForEach-Object {
# Only reasonably evaluate any commands that match the one we are searching for
if ($_.key -match $Regex) {
$Aliases += $_.key
$Aliases += ,($_.Key, $_.Value)
}

# Substitute commands using ExecutionContext if possible
# Check if we have anything that has a $(...)
if ($_.key -match $SimpleSubRegex -and ([boolean](Initialize-EnvVariable "ALIASTIPS_FUNCTION_INTROSPECTION" $false)) -eq $true) {
if ($_.key -match $SimpleSubRegex -and ((Initialize-EnvVariable "ALIASTIPS_FUNCTION_INTROSPECTION" $false) -eq $true)) {
$NewKey = Format-CommandFromExecutionContext($_.value)
if (-not [string]::IsNullOrEmpty($NewKey) -and $($NewKey -replace '\$args', '') -match $Regex) {
$Aliases += $($NewKey -replace '\$args', '').Trim()
$Aliases += ,($($NewKey -replace '\$args', '').Trim(), $_.Value)
$AliasTipsHashEvaluated[$NewKey] = $_.value
}
}
}
Clear-AliasTipsInternalASTResults

Write-Verbose $($Aliases -Join ",")
# Use the longest candiate
$AliasCandidate = ($Aliases | Sort-Object -Descending -Property Length)[0]
# Sort by which alias removes the most, then if they both shorten by same amount, choose the shorter alias
$Aliases = @(@($Aliases
Fixed Show fixed Hide fixed
| Where-Object { $null -ne $_[0] -and $null -ne $_[1] })
| Sort-Object -Property @{Expression = { - ($_[0]).Length } }, @{Expression = { ($_[1]).Length} })
# foreach ($pair in $Aliases) {
# Write-Host "($($pair[0]), $($pair[1]))"
# }
# Use the longest candiate, if tied use shorter alias
Fixed Show fixed Hide fixed
# -- TODO? this is my opinionated way since it results in most coverage (one long alias is better than two combined shorter aliases),
Fixed Show fixed Hide fixed
$AliasCandidate = ($Aliases)[0][0]
Write-Verbose "Alias Candidate Chosen: $AliasCandidate"
$Alias = ""
if (-not [string]::IsNullOrEmpty($AliasCandidate)) {
$Remaining = "$($Command)"
Expand Down
18 changes: 18 additions & 0 deletions alias-tips/Private/Find-RegexThreadJob.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function Find-RegexThreadJob {
if ($null -ne $global:AliasTipsProxyFunctionRegex -and $null -ne $global:AliasTipsProxyFunctionRegexNoArgs) {
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
return
}

$existingJob = Get-Job -Name "FindAliasTipsJob" -ErrorAction SilentlyContinue | Select-Object -Last 1
if ($null -ne $existingJob) {
$existingJob = Wait-Job -Job $existingJob
}
else {
$job = Start-RegexThreadJob

$existingJob = Wait-Job -Job $job
}
$result = Receive-Job -Job $existingJob -Wait -AutoRemoveJob

$global:AliasTipsProxyFunctionRegex, $global:AliasTipsProxyFunctionRegexNoArgs = $result
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
}
23 changes: 21 additions & 2 deletions alias-tips/Private/Get-Aliases.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Return a hashtable of possible aliases
function Get-Aliases {
$Hash = @{}
Find-RegexThreadJob

# generate aliases for commands aliases created via native PowerShell functions
$proxyAliases = Get-Item -Path Function:\
Expand All @@ -11,12 +12,30 @@ function Get-Aliases {
# validate there is a command
if ($ProxyDef -match $AliasTipsProxyFunctionRegex) {
$CleanedCommand = ("$($matches['cmd'].TrimStart()) $($matches['params'])") | Format-Command

Fixed Show fixed Hide fixed
if ($ProxyDef -match '\$args') {
$Hash[$CleanedCommand + ' $args'] = $ProxyName
# Use the shorter of two if we already have hashed this command
if ($Hash.ContainsKey($CleanedCommand + ' $args')) {
if ($ProxyName.Length -lt $Hash[$CleanedCommand + ' $args'].Length) {
$Hash[$CleanedCommand + ' $args'] = $ProxyName
}
}
else {
$Hash[$CleanedCommand + ' $args'] = $ProxyName
}

Fixed Show fixed Hide fixed
}

# quick alias
$Hash[$CleanedCommand] = $ProxyName
# use the shorter of two if we already have hashed this command
if ($Hash.ContainsKey($CleanedCommand)) {
if ($ProxyName.Length -lt $Hash[$CleanedCommand].Length) {
$Hash[$CleanedCommand] = $ProxyName
}
}
else {
$Hash[$CleanedCommand] = $ProxyName
}
}
}

Expand Down
15 changes: 4 additions & 11 deletions alias-tips/Private/Get-CommandRegex.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,19 @@ function Get-CommandRegex {
[OutputType([System.String])]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]${Command},

[Parameter()]
[switch]${Simple}
[string]${Command}
)

process {
if ($Simple) {
$CleanCommand = $Command | Format-Command
return "(" + ([Regex]::Escape($CleanCommand) -split " " -join "|") + ")"
}

# The parse is a bit naive...
if ($Command -match $AliasTipsProxyFunctionRegexNoArgs) {
if ($Command -match $global:AliasTipsProxyFunctionRegexNoArgs) {
Fixed Show fixed Hide fixed
# Clean up the command by removing extra delimiting whitespace and backtick preceding newlines
$CommandString = ("$($matches['cmd'].TrimStart())") | Format-Command
$CommandString = ("$($matches['cmd'].TrimStart())")

if ([string]::IsNullOrEmpty($CommandString)) {
return ""
}
$CommandString = $CommandString | Format-Command

$ReqParams = $($matches['params']) -split " "
$ReqParamRegex = "(" + ($ReqParams.ForEach({
Expand Down
14 changes: 0 additions & 14 deletions alias-tips/Private/Get-CommandsRegex.ps1

This file was deleted.

24 changes: 0 additions & 24 deletions alias-tips/Private/Get-ProxyFunctionRegex.ps1

This file was deleted.

53 changes: 53 additions & 0 deletions alias-tips/Private/Start-RegexThreadJob.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
function Start-RegexThreadJob {
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
$existingJob = Get-Job -Name "FindAliasTipsJob" -ErrorAction SilentlyContinue | Select-Object -Last 1
if ($null -ne $existingJob) {
$existingJob = Wait-Job -Job $existingJob
}

return Start-ThreadJob -Name "FindAliasTipsJob" -ScriptBlock {
function Get-CommandsRegex {
(Get-Command * | ForEach-Object {
$CommandUnsafe = $_ | Select-Object -ExpandProperty 'Name'
$Command = [Regex]::Escape($CommandUnsafe)
# check if it has a file extensions
if ($CommandUnsafe -match "(?<cmd>[^.\s]+)\.(?<ext>[^.\s]+)$") {
$CommandWithoutExtension = [Regex]::Escape($matches['cmd'])
return $Command, $CommandWithoutExtension
}
else {
return $Command
}
}) -Join '|'
}

# The regular expression here roughly follows this pattern:
#
# <begin anchor><whitespace>*<command>(<whitespace><parameter>)*<whitespace>+<$args><whitespace>*<end anchor>
#
# The delimiters inside the parameter list and between some of the elements are non-newline whitespace characters ([^\S\r\n]).
# In those instances, newlines are only allowed if they preceded by a non-newline whitespace character.
#
# Begin anchor (^|[;`n])
# Whitespace (\s*)
# Any Command (?<cmd>)
# Parameters (?<params>(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*)
# $args Anchor (([^\S\r\n]|[^\S\r\n]``\r?\n)+\`$args)
# Whitespace (\s|``\r?\n)*
# End Anchor ($|[|;`n])
function Get-ProxyFunctionRegexes {
Fixed Show fixed Hide fixed
param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline = $true)][regex]${CommandPattern}
)

process {
[regex]"(^|[;`n])(\s*)(?<cmd>($CommandPattern))(?<params>(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*)(([^\S\r\n]|[^\S\r\n]``\r?\n)+\`$args)(\s|``\r?\n)*($|[|;`n])",
[regex]"(^|[;`n])(\s*)(?<cmd>($CommandPattern))(?<params>(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*)(\s|``\r?\n)*($|[|;`n])"
}
}


Get-CommandsRegex | Get-ProxyFunctionRegexes
}
}

Start-RegexThreadJob | Out-Null
Loading