ZLocation.Search.psm1

Set-StrictMode -Version Latest

function Find-Matches([hashtable]$hash, [string[]]$query)
{
    $hash = $hash.Clone()
    foreach ($key in ($hash.GetEnumerator() | %{$_.Name})) 
    {
        if (-not (Test-FuzzyMatch $key $query)) 
        {
            $hash.Remove($key)
        }
    }
    $res = $hash.GetEnumerator() | sort -Property Value -Descending
    if ($res) {
        $res | %{$_.Name}
    }
}

function Test-FuzzyMatch([string]$path, [string[]]$query)
{
    function contains([string]$left, [string]$right) {
        return [bool]($left.IndexOf($right, [System.StringComparison]::OrdinalIgnoreCase) -ge 0)
    }

    if ($query -eq $null) {
        return $true
    }
    $n = $query.Length
    if ($n -eq 0)
    {
        # empty query match to everything
        return $true
    }

    for ($i=0; $i -lt $n-1; $i++)
    {
        if (-not (contains -left $path -right $query[$i]))
        {
            return $false
        }
    }   
    
    # after tab expansion, we get desired full path as a last query element.
    # tab expansion can come from our code, then it will represent the full path.
    # It also can come from the standard tab expansion (when our doesn't return anything), which is file system based.
    # It can produce relative paths.

    $rootQuery = $query[$n-1]

    if (-not [System.IO.Path]::IsPathRooted($rootQuery)) {
        # handle '..\foo' case
        $rootQueryCandidate = Join-Path $pwd $query[$n-1]
        if (Test-Path $rootQueryCandidate) {
            $rootQuery = (Resolve-Path $rootQueryCandidate).Path
        }
    }
    
    if ([System.IO.Path]::IsPathRooted($rootQuery)) 
    {
        # doing a tweak to handle 'C:' and 'C:\' cases correctly.
        if (($rootQuery.Length) -eq 2 -and ($rootQuery[-1] -eq ':'))
        {
            $rootQuery = $rootQuery + "\"
        }
        # doing a tweaks to handle 'C:\foo' and 'C:\foo\' cases correctly.
        elseif ($rootQuery[-1] -eq '\')
        {
            $rootQuery = $rootQuery.Substring(0, $rootQuery.Length-1)
        }
        return $path -eq $rootQuery
    }

    $leaf = Split-Path -Leaf $path
    return (contains -left $leaf -right $query[$n-1]) 
}

Export-ModuleMember -Function Find-Matches