Completed
Push — master ( aafc8c...73adad )
by Jitendra
11s
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\Exception\InvalidArgumentException;
6
use Ahc\Cli\Helper\InflectsString;
7
8
/**
9
 * Cli Writer.
10
 *
11
 * @author  Jitendra Adhikari <[email protected]>
12
 * @license MIT
13
 *
14
 * @link    https://github.com/adhocore/cli
15
 *
16
 * @method Writer bgBlack($text, $eol = false)
17
 * @method Writer bgBlue($text, $eol = false)
18
 * @method Writer bgCyan($text, $eol = false)
19
 * @method Writer bgGreen($text, $eol = false)
20
 * @method Writer bgPurple($text, $eol = false)
21
 * @method Writer bgRed($text, $eol = false)
22
 * @method Writer bgWhite($text, $eol = false)
23
 * @method Writer bgYellow($text, $eol = false)
24
 * @method Writer black($text, $eol = false)
25
 * @method Writer blackBgBlue($text, $eol = false)
26
 * @method Writer blackBgCyan($text, $eol = false)
27
 * @method Writer blackBgGreen($text, $eol = false)
28
 * @method Writer blackBgPurple($text, $eol = false)
29
 * @method Writer blackBgRed($text, $eol = false)
30
 * @method Writer blackBgWhite($text, $eol = false)
31
 * @method Writer blackBgYellow($text, $eol = false)
32
 * @method Writer blue($text, $eol = false)
33
 * @method Writer blueBgBlack($text, $eol = false)
34
 * @method Writer blueBgCyan($text, $eol = false)
35
 * @method Writer blueBgGreen($text, $eol = false)
36
 * @method Writer blueBgPurple($text, $eol = false)
37
 * @method Writer blueBgRed($text, $eol = false)
38
 * @method Writer blueBgWhite($text, $eol = false)
39
 * @method Writer blueBgYellow($text, $eol = false)
40
 * @method Writer bold($text, $eol = false)
41
 * @method Writer boldBlack($text, $eol = false)
42
 * @method Writer boldBlackBgBlue($text, $eol = false)
43
 * @method Writer boldBlackBgCyan($text, $eol = false)
44
 * @method Writer boldBlackBgGreen($text, $eol = false)
45
 * @method Writer boldBlackBgPurple($text, $eol = false)
46
 * @method Writer boldBlackBgRed($text, $eol = false)
47
 * @method Writer boldBlackBgWhite($text, $eol = false)
48
 * @method Writer boldBlackBgYellow($text, $eol = false)
49
 * @method Writer boldBlue($text, $eol = false)
50
 * @method Writer boldBlueBgBlack($text, $eol = false)
51
 * @method Writer boldBlueBgCyan($text, $eol = false)
52
 * @method Writer boldBlueBgGreen($text, $eol = false)
53
 * @method Writer boldBlueBgPurple($text, $eol = false)
54
 * @method Writer boldBlueBgRed($text, $eol = false)
55
 * @method Writer boldBlueBgWhite($text, $eol = false)
56
 * @method Writer boldBlueBgYellow($text, $eol = false)
57
 * @method Writer boldCyan($text, $eol = false)
58
 * @method Writer boldCyanBgBlack($text, $eol = false)
59
 * @method Writer boldCyanBgBlue($text, $eol = false)
60
 * @method Writer boldCyanBgGreen($text, $eol = false)
61
 * @method Writer boldCyanBgPurple($text, $eol = false)
62
 * @method Writer boldCyanBgRed($text, $eol = false)
63
 * @method Writer boldCyanBgWhite($text, $eol = false)
64
 * @method Writer boldCyanBgYellow($text, $eol = false)
65
 * @method Writer boldGreen($text, $eol = false)
66
 * @method Writer boldGreenBgBlack($text, $eol = false)
67
 * @method Writer boldGreenBgBlue($text, $eol = false)
68
 * @method Writer boldGreenBgCyan($text, $eol = false)
69
 * @method Writer boldGreenBgPurple($text, $eol = false)
70
 * @method Writer boldGreenBgRed($text, $eol = false)
71
 * @method Writer boldGreenBgWhite($text, $eol = false)
72
 * @method Writer boldGreenBgYellow($text, $eol = false)
73
 * @method Writer boldPurple($text, $eol = false)
74
 * @method Writer boldPurpleBgBlack($text, $eol = false)
75
 * @method Writer boldPurpleBgBlue($text, $eol = false)
76
 * @method Writer boldPurpleBgCyan($text, $eol = false)
77
 * @method Writer boldPurpleBgGreen($text, $eol = false)
78
 * @method Writer boldPurpleBgRed($text, $eol = false)
79
 * @method Writer boldPurpleBgWhite($text, $eol = false)
80
 * @method Writer boldPurpleBgYellow($text, $eol = false)
81
 * @method Writer boldRed($text, $eol = false)
82
 * @method Writer boldRedBgBlack($text, $eol = false)
83
 * @method Writer boldRedBgBlue($text, $eol = false)
84
 * @method Writer boldRedBgCyan($text, $eol = false)
85
 * @method Writer boldRedBgGreen($text, $eol = false)
86
 * @method Writer boldRedBgPurple($text, $eol = false)
87
 * @method Writer boldRedBgWhite($text, $eol = false)
88
 * @method Writer boldRedBgYellow($text, $eol = false)
89
 * @method Writer boldWhite($text, $eol = false)
90
 * @method Writer boldWhiteBgBlack($text, $eol = false)
91
 * @method Writer boldWhiteBgBlue($text, $eol = false)
92
 * @method Writer boldWhiteBgCyan($text, $eol = false)
93
 * @method Writer boldWhiteBgGreen($text, $eol = false)
94
 * @method Writer boldWhiteBgPurple($text, $eol = false)
95
 * @method Writer boldWhiteBgRed($text, $eol = false)
96
 * @method Writer boldWhiteBgYellow($text, $eol = false)
97
 * @method Writer boldYellow($text, $eol = false)
98
 * @method Writer boldYellowBgBlack($text, $eol = false)
99
 * @method Writer boldYellowBgBlue($text, $eol = false)
100
 * @method Writer boldYellowBgCyan($text, $eol = false)
101
 * @method Writer boldYellowBgGreen($text, $eol = false)
102
 * @method Writer boldYellowBgPurple($text, $eol = false)
103
 * @method Writer boldYellowBgRed($text, $eol = false)
104
 * @method Writer boldYellowBgWhite($text, $eol = false)
105
 * @method Writer colors($text)
106
 * @method Writer comment($text, $eol = false)
107
 * @method Writer cyan($text, $eol = false)
108
 * @method Writer cyanBgBlack($text, $eol = false)
109
 * @method Writer cyanBgBlue($text, $eol = false)
110
 * @method Writer cyanBgGreen($text, $eol = false)
111
 * @method Writer cyanBgPurple($text, $eol = false)
112
 * @method Writer cyanBgRed($text, $eol = false)
113
 * @method Writer cyanBgWhite($text, $eol = false)
114
 * @method Writer cyanBgYellow($text, $eol = false)
115
 * @method Writer error($text, $eol = false)
116
 * @method Writer green($text, $eol = false)
117
 * @method Writer greenBgBlack($text, $eol = false)
118
 * @method Writer greenBgBlue($text, $eol = false)
119
 * @method Writer greenBgCyan($text, $eol = false)
120
 * @method Writer greenBgPurple($text, $eol = false)
121
 * @method Writer greenBgRed($text, $eol = false)
122
 * @method Writer greenBgWhite($text, $eol = false)
123
 * @method Writer greenBgYellow($text, $eol = false)
124
 * @method Writer info($text, $eol = false)
125
 * @method Writer ok($text, $eol = false)
126
 * @method Writer purple($text, $eol = false)
127
 * @method Writer purpleBgBlack($text, $eol = false)
128
 * @method Writer purpleBgBlue($text, $eol = false)
129
 * @method Writer purpleBgCyan($text, $eol = false)
130
 * @method Writer purpleBgGreen($text, $eol = false)
131
 * @method Writer purpleBgRed($text, $eol = false)
132
 * @method Writer purpleBgWhite($text, $eol = false)
133
 * @method Writer purpleBgYellow($text, $eol = false)
134
 * @method Writer red($text, $eol = false)
135
 * @method Writer redBgBlack($text, $eol = false)
136
 * @method Writer redBgBlue($text, $eol = false)
137
 * @method Writer redBgCyan($text, $eol = false)
138
 * @method Writer redBgGreen($text, $eol = false)
139
 * @method Writer redBgPurple($text, $eol = false)
140
 * @method Writer redBgWhite($text, $eol = false)
141
 * @method Writer redBgYellow($text, $eol = false)
142
 * @method Writer warn($text, $eol = false)
143
 * @method Writer white($text, $eol = false)
144
 * @method Writer yellow($text, $eol = false)
145
 * @method Writer yellowBgBlack($text, $eol = false)
146
 * @method Writer yellowBgBlue($text, $eol = false)
147
 * @method Writer yellowBgCyan($text, $eol = false)
148
 * @method Writer yellowBgGreen($text, $eol = false)
149
 * @method Writer yellowBgPurple($text, $eol = false)
150
 * @method Writer yellowBgRed($text, $eol = false)
151
 * @method Writer yellowBgWhite($text, $eol = false)
152
 */
153
class Writer
154
{
155
    use InflectsString;
156
157
    /** @var resource Output file handle */
158
    protected $stream;
159
160
    /** @var resource Error output file handle */
161
    protected $eStream;
162
163
    /** @var string Write method to be relayed to Colorizer */
164
    protected $method;
165
166
    /** @var Color */
167
    protected $colorizer;
168
169
    /** @var Cursor */
170
    protected $cursor;
171
172
    public function __construct(string $path = null, Color $colorizer = null)
173
    {
174
        if ($path) {
175
            $path = \fopen($path, 'w');
176
        }
177
178
        $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...
179
        $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...
180
181
        $this->cursor    = new Cursor;
182
        $this->colorizer = $colorizer ?? new Color;
183
    }
184
185
    /**
186
     * Get Colorizer.
187
     *
188
     * @return Color
189
     */
190
    public function colorizer(): Color
191
    {
192
        return $this->colorizer;
193
    }
194
195
    /**
196
     * Magically set methods.
197
     *
198
     * @param string $name Like `red`, `bgRed`, 'bold', `error` etc
199
     *
200
     * @return self
201
     */
202
    public function __get(string $name): self
203
    {
204
        if (\strpos($this->method, $name) === false) {
205
            $this->method .= $this->method ? \ucfirst($name) : $name;
206
        }
207
208
        return $this;
209
    }
210
211
    /**
212
     * Write the formatted text to stdout or stderr.
213
     *
214
     * @param string $text
215
     * @param bool   $eol
216
     *
217
     * @return self
218
     */
219
    public function write(string $text, bool $eol = false): self
220
    {
221
        list($method, $this->method) = [$this->method ?: 'line', ''];
222
223
        $text  = $this->colorizer->{$method}($text, []);
224
        $error = \stripos($method, 'error') !== false;
225
226
        if ($eol) {
227
            $text .= \PHP_EOL;
228
        }
229
230
        return $this->doWrite($text, $error);
231
    }
232
233
    /**
234
     * Really write to the stream.
235
     *
236
     * @param string $text
237
     * @param bool   $error
238
     *
239
     * @return self
240
     */
241
    protected function doWrite(string $text, bool $error = false): self
242
    {
243
        $stream = $error ? $this->eStream : $this->stream;
244
245
        \fwrite($stream, $text);
246
247
        return $this;
248
    }
249
250
    /**
251
     * Write EOL n times.
252
     *
253
     * @param int $n
254
     *
255
     * @return self
256
     */
257
    public function eol(int $n = 1): self
258
    {
259
        return $this->doWrite(\str_repeat(PHP_EOL, \max($n, 1)));
260
    }
261
262
    /**
263
     * Write raw text (as it is).
264
     *
265
     * @param string $text
266
     * @param bool   $error
267
     *
268
     * @return self
269
     */
270
    public function raw($text, bool $error = false): self
271
    {
272
        return $this->doWrite((string) $text, $error);
273
    }
274
275
    /**
276
     * Generate table for the console. Keys of first row are taken as header.
277
     *
278
     * @param array[] $rows   Array of assoc arrays.
279
     * @param array   $styles Eg: ['head' => 'bold', 'odd' => 'comment', 'even' => 'green']
280
     *
281
     * @return self
282
     */
283
    public function table(array $rows, array $styles = []): self
284
    {
285
        if ([] === $table = $this->normalizeTable($rows)) {
286
            return $this;
287
        }
288
289
        list($head, $rows) = $table;
290
291
        $styles = $this->normalizeStyles($styles);
292
        $title  = $body = $dash = [];
293
294
        list($start, $end) = $styles['head'];
295
        foreach ($head as $col => $size) {
296
            $dash[]  = \str_repeat('-', $size + 2);
297
            $title[] = \str_pad($this->toWords($col), $size, ' ');
298
        }
299
300
        $title = "|$start " . \implode(" $end|$start ", $title) . " $end|" . \PHP_EOL;
301
302
        $odd = true;
303
        foreach ($rows as $row) {
304
            $parts = [];
305
306
            list($start, $end) = $styles[['even', 'odd'][(int) $odd]];
307
            foreach ($head as $col => $size) {
308
                $parts[] = \str_pad($row[$col] ?? '', $size, ' ');
309
            }
310
311
            $odd    = !$odd;
0 ignored issues
show
introduced by
The condition $odd is always true.
Loading history...
312
            $body[] = "|$start " . \implode(" $end|$start ", $parts) . " $end|";
313
        }
314
315
        $dash  = '+' . \implode('+', $dash) . '+' . \PHP_EOL;
316
        $body  = \implode(\PHP_EOL, $body) . \PHP_EOL;
317
318
        return $this->colors("$dash$title$dash$body$dash");
319
    }
320
321
    protected function normalizeTable(array $rows): array
322
    {
323
        $head = \reset($rows);
324
        if (empty($head)) {
325
            return [];
326
        }
327
328
        if (!\is_array($head)) {
329
            throw new InvalidArgumentException(
330
                \sprintf('Rows must be array of assoc arrays, %s given', \gettype($head))
331
            );
332
        }
333
334
        $head = \array_fill_keys(\array_keys($head), null);
335
        foreach ($rows as $i => &$row) {
336
            $row = \array_merge($head, $row);
337
        }
338
339
        foreach ($head as $col => &$value) {
340
            $cols   = \array_column($rows, $col);
341
            $span   = \array_map('strlen', $cols);
342
            $span[] = \strlen($col);
343
            $value  = \max($span);
344
        }
345
346
        return [$head, $rows];
347
    }
348
349
    protected function normalizeStyles(array $styles)
350
    {
351
        $default = [
352
            // styleFor => ['styleStartFn', 'end']
353
            'head' => ['', ''],
354
            'odd'  => ['', ''],
355
            'even' => ['', ''],
356
        ];
357
358
        foreach ($styles as $for => $style) {
359
            if (isset($default[$for])) {
360
                $default[$for] = ['<' . \trim($style, '<> ') . '>', '</end>'];
361
            }
362
        }
363
364
        return $default;
365
    }
366
367
    /**
368
     * Write to stdout or stderr magically.
369
     *
370
     * @param string $method
371
     * @param array  $arguments
372
     *
373
     * @return self
374
     */
375
    public function __call(string $method, array $arguments): self
376
    {
377
        if (\method_exists($this->cursor, $method)) {
378
            return $this->doWrite($this->cursor->{$method}(...$arguments));
379
        }
380
381
        $this->method = $method;
382
383
        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

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