MenuStyle   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 731
Duplicated Lines 0 %

Importance

Changes 32
Bugs 1 Features 1
Metric Value
eloc 290
c 32
b 1
f 1
dl 0
loc 731
rs 2.8
wmc 70

53 Methods

Rating   Name   Duplication   Size   Complexity  
A getDisplaysExtra() 0 3 1
A getContentWidth() 0 3 1
A getColoursResetCode() 0 3 1
A getItemExtra() 0 3 1
A getMarker() 0 3 2
A getInvertedColoursSetCode() 0 3 1
A getTitleSeparator() 0 3 1
A getFg() 0 3 1
A getColoursSetCode() 0 3 1
A getUnselectedMarker() 0 3 1
A getSelectedMarker() 0 3 1
A setTitleSeparator() 0 5 1
A setUnselectedMarker() 0 5 1
A setSelectedMarker() 0 5 1
A setDisplaysExtra() 0 5 1
A setItemExtra() 0 5 1
A hasChangedFromDefaults() 0 23 1
A setMargin() 0 11 1
A getBorderLeftWidth() 0 3 1
B setBorder() 0 35 6
A getWidth() 0 3 1
A getBg() 0 3 1
A setPadding() 0 13 2
A getBorderRightWidth() 0 3 1
A setFg() 0 10 1
A getBorderColour() 0 3 1
A getMargin() 0 3 1
A calculateContentWidth() 0 8 2
A setPaddingTopBottom() 0 8 1
A generatePaddingTopBottomRows() 0 23 3
A setPaddingLeftRight() 0 9 1
A getBorderTopRows() 0 3 1
A getBorderTopWidth() 0 3 1
A getRightHandPadding() 0 9 2
A setWidth() 0 18 3
A setBorderLeftWidth() 0 8 1
A setBorderColour() 0 12 1
A __construct() 0 23 2
A setBorderBottomWidth() 0 7 1
A setBorderTopWidth() 0 7 1
A generateColoursSetCode() 0 15 3
A getBorderColourCode() 0 9 2
A getInvertedColoursUnsetCode() 0 3 1
A getPaddingTopBottom() 0 3 1
A setBg() 0 12 1
A getPaddingLeftRight() 0 3 1
A getBorderBottomWidth() 0 3 1
A getDisabledItemText() 0 7 1
A setBorderRightWidth() 0 8 1
A getBorderBottomRows() 0 3 1
A getPaddingTopBottomRows() 0 3 1
A generateBorderRows() 0 12 1
A setMarginAuto() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like MenuStyle often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MenuStyle, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpSchool\CliMenu;
4
5
use PhpSchool\CliMenu\Terminal\TerminalFactory;
6
use PhpSchool\CliMenu\Util\ColourUtil;
7
use PhpSchool\Terminal\Terminal;
8
use Assert\Assertion;
9
10
//TODO: B/W fallback
11
12
/**
13
 * @author Michael Woodward <[email protected]>
14
 */
15
class MenuStyle
16
{
17
    /**
18
     * @var Terminal
19
     */
20
    protected $terminal;
21
22
    /**
23
     * @var string
24
     */
25
    protected $fg;
26
27
    /**
28
     * @var string
29
     */
30
    protected $bg;
31
32
    /**
33
     * @var int
34
     */
35
    protected $width;
36
37
    /**
38
     * @var int
39
     */
40
    protected $margin;
41
42
    /**
43
     * @var int
44
     */
45
    protected $paddingTopBottom;
46
47
    /**
48
     * @var int
49
     */
50
    protected $paddingLeftRight;
51
52
    /**
53
     * @var array
54
     */
55
    private $paddingTopBottomRows = [];
56
57
    /**
58
     * @var int
59
     */
60
    protected $contentWidth;
61
62
    /**
63
     * @var string
64
     */
65
    private $selectedMarker;
66
67
    /**
68
     * @var string
69
     */
70
    private $unselectedMarker;
71
72
    /**
73
     * @var string
74
     */
75
    private $itemExtra;
76
77
    /**
78
     * @var bool
79
     */
80
    private $displaysExtra;
81
82
    /**
83
     * @var string
84
     */
85
    private $titleSeparator;
86
87
    /**
88
     * @var string
89
     */
90
    private $coloursSetCode;
91
92
    /**
93
     * @var string
94
     */
95
    private $invertedColoursSetCode = "\033[7m";
96
97
    /**
98
     * @var string
99
     */
100
    private $invertedColoursUnsetCode = "\033[27m";
101
102
    /**
103
     * @var string
104
     */
105
    private $coloursResetCode = "\033[0m";
106
107
    /**
108
     * @var int
109
     */
110
    private $borderTopWidth;
111
112
    /**
113
     * @var int
114
     */
115
    private $borderRightWidth;
116
117
    /**
118
     * @var int
119
     */
120
    private $borderBottomWidth;
121
122
    /**
123
     * @var int
124
     */
125
    private $borderLeftWidth;
126
127
    /**
128
     * @var string
129
     */
130
    private $borderColour = 'white';
131
132
    /**
133
     * @var array
134
     */
135
    private $borderTopRows = [];
136
137
    /**
138
     * @var array
139
     */
140
    private $borderBottomRows = [];
141
142
    /**
143
     * @var bool
144
     */
145
    private $marginAuto = false;
146
147
    /**
148
     * Default Values
149
     *
150
     * @var array
151
     */
152
    private static $defaultStyleValues = [
153
        'fg' => 'white',
154
        'bg' => 'blue',
155
        'width' => 100,
156
        'paddingTopBottom' => 1,
157
        'paddingLeftRight' => 2,
158
        'margin' => 2,
159
        'selectedMarker' => '● ',
160
        'unselectedMarker' => '○ ',
161
        'itemExtra' => '✔',
162
        'displaysExtra' => false,
163
        'titleSeparator' => '=',
164
        'borderTopWidth' => 0,
165
        'borderRightWidth' => 0,
166
        'borderBottomWidth' => 0,
167
        'borderLeftWidth' => 0,
168
        'borderColour' => 'white',
169
        'marginAuto' => false,
170
    ];
171
172
    /**
173
     * @var array
174
     */
175
    private static $availableForegroundColors = array(
176
        'black'   => 30,
177
        'red'     => 31,
178
        'green'   => 32,
179
        'yellow'  => 33,
180
        'blue'    => 34,
181
        'magenta' => 35,
182
        'cyan'    => 36,
183
        'white'   => 37,
184
        'default' => 39,
185
    );
186
187
    /**
188
     * @var array
189
     */
190
    private static $availableBackgroundColors = array(
191
        'black'   => 40,
192
        'red'     => 41,
193
        'green'   => 42,
194
        'yellow'  => 43,
195
        'blue'    => 44,
196
        'magenta' => 45,
197
        'cyan'    => 46,
198
        'white'   => 47,
199
        'default' => 49,
200
    );
201
202
    /**
203
     * @var array
204
     */
205
    private static $availableOptions = array(
206
        'bold'       => array('set' => 1, 'unset' => 22),
207
        'dim'        => array('set' => 2, 'unset' => 22),
208
        'underscore' => array('set' => 4, 'unset' => 24),
209
        'blink'      => array('set' => 5, 'unset' => 25),
210
        'reverse'    => array('set' => 7, 'unset' => 27),
211
        'conceal'    => array('set' => 8, 'unset' => 28)
212
    );
213
214
    /**
215
     * Initialise style
216
     */
217
    public function __construct(Terminal $terminal = null)
218
    {
219
        $this->terminal = $terminal ?: TerminalFactory::fromSystem();
220
221
        $this->fg = self::$defaultStyleValues['fg'];
222
        $this->bg = self::$defaultStyleValues['bg'];
223
224
        $this->generateColoursSetCode();
225
226
        $this->setWidth(self::$defaultStyleValues['width']);
227
        $this->setPaddingTopBottom(self::$defaultStyleValues['paddingTopBottom']);
228
        $this->setPaddingLeftRight(self::$defaultStyleValues['paddingLeftRight']);
229
        $this->setMargin(self::$defaultStyleValues['margin']);
230
        $this->setSelectedMarker(self::$defaultStyleValues['selectedMarker']);
231
        $this->setUnselectedMarker(self::$defaultStyleValues['unselectedMarker']);
232
        $this->setItemExtra(self::$defaultStyleValues['itemExtra']);
233
        $this->setDisplaysExtra(self::$defaultStyleValues['displaysExtra']);
234
        $this->setTitleSeparator(self::$defaultStyleValues['titleSeparator']);
235
        $this->setBorderTopWidth(self::$defaultStyleValues['borderTopWidth']);
236
        $this->setBorderRightWidth(self::$defaultStyleValues['borderRightWidth']);
237
        $this->setBorderBottomWidth(self::$defaultStyleValues['borderBottomWidth']);
238
        $this->setBorderLeftWidth(self::$defaultStyleValues['borderLeftWidth']);
239
        $this->setBorderColour(self::$defaultStyleValues['borderColour']);
240
    }
241
242
    public function hasChangedFromDefaults() : bool
243
    {
244
        $currentValues = [
245
            $this->fg,
246
            $this->bg,
247
            $this->width,
248
            $this->paddingTopBottom,
249
            $this->paddingLeftRight,
250
            $this->margin,
251
            $this->selectedMarker,
252
            $this->unselectedMarker,
253
            $this->itemExtra,
254
            $this->displaysExtra,
255
            $this->titleSeparator,
256
            $this->borderTopWidth,
257
            $this->borderRightWidth,
258
            $this->borderBottomWidth,
259
            $this->borderLeftWidth,
260
            $this->borderColour,
261
            $this->marginAuto,
262
        ];
263
                
264
        return $currentValues !== array_values(self::$defaultStyleValues);
265
    }
266
267
    public function getDisabledItemText(string $text) : string
268
    {
269
        return sprintf(
270
            "\033[%sm%s\033[%sm",
271
            self::$availableOptions['dim']['set'],
272
            $text,
273
            self::$availableOptions['dim']['unset']
274
        );
275
    }
276
277
    /**
278
     * Generates the ansi escape sequence to set the colours
279
     */
280
    private function generateColoursSetCode() : void
281
    {
282
        if (!ctype_digit($this->fg)) {
283
            $fgCode = self::$availableForegroundColors[$this->fg];
284
        } else {
285
            $fgCode = sprintf("38;5;%s", $this->fg);
286
        }
287
288
        if (!ctype_digit($this->bg)) {
289
            $bgCode = self::$availableBackgroundColors[$this->bg];
290
        } else {
291
            $bgCode = sprintf("48;5;%s", $this->bg);
292
        }
293
294
        $this->coloursSetCode = sprintf("\033[%s;%sm", $fgCode, $bgCode);
295
    }
296
297
    /**
298
     * Get the colour code for Bg and Fg
299
     */
300
    public function getColoursSetCode() : string
301
    {
302
        return $this->coloursSetCode;
303
    }
304
305
    /**
306
     * Get the inverted escape sequence (used for selected elements)
307
     */
308
    public function getInvertedColoursSetCode() : string
309
    {
310
        return $this->invertedColoursSetCode;
311
    }
312
313
    /**
314
     * Get the inverted escape sequence (used for selected elements)
315
     */
316
    public function getInvertedColoursUnsetCode() : string
317
    {
318
        return $this->invertedColoursUnsetCode;
319
    }
320
321
    /**
322
     * Get the escape sequence used to reset colours to default
323
     */
324
    public function getColoursResetCode() : string
325
    {
326
        return $this->coloursResetCode;
327
    }
328
329
    /**
330
     * Calculate the contents width
331
     */
332
    protected function calculateContentWidth() : void
333
    {
334
        $this->contentWidth = $this->width
335
            - ($this->paddingLeftRight * 2)
336
            - ($this->borderRightWidth + $this->borderLeftWidth);
337
338
        if ($this->contentWidth < 0) {
339
            $this->contentWidth = 0;
340
        }
341
    }
342
343
    public function getFg()
344
    {
345
        return $this->fg;
346
    }
347
348
    public function setFg(string $fg, string $fallback = null) : self
349
    {
350
        $this->fg = ColourUtil::validateColour(
351
            $this->terminal,
352
            $fg,
353
            $fallback
354
        );
355
        $this->generateColoursSetCode();
356
357
        return $this;
358
    }
359
360
    public function getBg()
361
    {
362
        return $this->bg;
363
    }
364
365
    public function setBg(string $bg, string $fallback = null) : self
366
    {
367
        $this->bg = ColourUtil::validateColour(
368
            $this->terminal,
369
            $bg,
370
            $fallback
371
        );
372
373
        $this->generateColoursSetCode();
374
        $this->generatePaddingTopBottomRows();
375
376
        return $this;
377
    }
378
379
    public function getWidth() : int
380
    {
381
        return $this->width;
382
    }
383
384
    public function setWidth(int $width) : self
385
    {
386
        Assertion::greaterOrEqualThan($width, 0);
387
388
        if ($width >= $this->terminal->getWidth()) {
389
            $width = $this->terminal->getWidth();
390
        }
391
392
        $this->width = $width;
393
        if ($this->marginAuto) {
394
            $this->setMarginAuto();
395
        }
396
397
        $this->calculateContentWidth();
398
        $this->generateBorderRows();
399
        $this->generatePaddingTopBottomRows();
400
401
        return $this;
402
    }
403
404
    public function getPaddingTopBottom() : int
405
    {
406
        return $this->paddingTopBottom;
407
    }
408
409
    public function getPaddingLeftRight() : int
410
    {
411
        return $this->paddingLeftRight;
412
    }
413
414
    private function generatePaddingTopBottomRows() : void
415
    {
416
        if ($this->borderLeftWidth || $this->borderRightWidth) {
417
            $borderColour = $this->getBorderColourCode();
418
        } else {
419
            $borderColour = '';
420
        }
421
422
        $paddingRow = sprintf(
423
            "%s%s%s%s%s%s%s%s%s%s\n",
424
            str_repeat(' ', $this->margin),
425
            $borderColour,
426
            str_repeat(' ', $this->borderLeftWidth),
427
            $this->getColoursSetCode(),
428
            str_repeat(' ', $this->paddingLeftRight),
429
            str_repeat(' ', $this->contentWidth),
430
            str_repeat(' ', $this->paddingLeftRight),
431
            $borderColour,
432
            str_repeat(' ', $this->borderRightWidth),
433
            $this->coloursResetCode
434
        );
435
436
        $this->paddingTopBottomRows = array_fill(0, $this->paddingTopBottom, $paddingRow);
437
    }
438
439
    public function getPaddingTopBottomRows() : array
440
    {
441
        return $this->paddingTopBottomRows;
442
    }
443
444
    public function setPadding(int $topBottom, int $leftRight = null) : self
445
    {
446
        if ($leftRight === null) {
447
            $leftRight = $topBottom;
448
        }
449
450
        $this->setPaddingTopBottom($topBottom);
451
        $this->setPaddingLeftRight($leftRight);
452
453
        $this->calculateContentWidth();
454
        $this->generatePaddingTopBottomRows();
455
456
        return $this;
457
    }
458
459
    public function setPaddingTopBottom(int $topBottom) : self
460
    {
461
        Assertion::greaterOrEqualThan($topBottom, 0);
462
        $this->paddingTopBottom = $topBottom;
463
464
        $this->generatePaddingTopBottomRows();
465
466
        return $this;
467
    }
468
469
    public function setPaddingLeftRight(int $leftRight) : self
470
    {
471
        Assertion::greaterOrEqualThan($leftRight, 0);
472
        $this->paddingLeftRight = $leftRight;
473
474
        $this->calculateContentWidth();
475
        $this->generatePaddingTopBottomRows();
476
477
        return $this;
478
    }
479
480
    public function getMargin() : int
481
    {
482
        return $this->margin;
483
    }
484
485
    public function setMarginAuto() : self
486
    {
487
        $this->marginAuto = true;
488
        $this->margin = (int) floor(($this->terminal->getWidth() - $this->width) / 2);
489
490
        $this->generateBorderRows();
491
        $this->generatePaddingTopBottomRows();
492
493
        return $this;
494
    }
495
496
    public function setMargin(int $margin) : self
497
    {
498
        Assertion::greaterOrEqualThan($margin, 0);
499
500
        $this->marginAuto = false;
501
        $this->margin = $margin;
502
503
        $this->generateBorderRows();
504
        $this->generatePaddingTopBottomRows();
505
506
        return $this;
507
    }
508
509
    public function getContentWidth() : int
510
    {
511
        return $this->contentWidth;
512
    }
513
514
    /**
515
     * Get padding for right had side of content
516
     */
517
    public function getRightHandPadding(int $contentLength) : int
518
    {
519
        $rightPadding = $this->getContentWidth() - $contentLength + $this->getPaddingLeftRight();
520
521
        if ($rightPadding < 0) {
522
            $rightPadding = 0;
523
        }
524
525
        return $rightPadding;
526
    }
527
528
    public function getSelectedMarker() : string
529
    {
530
        return $this->selectedMarker;
531
    }
532
533
    public function setSelectedMarker(string $marker) : self
534
    {
535
        $this->selectedMarker = $marker;
536
537
        return $this;
538
    }
539
540
    public function getUnselectedMarker() : string
541
    {
542
        return $this->unselectedMarker;
543
    }
544
545
    public function setUnselectedMarker(string $marker) : self
546
    {
547
        $this->unselectedMarker = $marker;
548
549
        return $this;
550
    }
551
552
    /**
553
     * Get the correct marker for the item
554
     */
555
    public function getMarker(bool $selected) : string
556
    {
557
        return $selected ? $this->selectedMarker : $this->unselectedMarker;
558
    }
559
560
    public function setItemExtra(string $itemExtra) : self
561
    {
562
        $this->itemExtra = $itemExtra;
563
564
        return $this;
565
    }
566
567
    public function getItemExtra() : string
568
    {
569
        return $this->itemExtra;
570
    }
571
572
    public function getDisplaysExtra() : bool
573
    {
574
        return $this->displaysExtra;
575
    }
576
577
    public function setDisplaysExtra(bool $displaysExtra) : self
578
    {
579
        $this->displaysExtra = $displaysExtra;
580
581
        return $this;
582
    }
583
584
    public function getTitleSeparator() : string
585
    {
586
        return $this->titleSeparator;
587
    }
588
589
    public function setTitleSeparator(string $actionSeparator) : self
590
    {
591
        $this->titleSeparator = $actionSeparator;
592
593
        return $this;
594
    }
595
596
    private function generateBorderRows() : void
597
    {
598
        $borderRow = sprintf(
599
            "%s%s%s%s\n",
600
            str_repeat(' ', $this->margin),
601
            $this->getBorderColourCode(),
602
            str_repeat(' ', $this->width),
603
            $this->coloursResetCode
604
        );
605
606
        $this->borderTopRows = array_fill(0, $this->borderTopWidth, $borderRow);
607
        $this->borderBottomRows = array_fill(0, $this->borderBottomWidth, $borderRow);
608
    }
609
610
    public function getBorderTopRows() : array
611
    {
612
        return $this->borderTopRows;
613
    }
614
615
    public function getBorderBottomRows() : array
616
    {
617
        return $this->borderBottomRows;
618
    }
619
620
    /**
621
     * Shorthand function to set all borders values at once
622
     */
623
    public function setBorder(
624
        int $topWidth,
625
        $rightWidth = null,
626
        $bottomWidth = null,
627
        $leftWidth = null,
628
        string $colour = null
629
    ) : self {
630
        if (!is_int($rightWidth)) {
631
            $colour = $rightWidth;
632
            $rightWidth = $bottomWidth = $leftWidth = $topWidth;
633
        } elseif (!is_int($bottomWidth)) {
634
            $colour = $bottomWidth;
635
            $bottomWidth = $topWidth;
636
            $leftWidth = $rightWidth;
637
        } elseif (!is_int($leftWidth)) {
638
            $colour = $leftWidth;
639
            $leftWidth = $rightWidth;
640
        }
641
642
        $this->borderTopWidth = $topWidth;
643
        $this->borderRightWidth = $rightWidth;
644
        $this->borderBottomWidth = $bottomWidth;
645
        $this->borderLeftWidth = $leftWidth;
646
647
        if (is_string($colour)) {
648
            $this->setBorderColour($colour);
649
        } elseif ($colour !== null) {
650
            throw new \InvalidArgumentException('Invalid colour');
651
        }
652
653
        $this->calculateContentWidth();
654
        $this->generateBorderRows();
655
        $this->generatePaddingTopBottomRows();
656
657
        return $this;
658
    }
659
660
    public function setBorderTopWidth(int $width) : self
661
    {
662
        $this->borderTopWidth = $width;
663
664
        $this->generateBorderRows();
665
666
        return $this;
667
    }
668
669
    public function setBorderRightWidth(int $width) : self
670
    {
671
        $this->borderRightWidth = $width;
672
        $this->calculateContentWidth();
673
674
        $this->generatePaddingTopBottomRows();
675
676
        return $this;
677
    }
678
679
    public function setBorderBottomWidth(int $width) : self
680
    {
681
        $this->borderBottomWidth = $width;
682
683
        $this->generateBorderRows();
684
685
        return $this;
686
    }
687
688
    public function setBorderLeftWidth(int $width) : self
689
    {
690
        $this->borderLeftWidth = $width;
691
        $this->calculateContentWidth();
692
693
        $this->generatePaddingTopBottomRows();
694
695
        return $this;
696
    }
697
698
    public function setBorderColour(string $colour, $fallback = null) : self
699
    {
700
        $this->borderColour = ColourUtil::validateColour(
701
            $this->terminal,
702
            $colour,
703
            $fallback
704
        );
705
706
        $this->generateBorderRows();
707
        $this->generatePaddingTopBottomRows();
708
709
        return $this;
710
    }
711
712
    public function getBorderTopWidth() : int
713
    {
714
        return $this->borderTopWidth;
715
    }
716
717
    public function getBorderRightWidth() : int
718
    {
719
        return $this->borderRightWidth;
720
    }
721
722
    public function getBorderBottomWidth() : int
723
    {
724
        return $this->borderBottomWidth;
725
    }
726
727
    public function getBorderLeftWidth() : int
728
    {
729
        return $this->borderLeftWidth;
730
    }
731
732
    public function getBorderColour() : string
733
    {
734
        return $this->borderColour;
735
    }
736
737
    public function getBorderColourCode() : string
738
    {
739
        if (!ctype_digit($this->borderColour)) {
740
            $borderColourCode = self::$availableBackgroundColors[$this->borderColour];
741
        } else {
742
            $borderColourCode = sprintf("48;5;%s", $this->borderColour);
743
        }
744
745
        return sprintf("\033[%sm", $borderColourCode);
746
    }
747
}
748