Private/Write-SLReport.ps1

# Prints the human-readable audit report. Plain ASCII so it renders the same
# in Windows PowerShell 5.1, PowerShell 7, and a CI log.

function Write-SLReport {
    param(
        [Parameter(Mandatory)] $Standard,
        [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Results,
        # Test-SecondLine -RepoPath: repo checks (Get-SLRepoCheck) shown after the tools.
        [object[]] $Repos,
        [string] $RepoPath
    )

    $colors = @{ OK = 'Green'; MISMATCH = 'Red'; MISSING = 'Red'; UNKNOWN = 'Yellow' }

    $title = $Standard.Name
    if ($Standard.Version) { $title = "$title v$($Standard.Version)" }

    Write-Host ''
    Write-Host " SecondLine - $title" -ForegroundColor White
    Write-Host " $($Standard.Path)" -ForegroundColor DarkGray
    Write-Host ''

    $nameWidth = 4
    $foundWidth = 8
    foreach ($r in $Results) {
        if ($r.Name.Length -gt $nameWidth) { $nameWidth = $r.Name.Length }
        if ("$($r.Found)".Length -gt $foundWidth) { $foundWidth = "$($r.Found)".Length }
    }

    foreach ($r in $Results) {
        $found = $r.Found
        if (-not $found) { $found = '-' }
        $line = ' {0,-9} {1} {2} expect {3} ({4})' -f $r.Status, $r.Name.PadRight($nameWidth), $found.PadRight($foundWidth), $r.Expect, $r.Rule
        Write-Host $line -ForegroundColor $colors[$r.Status]
        if ($r.Note) {
            Write-Host (' ' + $r.Note) -ForegroundColor DarkGray
        }
        if ($r.Status -ne 'OK' -and $r.Install) {
            Write-Host (' fix: ' + $r.Install) -ForegroundColor DarkGray
        }
    }

    $ok = @($Results | Where-Object { $_.Status -eq 'OK' }).Count
    $summaryColor = 'Green'
    if ($ok -ne $Results.Count) { $summaryColor = 'Yellow' }
    $summary = "{0} of {1} tools OK" -f $ok, $Results.Count

    if ($PSBoundParameters.ContainsKey('Repos')) {
        if ($Results.Count -gt 0) { Write-Host '' }
        Write-Host " Repos: line endings ($RepoPath)" -ForegroundColor White
        Write-SLRepoLines -Checks $Repos
        $repoOk = @($Repos | Where-Object { Test-SLRepoOk $_ }).Count
        if ($repoOk -ne @($Repos).Count) { $summaryColor = 'Yellow' }
        $repoSummary = '{0} of {1} repos OK' -f $repoOk, @($Repos).Count
        if ($Results.Count -gt 0) { $summary = "$summary, $repoSummary" } else { $summary = $repoSummary }
    }

    Write-Host ''
    Write-Host " $summary" -ForegroundColor $summaryColor
    Write-Host ''
}

# Groups 'pattern attributes' strings by their attributes, so a long list of
# missing rules reads as a few lines: 'text eol=lf: *.cs *.css *.js'.
function Format-SLRuleGroups {
    param([string[]] $Rules)
    $groups = New-Object System.Collections.Specialized.OrderedDictionary
    foreach ($r in $Rules) {
        $parts = $r -split ' ', 2
        $attrs = ''
        if ($parts.Count -gt 1) { $attrs = $parts[1] }
        if (-not $groups.Contains($attrs)) { $groups[$attrs] = New-Object System.Collections.Generic.List[string] }
        $groups[$attrs].Add($parts[0])
    }
    foreach ($attrs in $groups.Keys) {
        $line = "${attrs}:"
        foreach ($p in $groups[$attrs]) {
            if ($line.Length + $p.Length -gt 90) { "$line"; $line = (' ' * ($attrs.Length + 1)) }
            $line += " $p"
        }
        $line
    }
}

# Test-SLRepo's report.
function Write-SLRepoReport {
    param(
        [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Checks,
        [Parameter(Mandatory)] [string] $Path,
        $Standard
    )
    $source = 'built-in profiles'
    if ($Standard) { $source = "profiles: built-in + $($Standard.Path)" }

    Write-Host ''
    Write-Host ' SecondLine - repo line endings' -ForegroundColor White
    Write-Host " $Path ($source)" -ForegroundColor DarkGray

    Write-SLRepoLines -Checks $Checks

    $ok = @($Checks | Where-Object { Test-SLRepoOk $_ }).Count
    $color = 'Green'
    if ($ok -ne $Checks.Count) { $color = 'Yellow' }
    Write-Host ''
    Write-Host (' {0} of {1} repos OK' -f $ok, $Checks.Count) -ForegroundColor $color
    Write-Host ''
}

# One line per repo (status, name, profile and why, files to renormalize), then
# what's wrong and how to fix it. Shared by both reports.
function Write-SLRepoLines {
    param([AllowEmptyCollection()] [object[]] $Checks)
    $colors = @{ MATCHES = 'Green'; EXTENDS = 'Green'; DIFFERS = 'Red'; MISSING = 'Red' }

    $nameWidth = 4
    $profileWidth = 8
    foreach ($c in $Checks) {
        if ($c.Repo.Length -gt $nameWidth) { $nameWidth = $c.Repo.Length }
        $p = "$($c.Profile) ($($c.ProfileReason))"
        if ($p.Length -gt $profileWidth) { $profileWidth = $p.Length }
    }

    Write-Host ''
    foreach ($c in $Checks) {
        $renorm = '-'
        if ($c.FilesToRenormalize -gt 0) { $renorm = "$($c.FilesToRenormalize) to renormalize" }
        $color = $colors[$c.Status]
        if ($color -eq 'Green' -and $c.FilesToRenormalize -gt 0) { $color = 'Yellow' }
        $line = ' {0,-9} {1} {2} {3}' -f $c.Status, $c.Repo.PadRight($nameWidth), "$($c.Profile) ($($c.ProfileReason))".PadRight($profileWidth), $renorm
        Write-Host $line.TrimEnd() -ForegroundColor $color

        $pad = ' '
        if ($c.Status -eq 'MISSING') { Write-Host "${pad}no .gitattributes" -ForegroundColor DarkGray }
        if ($c.Status -ne 'MISSING') {
            foreach ($line in @(Format-SLRuleGroups -Rules $c.MissingRules)) {
                $label = 'missing: '
                if ($line.StartsWith(' ')) { $label = ' ' }   # continuation
                Write-Host "${pad}$label$line" -ForegroundColor DarkGray
            }
        }
        foreach ($r in @($c.ConflictingRules)) { Write-Host "${pad}conflict: $r" -ForegroundColor DarkGray }
        foreach ($r in @($c.ExtraRules)) { Write-Host "${pad}extra: $r" -ForegroundColor DarkGray }
        if ($c.FilesToRenormalize -gt 0) {
            Write-Host "${pad}committed with CRLF or mixed endings:" -ForegroundColor DarkGray
            foreach ($f in @($c.RenormalizeSample)) { Write-Host "${pad} $f" -ForegroundColor DarkGray }
            $more = $c.FilesToRenormalize - @($c.RenormalizeSample).Count
            if ($more -gt 0) { Write-Host "${pad} ... and $more more" -ForegroundColor DarkGray }
        }
        if ($c.Note) { Write-Host "${pad}$($c.Note)" -ForegroundColor DarkGray }
        if (-not (Test-SLRepoOk $c)) {
            Write-Host "${pad}fix: $(Get-SLRepoFixHint $c)" -ForegroundColor DarkGray
        }
    }
}