Completed
Push — master ( d97bf1...ae7c83 )
by ignace nyamagana
04:39
created

GanttChartConfig::filterUnicodeCharacter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * League.Period (https://period.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Period;
15
16
use League\Period\Chart\ConsoleOutput;
17
use League\Period\Chart\Output;
18
use function array_filter;
19
use function array_map;
20
use function mb_convert_encoding;
21
use function mb_strlen;
22
use function preg_match;
23
use function preg_replace;
24
use function sprintf;
25
use const STDOUT;
26
use const STR_PAD_BOTH;
27
use const STR_PAD_LEFT;
28
use const STR_PAD_RIGHT;
29
30
/**
31
 * A class to configure the console output settings.
32
 */
33
final class GanttChartConfig
34
{
35
    private const REGEXP_UNICODE = '/\\\\u(?<unicode>[0-9A-F]{1,4})/i';
36
37
    public const ALIGN_LEFT = STR_PAD_RIGHT;
38
39
    public const ALIGN_RIGHT = STR_PAD_LEFT;
40
41
    public const ALIGN_CENTER = STR_PAD_BOTH;
42
43
    /**
44
     * @var Output
45
     */
46
    private $output;
47
48
    /**
49
     * @var string[]
50
     */
51
    private $colors = [Output::COLOR_DEFAULT];
52
53
    /**
54
     * @var int
55
     */
56
    private $width = 60;
57
58
    /**
59
     * @var string
60
     */
61
    private $endExcludedChar = ')';
62
63
    /**
64
     * @var string
65
     */
66
    private $endIncludedChar = ']';
67
68
    /**
69
     * @var string
70
     */
71
    private $startExcludedChar = '(';
72
73
    /**
74
     * @var string
75
     */
76
    private $startIncludedChar = '[';
77
78
    /**
79
     * @var string
80
     */
81
    private $body = '-';
82
83
    /**
84
     * @var string
85
     */
86
    private $space = ' ';
87
88
    /**
89
     * @var int
90
     */
91
    private $leftMarginSize = 1;
92
93
    /**
94
     * @var int
95
     */
96
    private $gapSize = 1;
97
98
    /**
99
     * @var int
100
     */
101
    private $alignLabel = self::ALIGN_LEFT;
102
103
    /**
104
     * New instance.
105
     *
106
     * @param ?Output $output
107
     */
108 297
    public function __construct(?Output $output = null)
109
    {
110 297
        $this->output = $output ?? new ConsoleOutput(STDOUT);
111 297
    }
112
113
    /**
114
     * Create a Cli Renderer to Display the millipede in Rainbow.
115
     *
116
     * @param ?Output $output
117
     */
118 3
    public static function createFromRandom(?Output $output = null): self
119
    {
120 3
        $index = array_rand(Output::COLORS);
121
122 3
        $config = new self($output);
123 3
        $config->colors = [Output::COLORS[$index]];
124
125 3
        return $config;
126
    }
127
128
    /**
129
     * Create a Cli Renderer to Display the millipede in Rainbow.
130
     *
131
     * @param ?Output $output
132
     */
133 3
    public static function createFromRainbow(?Output $output = null): self
134
    {
135 3
        $config = new self($output);
136 3
        $config->colors = Output::COLORS;
137
138 3
        return $config;
139
    }
140
141
    /**
142
     * Returns the Output class.
143
     */
144 3
    public function output(): Output
145
    {
146 3
        return $this->output;
147
    }
148
149
    /**
150
     * Retrieves the start excluded block character.
151
     */
152 42
    public function startExcluded(): string
153
    {
154 42
        return $this->startExcludedChar;
155
    }
156
    /**
157
     * Retrieves the start included block character.
158
     */
159 42
    public function startIncluded(): string
160
    {
161 42
        return $this->startIncludedChar;
162
    }
163
164
    /**
165
     * Retrieves the excluded end block character.
166
     */
167 42
    public function endExcluded(): string
168
    {
169 42
        return $this->endExcludedChar;
170
    }
171
172
    /**
173
     * Retrieves the excluded end block character.
174
     */
175 42
    public function endIncluded(): string
176
    {
177 42
        return $this->endIncludedChar;
178
    }
179
180
    /**
181
     * Retrieves the row width.
182
     */
183 15
    public function width(): int
184
    {
185 15
        return $this->width;
186
    }
187
188
    /**
189
     * Retrieves the body block character.
190
     */
191 42
    public function body(): string
192
    {
193 42
        return $this->body;
194
    }
195
196
    /**
197
     * Retrieves the row space character.
198
     */
199 42
    public function space(): string
200
    {
201 42
        return $this->space;
202
    }
203
204
    /**
205
     * The selected colors for each rows.
206
     *
207
     * @return string[]
208
     */
209 12
    public function colors(): array
210
    {
211 12
        return $this->colors;
212
    }
213
214
    /**
215
     * Retrieves the left margin size before the label name.
216
     */
217 9
    public function leftMarginSize(): int
218
    {
219 9
        return $this->leftMarginSize;
220
    }
221
222
    /**
223
     * Retrieves the gap size between the label and the line.
224
     */
225 12
    public function gapSize(): int
226
    {
227 12
        return $this->gapSize;
228
    }
229
230
    /**
231
     * Returns how label should be aligned.
232
     */
233 12
    public function labelAlign(): int
234
    {
235 12
        return $this->alignLabel;
236
    }
237
238
    /**
239
     * Return an instance with the start excluded pattern.
240
     *
241
     * This method MUST retain the state of the current instance, and return
242
     * an instance that contains the specified start excluded character.
243
     */
244 39
    public function withStartExcluded(string $startExcludedChar): self
245
    {
246 39
        $startExcludedChar = $this->filterPattern($startExcludedChar, 'startExcluded');
247 39
        if ($startExcludedChar === $this->startExcludedChar) {
248 3
            return $this;
249
        }
250
251 36
        $clone = clone $this;
252 36
        $clone->startExcludedChar = $startExcludedChar;
253
254 36
        return $clone;
255
    }
256
257
    /**
258
     * Filter the submitted string.
259
     *
260
     * @throws \InvalidArgumentException if the pattern is invalid
261
     */
262 240
    private function filterPattern(string $str, string $part): string
263
    {
264 240
        if (1 === mb_strlen($str)) {
265 216
            return $str;
266
        }
267
268 24
        if (1 === preg_match(self::REGEXP_UNICODE, $str)) {
269 21
            return $this->filterUnicodeCharacter($str);
270
        }
271
272 3
        throw new \InvalidArgumentException(sprintf('The %s pattern must be a single character', $part));
273
    }
274
275
    /**
276
     * Decode unicode characters.
277
     *
278
     * @see http://stackoverflow.com/a/37415135/2316257
279
     *
280
     * @throws \InvalidArgumentException if the character is not valid.
281
     */
282 21
    private function filterUnicodeCharacter(string $str): string
283
    {
284 21
        $replaced = (string) preg_replace(self::REGEXP_UNICODE, '&#x$1;', $str);
285 21
        $result = mb_convert_encoding($replaced, 'UTF-16', 'HTML-ENTITIES');
286 21
        $result = mb_convert_encoding($result, 'UTF-8', 'UTF-16');
287 21
        if (1 === mb_strlen($result)) {
288 18
            return $result;
289
        }
290
291 3
        throw new \InvalidArgumentException(sprintf('The given string `%s` is not a valid unicode string', $str));
292
    }
293
294
    /**
295
     * Return an instance with a new output object.
296
     *
297
     * This method MUST retain the state of the current instance, and return
298
     * an instance that contains the specified output class.
299
     */
300 3
    public function withOutput(Output $output): self
301
    {
302 3
        $clone = clone $this;
303 3
        $clone->output = $output;
304
305 3
        return $clone;
306
    }
307
308
    /**
309
     * Return an instance with the start included pattern.
310
     *
311
     * This method MUST retain the state of the current instance, and return
312
     * an instance that contains the specified start included character.
313
     */
314 39
    public function withStartIncluded(string $startIncludedChar): self
315
    {
316 39
        $startIncludedChar = $this->filterPattern($startIncludedChar, 'startIncluded');
317 39
        if ($startIncludedChar === $this->startIncludedChar) {
318 3
            return $this;
319
        }
320
321 36
        $clone = clone $this;
322 36
        $clone->startIncludedChar = $startIncludedChar;
323
324 36
        return $clone;
325
    }
326
327
    /**
328
     * Return an instance with the end excluded pattern.
329
     *
330
     * This method MUST retain the state of the current instance, and return
331
     * an instance that contains the specified end excluded character.
332
     */
333 39
    public function withEndExcluded(string $endExcludedChar): self
334
    {
335 39
        $endExcludedChar = $this->filterPattern($endExcludedChar, 'endExcluded');
336 39
        if ($endExcludedChar === $this->endExcludedChar) {
337 3
            return $this;
338
        }
339
340 36
        $clone = clone $this;
341 36
        $clone->endExcludedChar = $endExcludedChar;
342
343 36
        return $clone;
344
    }
345
346
    /**
347
     * Return an instance with the end included pattern.
348
     *
349
     * This method MUST retain the state of the current instance, and return
350
     * an instance that contains the specified end included character.
351
     */
352 39
    public function withEndIncluded(string $endIncludedChar): self
353
    {
354 39
        $endIncludedChar = $this->filterPattern($endIncludedChar, 'endIncluded');
355 39
        if ($endIncludedChar === $this->endIncludedChar) {
356 3
            return $this;
357
        }
358
359 36
        $clone = clone $this;
360 36
        $clone->endIncludedChar = $endIncludedChar;
361
362 36
        return $clone;
363
    }
364
365
    /**
366
     * Return an instance with the specified row width.
367
     *
368
     * This method MUST retain the state of the current instance, and return
369
     * an instance that contains the specified width.
370
     */
371 12
    public function withWidth(int $width): self
372
    {
373 12
        if ($width < 10) {
374 6
            $width = 10;
375
        }
376
377 12
        if ($width === $this->width) {
378 3
            return $this;
379
        }
380
381 9
        $clone = clone $this;
382 9
        $clone->width = $width;
383
384 9
        return $clone;
385
    }
386
387
    /**
388
     * Return an instance with the specified body block.
389
     *
390
     * This method MUST retain the state of the current instance, and return
391
     * an instance that contains the specified body pattern.
392
     */
393 45
    public function withBody(string $bodyChar): self
394
    {
395 45
        $bodyChar = $this->filterPattern($bodyChar, 'body');
396 39
        if ($bodyChar === $this->body) {
397 3
            return $this;
398
        }
399
400 36
        $clone = clone $this;
401 36
        $clone->body = $bodyChar;
402
403 36
        return $clone;
404
    }
405
406
    /**
407
     * Return an instance with the space pattern.
408
     *
409
     * This method MUST retain the state of the current instance, and return
410
     * an instance that contains the specified space character.
411
     */
412 39
    public function withSpace(string $spaceChar): self
413
    {
414 39
        $spaceChar = $this->filterPattern($spaceChar, 'space');
415 39
        if ($spaceChar === $this->space) {
416 6
            return $this;
417
        }
418
419 33
        $clone = clone $this;
420 33
        $clone->space = $spaceChar;
421
422 33
        return $clone;
423
    }
424
425
    /**
426
     * Return an instance with a new color palette.
427
     *
428
     * This method MUST retain the state of the current instance, and return
429
     * an instance that contains the specified color palette.
430
     *
431
     * @param string... $colors
0 ignored issues
show
Documentation Bug introduced by
The doc comment string... at position 0 could not be parsed: Unknown type name 'string...' at position 0 in string....
Loading history...
432
     */
433 9
    public function withColors(string ...$colors): self
434
    {
435
        $filter = static function ($value): bool {
436 6
            return in_array($value, Output::COLORS, true);
437 9
        };
438
439 9
        $colors = array_filter(array_map('strtolower', $colors), $filter);
440 9
        if ([] === $colors) {
441 6
            $colors = [Output::COLOR_DEFAULT];
442
        }
443
444 9
        if ($colors === $this->colors) {
445 6
            return $this;
446
        }
447
448 3
        $clone = clone $this;
449 3
        $clone->colors = $colors;
450
451 3
        return $clone;
452
    }
453
454
    /**
455
     * Return an instance with a new left margin size.
456
     *
457
     * This method MUST retain the state of the current instance, and return
458
     * an instance that contains the specified left margin size.
459
     */
460 9
    public function withLeftMarginSize(int $leftMarginSize): self
461
    {
462 9
        if ($leftMarginSize  === $this->leftMarginSize) {
463 3
            return $this;
464
        }
465
466 6
        if ($leftMarginSize < 0) {
467 3
            $leftMarginSize = 1;
468
        }
469
470 6
        $clone = clone $this;
471 6
        $clone->leftMarginSize = $leftMarginSize;
472
473 6
        return $clone;
474
    }
475
    /**
476
     * Return an instance with a new gap size.
477
     *
478
     * This method MUST retain the state of the current instance, and return
479
     * an instance that contains the specified gap size.
480
     */
481 9
    public function withGapSize(int $gapSize): self
482
    {
483 9
        if ($gapSize === $this->gapSize) {
484 3
            return $this;
485
        }
486
487 6
        if ($gapSize < 0) {
488 3
            $gapSize = 1;
489
        }
490
491 6
        $clone = clone $this;
492 6
        $clone->gapSize = $gapSize;
493
494 6
        return $clone;
495
    }
496
497
    /**
498
     * Return an instance with a left padding.
499
     *
500
     * This method MUST retain the state of the current instance, and return
501
     * an instance that set a left padding to the line label.
502
     */
503 9
    public function withLabelAlign(int $align): self
504
    {
505 9
        if (!in_array($align, [STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH], true)) {
506 3
            $align = STR_PAD_RIGHT;
507
        }
508
509 9
        if ($this->alignLabel === $align) {
510 6
            return $this;
511
        }
512
513 3
        $clone = clone $this;
514 3
        $clone->alignLabel = $align;
515
516 3
        return $clone;
517
    }
518
}
519