Private/Console/Table.psm1

using namespace System
using namespace System.Collections.Generic
using namespace System.Linq

using module ..\Enums.psm1
using module ..\Abstracts.psm1
using module .\Rendering.psm1
using module .\Widgets.psm1
using module .\Tables.psm1
using module .\TableRenderer.psm1

class Table : IRenderable {
  hidden [List[TableColumn]]$_columns
  [List[TableColumn]]$Columns
  [TableRowCollection]$Rows

  [TableBorder]$Border
  [Style]$BorderStyle
  [bool]$UseSafeBorder
  [bool]$ShowHeaders
  [bool]$ShowRowSeparators
  [bool]$ShowFooters
  [bool]$Expand
  [Nullable[int]]$Width
  [TableTitle]$Title
  [TableTitle]$Caption
  [bool]$IsGrid
  [bool]$PadRightCell

  Table() {
    $this._columns = [List[TableColumn]]::new()
    $this.Columns = $this._columns
    $this.Rows = [TableRowCollection]::new($this)
    $this.Border = [SquareTableBorder]::new()
    $this.UseSafeBorder = $true
    $this.ShowHeaders = $true
    $this.ShowFooters = $true
    $this.PadRightCell = $true
  }

  [Table] AddColumn([TableColumn]$column) {
    if ($this.Rows.Count -gt 0) {
      throw [InvalidOperationException]::new("Cannot add new columns to table with existing rows.")
    }
    $this._columns.Add($column)
    return $this
  }

  [Table] AddColumn([string]$header) {
    return $this.AddColumn([TableColumn]::new($header))
  }

  [Table] AddRow([IEnumerable[IRenderable]]$columns) {
    $this.Rows.Add([TableRow]::new($columns))
    return $this
  }

  [Table] AddRow([IRenderable[]]$columns) {
    return $this.AddRow([IEnumerable[IRenderable]]$columns)
  }

  [Table] AddRow([string[]]$columns) {
    $list = [List[IRenderable]]::new()
    foreach ($c in $columns) { $list.Add([Markup]::new($c)) }
    return $this.AddRow($list)
  }

  [Measurement] Measure([RenderOptions]$options, [int]$maxWidth) {
    $measurer = [TableMeasurer]::new($this, $options)
    $totalCellWidth = $measurer.CalculateTotalCellWidth($maxWidth)

    $min = 0; $max = 0
    foreach ($c in $this._columns) {
      $m = $measurer.MeasureColumn($c, $totalCellWidth)
      $min += $m.Min
      $max += $m.Max
    }

    $nonCol = $measurer.GetNonColumnWidth()
    $minTableWidth = $min + $nonCol
    $maxTableWidth = if ($null -ne $this.Width) { $this.Width } else { $max + $nonCol }

    return [Measurement]::new($minTableWidth, $maxTableWidth)
  }

  [Segment[]] Render([RenderOptions]$options, [int]$maxWidth) {
    $measurer = [TableMeasurer]::new($this, $options)
    $totalCellWidth = $measurer.CalculateTotalCellWidth($maxWidth)
    $columnWidths = $measurer.CalculateColumnWidths($totalCellWidth)

    $tableWidth = 0
    foreach ($w in $columnWidths) { $tableWidth += $w }
    $tableWidth += $measurer.GetNonColumnWidth()

    $renderedRows = $this.GetRenderableRows()
    $ctx = [TableRendererContext]::new($this, $options, $renderedRows, $tableWidth, $maxWidth)

    return [TableRenderer]::Render($ctx, $columnWidths).ToArray()
  }

  hidden [List[TableRow]] GetRenderableRows() {
    $renderedRows = [List[TableRow]]::new()

    if ($this.ShowHeaders) {
      $heads = [List[IRenderable]]::new()
      foreach ($c in $this._columns) { $heads.Add($c.Header) }
      $renderedRows.Add([TableRow]::Header($heads))
    }

    foreach ($r in $this.Rows) {
      $renderedRows.Add($r)
    }

    $anyFooter = $false
    foreach ($c in $this._columns) { if ($null -ne $c.Footer) { $anyFooter = $true; break } }

    if ($this.ShowFooters -and $anyFooter) {
      $foots = [List[IRenderable]]::new()
      foreach ($c in $this._columns) {
        if ($null -ne $c.Footer) { $foots.Add($c.Footer) } else { $foots.Add([Text]::Empty) }
      }
      $renderedRows.Add([TableRow]::Footer($foots))
    }

    return $renderedRows
  }
}