Completed
Pull Request — master (#28)
by Jitendra
01:50
created

Writer::table()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 21
nc 7
nop 2
dl 0
loc 36
rs 9.2728
c 0
b 0
f 0
1
<?php
2
3
namespace Ahc\Cli\Output;
4
5
use Ahc\Cli\Helper\InflectsString;
6
7
/**
8
 * Cli Writer.
9
 *
10
 * @author  Jitendra Adhikari <[email protected]>
11
 * @license MIT
12
 *
13
 * @link    https://github.com/adhocore/cli
14
 *
15
 * @method Writer bgBlack($text, $eol = false)
16
 * @method Writer bgBlue($text, $eol = false)
17
 * @method Writer bgCyan($text, $eol = false)
18
 * @method Writer bgGreen($text, $eol = false)
19
 * @method Writer bgPurple($text, $eol = false)
20
 * @method Writer bgRed($text, $eol = false)
21
 * @method Writer bgWhite($text, $eol = false)
22
 * @method Writer bgYellow($text, $eol = false)
23
 * @method Writer black($text, $eol = false)
24
 * @method Writer blackBgBlue($text, $eol = false)
25
 * @method Writer blackBgCyan($text, $eol = false)
26
 * @method Writer blackBgGreen($text, $eol = false)
27
 * @method Writer blackBgPurple($text, $eol = false)
28
 * @method Writer blackBgRed($text, $eol = false)
29
 * @method Writer blackBgWhite($text, $eol = false)
30
 * @method Writer blackBgYellow($text, $eol = false)
31
 * @method Writer blue($text, $eol = false)
32
 * @method Writer blueBgBlack($text, $eol = false)
33
 * @method Writer blueBgCyan($text, $eol = false)
34
 * @method Writer blueBgGreen($text, $eol = false)
35
 * @method Writer blueBgPurple($text, $eol = false)
36
 * @method Writer blueBgRed($text, $eol = false)
37
 * @method Writer blueBgWhite($text, $eol = false)
38
 * @method Writer blueBgYellow($text, $eol = false)
39
 * @method Writer bold($text, $eol = false)
40
 * @method Writer boldBlack($text, $eol = false)
41
 * @method Writer boldBlackBgBlue($text, $eol = false)
42
 * @method Writer boldBlackBgCyan($text, $eol = false)
43
 * @method Writer boldBlackBgGreen($text, $eol = false)
44
 * @method Writer boldBlackBgPurple($text, $eol = false)
45
 * @method Writer boldBlackBgRed($text, $eol = false)
46
 * @method Writer boldBlackBgWhite($text, $eol = false)
47
 * @method Writer boldBlackBgYellow($text, $eol = false)
48
 * @method Writer boldBlue($text, $eol = false)
49
 * @method Writer boldBlueBgBlack($text, $eol = false)
50
 * @method Writer boldBlueBgCyan($text, $eol = false)
51
 * @method Writer boldBlueBgGreen($text, $eol = false)
52
 * @method Writer boldBlueBgPurple($text, $eol = false)
53
 * @method Writer boldBlueBgRed($text, $eol = false)
54
 * @method Writer boldBlueBgWhite($text, $eol = false)
55
 * @method Writer boldBlueBgYellow($text, $eol = false)
56
 * @method Writer boldCyan($text, $eol = false)
57
 * @method Writer boldCyanBgBlack($text, $eol = false)
58
 * @method Writer boldCyanBgBlue($text, $eol = false)
59
 * @method Writer boldCyanBgGreen($text, $eol = false)
60
 * @method Writer boldCyanBgPurple($text, $eol = false)
61
 * @method Writer boldCyanBgRed($text, $eol = false)
62
 * @method Writer boldCyanBgWhite($text, $eol = false)
63
 * @method Writer boldCyanBgYellow($text, $eol = false)
64
 * @method Writer boldGreen($text, $eol = false)
65
 * @method Writer boldGreenBgBlack($text, $eol = false)
66
 * @method Writer boldGreenBgBlue($text, $eol = false)
67
 * @method Writer boldGreenBgCyan($text, $eol = false)
68
 * @method Writer boldGreenBgPurple($text, $eol = false)
69
 * @method Writer boldGreenBgRed($text, $eol = false)
70
 * @method Writer boldGreenBgWhite($text, $eol = false)
71
 * @method Writer boldGreenBgYellow($text, $eol = false)
72
 * @method Writer boldPurple($text, $eol = false)
73
 * @method Writer boldPurpleBgBlack($text, $eol = false)
74
 * @method Writer boldPurpleBgBlue($text, $eol = false)
75
 * @method Writer boldPurpleBgCyan($text, $eol = false)
76
 * @method Writer boldPurpleBgGreen($text, $eol = false)
77
 * @method Writer boldPurpleBgRed($text, $eol = false)
78
 * @method Writer boldPurpleBgWhite($text, $eol = false)
79
 * @method Writer boldPurpleBgYellow($text, $eol = false)
80
 * @method Writer boldRed($text, $eol = false)
81
 * @method Writer boldRedBgBlack($text, $eol = false)
82
 * @method Writer boldRedBgBlue($text, $eol = false)
83
 * @method Writer boldRedBgCyan($text, $eol = false)
84
 * @method Writer boldRedBgGreen($text, $eol = false)
85
 * @method Writer boldRedBgPurple($text, $eol = false)
86
 * @method Writer boldRedBgWhite($text, $eol = false)
87
 * @method Writer boldRedBgYellow($text, $eol = false)
88
 * @method Writer boldWhite($text, $eol = false)
89
 * @method Writer boldWhiteBgBlack($text, $eol = false)
90
 * @method Writer boldWhiteBgBlue($text, $eol = false)
91
 * @method Writer boldWhiteBgCyan($text, $eol = false)
92
 * @method Writer boldWhiteBgGreen($text, $eol = false)
93
 * @method Writer boldWhiteBgPurple($text, $eol = false)
94
 * @method Writer boldWhiteBgRed($text, $eol = false)
95
 * @method Writer boldWhiteBgYellow($text, $eol = false)
96
 * @method Writer boldYellow($text, $eol = false)
97
 * @method Writer boldYellowBgBlack($text, $eol = false)
98
 * @method Writer boldYellowBgBlue($text, $eol = false)
99
 * @method Writer boldYellowBgCyan($text, $eol = false)
100
 * @method Writer boldYellowBgGreen($text, $eol = false)
101
 * @method Writer boldYellowBgPurple($text, $eol = false)
102
 * @method Writer boldYellowBgRed($text, $eol = false)
103
 * @method Writer boldYellowBgWhite($text, $eol = false)
104
 * @method Writer colors($text)
105
 * @method Writer comment($text, $eol = false)
106
 * @method Writer cyan($text, $eol = false)
107
 * @method Writer cyanBgBlack($text, $eol = false)
108
 * @method Writer cyanBgBlue($text, $eol = false)
109
 * @method Writer cyanBgGreen($text, $eol = false)
110
 * @method Writer cyanBgPurple($text, $eol = false)
111
 * @method Writer cyanBgRed($text, $eol = false)
112
 * @method Writer cyanBgWhite($text, $eol = false)
113
 * @method Writer cyanBgYellow($text, $eol = false)
114
 * @method Writer error($text, $eol = false)
115
 * @method Writer green($text, $eol = false)
116
 * @method Writer greenBgBlack($text, $eol = false)
117
 * @method Writer greenBgBlue($text, $eol = false)
118
 * @method Writer greenBgCyan($text, $eol = false)
119
 * @method Writer greenBgPurple($text, $eol = false)
120
 * @method Writer greenBgRed($text, $eol = false)
121
 * @method Writer greenBgWhite($text, $eol = false)
122
 * @method Writer greenBgYellow($text, $eol = false)
123
 * @method Writer info($text, $eol = false)
124
 * @method Writer ok($text, $eol = false)
125
 * @method Writer purple($text, $eol = false)
126
 * @method Writer purpleBgBlack($text, $eol = false)
127
 * @method Writer purpleBgBlue($text, $eol = false)
128
 * @method Writer purpleBgCyan($text, $eol = false)
129
 * @method Writer purpleBgGreen($text, $eol = false)
130
 * @method Writer purpleBgRed($text, $eol = false)
131
 * @method Writer purpleBgWhite($text, $eol = false)
132
 * @method Writer purpleBgYellow($text, $eol = false)
133
 * @method Writer red($text, $eol = false)
134
 * @method Writer redBgBlack($text, $eol = false)
135
 * @method Writer redBgBlue($text, $eol = false)
136
 * @method Writer redBgCyan($text, $eol = false)
137
 * @method Writer redBgGreen($text, $eol = false)
138
 * @method Writer redBgPurple($text, $eol = false)
139
 * @method Writer redBgWhite($text, $eol = false)
140
 * @method Writer redBgYellow($text, $eol = false)
141
 * @method Writer warn($text, $eol = false)
142
 * @method Writer white($text, $eol = false)
143
 * @method Writer yellow($text, $eol = false)
144
 * @method Writer yellowBgBlack($text, $eol = false)
145
 * @method Writer yellowBgBlue($text, $eol = false)
146
 * @method Writer yellowBgCyan($text, $eol = false)
147
 * @method Writer yellowBgGreen($text, $eol = false)
148
 * @method Writer yellowBgPurple($text, $eol = false)
149
 * @method Writer yellowBgRed($text, $eol = false)
150
 * @method Writer yellowBgWhite($text, $eol = false)
151
 */
152
class Writer
153
{
154
    use InflectsString;
155
156
    /** @var resource Output file handle */
157
    protected $stream;
158
159
    /** @var resource Error output file handle */
160
    protected $eStream;
161
162
    /** @var string Write method to be relayed to Colorizer */
163
    protected $method;
164
165
    /** @var Color */
166
    protected $colorizer;
167
168
    /** @var Cursor */
169
    protected $cursor;
170
171
    public function __construct(string $path = null, Color $colorizer = null)
172
    {
173
        if ($path) {
174
            $path = \fopen($path, 'w');
175
        }
176
177
        $this->stream  = $path ?: \STDOUT;
0 ignored issues
show
Documentation Bug introduced by
It seems like $path ?: STDOUT can also be of type string. However, the property $stream is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
178
        $this->eStream = $path ?: \STDERR;
0 ignored issues
show
Documentation Bug introduced by
It seems like $path ?: STDERR can also be of type string. However, the property $eStream is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
179
180
        $this->cursor    = new Cursor;
181
        $this->colorizer = $colorizer ?? new Color;
182
    }
183
184
    /**
185
     * Get Colorizer.
186
     *
187
     * @return Color
188
     */
189
    public function colorizer(): Color
190
    {
191
        return $this->colorizer;
192
    }
193
194
    /**
195
     * Magically set methods.
196
     *
197
     * @param string $name Like `red`, `bgRed`, 'bold', `error` etc
198
     *
199
     * @return self
200
     */
201
    public function __get(string $name): self
202
    {
203
        if (\strpos($this->method, $name) === false) {
204
            $this->method .= $this->method ? \ucfirst($name) : $name;
205
        }
206
207
        return $this;
208
    }
209
210
    /**
211
     * Write the formatted text to stdout or stderr.
212
     *
213
     * @param string $text
214
     * @param bool   $eol
215
     *
216
     * @return self
217
     */
218
    public function write(string $text, bool $eol = false): self
219
    {
220
        list($method, $this->method) = [$this->method ?: 'line', ''];
221
222
        $text  = $this->colorizer->{$method}($text, []);
223
        $error = \stripos($method, 'error') !== false;
224
225
        if ($eol) {
226
            $text .= \PHP_EOL;
227
        }
228
229
        return $this->doWrite($text, $error);
230
    }
231
232
    /**
233
     * Really write to the stream.
234
     *
235
     * @param string $text
236
     * @param bool   $error
237
     *
238
     * @return self
239
     */
240
    protected function doWrite(string $text, bool $error = false): self
241
    {
242
        $stream = $error ? $this->eStream : $this->stream;
243
244
        \fwrite($stream, $text);
245
246
        return $this;
247
    }
248
249
    /**
250
     * Write EOL n times.
251
     *
252
     * @param int $n
253
     *
254
     * @return self
255
     */
256
    public function eol(int $n = 1): self
257
    {
258
        return $this->doWrite(\str_repeat(PHP_EOL, \max($n, 1)));
259
    }
260
261
    /**
262
     * Write raw text (as it is).
263
     *
264
     * @param string $text
265
     * @param bool   $error
266
     *
267
     * @return self
268
     */
269
    public function raw($text, bool $error = false): self
270
    {
271
        return $this->doWrite((string) $text, $error);
272
    }
273
274
    /**
275
     * Generate table for the console. Keys of first row are taken as header.
276
     *
277
     * @param array[] $rows   Array of assoc arrays.
278
     * @param array   $styles Eg: ['head' => 'bold', 'odd' => 'comment', 'even' => 'green']
279
     *
280
     * @return self
281
     */
282
    public function table(array $rows, array $styles = []): self
283
    {
284
        if ([] === $table = $this->normalizeTable($rows)) {
285
            return $this;
286
        }
287
288
        list($head, $rows) = $table;
289
290
        $styles = $this->normalizeStyles($styles);
291
        $title  = $body = $dash = [];
292
293
        list($start, $end) = $styles['head'];
294
        foreach ($head as $col => $size) {
295
            $dash[]  = \str_repeat('-', $size + 2);
296
            $title[] = $start . \str_pad($this->toWords($col), $size, ' ') . $end;
297
        }
298
299
        $title = "|$start " . \implode(" $end|$start ", $title) . " $end|" . \PHP_EOL;
300
301
        $odd = true;
302
        foreach ($rows as $row) {
303
            $parts = [];
304
305
            list($start, $end) = $styles[['even', 'odd'][(int) $odd]];
306
            foreach ($head as $col => $size) {
307
                $parts[] = \str_pad($row[$col] ?? '', $size, ' ');
308
            }
309
310
            $odd    = !$odd;
0 ignored issues
show
introduced by
The condition $odd is always true.
Loading history...
311
            $body[] = "|$start ". \implode(" $end|$start ", $parts) . " $end|";
312
        }
313
314
        $dash  = '+' . \implode('+', $dash) . '+' . \PHP_EOL;
315
        $body  = \implode(\PHP_EOL, $body) . \PHP_EOL;
316
317
        return $this->colors("$dash$title$dash$body$dash");
318
    }
319
320
    protected function normalizeTable(array $rows): array
321
    {
322
        $head = \reset($rows);
323
        if (empty($head)) {
324
            return [];
325
        }
326
327
        if (!\is_array($head)) {
328
            throw new InvalidArgumentException(
0 ignored issues
show
Bug introduced by
The type Ahc\Cli\Output\InvalidArgumentException was not found. Did you mean InvalidArgumentException? If so, make sure to prefix the type with \.
Loading history...
329
                \sprintf('Rows must be array of assoc arrays, %s given', \gettype($head))
330
            );
331
        }
332
333
        $head = \array_fill_keys(\array_keys($head), null);
334
        foreach ($rows as $i => &$row) {
335
            $row = \array_merge($head, $row);
336
        }
337
338
        foreach ($head as $col => &$value) {
339
            $cols   = \array_column($rows, $col);
340
            $span   = \array_map('strlen', $cols);
341
            $span[] = \strlen($col);
342
            $value  = \max($span);
343
        }
344
345
        return [$head, $rows];
346
    }
347
348
    protected function normalizeStyles(array $styles)
349
    {
350
        $default = [
351
            // styleFor => ['styleStartFn', 'end']
352
            'head' => ['', ''],
353
            'odd'  => ['', ''],
354
            'even' => ['', ''],
355
        ];
356
357
        foreach ($styles as $for => $style) {
358
            if (isset($default[$for])) {
359
                $default[$for] = ['<' . \trim($style, '<> ') . '>', '</end>'];
360
            }
361
        }
362
363
        return $default;
364
    }
365
366
    /**
367
     * Write to stdout or stderr magically.
368
     *
369
     * @param string $method
370
     * @param array  $arguments
371
     *
372
     * @return self
373
     */
374
    public function __call(string $method, array $arguments): self
375
    {
376
        if (\method_exists($this->cursor, $method)) {
377
            return $this->doWrite($this->cursor->{$method}(...$arguments));
378
        }
379
380
        $this->method = $method;
381
382
        return $this->write(...$arguments);
0 ignored issues
show
Bug introduced by
$arguments is expanded, but the parameter $text of Ahc\Cli\Output\Writer::write() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

382
        return $this->write(/** @scrutinizer ignore-type */ ...$arguments);
Loading history...
383
    }
384
}
385