core/powershell/utils.ps1

Add-Member -InputObject $PSCompletions -MemberType ScriptMethod ConvertFrom_JsonToHashtable {
    param([string]$json)
    # Handle json string
    $matches = [regex]::Matches($json, '\s*"\s*"\s*:')
    foreach ($match in $matches) {
        $json = $json -replace $match.Value, "`"empty_key_$([System.Guid]::NewGuid().Guid)`":"
    }
    $json = [regex]::Replace($json, ",`n?(\s*`n)?\}", "}")
    function ConvertToHashtable {
        param($obj)
        $hash = @{}
        if ($obj -is [System.Management.Automation.PSCustomObject]) {
            foreach ($_ in $obj | Get-Member -MemberType Properties) {
                $k = $_.Name # Key
                $v = $obj.$k # Value
                if ($v -is [System.Collections.IEnumerable] -and $v -isnot [string]) {
                    # Handle array
                    $arr = @()
                    foreach ($item in $v) {
                        $arr += if ($item -is [System.Management.Automation.PSCustomObject]) { ConvertToHashtable($item) }else { $item }
                    }
                    $hash[$k] = $arr
                }
                elseif ($v -is [System.Management.Automation.PSCustomObject]) {
                    # Handle object
                    $hash[$k] = ConvertToHashtable($v)
                }
                else { $hash[$k] = $v }
            }
        }
        else { $hash = $obj }
        $hash
    }
    # Recurse
    ConvertToHashtable ($json | ConvertFrom-Json)
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod start_job {
    $PSCompletions.job = Start-Job -ScriptBlock {
        param($PSCompletions)

        $null = Start-Job -ScriptBlock {
            param($PSCompletions)
            function convert_from_json_to_hashtable {
                param(
                    [Parameter(ValueFromPipeline = $true)]
                    [string]$json
                )
                $matches = [regex]::Matches($json, '\s*"\s*"\s*:')
                foreach ($match in $matches) {
                    $json = $json -replace $match.Value, "`"empty_key_$([System.Guid]::NewGuid().Guid)`":"
                }
                $json = [regex]::Replace($json, ",`n?(\s*`n)?\}", "}")
                function ConvertToHashtable {
                    param($obj)
                    $hash = @{}
                    if ($obj -is [System.Management.Automation.PSCustomObject]) {
                        foreach ($_ in $obj | Get-Member -MemberType Properties) {
                            $k = $_.Name # Key
                            $v = $obj.$k # Value
                            if ($v -is [System.Collections.IEnumerable] -and $v -isnot [string]) {
                                # Handle array
                                $arr = @()
                                foreach ($item in $v) {
                                    $arr += if ($item -is [System.Management.Automation.PSCustomObject]) { ConvertToHashtable($item) }else { $item }
                                }
                                $hash[$k] = $arr
                            }
                            elseif ($v -is [System.Management.Automation.PSCustomObject]) {
                                # Handle object
                                $hash[$k] = ConvertToHashtable($v)
                            }
                            else { $hash[$k] = $v }
                        }
                    }
                    else { $hash = $obj }
                    $hash
                }
                # Recurse
                ConvertToHashtable ($json | ConvertFrom-Json)
            }
            function get_raw_content {
                param ([string]$path, [bool]$trim = $true)
                $res = Get-Content $path -Raw -Encoding utf8 -ErrorAction SilentlyContinue
                if ($res) {
                    if ($trim) { return $res.Trim() }
                    return $res
                }
                return ''
            }
            function set_config {
                param ([string]$k, [string]$v)
                $c = get_raw_content $PScompletions.path.config | convert_from_json_to_hashtable
                $c.$k = $v
                $c | ConvertTo-Json -Depth 100 -Compress | Out-File $PScompletions.path.config -Encoding utf8 -Force
            }
            function download_list {
                if (!(Test-Path $PScompletions.path.completions_json)) {
                    @{ list = @('psc') } | ConvertTo-Json -Compress | Out-File $PScompletions.path.completions_json -Encoding utf8 -Force
                }
                $current_list = (get_raw_content $PScompletions.path.completions_json | ConvertFrom-Json).list
                if ($PScompletions.url) {
                    try {
                        $content = (Invoke-WebRequest -Uri "$($PScompletions.url)/completions.json").Content | ConvertFrom-Json
                        $remote_list = $content.list

                        $diff = Compare-Object $remote_list $current_list -PassThru
                        if ($diff) {
                            $diff | Out-File $PScompletions.path.change -Force -Encoding utf8
                            $content | ConvertTo-Json -Depth 100 -Compress | Out-File $PScompletions.path.completions_json -Encoding utf8 -Force
                        }
                        else {
                            Clear-Content $PScompletions.path.change -Force
                        }
                    }
                    catch {}
                }
            }

            download_list

            # ensure completion config
            foreach ($_ in $PSCompletions.cmd.Keys) {
                $path = "$($PSCompletions.path.completions)/$($_)/config.json"
                $json = get_raw_content $path | ConvertFrom-Json
                $path = "$($PSCompletions.path.completions)/$($_)/language/$($json.language[0]).json"
                $json = get_raw_content $path | convert_from_json_to_hashtable
                if ($json.config) {
                    foreach ($item in $json.config) {
                        if ($PSCompletions.config.comp_config.$_.$($item.name) -in @('', $null)) {
                            $PSCompletions.config.comp_config.$_.$($item.name) = $item.value
                            $need_update_config = $true
                        }
                    }
                }
            }
            if ($need_update_config) { $PSCompletions.config | ConvertTo-Json -Depth 100 -Compress | Out-File $PSCompletions.path.config -Encoding utf8 -Force }

            # check version
            try {
                if ($PSCompletions.config.module_update -eq 1) {
                    $response = Invoke-WebRequest -Uri "$($PSCompletions.url)/module/version.txt"
                    $content = $response.Content.Trim()
                    $versions = @($PSCompletions.version, $content) | Sort-Object { [Version] $_ }
                    if ($versions[-1] -ne $PSCompletions.version) {
                        set_config 'module_update' $versions[-1]
                    }
                }
            }
            catch {}

            # check update
            if ($PSCompletions.config.update -eq 1) {
                $update_list = [System.Collections.Generic.List[string]]@()
                foreach ($_ in Get-ChildItem $PSCompletions.path.completions -ErrorAction SilentlyContinue | Where-Object { $_.Name -in $PSCompletions.list }) {
                    try {
                        $response = Invoke-WebRequest -Uri "$($PSCompletions.url)/completions/$($_.Name)/guid.txt"
                        $content = $response.Content.Trim()
                        $guid = get_raw_content "$($PSCompletions.path.completions)/$($_.Name)/guid.txt"
                        if ($guid -ne $content) { $update_list.Add($_.Name) }
                    }
                    catch {}
                }
                if ($update_list) { $update_list | Out-File $PSCompletions.path.update -Force -Encoding utf8 }
                else { Clear-Content $PSCompletions.path.update -Force }
            }

        } -ArgumentList $PSCompletions

        $PSCompletions.wc = New-Object System.Net.WebClient
        function convert_from_json_to_hashtable {
            param(
                [Parameter(ValueFromPipeline = $true)]
                [string]$json
            )
            $matches = [regex]::Matches($json, '\s*"\s*"\s*:')
            foreach ($match in $matches) {
                $json = $json -replace $match.Value, "`"empty_key_$([System.Guid]::NewGuid().Guid)`":"
            }
            $json = [regex]::Replace($json, ",`n?(\s*`n)?\}", "}")
            function ConvertToHashtable {
                param($obj)
                $hash = @{}
                if ($obj -is [System.Management.Automation.PSCustomObject]) {
                    foreach ($_ in $obj | Get-Member -MemberType Properties) {
                        $k = $_.Name # Key
                        $v = $obj.$k # Value
                        if ($v -is [System.Collections.IEnumerable] -and $v -isnot [string]) {
                            # Handle array
                            $arr = @()
                            foreach ($item in $v) {
                                $arr += if ($item -is [System.Management.Automation.PSCustomObject]) { ConvertToHashtable($item) }else { $item }
                            }
                            $hash[$k] = $arr
                        }
                        elseif ($v -is [System.Management.Automation.PSCustomObject]) {
                            # Handle object
                            $hash[$k] = ConvertToHashtable($v)
                        }
                        else { $hash[$k] = $v }
                    }
                }
                else { $hash = $obj }
                $hash
            }
            # Recurse
            ConvertToHashtable ($json | ConvertFrom-Json)
        }
        function get_raw_content {
            param ([string]$path, [bool]$trim = $true)
            $res = Get-Content $path -Raw -Encoding utf8 -ErrorAction SilentlyContinue
            if ($res) {
                if ($trim) { return $res.Trim() }
                return $res
            }
            return ''
        }
        function get_language {
            param ([string]$completion)
            $path_config = "$($PSCompletions.path.completions)/$($completion)/config.json"
            if (!(Test-Path $path_config) -or !( get_raw_content $path_config)) {
                try {
                    $PSCompletions.wc.DownloadFile("$($PSCompletions.url)/completions/$($completion)/config.json", $path_config)
                }
                catch {}
            }
            $content_config = (get_raw_content $path_config) | ConvertFrom-Json
            if ($PSCompletions.config.comp_config.$completion -and $PSCompletions.config.comp_config.$completion.language) {
                $config_language = $PSCompletions.config.comp_config.$completion.language
            }
            else {
                $config_language = $null
            }
            if ($config_language) {
                $language = if ($config_language -in $content_config.language) { $config_language }else { $content_config.language[0] }
            }
            else {
                $language = if ($PSCompletions.language -in $content_config.language) { $PSCompletions.language }else { $content_config.language[0] }
            }
            return $language
        }

        $completion_datas = @{}
        Get-ChildItem $PSCompletions.path.completions -Include "order.json" -File -Recurse | Where-Object {
            $_.LastWriteTime -gt (Get-Date).AddMonths(-1)
        } | ForEach-Object {
            $cmd = Split-Path (Split-Path $_.FullName -Parent) -Leaf
            if ($cmd -in $PSCompletions.cmd.Keys) {
                $language = get_language $cmd
                $path_language = "$($PSCompletions.path.completions)/$($cmd)/language/$($language).json"
                if (Test-Path $path_language) {
                    $completion_datas.$cmd = (get_raw_content $path_language) | convert_from_json_to_hashtable
                }
                else {
                    try {
                        $PSCompletions.wc.DownloadFile("$($PSCompletions.url)/completions/$($cmd)/language/$($language).json", $path_language)
                        $completion_datas.$cmd = (get_raw_content $path_language) | convert_from_json_to_hashtable
                    }
                    catch {}
                }
            }
        }
        return $completion_datas
    } -ArgumentList $PSCompletions
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod order_job {
    param($completions, $history_path, $root, $path_order)
    $PSCompletions.order."$($root)_job" = Start-Job -ScriptBlock {
        param($PScompletions, $completions, $path_history, $root, $path_order)
        $order = [ordered]@{}
        $index = 1
        foreach ($_ in $completions) {
            $order.$($_.name -join ' ') = $index
            $index++
        }
        $historys = [System.Collections.Generic.List[string]]@()
        foreach ($_ in Get-Content $path_history -Encoding utf8 -ErrorAction SilentlyContinue) {
            foreach ($alias in $PSCompletions.cmd.$root) {
                if ($_ -match "^[^\S\n]*$($alias)\s+.+") {
                    $historys.Add($_)
                    break
                }
            }
        }
        $index = -1
        function handle_order {
            param([array]$history)
            $str = $history -join ' '
            if ($str -in $order.Keys) {
                $order.$str = $index
            }
            if ($history.Count -eq 1) {
                return
            }
            else {
                handle_order $history[0..($history.Count - 2)]
            }
        }
        foreach ($_ in $historys) {
            $matches = [regex]::Matches($_, "(?:`"[^`"]*`"|'[^']*'|\S)+")
            $cmd = [System.Collections.Generic.List[string]]@()
            foreach ($m in $matches) { $cmd.Add($m.Value) }
            if ($cmd.Count -gt 1) {
                handle_order $cmd[1..($cmd.Count - 1)]
                $index--
            }
        }
        $json = $order | ConvertTo-Json -Depth 100 -Compress
        $matches = [regex]::Matches($json, '\s*"\s*"\s*:')
        foreach ($match in $matches) {
            $json = $json -replace $match.Value, "`"empty_key_$([System.Guid]::NewGuid().Guid)`":"
        }
        $json | Out-File $path_order -Encoding utf8 -Force
        return $order
    } -ArgumentList $PScompletions, $completions, $history_path, $root, $path_order
}