public/formatting/Format-SpectreTable.ps1

using module "..\..\private\completions\Completers.psm1"
using module "..\..\private\completions\Transformers.psm1"

function Format-SpectreTable {
    <#
    .SYNOPSIS
    Formats an array of objects into a Spectre Console table.
 
    .DESCRIPTION
    This function takes an array of objects and formats them into a table using the Spectre Console library. The table can be customized with a border style and color.
    Thanks to [trackd](https://github.com/trackd) and [fmotion1](https://github.com/fmotion1) for the updates to support markdown and color in the table contents.
    See https://spectreconsole.net/widgets/table for more information.
 
    .PARAMETER Property
    Specifies the object properties that appear in the display and the order in which they appear.
    Type one or more property names, separated by commas, or use a hash table to display a calculated property.
    Wildcards are permitted.
    The Property parameter is optional. You can't use the Property and View parameters in the same command.
    The value of the Property parameter can be a new calculated property.
    The calculated property can be a script block or a hash table. Valid key-value pairs are:
    - Name (or Label) `<string>`
    - Expression - `<string>` or `<script block>`
    - FormatString - `<string>`
    - Width - `<int32>` - must be greater than `0`
    - Alignment - value can be `Left`, `Center`, or `Right`
 
    .PARAMETER Data
    The array of objects to be formatted into a table.
    Takes pipeline input.
 
    .PARAMETER Border
    The border style of the table. Default is "Rounded".
 
    .PARAMETER Color
    The color of the table border. Default is the accent color of the script.
 
    .PARAMETER HeaderColor
    The color of the table header text. Default is the DefaultTableHeaderColor.
 
    .PARAMETER TextColor
    The color of the table text. Default is the DefaultTableTextColor.
 
    .PARAMETER Width
    The width of the table.
 
    .PARAMETER HideHeaders
    Hides the headers of the table.
 
    .PARAMETER Title
    The title of the table.
 
    .PARAMETER AllowMarkup
    Allow Spectre markup in the table elements e.g. [green]message[/].
 
    .PARAMETER Wrap
    Displays text that exceeds the column width on the next line. By default, text that exceeds the column width is truncated
    Currently there is a bug with this, spectre.console/issues/1185
 
    .PARAMETER View
    The View parameter lets you specify an alternate format or custom view for the table.
 
    .PARAMETER Expand
    Take up all of the horizontal space available.
 
    .EXAMPLE
    # **Example 1**
    # This example demonstrates how to format an array of objects into a Spectre Console table.
    $data = @(
        [pscustomobject]@{Name="John"; Age=25; City="New York"},
        [pscustomobject]@{Name="Jane"; Age=30; City="Los Angeles"}
    )
    Format-SpectreTable -Data $data
 
    .EXAMPLE
    # **Example 2**
    # This example demonstrates how to format an array of objects into a Spectre Console table with custom properties generated by scriptblock expressions.
    $Properties = @(
        # foreground + background
        @{'Name'='FileName'; Expression={ "[white on DeepSkyBlue3_1]" + $_.Name + "[/]" }},
        # foreground
        @{'Name'='Last Updated'; Expression={ "[DeepSkyBlue3_1]" + $_.LastWriteTime.ToString() + "[/]" }},
        # background
        @{'Name'='Drive'; Expression={ "[black on LightGreen_1]" + $_.PSDrive.Root + "[/]" }}
    )
    Get-ChildItem | Format-SpectreTable -Property $Properties -AllowMarkup
 
    .EXAMPLE
    # **Example 3**
    # This example demonstrates how to format an array of scalar objects into a Spectre Console table.
    1..10 | Format-SpectreTable -Title Numbers
 
    .EXAMPLE
    # **Example 4**
    # This example demonstrates how to nest other renderable objects inside a table.
    $calendar = Write-SpectreCalendar -Date (Get-Date) -PassThru
 
    $fruits = @(
        (New-SpectreChartItem -Label "Bananas" -Value 2.2 -Color Yellow),
        (New-SpectreChartItem -Label "Oranges" -Value 6.6 -Color Orange1),
        (New-SpectreChartItem -Label "Apples" -Value 1 -Color Red)
    ) | Format-SpectreBarChart -Width 45
 
    @{
        Calendar = $calendar
        Fruits = $fruits
    } | Format-SpectreTable -Color Cyan1
    #>

    [Reflection.AssemblyMetadata("title", "Format-SpectreTable")]
    [cmdletbinding(DefaultParameterSetName = '__AllParameterSets')]
    [Alias('fst')]
    param(
        [Parameter(ValueFromPipeline, Mandatory)]
        [Alias('InputObject')]
        [object] $Data,
        [Parameter(Position = 0, ParameterSetName = 'Property')]
        [object[]] $Property,
        [Switch] $Wrap,
        [Parameter(ParameterSetName = 'View')]
        [String] $View,
        [ValidateSet([SpectreConsoleTableBorder], ErrorMessage = "Value '{0}' is invalid. Try one of: {1}")]
        [string] $Border = "Rounded",
        [ColorTransformationAttribute()]
        [ArgumentCompletionsSpectreColors()]
        [Spectre.Console.Color] $Color = $script:AccentColor,
        [ColorTransformationAttribute()]
        [ArgumentCompletionsSpectreColors()]
        [Spectre.Console.Color] $HeaderColor = $script:DefaultTableHeaderColor,
        [ColorTransformationAttribute()]
        [ArgumentCompletionsSpectreColors()]
        [Spectre.Console.Color] $TextColor = $script:DefaultTableTextColor,
        [ValidateScript({ $_ -gt 0 -and $_ -le (Get-HostWidth) }, ErrorMessage = "Value '{0}' is invalid. Cannot be negative or exceed console width.")]
        [int] $Width,
        [switch] $HideHeaders,
        [String] $Title,
        [switch] $AllowMarkup,
        [switch] $Expand
    )
    begin {
        Write-Debug "Module: $($ExecutionContext.SessionState.Module.Name) Command: $($MyInvocation.MyCommand.Name) Param: $($PSBoundParameters.GetEnumerator())"
        $tableoptions = @{}
        $rowoptions = @{}
        $FormatTableParams = @{}
        $collector = [System.Collections.Generic.List[psobject]]::new()
        $renderables = @{}
        $table = [Spectre.Console.Table]::new()
        $table.Border = [Spectre.Console.TableBorder]::$Border
        $table.BorderStyle = [Spectre.Console.Style]::new($Color)
        switch ($PSBoundParameters.Keys) {
            'Width' { $table.Width = $Width }
            'HideHeaders' { $table.ShowHeaders = $false }
            'Title' { $tableoptions.Title = $Title }
            'AllowMarkup' { $rowoptions.AllowMarkup = $true }
            'Wrap' { $tableoptions.Wrap = $true ; $FormatTableParams.Wrap = $true }
            'View' { $FormatTableParams.View = $View }
            'Property' { $FormatTableParams.Property = $Property }
            'Expand' { $table.Expand = $Expand }
        }
    }
    process {
        foreach ($entry in $data) {
            if ($entry -is [Spectre.Console.Rendering.Renderable]) {
                # The fancy stuff below to handle powershell formatting turns renderable spectre objects into strings.
                # Here we're storing the original object in a lookup table to be read back when it's time to add cells to the table.
                $renderableKey = "RENDERABLE__$([Guid]::NewGuid().Guid)"
                $renderables[$renderableKey] = $entry
                $collector.add($renderableKey)
            } elseif ($entry -is [hashtable] -or $entry -is [ordered]) {
                # Recursively expand values in the hashtable finding any renderables and putting them in the lookup table
                # Renderables is mutable (hashtables just are) so the Convert-HashtableToRenderSafePSObject will add the renderables to the lookup table
                $entry = Convert-HashtableToRenderSafePSObject -Hashtable $entry -Renderables $renderables
                $collector.add($entry)
            } else {
                $collector.add($entry)
            }
        }
    }
    end {
        if ($collector.count -eq 0) {
            return
        }
        if ($FormatTableParams.Keys.Count -gt 0) {
            Write-Debug "Using Format-Table with parameters: $($FormatTableParams.Keys -join ', ')"
            $collector = $collector | Format-Table @FormatTableParams
        } else {
            $collector = $collector | Format-Table
        }
        if (-Not $collector.shapeInfo) {
            # scalar array, no header
            $rowoptions.scalar = $tableoptions.scalar = $true
            $table = Add-TableColumns -Table $table @tableoptions -Color $HeaderColor
        } else {
            # grab the FormatStartData
            $Headers = Get-TableHeader $collector[0]
            if ($Headers) {
                $table = Add-TableColumns -Table $table -formatData $Headers -Color $HeaderColor
            } else {
                return
            }
        }
        foreach ($item in $collector.FormatEntryInfo) {
            if ($rowoptions.scalar) {
                $row = New-TableRow -Entry $item.Text -Renderables $renderables -Color $TextColor @rowoptions
            } else {
                if ($null -eq $item.FormatPropertyFieldList.propertyValue) {
                    continue
                }
                $row = New-TableRow -Entry $item.FormatPropertyFieldList.propertyValue -Renderables $renderables -Color $TextColor @rowoptions
            }
            if ($AllowMarkup) {
                $table = [Spectre.Console.TableExtensions]::AddRow($table, [Spectre.Console.Markup[]]$row)
            } else {
                $table = [Spectre.Console.TableExtensions]::AddRow($table, [Spectre.Console.Rendering.Renderable[]]$row)
            }
        }
        if ($Title -And -Not $rowoptions.scalar) {
            $table.Title = [Spectre.Console.TableTitle]::new($Title, [Spectre.Console.Style]::new($Color))
        }

        return $table
    }
}