1<?php
2
3/*
4 * This file is part of the PHP-CLI package.
5 *
6 * (c) Jitendra Adhikari <jiten.adhikary@gmail.com>
7 *     <https://github.com/adhocore>
8 *
9 * Licensed under MIT license.
10 */
11
12namespace Ahc\Cli\Output;
13
14use Ahc\Cli\Exception\InvalidArgumentException;
15use Ahc\Cli\Helper\InflectsString;
16
17class Table
18{
19    use InflectsString;
20
21    public function render(array $rows, array $styles = []): string
22    {
23        if ([] === $table = $this->normalize($rows)) {
24            return '';
25        }
26
27        list($head, $rows) = $table;
28
29        $styles = $this->normalizeStyles($styles);
30        $title  = $body = $dash = [];
31
32        list($start, $end) = $styles['head'];
33        foreach ($head as $col => $size) {
34            $dash[]  = \str_repeat('-', $size + 2);
35            $title[] = \str_pad($this->toWords($col), $size, ' ');
36        }
37
38        $title = "|$start " . \implode(" $end|$start ", $title) . " $end|" . \PHP_EOL;
39
40        $odd = true;
41        foreach ($rows as $row) {
42            $parts = [];
43
44            list($start, $end) = $styles[['even', 'odd'][(int) $odd]];
45            foreach ($head as $col => $size) {
46                $parts[] = \str_pad($row[$col] ?? '', $size, ' ');
47            }
48
49            $odd    = !$odd;
50            $body[] = "|$start " . \implode(" $end|$start ", $parts) . " $end|";
51        }
52
53        $dash  = '+' . \implode('+', $dash) . '+' . \PHP_EOL;
54        $body  = \implode(\PHP_EOL, $body) . \PHP_EOL;
55
56        return "$dash$title$dash$body$dash";
57    }
58
59    protected function normalize(array $rows): array
60    {
61        $head = \reset($rows);
62        if (empty($head)) {
63            return [];
64        }
65
66        if (!\is_array($head)) {
67            throw new InvalidArgumentException(
68                \sprintf('Rows must be array of assoc arrays, %s given', \gettype($head))
69            );
70        }
71
72        $head = \array_fill_keys(\array_keys($head), null);
73        foreach ($rows as $i => &$row) {
74            $row = \array_merge($head, $row);
75        }
76
77        foreach ($head as $col => &$value) {
78            $cols   = \array_column($rows, $col);
79            $span   = \array_map('strlen', $cols);
80            $span[] = \strlen($col);
81            $value  = \max($span);
82        }
83
84        return [$head, $rows];
85    }
86
87    protected function normalizeStyles(array $styles)
88    {
89        $default = [
90            // styleFor => ['styleStartFn', 'end']
91            'head' => ['', ''],
92            'odd'  => ['', ''],
93            'even' => ['', ''],
94        ];
95
96        foreach ($styles as $for => $style) {
97            if (isset($default[$for])) {
98                $default[$for] = ['<' . \trim($style, '<> ') . '>', '</end>'];
99            }
100        }
101
102        return $default;
103    }
104}
105