Completed
Push — master ( df8184...e3b994 )
by Aydin
02:31
created

src/Builder/CliMenuBuilder.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace PhpSchool\CliMenu\Builder;
4
5
use PhpSchool\CliMenu\Action\ExitAction;
6
use PhpSchool\CliMenu\Action\GoBackAction;
7
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
8
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
9
use PhpSchool\CliMenu\MenuItem\SelectableItem;
10
use PhpSchool\CliMenu\MenuItem\SplitItem;
11
use PhpSchool\CliMenu\CliMenu;
12
use PhpSchool\CliMenu\MenuStyle;
13
use PhpSchool\CliMenu\Terminal\TerminalFactory;
14
use PhpSchool\CliMenu\Util\ColourUtil;
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 implements Builder
23
{
24
    use BuilderUtils;
25
    
26
    /**
27
     * @var bool
28
     */
29
    private $isBuilt = false;
30
31
    /**
32
     * @var SplitItemBuilder[]
33
     */
34
    private $splitItemBuilders = [];
35
36
    /**
37
     * @var SplitItem[]
38
     */
39
    private $splitItems = [];
40
41
    /**
42
     * @var string
43
     */
44
    private $goBackButtonText = 'Go Back';
45
46
    /**
47
     * @var string
48
     */
49
    private $exitButtonText = 'Exit';
50
51
    /**
52
     * @var array
53
     */
54
    private $style;
55
56
    /**
57
     * @var Terminal
58
     */
59
    private $terminal;
60
61
    /**
62
     * @var string
63
     */
64
    private $menuTitle;
65
66
    /**
67
     * @var bool
68
     */
69
    private $disableDefaultItems = false;
70
71
    /**
72
     * @var bool
73
     */
74
    private $disabled = false;
75
76
    public function __construct(Builder $parent = null)
77
    {
78
        $this->parent   = $parent;
79
        $this->terminal = $this->parent !== null
80
            ? $this->parent->getTerminal()
81
            : TerminalFactory::fromSystem();
82
        $this->style = MenuStyle::getDefaultStyleValues();
83
    }
84
85
    public function setTitle(string $title) : self
86
    {
87
        $this->menuTitle = $title;
88
89
        return $this;
90
    }
91
92
    public function addMenuItem(MenuItemInterface $item) : self
93
    {
94
        $this->menuItems[] = $item;
95
96
        return $this;
97
    }
98
99
    public function addItems(array $items) : self
100
    {
101
        foreach ($items as $item) {
102
            $this->addItem(...$item);
103
        }
104
105
        return $this;
106
    }
107
108
    public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self
109
    {
110
        $this->addMenuItem(new AsciiArtItem($art, $position, $alt));
111
112
        return $this;
113
    }
114
115
    /**
116
     * Add a split item
117
     */
118
    public function addSplitItem() : SplitItemBuilder
119
    {
120
        $this->menuItems[] = $id = uniqid('splititem-placeholder-', true);
121
        
122
        $this->splitItemBuilders[$id] = new SplitItemBuilder($this);
123
        return $this->splitItemBuilders[$id];
124
    }
125
126
    /**
127
     * Disable a submenu
128
     *
129
     * @throws \InvalidArgumentException
130
     */
131
    public function disableMenu() : self
132
    {
133
        if (!$this->parent) {
134
            throw new \InvalidArgumentException(
135
                'You can\'t disable the root menu'
136
            );
137
        }
138
139
        $this->disabled = true;
140
141
        return $this;
142
    }
143
144
    public function isMenuDisabled() : bool
145
    {
146
        return $this->disabled;
147
    }
148
149
    public function setGoBackButtonText(string $goBackButtonTest) : self
150
    {
151
        $this->goBackButtonText = $goBackButtonTest;
152
153
        return $this;
154
    }
155
156
    public function setExitButtonText(string $exitButtonText) : self
157
    {
158
        $this->exitButtonText = $exitButtonText;
159
160
        return $this;
161
    }
162
163
    public function setBackgroundColour(string $colour, string $fallback = null) : self
164
    {
165
        $this->style['bg'] = ColourUtil::validateColour(
166
            $this->terminal,
167
            $colour,
168
            $fallback
169
        );
170
171
        return $this;
172
    }
173
174 View Code Duplication
    public function setForegroundColour(string $colour, string $fallback = null) : self
0 ignored issues
show
This method seems to be duplicated in 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...
175
    {
176
        $this->style['fg'] = ColourUtil::validateColour(
177
            $this->terminal,
178
            $colour,
179
            $fallback
180
        );
181
182
        return $this;
183
    }
184
185
    public function setWidth(int $width) : self
186
    {
187
        $this->style['width'] = $width;
188
189
        return $this;
190
    }
191
192 View Code Duplication
    public function setPadding(int $topBottom, int $leftRight = null) : self
193
    {
194
        if ($leftRight === null) {
195
            $leftRight = $topBottom;
196
        }
197
198
        $this->setPaddingTopBottom($topBottom);
199
        $this->setPaddingLeftRight($leftRight);
200
201
        return $this;
202
    }
203
204
    public function setPaddingTopBottom(int $topBottom) : self
205
    {
206
        $this->style['paddingTopBottom'] = $topBottom;
207
208
        return $this;
209
    }
210
211
    public function setPaddingLeftRight(int $leftRight) : self
212
    {
213
        $this->style['paddingLeftRight'] = $leftRight;
214
215
        return $this;
216
    }
217
218
    public function setMarginAuto() : self
219
    {
220
        $this->style['marginAuto'] = true;
221
222
        return $this;
223
    }
224
225
    public function setMargin(int $margin) : self
226
    {
227
        $this->style['marginAuto'] = false;
228
        $this->style['margin'] = $margin;
229
230
        return $this;
231
    }
232
233
    public function setUnselectedMarker(string $marker) : self
234
    {
235
        $this->style['unselectedMarker'] = $marker;
236
237
        return $this;
238
    }
239
240
    public function setSelectedMarker(string $marker) : self
241
    {
242
        $this->style['selectedMarker'] = $marker;
243
244
        return $this;
245
    }
246
247
    public function setItemExtra(string $extra) : self
248
    {
249
        $this->style['itemExtra'] = $extra;
250
251
        return $this;
252
    }
253
254
    public function setTitleSeparator(string $separator) : self
255
    {
256
        $this->style['titleSeparator'] = $separator;
257
258
        return $this;
259
    }
260
261
    public function setBorder(
262
        int $topWidth,
263
        $rightWidth = null,
264
        $bottomWidth = null,
265
        $leftWidth = null,
266
        string $colour = null
267
    ) : self {
268 View Code Duplication
        if (!is_int($rightWidth)) {
0 ignored issues
show
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...
269
            $colour = $rightWidth;
270
            $rightWidth = $bottomWidth = $leftWidth = $topWidth;
271
        } elseif (!is_int($bottomWidth)) {
272
            $colour = $bottomWidth;
273
            $bottomWidth = $topWidth;
274
            $leftWidth = $rightWidth;
275
        } elseif (!is_int($leftWidth)) {
276
            $colour = $leftWidth;
277
            $leftWidth = $rightWidth;
278
        }
279
280
        $this->style['borderTopWidth'] = $topWidth;
281
        $this->style['borderRightWidth'] = $rightWidth;
282
        $this->style['borderBottomWidth'] = $bottomWidth;
283
        $this->style['borderLeftWidth'] = $leftWidth;
284
285
        if (is_string($colour)) {
286
            $this->style['borderColour'] = $colour;
287
        } elseif ($colour !== null) {
288
            throw new \InvalidArgumentException('Invalid colour');
289
        }
290
291
        return $this;
292
    }
293
294
    public function setBorderTopWidth(int $width) : self
295
    {
296
        $this->style['borderTopWidth'] = $width;
297
        
298
        return $this;
299
    }
300
301
    public function setBorderRightWidth(int $width) : self
302
    {
303
        $this->style['borderRightWidth'] = $width;
304
305
        return $this;
306
    }
307
308
    public function setBorderBottomWidth(int $width) : self
309
    {
310
        $this->style['borderBottomWidth'] = $width;
311
312
        return $this;
313
    }
314
315
    public function setBorderLeftWidth(int $width) : self
316
    {
317
        $this->style['borderLeftWidth'] = $width;
318
319
        return $this;
320
    }
321
322
    public function setBorderColour(string $colour, $fallback = null) : self
323
    {
324
        $this->style['borderColour'] = $colour;
325
        $this->style['borderColourFallback'] = $fallback;
326
327
        return $this;
328
    }
329
330
    public function setTerminal(Terminal $terminal) : self
331
    {
332
        $this->terminal = $terminal;
333
        return $this;
334
    }
335
336
    public function getTerminal() : Terminal
337
    {
338
        return $this->terminal;
339
    }
340
341
    private function getDefaultItems() : array
342
    {
343
        $actions = [];
344
        if ($this->parent) {
345
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
346
        }
347
348
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
349
        return $actions;
350
    }
351
352
    public function disableDefaultItems() : self
353
    {
354
        $this->disableDefaultItems = true;
355
356
        return $this;
357
    }
358
359
    private function itemsHaveExtra(array $items) : bool
360
    {
361
        return !empty(array_filter($items, function (MenuItemInterface $item) {
362
            return $item->showsItemExtra();
363
        }));
364
    }
365
366
    /**
367
     * Recursively drop back to the parents menu style
368
     * when the current menu has a parent and has no changes
369
     */
370
    public function getMenuStyle() : MenuStyle
371
    {
372
        if (null === $this->parent) {
373
            return $this->buildStyle();
374
        }
375
376
        if ($this->style !== MenuStyle::getDefaultStyleValues()) {
377
            return $this->buildStyle();
378
        }
379
380
        return $this->parent->getMenuStyle();
381
    }
382
383
    private function buildStyle() : MenuStyle
384
    {
385
        $style = (new MenuStyle($this->terminal))
386
            ->setFg($this->style['fg'])
387
            ->setBg($this->style['bg'])
388
            ->setWidth($this->style['width'])
389
            ->setPaddingTopBottom($this->style['paddingTopBottom'])
390
            ->setPaddingLeftRight($this->style['paddingLeftRight'])
391
            ->setSelectedMarker($this->style['selectedMarker'])
392
            ->setUnselectedMarker($this->style['unselectedMarker'])
393
            ->setItemExtra($this->style['itemExtra'])
394
            ->setDisplaysExtra($this->style['displaysExtra'])
395
            ->setTitleSeparator($this->style['titleSeparator'])
396
            ->setBorderTopWidth($this->style['borderTopWidth'])
397
            ->setBorderRightWidth($this->style['borderRightWidth'])
398
            ->setBorderBottomWidth($this->style['borderBottomWidth'])
399
            ->setBorderLeftWidth($this->style['borderLeftWidth'])
400
            ->setBorderColour($this->style['borderColour'], $this->style['borderColourFallback']);
401
402
        $this->style['marginAuto'] ? $style->setMarginAuto() : $style->setMargin($this->style['margin']);
403
404
        return $style;
405
    }
406
407
    /**
408
     * @throws RuntimeException
409
     */
410
    public function getSubMenu(string $id) : CliMenu
411
    {
412
        if (false === $this->isBuilt) {
413
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
414
        }
415
416
        return $this->subMenus['submenu-placeholder-' . $id];
417
    }
418
    
419
    private function buildSplitItems(array $items) : array
420
    {
421
        return array_map(function ($item) {
422
            if (!is_string($item) || 0 !== strpos($item, 'splititem-placeholder-')) {
423
                return $item;
424
            }
425
426
            $splitItemBuilder        = $this->splitItemBuilders[$item];
427
            $this->splitItems[$item] = $splitItemBuilder->build();
428
429
            return $this->splitItems[$item];
430
        }, $items);
431
    }
432
433
    public function build() : CliMenu
434
    {
435
        $this->isBuilt = true;
436
437
        $mergedItems = $this->disableDefaultItems
438
            ? $this->menuItems
439
            : array_merge($this->menuItems, $this->getDefaultItems());
440
441
        
442
        $menuItems = $this->buildSplitItems($mergedItems);
443
        $menuItems = $this->buildSubMenus($menuItems);
444
445
        $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems);
446
447
        $menu = new CliMenu(
448
            $this->menuTitle,
449
            $menuItems,
450
            $this->terminal,
451
            $this->getMenuStyle()
452
        );
453
454
        foreach ($this->subMenus as $subMenu) {
455
            $subMenu->setParent($menu);
456
        }
457
        
458
        foreach ($this->splitItemBuilders as $splitItemBuilder) {
459
            $splitItemBuilder->setSubMenuParents($menu);
460
        }
461
462
        return $menu;
463
    }
464
}
465