Private/Console/Write-SpectreTable.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Write-SpectreTable { <# .SYNOPSIS Renders a themed table using Spectre.Console when available, falling back to box-drawing characters. .PARAMETER Title Optional table title. .PARAMETER Columns Array of column definition hashtables: @{ Name = 'Col'; Color = 'Olive'; Alignment = 'Left' } .PARAMETER Rows Array of row arrays. Each row is an array of cell values (strings). .PARAMETER RowColors Optional array of guerrilla color names, one per row. Colors the entire row. .PARAMETER BorderColor Guerrilla color name for the table border. Default: 'Dim'. .PARAMETER HideBorder Suppress the table border entirely. #> [CmdletBinding()] param( [string]$Title, [Parameter(Mandatory)] [hashtable[]]$Columns, [Parameter(Mandatory)] [array[]]$Rows, [string[]]$RowColors, [string]$BorderColor = 'Dim', [switch]$HideBorder ) if ($script:HasSpectre) { # Fall back to the box-drawing renderer if the Spectre call path fails (e.g. a # future Spectre.Console version moves/renames a method) instead of spamming errors. try { Write-SpectreTableEnhanced @PSBoundParameters } catch { Write-Verbose "Spectre table failed, using text fallback: $_" Write-SpectreTableFallback @PSBoundParameters } } else { Write-SpectreTableFallback @PSBoundParameters } } function Write-SpectreTableEnhanced { [CmdletBinding()] param( [string]$Title, [hashtable[]]$Columns, [array[]]$Rows, [string[]]$RowColors, [string]$BorderColor = 'Dim', [switch]$HideBorder ) $table = [Spectre.Console.Table]::new() if ($HideBorder) { $table.Border = [Spectre.Console.TableBorder]::None } else { $table.Border = [Spectre.Console.TableBorder]::Rounded # BorderColor is an extension method that isn't callable from PowerShell (and isn't # in TableExtensions in current Spectre.Console) — set the BorderStyle directly. $table.BorderStyle = [Spectre.Console.Style]::new($script:SpectreColors[$BorderColor] ?? $script:SpectreColors.Dim) } if ($Title) { $titleMarkup = "[bold $($script:SpectreColors.Olive.ToMarkup())]$([Spectre.Console.Markup]::Escape($Title))[/]" $table.Title = [Spectre.Console.TableTitle]::new($titleMarkup) } foreach ($col in $Columns) { $colColor = $script:SpectreColors[$col.Color] ?? $script:SpectreColors.Olive $colMarkup = "[$($colColor.ToMarkup()) bold]$([Spectre.Console.Markup]::Escape($col.Name))[/]" $tableCol = [Spectre.Console.TableColumn]::new($colMarkup) if ($col.Alignment -eq 'Right') { $tableCol.Alignment = [Spectre.Console.Justify]::Right } elseif ($col.Alignment -eq 'Center') { $tableCol.Alignment = [Spectre.Console.Justify]::Center } $table.AddColumn($tableCol) } for ($i = 0; $i -lt $Rows.Count; $i++) { $row = $Rows[$i] $rowColor = if ($RowColors -and $i -lt $RowColors.Count -and $RowColors[$i]) { $script:SpectreColors[$RowColors[$i]] ?? $script:SpectreColors.Parchment } else { $script:SpectreColors.Parchment } $cells = @() for ($j = 0; $j -lt $row.Count; $j++) { $cellText = [Spectre.Console.Markup]::Escape([string]$row[$j]) # Use column color for first column, row color for data if ($j -eq 0) { $colColor = $script:SpectreColors[$Columns[$j].Color] ?? $script:SpectreColors.Olive $cells += [Spectre.Console.Markup]::new("[$($colColor.ToMarkup())]$cellText[/]") } else { $cells += [Spectre.Console.Markup]::new("[$($rowColor.ToMarkup())]$cellText[/]") } } # AddRow is a C# extension method — call it on the static extension class and pass # the cells as a typed IRenderable[] so the overload resolves. [void][Spectre.Console.TableExtensions]::AddRow($table, [Spectre.Console.Rendering.IRenderable[]]$cells) } [Spectre.Console.AnsiConsole]::Write($table) } function Write-SpectreTableFallback { [CmdletBinding()] param( [string]$Title, [hashtable[]]$Columns, [array[]]$Rows, [string[]]$RowColors, [string]$BorderColor = 'Dim', [switch]$HideBorder ) # Calculate column widths $widths = @() for ($j = 0; $j -lt $Columns.Count; $j++) { $maxWidth = $Columns[$j].Name.Length foreach ($row in $Rows) { if ($j -lt $row.Count) { $cellLen = ([string]$row[$j]).Length if ($cellLen -gt $maxWidth) { $maxWidth = $cellLen } } } $widths += [Math]::Min($maxWidth, 50) } if (-not $HideBorder) { # Top border $topLine = ' ' + [char]0x250C # ┌ for ($j = 0; $j -lt $widths.Count; $j++) { $topLine += [string]::new([char]0x2500, $widths[$j] + 2) # ─ $topLine += if ($j -lt $widths.Count - 1) { [char]0x252C } else { [char]0x2510 } # ┬ or ┐ } Write-GuerrillaText $topLine -Color $BorderColor } # Header row $headerLine = ' ' + $(if (-not $HideBorder) { [char]0x2502 + ' ' } else { ' ' }) for ($j = 0; $j -lt $Columns.Count; $j++) { $padded = if ($Columns[$j].Alignment -eq 'Right') { $Columns[$j].Name.PadLeft($widths[$j]) } else { $Columns[$j].Name.PadRight($widths[$j]) } Write-GuerrillaText $headerLine -Color $BorderColor -NoNewline Write-GuerrillaText $padded -Color ($Columns[$j].Color ?? 'Olive') -NoNewline $headerLine = if (-not $HideBorder) { ' ' + [char]0x2502 + ' ' } else { ' ' } } if (-not $HideBorder) { Write-GuerrillaText " $([char]0x2502)" -Color $BorderColor } else { Write-Host '' } if (-not $HideBorder) { # Separator $sepLine = ' ' + [char]0x251C # ├ for ($j = 0; $j -lt $widths.Count; $j++) { $sepLine += [string]::new([char]0x2500, $widths[$j] + 2) # ─ $sepLine += if ($j -lt $widths.Count - 1) { [char]0x253C } else { [char]0x2524 } # ┼ or ┤ } Write-GuerrillaText $sepLine -Color $BorderColor } # Data rows for ($i = 0; $i -lt $Rows.Count; $i++) { $row = $Rows[$i] $rowColor = if ($RowColors -and $i -lt $RowColors.Count -and $RowColors[$i]) { $RowColors[$i] } else { 'Parchment' } $prefix = ' ' + $(if (-not $HideBorder) { [char]0x2502 + ' ' } else { ' ' }) for ($j = 0; $j -lt $Columns.Count; $j++) { $cellText = if ($j -lt $row.Count) { [string]$row[$j] } else { '' } $padded = if ($Columns[$j].Alignment -eq 'Right') { $cellText.PadLeft($widths[$j]) } else { $cellText.PadRight($widths[$j]) } # Truncate if too long if ($padded.Length -gt $widths[$j]) { $padded = $padded.Substring(0, $widths[$j] - 3) + '...' } Write-GuerrillaText $prefix -Color $BorderColor -NoNewline $cellColor = if ($j -eq 0) { $Columns[$j].Color ?? 'Olive' } else { $rowColor } Write-GuerrillaText $padded -Color $cellColor -NoNewline $prefix = if (-not $HideBorder) { ' ' + [char]0x2502 + ' ' } else { ' ' } } if (-not $HideBorder) { Write-GuerrillaText " $([char]0x2502)" -Color $BorderColor } else { Write-Host '' } } if (-not $HideBorder) { # Bottom border $botLine = ' ' + [char]0x2514 # └ for ($j = 0; $j -lt $widths.Count; $j++) { $botLine += [string]::new([char]0x2500, $widths[$j] + 2) # ─ $botLine += if ($j -lt $widths.Count - 1) { [char]0x2534 } else { [char]0x2518 } # ┴ or ┘ } Write-GuerrillaText $botLine -Color $BorderColor } } |