src/PSMutation.Output.ps1
|
# The console seam: what a run says, separated from the act of saying it. # # Everything above this file DECIDES output by returning lines. This file EMITS them, and # it holds the module's only Write-Host call -- a rule tests/Layering.Tests.ps1 asserts, so # a second one anywhere in src/ fails rather than quietly bypassing the seam. # # A line carries a ROLE, not a colour. The console renderer maps role to colour; a renderer # for CI annotations or markdown maps the same roles to its own vocabulary, and gets the # structured Data a survivor line carries rather than having to parse the text back out. $script:PSMutationRoleColour = [ordered]@{ Banner = 'Cyan' Good = 'Green' Warn = 'Yellow' Bad = 'Red' Detail = 'Gray' Muted = 'DarkGray' # Distinct from Muted although both print DarkGray. A rule is a separator, so a # renderer that is not a console -- an annotation stream, a markdown table -- drops it # while still wanting the caveats Muted carries. Rule = 'DarkGray' } function Get-PSMutationKnownRole { # Every role a line may carry, sorted. A function rather than a bare constant so a # caller can name the alternatives without reading this file's $script: state. [OutputType([string[]])] [CmdletBinding()] param() return [string[]]@($script:PSMutationRoleColour.Keys | Sort-Object) } function New-PSMutationLine { <# .SYNOPSIS One line of run output: a role, the text, and optionally the record it describes. .DESCRIPTION The only place a line is shaped, so a new caller cannot invent a field or a role. An unknown role THROWS rather than falling back to a default colour. A silently uncoloured line looks like a styling slip; the failure it actually signals is a renderer that will not know what to do with the line at all -- and that surfaces as missing output, in whichever renderer was added last. .PARAMETER Role What kind of line this is. See Get-PSMutationKnownRole. .PARAMETER Text The console rendering. A non-console renderer may ignore it in favour of Data. .PARAMETER Data The record behind the line, when there is one -- a survivor's mutant row, so an annotation renderer has the file, line and description without parsing Text. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Pure factory: returns an object, changes no system state.')] [OutputType([pscustomobject])] [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Role, [Parameter(Mandatory)] [AllowEmptyString()] [string]$Text, $Data ) if (-not $script:PSMutationRoleColour.Contains($Role)) { throw "Unknown output role '$Role'. Valid roles: $((Get-PSMutationKnownRole) -join ', ')." } return [pscustomobject]@{ Role = $Role; Text = $Text; Data = $Data } } function Get-PSMutationRoleColour { # The console colour for a role. Pure, so the mapping is testable on its own. Computed # inline as an argument to Write-Host instead, a colour decision has no seam to assert # against: a comparison that makes every score green is invisible until someone reads # the output and notices 0% is not red. [OutputType([string])] [CmdletBinding()] param([Parameter(Mandatory)] [string]$Role) if (-not $script:PSMutationRoleColour.Contains($Role)) { throw "Unknown output role '$Role'. Valid roles: $((Get-PSMutationKnownRole) -join ', ')." } return [string]$script:PSMutationRoleColour[$Role] } function Write-PSMutationOutput { <# .SYNOPSIS Render lines to the console. The module's only Write-Host site, and the only place -Quiet is honoured. .DESCRIPTION -Quiet lives HERE rather than at each call site. Guarding at the caller means every new emitter has to remember, and a caller that forgets prints in quiet mode while the tests -- which assert on the strings the current callers emit -- stay green for output that did not exist when they were written. .PARAMETER Lines Lines from New-PSMutationLine. An empty collection is valid and emits nothing. .PARAMETER Quiet Suppress the output entirely. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'The single console renderer. Colour-coded progress is this CLI tool intended output, and confining Write-Host to one function is what lets the rule stay enforced everywhere else.')] [CmdletBinding()] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]]$Lines, [switch]$Quiet ) if ($Quiet) { return } foreach ($line in $Lines) { Write-Host $line.Text -ForegroundColor (Get-PSMutationRoleColour -Role $line.Role) } } |