Passed
Pull Request — master (#203)
by
unknown
02:08
created

CliMenuBuilder::setPaddingLeftRight()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpSchool\CliMenu\Builder;
4
5
use Closure;
6
use PhpSchool\CliMenu\Action\ExitAction;
7
use PhpSchool\CliMenu\Action\GoBackAction;
8
use PhpSchool\CliMenu\Exception\InvalidShortcutException;
9
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
10
use PhpSchool\CliMenu\MenuItem\CheckableItem;
11
use PhpSchool\CliMenu\MenuItem\ItemStyleInterface;
12
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
13
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
14
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
15
use PhpSchool\CliMenu\MenuItem\RadioItem;
16
use PhpSchool\CliMenu\MenuItem\SelectableInterface;
17
use PhpSchool\CliMenu\MenuItem\SelectableItem;
18
use PhpSchool\CliMenu\CliMenu;
19
use PhpSchool\CliMenu\MenuItem\SplitItem;
20
use PhpSchool\CliMenu\MenuItem\StaticItem;
21
use PhpSchool\CliMenu\MenuStyle;
22
use PhpSchool\CliMenu\Style\CheckableStyle;
23
use PhpSchool\CliMenu\Style\RadioStyle;
24
use PhpSchool\CliMenu\Style\SelectableStyle;
25
use PhpSchool\CliMenu\Style\SplitStyle;
26
use PhpSchool\CliMenu\Terminal\TerminalFactory;
27
use PhpSchool\Terminal\Terminal;
28
29
/**
30
 * @author Michael Woodward <[email protected]>
31
 * @author Aydin Hassan <[email protected]>
32
 */
33
class CliMenuBuilder
34
{
35
    /**
36
     * @var CliMenu
37
     */
38
    private $menu;
39
40
    /**
41
     * @var string
42
     */
43
    private $goBackButtonText = 'Go Back';
44
45
    /**
46
     * @var string
47
     */
48
    private $exitButtonText = 'Exit';
49
50
    /**
51
     * @var MenuStyle
52
     */
53
    private $style;
54
55
    /**
56
     * @var Terminal
57
     */
58
    private $terminal;
59
60
    /**
61
     * @var bool
62
     */
63
    private $disableDefaultItems = false;
64
65
    /**
66
     * @var bool
67
     */
68
    private $disabled = false;
69
70
    /**
71
     * Whether or not to auto create keyboard shortcuts for items
72
     * when they contain square brackets. Eg: [M]y item
73
     *
74
     * @var bool
75
     */
76
    private $autoShortcuts = false;
77
78
    /**
79
     * Regex to auto match for shortcuts defaults to looking
80
     * for a single character encased in square brackets
81
     *
82
     * @var string
83
     */
84
    private $autoShortcutsRegex = '/\[(.)\]/';
85
86
    /**
87
     * @var bool
88
     */
89
    private $subMenu = false;
90
91
    public function __construct(Terminal $terminal = null)
92
    {
93
        $this->terminal = $terminal ?? TerminalFactory::fromSystem();
94
        $this->style    = new MenuStyle($this->terminal);
95
        $this->menu     = new CliMenu(null, [], $this->terminal, $this->style);
96
    }
97
    
98
    public static function newSubMenu(Terminal $terminal) : self
99
    {
100
        $instance = new self($terminal);
101
        $instance->subMenu = true;
102
        
103
        return $instance;
104
    }
105
106
    public function setTitle(string $title) : self
107
    {
108
        $this->menu->setTitle($title);
109
110
        return $this;
111
    }
112
113
    public function addMenuItem(MenuItemInterface $item) : self
114
    {
115
        $this->menu->addItem($item);
116
117
        $this->processItemShortcut($item);
118
119
        return $this;
120
    }
121
122
    public function addItem(
123
        string $text,
124
        callable $itemCallable,
125
        bool $showItemExtra = false,
126
        bool $disabled = false
127
    ) : self {
128
        $item = (new SelectableItem($text, $itemCallable, $showItemExtra, $disabled))
129
            ->setStyle($this->menu->getSelectableStyle());
130
131
        $this->addMenuItem($item);
132
133
        return $this;
134
    }
135
136
    public function addItems(array $items) : self
137
    {
138
        foreach ($items as $item) {
139
            $this->addItem(...$item);
0 ignored issues
show
Bug introduced by
The call to PhpSchool\CliMenu\Builde...iMenuBuilder::addItem() has too few arguments starting with itemCallable. ( Ignorable by Annotation )

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

139
            $this->/** @scrutinizer ignore-call */ 
140
                   addItem(...$item);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
140
        }
141
142
        return $this;
143
    }
144
145
    public function addCheckableItem(
146
        string $text,
147
        callable $itemCallable,
148
        bool $showItemExtra = false,
149
        bool $disabled = false
150
    ) : self {
151
        $item = (new CheckableItem($text, $itemCallable, $showItemExtra, $disabled))
152
            ->setStyle($this->menu->getCheckableStyle());
153
154
        $this->addMenuItem($item);
155
156
        return $this;
157
    }
158
159
    public function addRadioItem(
160
        string $text,
161
        callable $itemCallable,
162
        bool $showItemExtra = false,
163
        bool $disabled = false
164
    ) : self {
165
        $item = (new RadioItem($text, $itemCallable, $showItemExtra, $disabled))
166
            ->setStyle($this->menu->getRadioStyle());
167
168
        $this->addMenuItem($item);
169
170
        return $this;
171
    }
172
173
    public function addStaticItem(string $text) : self
174
    {
175
        $this->addMenuItem(new StaticItem($text));
176
177
        return $this;
178
    }
179
180
    public function addLineBreak(string $breakChar = ' ', int $lines = 1) : self
181
    {
182
        $this->addMenuItem(new LineBreakItem($breakChar, $lines));
183
184
        return $this;
185
    }
186
187
    public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self
188
    {
189
        $this->addMenuItem(new AsciiArtItem($art, $position, $alt));
190
191
        return $this;
192
    }
193
194
    public function addSubMenu(string $text, Closure $callback) : self
195
    {
196
        $builder = self::newSubMenu($this->terminal);
197
198
        if ($this->autoShortcuts) {
199
            $builder->enableAutoShortcuts($this->autoShortcutsRegex);
200
        }
201
202
        $callback($builder);
203
204
        $menu = $this->createMenuClosure($builder);
205
206
        $item = (new MenuMenuItem($text, $menu, $builder->isMenuDisabled()))
207
            ->setStyle($this->menu->getSelectableStyle());
208
209
        $this->menu->addItem($item);
210
211
        $this->processItemShortcut($item);
212
213
        return $this;
214
    }
215
216
    public function addSubMenuFromBuilder(string $text, CliMenuBuilder $builder) : self
217
    {
218
        $menu = $this->createMenuClosure($builder);
219
220
        $item = (new MenuMenuItem($text, $menu, $builder->isMenuDisabled()))
221
            ->setStyle($this->menu->getSelectableStyle());
222
223
        $this->menu->addItem($item);
224
225
        $this->processItemShortcut($item);
226
227
        return $this;
228
    }
229
230
    /**
231
     * Create the submenu as a closure which is then unpacked in MenuMenuItem::showSubMenu
232
     * This allows us to wait until all user-provided styles are parsed and apply them to nested items
233
     *
234
     * @param CliMenuBuilder|SplitItemBuilder $builder
235
     * @return Closure
236
     */
237
    protected function createMenuClosure($builder) : Closure
238
    {
239
        return function () use ($builder) {
240
            $menu = $builder->build();
241
242
            $menu->setParent($this->menu);
0 ignored issues
show
Bug introduced by
The method setParent() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

242
            $menu->/** @scrutinizer ignore-call */ 
243
                   setParent($this->menu);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
243
244
            // we apply the parent theme if nothing was changed
245
            // if no styles were changed in this sub-menu
246
            if (!$menu->getStyle()->hasChangedFromDefaults()) {
0 ignored issues
show
Bug introduced by
The method getStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

246
            if (!$menu->/** @scrutinizer ignore-call */ getStyle()->hasChangedFromDefaults()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
247
                $menu->setStyle($this->menu->getStyle());
0 ignored issues
show
Bug introduced by
The method setStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. Did you maybe mean setStyleCallback()? ( Ignorable by Annotation )

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

247
                $menu->/** @scrutinizer ignore-call */ 
248
                       setStyle($this->menu->getStyle());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
248
            }
249
250
            $menu->checkableStyle(function (CheckableStyle $style) {
0 ignored issues
show
Bug introduced by
The method checkableStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

250
            $menu->/** @scrutinizer ignore-call */ 
251
                   checkableStyle(function (CheckableStyle $style) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
251
                $style->fromArray($this->menu->getCheckableStyle()->toArray());
252
            });
253
254
            $menu->radioStyle(function (RadioStyle $style) {
0 ignored issues
show
Bug introduced by
The method radioStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

254
            $menu->/** @scrutinizer ignore-call */ 
255
                   radioStyle(function (RadioStyle $style) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
255
                $style->fromArray($this->menu->getRadioStyle()->toArray());
256
            });
257
258
            $menu->selectableStyle(function (SelectableStyle $style) {
0 ignored issues
show
Bug introduced by
The method selectableStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

258
            $menu->/** @scrutinizer ignore-call */ 
259
                   selectableStyle(function (SelectableStyle $style) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
259
                $style->fromArray($this->menu->getSelectableStyle()->toArray());
260
            });
261
262
            // This will be filled with user-provided items
263
            foreach ($menu->getItems() as $item) {
264
                // Only set style for compatible items
265
                if (!$item instanceof ItemStyleInterface) {
266
                    continue;
267
                }
268
269
                // If item has a custom style, skip overriding
270
                if ($item->getStyle()->getIsCustom()) {
271
                    continue;
272
                }
273
274
                if ($item instanceof CheckableStyle) {
275
                    $item->setStyle(clone $menu->getCheckableStyle());
0 ignored issues
show
Bug introduced by
The method getCheckableStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

275
                    $item->setStyle(clone $menu->/** @scrutinizer ignore-call */ getCheckableStyle());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
276
                }
277
278
                if ($item instanceof RadioStyle) {
279
                    $item->setStyle(clone $menu->getRadioStyle());
0 ignored issues
show
Bug introduced by
The method setStyle() does not exist on PhpSchool\CliMenu\Style\RadioStyle. ( Ignorable by Annotation )

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

279
                    $item->/** @scrutinizer ignore-call */ 
280
                           setStyle(clone $menu->getRadioStyle());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getRadioStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. ( Ignorable by Annotation )

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

279
                    $item->setStyle(clone $menu->/** @scrutinizer ignore-call */ getRadioStyle());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
280
                }
281
282
                if ($item instanceof SelectableInterface) {
283
                    $item->setStyle(clone $menu->getSelectableStyle());
0 ignored issues
show
Bug introduced by
The method getSelectableStyle() does not exist on PhpSchool\CliMenu\MenuItem\SplitItem. Did you maybe mean getSelectedItem()? ( Ignorable by Annotation )

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

283
                    $item->setStyle(clone $menu->/** @scrutinizer ignore-call */ getSelectableStyle());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
284
                }
285
            }
286
287
            return $menu;
288
        };
289
    }
290
291
    public function enableAutoShortcuts(string $regex = null) : self
292
    {
293
        $this->autoShortcuts = true;
294
295
        if (null !== $regex) {
296
            $this->autoShortcutsRegex = $regex;
297
        }
298
299
        return $this;
300
    }
301
302
    private function extractShortcut(string $title) : ?string
303
    {
304
        preg_match($this->autoShortcutsRegex, $title, $match);
305
306
        if (!isset($match[1])) {
307
            return null;
308
        }
309
310
        if (mb_strlen($match[1]) > 1) {
311
            throw InvalidShortcutException::fromShortcut($match[1]);
312
        }
313
314
        return isset($match[1]) ? strtolower($match[1]) : null;
315
    }
316
317
    private function processItemShortcut(MenuItemInterface $item) : void
318
    {
319
        $this->processIndividualShortcut($item, function (CliMenu $menu) use ($item) {
320
            $menu->executeAsSelected($item);
321
        });
322
    }
323
324
    private function processSplitItemShortcuts(SplitItem $splitItem) : void
325
    {
326
        foreach ($splitItem->getItems() as $item) {
327
            $this->processIndividualShortcut($item, function (CliMenu $menu) use ($splitItem, $item) {
328
                $current = $splitItem->getSelectedItemIndex();
329
330
                $splitItem->setSelectedItemIndex(
331
                    array_search($item, $splitItem->getItems(), true)
0 ignored issues
show
Bug introduced by
It seems like array_search($item, $splitItem->getItems(), true) can also be of type false and string; however, parameter $index of PhpSchool\CliMenu\MenuIt...:setSelectedItemIndex() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

331
                    /** @scrutinizer ignore-type */ array_search($item, $splitItem->getItems(), true)
Loading history...
332
                );
333
334
                $menu->executeAsSelected($splitItem);
335
336
                if ($current !== null) {
337
                    $splitItem->setSelectedItemIndex($current);
338
                }
339
            });
340
        }
341
    }
342
343
    private function processIndividualShortcut(MenuItemInterface $item, callable $callback) : void
344
    {
345
        if (!$this->autoShortcuts) {
346
            return;
347
        }
348
349
        if ($shortcut = $this->extractShortcut($item->getText())) {
350
            $this->menu->addCustomControlMapping(
351
                $shortcut,
352
                $callback
353
            );
354
        }
355
    }
356
357
    public function addSplitItem(Closure $callback) : self
358
    {
359
        $builder = new SplitItemBuilder($this->menu);
360
361
        if ($this->autoShortcuts) {
362
            $builder->enableAutoShortcuts($this->autoShortcutsRegex);
363
        }
364
365
        $builder->checkableStyle(function (CheckableStyle $style) {
366
            $style->fromArray($this->menu->getCheckableStyle()->toArray());
367
        });
368
369
        $builder->radioStyle(function (RadioStyle $style) {
370
            $style->fromArray($this->menu->getRadioStyle()->toArray());
371
        });
372
373
        $builder->selectableStyle(function (SelectableStyle $style) {
374
            $style->fromArray($this->menu->getSelectableStyle()->toArray());
375
        });
376
377
        $callback($builder);
378
379
        $splitItem = $builder->build();
380
        $splitItem->setStyleCallback(function (SplitStyle $style) {
381
            $style->fromArray($this->menu->getSelectableStyle()->toArray());
382
        });
383
384
        $this->menu->addItem($splitItem);
385
386
        $this->processSplitItemShortcuts($splitItem);
387
388
        return $this;
389
    }
390
391
    /**
392
     * Disable a submenu
393
     *
394
     * @throws \InvalidArgumentException
395
     */
396
    public function disableMenu() : self
397
    {
398
        if (!$this->subMenu) {
399
            throw new \InvalidArgumentException(
400
                'You can\'t disable the root menu'
401
            );
402
        }
403
404
        $this->disabled = true;
405
406
        return $this;
407
    }
408
409
    public function isMenuDisabled() : bool
410
    {
411
        return $this->disabled;
412
    }
413
414
    public function setGoBackButtonText(string $goBackButtonTest) : self
415
    {
416
        $this->goBackButtonText = $goBackButtonTest;
417
418
        return $this;
419
    }
420
421
    public function setExitButtonText(string $exitButtonText) : self
422
    {
423
        $this->exitButtonText = $exitButtonText;
424
425
        return $this;
426
    }
427
428
    public function setBackgroundColour(string $colour, string $fallback = null) : self
429
    {
430
        $this->style->setBg($colour, $fallback);
431
432
        return $this;
433
    }
434
435
    public function setForegroundColour(string $colour, string $fallback = null) : self
436
    {
437
        $this->style->setFg($colour, $fallback);
438
439
        return $this;
440
    }
441
442
    public function setWidth(int $width) : self
443
    {
444
        $this->style->setWidth($width);
445
446
        return $this;
447
    }
448
449
    public function setPadding(int $topBottom, int $leftRight = null) : self
450
    {
451
        $this->style->setPadding($topBottom, $leftRight);
452
453
        return $this;
454
    }
455
456
    public function setPaddingTopBottom(int $topBottom) : self
457
    {
458
        $this->style->setPaddingTopBottom($topBottom);
459
460
        return $this;
461
    }
462
463
    public function setPaddingLeftRight(int $leftRight) : self
464
    {
465
        $this->style->setPaddingLeftRight($leftRight);
466
467
        return $this;
468
    }
469
470
    public function setMarginAuto() : self
471
    {
472
        $this->style->setMarginAuto();
473
474
        return $this;
475
    }
476
477
    public function setMargin(int $margin) : self
478
    {
479
        $this->style->setMargin($margin);
480
481
        return $this;
482
    }
483
484
    public function setUnselectedMarker(string $marker) : self
485
    {
486
        array_map(function (SelectableInterface $item) use ($marker) {
487
            $item->getStyle()
488
                ->setMarkerOff($marker);
489
        }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) {
490
            return $item instanceof SelectableInterface;
491
        }));
492
493
        $this->menu->getSelectableStyle()
494
            ->setMarkerOff($marker);
495
496
        return $this;
497
    }
498
499
    public function setSelectedMarker(string $marker) : self
500
    {
501
        array_map(function (SelectableInterface $item) use ($marker) {
502
            $item->getStyle()
503
                ->setMarkerOn($marker);
504
        }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) {
505
            return $item instanceof SelectableInterface;
506
        }));
507
508
        $this->menu->getSelectableStyle()
509
            ->setMarkerOn($marker);
510
511
        return $this;
512
    }
513
514
    public function setItemExtra(string $extra) : self
515
    {
516
        array_map(function (SelectableInterface $item) use ($extra) {
517
            $item->getStyle()
518
                ->setItemExtra($extra);
519
        }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) {
520
            return $item instanceof SelectableInterface;
521
        }));
522
523
        $this->menu->getSelectableStyle()
524
            ->setItemExtra($extra);
525
526
        // if we customise item extra, it means we most likely want to display it
527
        $this->displayExtra();
528
529
        return $this;
530
    }
531
532
    public function setTitleSeparator(string $separator) : self
533
    {
534
        $this->style->setTitleSeparator($separator);
535
536
        return $this;
537
    }
538
539
    public function setBorder(int $top, $right = null, $bottom = null, $left = null, string $colour = null) : self
540
    {
541
        $this->style->setBorder($top, $right, $bottom, $left, $colour);
542
543
        return $this;
544
    }
545
546
    public function setBorderTopWidth(int $width) : self
547
    {
548
        $this->style->setBorderTopWidth($width);
549
        
550
        return $this;
551
    }
552
553
    public function setBorderRightWidth(int $width) : self
554
    {
555
        $this->style->setBorderRightWidth($width);
556
557
        return $this;
558
    }
559
560
    public function setBorderBottomWidth(int $width) : self
561
    {
562
        $this->style->setBorderBottomWidth($width);
563
564
        return $this;
565
    }
566
567
    public function setBorderLeftWidth(int $width) : self
568
    {
569
        $this->style->setBorderLeftWidth($width);
570
571
        return $this;
572
    }
573
574
    public function setBorderColour(string $colour, $fallback = null) : self
575
    {
576
        $this->style->setBorderColour($colour, $fallback);
577
578
        return $this;
579
    }
580
581
    public function getStyle() : MenuStyle
582
    {
583
        return $this->style;
584
    }
585
586
    public function getTerminal() : Terminal
587
    {
588
        return $this->terminal;
589
    }
590
591
    private function getDefaultItems() : array
592
    {
593
        $actions = [];
594
        if ($this->subMenu) {
595
            $actions[] = (new SelectableItem($this->goBackButtonText, new GoBackAction))
596
                ->setStyle($this->menu->getSelectableStyle());
597
        }
598
599
        $actions[] = (new SelectableItem($this->exitButtonText, new ExitAction))
600
                ->setStyle($this->menu->getSelectableStyle());
601
602
        return $actions;
603
    }
604
605
    public function disableDefaultItems() : self
606
    {
607
        $this->disableDefaultItems = true;
608
609
        return $this;
610
    }
611
612
    public function displayExtra() : self
613
    {
614
        array_map(function (SelectableInterface $item) {
615
            $item->getStyle()
616
                ->setDisplaysExtra(true);
617
        }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) {
618
            return $item instanceof SelectableInterface;
619
        }));
620
621
        $this->menu->getSelectableStyle()
622
            ->setDisplaysExtra(true);
623
624
        return $this;
625
    }
626
627
    private function itemsHaveExtra(array $items) : bool
628
    {
629
        return !empty(array_filter($items, function (MenuItemInterface $item) {
630
            return $item->showsItemExtra();
631
        }));
632
    }
633
    
634
    public function build() : CliMenu
635
    {
636
        if (!$this->disableDefaultItems) {
637
            $this->menu->addItems($this->getDefaultItems());
638
        }
639
640
        if (!$this->menu->getSelectableStyle()->getDisplaysExtra()) {
641
            $displaysExtra = $this->itemsHaveExtra($this->menu->getItems());
642
643
            array_map(function (SelectableInterface $item) use ($displaysExtra) {
644
                $item->getStyle()
645
                    ->setDisplaysExtra($displaysExtra);
646
            }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) {
647
                return $item instanceof SelectableInterface;
648
            }));
649
        }
650
651
        return $this->menu;
652
    }
653
654
    /**
655
     * Use as
656
     *
657
        ->checkableStyle(function (CheckableStyle $style) {
658
            $style->setMarkerOff('- ');
659
        })
660
     *
661
     * @param callable $itemCallable
662
     * @return $this
663
     */
664
    public function checkableStyle(callable $itemCallable) : self
665
    {
666
        $this->menu->checkableStyle($itemCallable);
667
668
        return $this;
669
    }
670
671
    /**
672
     * Use as
673
     *
674
        ->radioStyle(function (RadioStyle $style) {
675
            $style->setMarkerOff('- ');
676
        })
677
     *
678
     * @param callable $itemCallable
679
     * @return $this
680
     */
681
    public function radioStyle(callable $itemCallable) : self
682
    {
683
        $this->menu->radioStyle($itemCallable);
684
685
        return $this;
686
    }
687
688
    /**
689
     * Use as
690
     *
691
        ->selectableStyle(function (SelectableStyle $style) {
692
            $style->setMarkerOff('- ');
693
        })
694
     *
695
     * @param callable $itemCallable
696
     * @return $this
697
     */
698
    public function selectableStyle(callable $itemCallable) : self
699
    {
700
        $this->menu->selectableStyle($itemCallable);
701
702
        return $this;
703
    }
704
}
705