Utils/dircolors.ps1

Register-GnuUtil @{
    Name         = 'dircolors'
    Category     = 'File and directory operations'
    Synopsis     = 'Color setup for ls.'
    Usage        = @('dircolors [OPTION]... [FILE]')
    Description  = @(
        'Output commands to set the LS_COLORS environment variable.',
        '',
        'Determine format of output:'
    )
    FileOperands = $true
    Options      = @(
        @{ Short = 'b'; Long = 'sh'; Key = 'shell'; Value = 'bourne'; Help = 'output Bourne shell code to set LS_COLORS' }
        @{ Long = 'bourne-shell'; Key = 'shell'; Value = 'bourne'; Help = 'output Bourne shell code to set LS_COLORS' }
        @{ Short = 'c'; Long = 'csh'; Key = 'shell'; Value = 'csh'; Help = 'output C shell code to set LS_COLORS' }
        @{ Long = 'c-shell'; Key = 'shell'; Value = 'csh'; Help = 'output C shell code to set LS_COLORS' }
        @{ Short = 'p'; Long = 'print-database'; Help = 'output defaults' }
    )
    Notes        = @(
        'If FILE is specified, read it to determine which colors to use for which',
        'file types and extensions. Otherwise, a precompiled database is used.',
        'For details on the format of these files, run ''dircolors --print-database''.',
        '',
        'PowerShell note: without -b / -c and without a SHELL variable (the usual case on',
        'Windows), PowerShell code is printed: $env:LS_COLORS = ''...''. Use it with',
        'dircolors | Invoke-Expression. Without a TERM variable, Windows assumes',
        'xterm-256color (Windows Terminal and conhost understand the colors).'
    )
}

$script:GnuDircolorsKeywords = @{
    NORMAL = 'no'; NORM = 'no'; FILE = 'fi'; RESET = 'rs'; DIR = 'di'; LNK = 'ln'; LINK = 'ln'; SYMLINK = 'ln'
    ORPHAN = 'or'; MISSING = 'mi'; FIFO = 'pi'; PIPE = 'pi'; SOCK = 'so'; BLK = 'bd'; BLOCK = 'bd'; CHR = 'cd'
    CHAR = 'cd'; DOOR = 'do'; EXEC = 'ex'; LEFT = 'lc'; LEFTCODE = 'lc'; RIGHT = 'rc'; RIGHTCODE = 'rc'; END = 'ec'
    ENDCODE = 'ec'; SUID = 'su'; SETUID = 'su'; SGID = 'sg'; SETGID = 'sg'; STICKY = 'st'; OTHER_WRITABLE = 'ow'
    OWR = 'ow'; STICKY_OTHER_WRITABLE = 'tw'; OWT = 'tw'; CAPABILITY = 'ca'; MULTIHARDLINK = 'mh'; CLRTOEOL = 'cl'
}



function script:ConvertTo-GnuLsColors {
    <#
    .SYNOPSIS
    The LS_COLORS value of a dircolors database for a terminal type. Returns @{ Value } or @{ Error }.
    #>

    param(
        [Parameter(Mandatory)][AllowEmptyString()][string]$Database,
        [Parameter(Mandatory)][string]$FileName,
        [Parameter(Mandatory)][string]$Term,
        [AllowEmptyString()][string]$ColorTerm = ''
    )

    $builder = New-Object System.Text.StringBuilder
    $state = 'global'
    $number = 0
    $errors = New-Object 'System.Collections.Generic.List[string]'
    foreach ($line in ($Database -split "`r?`n")) {
        $number++
        $match = [regex]::Match($line, '^\s*([^\s#]+)(?:\s+([^\s#]+))?')
        if (-not $match.Success) { continue }
        $keyword = $match.Groups[1].Value
        if (-not $match.Groups[2].Success) { $errors.Add("$($FileName):$($number): invalid line; missing second token"); continue }
        $argument = $match.Groups[2].Value
        $upper = $keyword.ToUpperInvariant()
        if ($upper -eq 'TERM' -or $upper -eq 'COLORTERM') {
            $subject = $Term
            if ($upper -eq 'COLORTERM') { $subject = $ColorTerm }
            if ((New-Object System.Management.Automation.WildcardPattern -ArgumentList $argument, ([System.Management.Automation.WildcardOptions]::None)).IsMatch($subject) -and
                -not ($upper -eq 'COLORTERM' -and [string]::IsNullOrEmpty($subject) -and $argument -ne '*')) {
                $state = 'sure'
            }
            elseif ($state -ne 'sure') { $state = 'no' }
            continue
        }
        if ($state -eq 'sure') { $state = 'yes' }
        if ($state -eq 'no') { continue }
        if ($keyword.StartsWith('.', [System.StringComparison]::Ordinal)) { [void]$builder.Append("*$keyword=${argument}:") }
        elseif ($keyword.StartsWith('*', [System.StringComparison]::Ordinal)) { [void]$builder.Append("$keyword=${argument}:") }
        elseif (@('OPTIONS', 'COLOR', 'EIGHTBIT') -contains $upper) { continue }
        elseif ($script:GnuDircolorsKeywords.ContainsKey($upper)) { [void]$builder.Append("$($script:GnuDircolorsKeywords[$upper])=${argument}:") }
        else { $errors.Add("$($FileName):$($number): unrecognized keyword $keyword") }
    }
    @{ Value = $builder.ToString(); Errors = $errors.ToArray() }
}

function script:Invoke-GnuDircolorsOutput {
    <#
    .SYNOPSIS
    Implementation of dircolors.
    #>

    param([Parameter(Mandatory)][hashtable]$Context)

    $operands = @($Context.Operands)
    $shell = [string](Get-GnuOptionValue -Context $Context -Key 'shell' -Default '')
    if (Test-GnuOption -Context $Context -Key 'print-database') {
        if (-not [string]::IsNullOrEmpty($shell)) {
            Write-GnuUsageError -Context $Context -Message "the options to output dircolors' internal database and`nto select a shell syntax are mutually exclusive"
            return
        }
        if ($operands.Count -gt 0) { Write-GnuUsageError -Context $Context -Message "extra operand '$($operands[0])'`nfile operands cannot be combined with --print-database (-p)"; return }
        ConvertTo-GnuOutputLine -Text ($script:GnuDircolorsDatabase + "`n")
        return
    }
    if ($operands.Count -gt 1) { Write-GnuUsageError -Context $Context -Message "extra operand '$($operands[1])'"; return }

    if ([string]::IsNullOrEmpty($shell)) {
        $shell = 'powershell'
        if (-not [string]::IsNullOrEmpty($env:SHELL)) {
            $shell = 'bourne'
            if ((Get-GnuBaseName -Path $env:SHELL) -match 'csh(\.exe)?$') { $shell = 'csh' }
        }
    }

    $database = $script:GnuDircolorsDatabase
    $fileName = '<internal>'
    if ($operands.Count -eq 1) {
        $database = Get-GnuInputText -Context $Context -Operand $operands[0]
        if ($null -eq $database) { return }
        $fileName = $operands[0]
    }
    $term = $env:TERM
    if ([string]::IsNullOrEmpty($term)) {
        $term = 'none'
        if (Test-GnuWindows) { $term = 'xterm-256color' }
    }
    $result = ConvertTo-GnuLsColors -Database $database -FileName $fileName -Term $term -ColorTerm ([string]$env:COLORTERM)
    foreach ($message in $result.Errors) { Write-GnuError -Context $Context -Message $message }
    $value = $result.Value.Replace("'", "'\''")
    switch ($shell) {
        'bourne' { "LS_COLORS='$value';"; 'export LS_COLORS' }
        'csh' { "setenv LS_COLORS '$value'" }
        default { "`$env:LS_COLORS = '$($result.Value.Replace("'", "''"))'" }
    }
}