Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
Table
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
9 / 9
24
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setHeaders
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addRow
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setRows
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setHeaderColor
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 getColumnWidths
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 buildBorderRow
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 buildDataRow
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Console;
16
17/**
18 * Console table class
19 *
20 * @category   Pop
21 * @package    Pop\Console
22 * @author     Nick Sagona, III <dev@noladev.com>
23 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    5.0.0
26 */
27class Table
28{
29
30    /**
31     * Table headers
32     * @var array
33     */
34    protected array $headers = [];
35
36    /**
37     * Table rows
38     * @var array
39     */
40    protected array $rows = [];
41
42    /**
43     * Horizontal border character
44     * @var string
45     */
46    protected string $h = '-';
47
48    /**
49     * Vertical border character (null omits vertical dividers)
50     * @var ?string
51     */
52    protected ?string $v = '|';
53
54    /**
55     * Header row foreground color
56     * @var ?int
57     */
58    protected ?int $headerFg = null;
59
60    /**
61     * Header row background color
62     * @var ?int
63     */
64    protected ?int $headerBg = null;
65
66    /**
67     * Instantiate the table object
68     *
69     * @param  array   $headers
70     * @param  array   $rows
71     * @param  string  $h
72     * @param  ?string $v
73     */
74    public function __construct(array $headers = [], array $rows = [], string $h = '-', ?string $v = '|')
75    {
76        $this->setHeaders($headers);
77        $this->setRows($rows);
78        $this->h = $h;
79        $this->v = $v;
80    }
81
82    /**
83     * Set the table headers
84     *
85     * @param  array $headers
86     * @return Table
87     */
88    public function setHeaders(array $headers): Table
89    {
90        $this->headers = $headers;
91        return $this;
92    }
93
94    /**
95     * Add a row to the table
96     *
97     * @param  array $row
98     * @return Table
99     */
100    public function addRow(array $row): Table
101    {
102        $this->rows[] = $row;
103        return $this;
104    }
105
106    /**
107     * Set the table rows
108     *
109     * @param  array $rows
110     * @return Table
111     */
112    public function setRows(array $rows): Table
113    {
114        $this->rows = $rows;
115        return $this;
116    }
117
118    /**
119     * Set the header row color
120     *
121     * @param  ?int $fg
122     * @param  ?int $bg
123     * @return Table
124     */
125    public function setHeaderColor(?int $fg, ?int $bg = null): Table
126    {
127        $this->headerFg = $fg;
128        $this->headerBg = $bg;
129        return $this;
130    }
131
132    /**
133     * Render the table
134     *
135     * @return string
136     */
137    public function render(): string
138    {
139        $widths = $this->getColumnWidths();
140
141        if (empty($widths)) {
142            return '';
143        }
144
145        $hasHeaders = !empty($this->headers);
146        $output     = $this->buildBorderRow($widths);
147
148        if ($hasHeaders) {
149            $output .= $this->buildDataRow($this->headers, $widths, true);
150            $output .= $this->buildBorderRow($widths);
151        }
152
153        foreach ($this->rows as $row) {
154            $output .= $this->buildDataRow($row, $widths, false);
155        }
156
157        $output .= $this->buildBorderRow($widths);
158
159        return $output;
160    }
161
162    /**
163     * Calculate the column widths from raw (uncolored) header/row content
164     *
165     * @return array
166     */
167    protected function getColumnWidths(): array
168    {
169        $widths = [];
170
171        foreach ($this->headers as $i => $header) {
172            $widths[$i] = strlen((string)$header);
173        }
174
175        foreach ($this->rows as $row) {
176            foreach ($row as $i => $cell) {
177                $length = strlen((string)$cell);
178                if (!isset($widths[$i]) || ($length > $widths[$i])) {
179                    $widths[$i] = $length;
180                }
181            }
182        }
183
184        ksort($widths);
185
186        return $widths;
187    }
188
189    /**
190     * Build a horizontal border row
191     *
192     * @param  array $widths
193     * @return string
194     */
195    protected function buildBorderRow(array $widths): string
196    {
197        if (empty($this->v)) {
198            $total = array_sum($widths) + (count($widths) * 2) + (count($widths) - 1);
199            return str_repeat($this->h, $total) . PHP_EOL;
200        }
201
202        $segments = [];
203        foreach ($widths as $width) {
204            $segments[] = str_repeat($this->h, $width + 2);
205        }
206
207        return '+' . implode('+', $segments) . '+' . PHP_EOL;
208    }
209
210    /**
211     * Build a header or data row
212     *
213     * @param  array $cells
214     * @param  array $widths
215     * @param  bool  $isHeader
216     * @return string
217     */
218    protected function buildDataRow(array $cells, array $widths, bool $isHeader = false): string
219    {
220        $rendered = [];
221
222        foreach ($widths as $i => $width) {
223            $text = (string)($cells[$i] ?? '');
224            $pad  = str_pad($text, $width);
225
226            if ($isHeader && (($this->headerFg !== null) || ($this->headerBg !== null))) {
227                $pad = Color::colorize($pad, $this->headerFg, $this->headerBg);
228            }
229
230            $rendered[] = ' ' . $pad . ' ';
231        }
232
233        if (empty($this->v)) {
234            return implode(' ', $rendered) . PHP_EOL;
235        }
236
237        return $this->v . implode($this->v, $rendered) . $this->v . PHP_EOL;
238    }
239
240}