Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
22 / 22 |
|
90.62% |
29 / 32 |
|
4.62% |
6 / 130 |
|
75.00% |
3 / 4 |
CRAP | |
0.00% |
0 / 1 |
| Table | |
100.00% |
22 / 22 |
|
90.62% |
29 / 32 |
|
4.62% |
6 / 130 |
|
100.00% |
4 / 4 |
116.01 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| row | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| rule | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| render | |
100.00% |
17 / 17 |
|
88.89% |
24 / 27 |
|
1.59% |
2 / 126 |
|
100.00% |
1 / 1 |
53.70 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Console; |
| 6 | |
| 7 | use ValueError; |
| 8 | |
| 9 | /** |
| 10 | * Renders rows of columns aligned on their visible width — markup |
| 11 | * tags and multibyte characters don't count, so cells may carry |
| 12 | * markup: `<green>1,075</green>`. |
| 13 | * |
| 14 | * Columns size to their widest cell and are separated by two spaces; |
| 15 | * `rule()` inserts a separator line spanning the table. Cells don't |
| 16 | * wrap: a table wider than the terminal simply overflows. Render the |
| 17 | * result through the Io echo methods: |
| 18 | * |
| 19 | * $table = new Table(align: [Align::Left, Align::Right]); |
| 20 | * $table->row(['<strong>Language</strong>', '<strong>Lines</strong>']); |
| 21 | * $table->rule(); |
| 22 | * $table->row(['PHP', '1,075']); |
| 23 | * $io->echo($table->render()); |
| 24 | * |
| 25 | * @api |
| 26 | */ |
| 27 | final class Table |
| 28 | { |
| 29 | private readonly Markup $markup; |
| 30 | |
| 31 | /** @var list<list<string>|string> Cell rows, or a rule's char. */ |
| 32 | private array $rows = []; |
| 33 | |
| 34 | /** @param list<Align> $align Per column; further columns align left. */ |
| 35 | public function __construct( |
| 36 | private readonly array $align = [], |
| 37 | ) { |
| 38 | $this->markup = new Markup(); |
| 39 | } |
| 40 | |
| 41 | /** @param list<string> $cells A short row leaves the rest empty. */ |
| 42 | public function row(array $cells): void |
| 43 | { |
| 44 | $this->rows[] = $cells; |
| 45 | } |
| 46 | |
| 47 | public function rule(string $char = '─'): void |
| 48 | { |
| 49 | if ($this->markup->width($char) < 1) { |
| 50 | throw new ValueError("Rule char '{$char}' has no visible width"); |
| 51 | } |
| 52 | |
| 53 | $this->rows[] = $char; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * The rendered table, lines separated and terminated by `\n`. |
| 58 | */ |
| 59 | public function render(): string |
| 60 | { |
| 61 | $widths = []; |
| 62 | |
| 63 | foreach ($this->rows as $row) { |
| 64 | if (is_string($row)) { |
| 65 | continue; |
| 66 | } |
| 67 | |
| 68 | foreach ($row as $i => $cell) { |
| 69 | $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell)); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1)); |
| 74 | $out = ''; |
| 75 | |
| 76 | foreach ($this->rows as $row) { |
| 77 | if (is_string($row)) { |
| 78 | $out .= str_repeat($row, intdiv($total, $this->markup->width($row))); |
| 79 | } else { |
| 80 | $cells = []; |
| 81 | |
| 82 | foreach ($widths as $i => $width) { |
| 83 | $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left); |
| 84 | } |
| 85 | |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 87 | } |
| 88 | |
| 89 | $out .= "\n"; |
| 90 | } |
| 91 | |
| 92 | return $out; |
| 93 | } |
| 94 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 36 | private readonly array $align = [], |
| 37 | ) { |
| 38 | $this->markup = new Markup(); |
| 39 | } |
| 61 | $widths = []; |
| 62 | |
| 63 | foreach ($this->rows as $row) { |
| 63 | foreach ($this->rows as $row) { |
| 64 | if (is_string($row)) { |
| 65 | continue; |
| 68 | foreach ($row as $i => $cell) { |
| 68 | foreach ($row as $i => $cell) { |
| 68 | foreach ($row as $i => $cell) { |
| 69 | $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell)); |
| 69 | $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell)); |
| 69 | $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell)); |
| 68 | foreach ($row as $i => $cell) { |
| 69 | $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell)); |
| 63 | foreach ($this->rows as $row) { |
| 64 | if (is_string($row)) { |
| 65 | continue; |
| 66 | } |
| 67 | |
| 68 | foreach ($row as $i => $cell) { |
| 63 | foreach ($this->rows as $row) { |
| 64 | if (is_string($row)) { |
| 65 | continue; |
| 66 | } |
| 67 | |
| 68 | foreach ($row as $i => $cell) { |
| 69 | $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell)); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1)); |
| 73 | $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1)); |
| 73 | $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1)); |
| 73 | $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1)); |
| 74 | $out = ''; |
| 75 | |
| 76 | foreach ($this->rows as $row) { |
| 76 | foreach ($this->rows as $row) { |
| 77 | if (is_string($row)) { |
| 77 | if (is_string($row)) { |
| 78 | $out .= str_repeat($row, intdiv($total, $this->markup->width($row))); |
| 80 | $cells = []; |
| 81 | |
| 82 | foreach ($widths as $i => $width) { |
| 82 | foreach ($widths as $i => $width) { |
| 82 | foreach ($widths as $i => $width) { |
| 82 | foreach ($widths as $i => $width) { |
| 83 | $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left); |
| 84 | } |
| 85 | |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 87 | } |
| 88 | |
| 89 | $out .= "\n"; |
| 76 | foreach ($this->rows as $row) { |
| 77 | if (is_string($row)) { |
| 78 | $out .= str_repeat($row, intdiv($total, $this->markup->width($row))); |
| 79 | } else { |
| 80 | $cells = []; |
| 81 | |
| 82 | foreach ($widths as $i => $width) { |
| 83 | $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left); |
| 84 | } |
| 85 | |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 87 | } |
| 88 | |
| 89 | $out .= "\n"; |
| 76 | foreach ($this->rows as $row) { |
| 77 | if (is_string($row)) { |
| 78 | $out .= str_repeat($row, intdiv($total, $this->markup->width($row))); |
| 79 | } else { |
| 80 | $cells = []; |
| 81 | |
| 82 | foreach ($widths as $i => $width) { |
| 83 | $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left); |
| 84 | } |
| 85 | |
| 86 | $out .= rtrim(implode(' ', $cells)); |
| 87 | } |
| 88 | |
| 89 | $out .= "\n"; |
| 90 | } |
| 91 | |
| 92 | return $out; |
| 93 | } |
| 42 | public function row(array $cells): void |
| 43 | { |
| 44 | $this->rows[] = $cells; |
| 45 | } |
| 47 | public function rule(string $char = '─'): void |
| 48 | { |
| 49 | if ($this->markup->width($char) < 1) { |
| 50 | throw new ValueError("Rule char '{$char}' has no visible width"); |
| 53 | $this->rows[] = $char; |
| 54 | } |