Completed
Pull Request — master (#100)
by
unknown
05:27 queued 03:17
created

CliMenuBuilder::disableDefaultItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
namespace PhpSchool\CliMenu;
4
5
use PhpSchool\CliMenu\Action\ExitAction;
6
use PhpSchool\CliMenu\Action\GoBackAction;
7
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
8
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
9
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
10
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
11
use PhpSchool\CliMenu\MenuItem\SelectableItem;
12
use PhpSchool\CliMenu\MenuItem\StaticItem;
13
use PhpSchool\CliMenu\Terminal\TerminalFactory;
14
use Assert\Assertion;
15
use PhpSchool\Terminal\Terminal;
16
use RuntimeException;
17
18
/**
19
 * @author Michael Woodward <[email protected]>
20
 * @author Aydin Hassan <[email protected]>
21
 */
22
class CliMenuBuilder
23
{
24
    /**
25
     * @var bool
26
     */
27
    private $isBuilt = false;
28
29
    /**
30
     * @var null|self
31
     */
32
    private $parent;
33
    
34
    /**
35
     * @var self[]
36
     */
37
    private $subMenuBuilders = [];
38
39
    /**
40
     * @var CliMenu[]
41
     */
42
    private $subMenus = [];
43
44
    /**
45
     * @var string
46
     */
47
    private $goBackButtonText = 'Go Back';
48
    
49
    /**
50
     * @var string
51
     */
52
    private $exitButtonText = 'Exit';
53
54
    /**
55
     * @var array
56
     */
57
    private $menuItems = [];
58
59
    /**
60
     * @var array
61
     */
62
    private $style;
63
64
    /**
65
     * @var Terminal
66
     */
67
    private $terminal;
68
69
    /**
70
     * @var string
71
     */
72
    private $menuTitle = null;
73
74
    /**
75
     * @var bool
76
     */
77
    private $disableDefaultItems = false;
78
79
    /**
80
     * @var bool
81
     */
82
    private $disabled = false;
83
84
    public function __construct(CliMenuBuilder $parent = null)
85
    {
86
        $this->parent   = $parent;
87
        $this->terminal = $this->parent !== null
88
            ? $this->parent->getTerminal()
89
            : TerminalFactory::fromSystem();
90
        $this->style    = MenuStyle::getDefaultStyleValues();
91
    }
92
93
    public function setTitle(string $title) : self
94
    {
95
        $this->menuTitle = $title;
96
97
        return $this;
98
    }
99
100
    public function addMenuItem(MenuItemInterface $item) : self
101
    {
102
        $this->menuItems[] = $item;
103
104
        return $this;
105
    }
106
107
    public function addItem(
108
        string $text,
109
        callable $itemCallable,
110
        bool $showItemExtra = false,
111
        bool $disabled = false
112
    ) : self {
113
        $this->addMenuItem(new SelectableItem($text, $itemCallable, $showItemExtra, $disabled));
114
115
        return $this;
116
    }
117
118
    public function addItems(array $items) : self
119
    {
120
        foreach ($items as $item) {
121
            $this->addItem(...$item);
0 ignored issues
show
Bug introduced by
The call to addItem() misses a required argument $itemCallable.

This check looks for function calls that miss required arguments.

Loading history...
122
        }
123
        
124
        return $this;
125
    }
126
127
    public function addStaticItem(string $text) : self
128
    {
129
        $this->addMenuItem(new StaticItem($text));
130
131
        return $this;
132
    }
133
134
    public function addLineBreak(string $breakChar = ' ', int $lines = 1) : self
135
    {
136
        $this->addMenuItem(new LineBreakItem($breakChar, $lines));
137
138
        return $this;
139
    }
140
141
    public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self
142
    {
143
        $this->addMenuItem(new AsciiArtItem($art, $position, $alt));
144
145
        return $this;
146
    }
147
148
    /**
149
     * Add a submenu with a string identifier
150
     */
151
    public function addSubMenu(string $id, CliMenuBuilder $subMenuBuilder = null) : CliMenuBuilder
152
    {
153
        $this->menuItems[]  = $id;
154
        
155
        if (null === $subMenuBuilder) {
156
            $this->subMenuBuilders[$id] = new static($this);
157
            return $this->subMenuBuilders[$id];
158
        }
159
        
160
        $this->subMenuBuilders[$id] = $subMenuBuilder;
161
        return $this;
162
    }
163
164
    /**
165
     * Disable a submenu
166
     *
167
     * @throws \InvalidArgumentException
168
     */
169
    public function disableMenu() : self
170
    {
171
        if (!$this->parent) {
172
            throw new \InvalidArgumentException(
173
                'You can\'t disable the root menu'
174
            );
175
        }
176
177
        $this->disabled = true;
178
179
        return $this;
180
    }
181
182
    public function isMenuDisabled() : bool
183
    {
184
        return $this->disabled;
185
    }
186
187
    public function setGoBackButtonText(string $goBackButtonTest) : self
188
    {
189
        $this->goBackButtonText = $goBackButtonTest;
190
        
191
        return $this;
192
    }
193
194
    public function setExitButtonText(string $exitButtonText) : self
195
    {
196
        $this->exitButtonText = $exitButtonText;
197
        
198
        return $this;
199
    }
200
201
    public function setBackgroundColour(string $colour) : self
202
    {
203
        Assertion::inArray($colour, MenuStyle::getAvailableColours());
204
205
        $this->style['bg'] = $colour;
206
207
        return $this;
208
    }
209
210
    public function setForegroundColour(string $colour) : self
211
    {
212
        Assertion::inArray($colour, MenuStyle::getAvailableColours());
213
214
        $this->style['fg'] = $colour;
215
216
        return $this;
217
    }
218
219
    public function setWidth(int $width) : self
220
    {
221
        $this->style['width'] = $width;
222
223
        return $this;
224
    }
225
226
    public function setPadding(int $padding) : self
227
    {
228
        $this->style['padding'] = $padding;
229
230
        return $this;
231
    }
232
233
    public function setMargin(int $margin) : self
234
    {
235
        $this->style['margin'] = $margin;
236
237
        return $this;
238
    }
239
240
    public function setUnselectedMarker(string $marker) : self
241
    {
242
        $this->style['unselectedMarker'] = $marker;
243
244
        return $this;
245
    }
246
247
    public function setSelectedMarker(string $marker) : self
248
    {
249
        $this->style['selectedMarker'] = $marker;
250
251
        return $this;
252
    }
253
254
    public function setItemExtra(string $extra) : self
255
    {
256
        $this->style['itemExtra'] = $extra;
257
258
        return $this;
259
    }
260
261
    public function setTitleSeparator(string $separator) : self
262
    {
263
        $this->style['titleSeparator'] = $separator;
264
265
        return $this;
266
    }
267
268
    public function setBorder(
269
        int $topWidth,
270
        $rightWidth = null,
271
        $bottomWidth = null,
272
        $leftWidth = null,
273
        string $colour = null
274
    ) : self {
275
        $this->style['borderTopWidth'] = $topWidth;
276
277
        if (!is_int($rightWidth)) {
278
            $this->style['borderRightWidth'] = $topWidth;
279
            $this->style['borderBottomWidth'] = $topWidth;
280
            $this->style['borderLeftWidth'] = $topWidth;
281
            $colour = $rightWidth;
282 View Code Duplication
        } elseif (!is_int($bottomWidth)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
283
            $this->style['borderBottomWidth'] = $topWidth;
284
            $this->style['borderLeftWidth'] = $rightWidth;
285
            $colour = $bottomWidth;
286
        } elseif (!is_int($leftWidth)) {
287
            $this->style['borderLeftWidth'] = $rightWidth;
288
            $colour = $leftWidth;
289
        }
290
291
        if (is_string($colour)) {
292
            $this->style['borderColour'] = $colour;
293
        }
294
295
        return $this;
296
    }
297
298
    public function setTerminal(Terminal $terminal) : self
299
    {
300
        $this->terminal = $terminal;
301
        return $this;
302
    }
303
304
    public function getTerminal() : Terminal
305
    {
306
        return $this->terminal;
307
    }
308
309
    private function getDefaultItems() : array
310
    {
311
        $actions = [];
312
        if ($this->parent) {
313
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
314
        }
315
        
316
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
317
        return $actions;
318
    }
319
320
    public function disableDefaultItems() : self
321
    {
322
        $this->disableDefaultItems = true;
323
324
        return $this;
325
    }
326
327
    private function itemsHaveExtra(array $items) : bool
328
    {
329
        return !empty(array_filter($items, function (MenuItemInterface $item) {
330
            return $item->showsItemExtra();
331
        }));
332
    }
333
334
    /**
335
     * Recursively drop back to the parents menu style
336
     * when the current menu has a parent and has no changes
337
     */
338
    private function getMenuStyle() : MenuStyle
339
    {
340
        if (null === $this->parent) {
341
            return $this->buildStyle();
342
        }
343
344
        if ($this->style !== MenuStyle::getDefaultStyleValues()) {
345
            return $this->buildStyle();
346
        }
347
348
        return $this->parent->getMenuStyle();
349
    }
350
351
    private function buildStyle() : MenuStyle
352
    {
353
        return (new MenuStyle($this->terminal))
354
            ->setFg($this->style['fg'])
355
            ->setBg($this->style['bg'])
356
            ->setWidth($this->style['width'])
357
            ->setPadding($this->style['padding'])
358
            ->setMargin($this->style['margin'])
359
            ->setSelectedMarker($this->style['selectedMarker'])
360
            ->setUnselectedMarker($this->style['unselectedMarker'])
361
            ->setItemExtra($this->style['itemExtra'])
362
            ->setDisplaysExtra($this->style['displaysExtra'])
363
            ->setTitleSeparator($this->style['titleSeparator'])
364
            ->setBorderTopWidth($this->style['borderTopWidth'])
365
            ->setBorderRightWidth($this->style['borderRightWidth'])
366
            ->setBorderBottomWidth($this->style['borderBottomWidth'])
367
            ->setBorderLeftWidth($this->style['borderLeftWidth'])
368
            ->setBorderColour($this->style['borderColour']);
369
    }
370
371
    /**
372
     * Return to parent builder
373
     *
374
     * @throws RuntimeException
375
     */
376
    public function end() : CliMenuBuilder
377
    {
378
        if (null === $this->parent) {
379
            throw new RuntimeException('No parent builder to return to');
380
        }
381
382
        return $this->parent;
383
    }
384
385
    /**
386
     * @throws RuntimeException
387
     */
388
    public function getSubMenu(string $id) : CliMenu
389
    {
390
        if (false === $this->isBuilt) {
391
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
392
        }
393
394
        return $this->subMenus[$id];
395
    }
396
397
    private function buildSubMenus(array $items) : array
398
    {
399
        return array_map(function ($item) {
400
            if (!is_string($item)) {
401
                return $item;
402
            }
403
404
            $menuBuilder           = $this->subMenuBuilders[$item];
405
            $this->subMenus[$item] = $menuBuilder->build();
406
407
            return new MenuMenuItem($item, $this->subMenus[$item], $menuBuilder->isMenuDisabled());
408
        }, $items);
409
    }
410
411
    public function build() : CliMenu
412
    {
413
        $this->isBuilt = true;
414
415
        $mergedItems = $this->disableDefaultItems
416
            ? $this->menuItems
417
            : array_merge($this->menuItems, $this->getDefaultItems());
418
419
        $menuItems = $this->buildSubMenus($mergedItems);
420
421
        $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems);
422
423
        $menu = new CliMenu(
424
            $this->menuTitle,
425
            $menuItems,
426
            $this->terminal,
427
            $this->getMenuStyle()
428
        );
429
        
430
        foreach ($this->subMenus as $subMenu) {
431
            $subMenu->setParent($menu);
432
        }
433
434
        return $menu;
435
    }
436
}
437