Completed
Pull Request — master (#104)
by
unknown
05:54 queued 29s
created

CliMenuBuilder::addAsciiArt()   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 3
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 View Code Duplication
    public function setBackgroundColour($colour) : self
0 ignored issues
show
Duplication introduced by
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...
202
    {
203
        if (is_int($colour)) {
204
            if ($this->terminal->getColourSupport() < 256) {
205
                // Need to fallback to 8 colors
206
                return $this;
207
            }
208
            if ($colour < 0 || $colour > 255) {
209
                throw new Exception("Invalid colour code");
210
            }
211
        } else {
212
            Assertion::inArray($colour, MenuStyle::getAvailableColours());
213
        }
214
215
        $this->style['bg'] = $colour;
216
217
        return $this;
218
    }
219
220 View Code Duplication
    public function setForegroundColour($colour) : self
0 ignored issues
show
Duplication introduced by
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...
221
    {
222
        if (is_int($colour)) {
223
            if ($this->terminal->getColourSupport() < 256) {
224
                // Need to fallback to 8 colors
225
                return $this;
226
            }
227
            if ($colour < 0 || $colour > 255) {
228
                throw new Exception("Invalid colour code");
229
            }
230
        } else {
231
            Assertion::inArray($colour, MenuStyle::getAvailableColours());
232
        }
233
234
        $this->style['fg'] = $colour;
235
236
        return $this;
237
    }
238
239
    public function setWidth(int $width) : self
240
    {
241
        $this->style['width'] = $width;
242
243
        return $this;
244
    }
245
246
    public function setPadding(int $padding) : self
247
    {
248
        $this->style['padding'] = $padding;
249
250
        return $this;
251
    }
252
253
    public function setMargin(int $margin) : self
254
    {
255
        $this->style['margin'] = $margin;
256
257
        return $this;
258
    }
259
260
    public function setUnselectedMarker(string $marker) : self
261
    {
262
        $this->style['unselectedMarker'] = $marker;
263
264
        return $this;
265
    }
266
267
    public function setSelectedMarker(string $marker) : self
268
    {
269
        $this->style['selectedMarker'] = $marker;
270
271
        return $this;
272
    }
273
274
    public function setItemExtra(string $extra) : self
275
    {
276
        $this->style['itemExtra'] = $extra;
277
278
        return $this;
279
    }
280
281
    public function setTitleSeparator(string $separator) : self
282
    {
283
        $this->style['titleSeparator'] = $separator;
284
285
        return $this;
286
    }
287
288
    public function setTerminal(Terminal $terminal) : self
289
    {
290
        $this->terminal = $terminal;
291
        return $this;
292
    }
293
294
    public function getTerminal() : Terminal
295
    {
296
        return $this->terminal;
297
    }
298
299
    private function getDefaultItems() : array
300
    {
301
        $actions = [];
302
        if ($this->parent) {
303
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
304
        }
305
        
306
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
307
        return $actions;
308
    }
309
310
    public function disableDefaultItems() : self
311
    {
312
        $this->disableDefaultItems = true;
313
314
        return $this;
315
    }
316
317
    private function itemsHaveExtra(array $items) : bool
318
    {
319
        return !empty(array_filter($items, function (MenuItemInterface $item) {
320
            return $item->showsItemExtra();
321
        }));
322
    }
323
324
    /**
325
     * Recursively drop back to the parents menu style
326
     * when the current menu has a parent and has no changes
327
     */
328
    private function getMenuStyle() : MenuStyle
329
    {
330
        if (null === $this->parent) {
331
            return $this->buildStyle();
332
        }
333
334
        if ($this->style !== MenuStyle::getDefaultStyleValues()) {
335
            return $this->buildStyle();
336
        }
337
338
        return $this->parent->getMenuStyle();
339
    }
340
341
    private function buildStyle() : MenuStyle
342
    {
343
        return (new MenuStyle($this->terminal))
344
            ->setFg($this->style['fg'])
345
            ->setBg($this->style['bg'])
346
            ->setWidth($this->style['width'])
347
            ->setPadding($this->style['padding'])
348
            ->setMargin($this->style['margin'])
349
            ->setSelectedMarker($this->style['selectedMarker'])
350
            ->setUnselectedMarker($this->style['unselectedMarker'])
351
            ->setItemExtra($this->style['itemExtra'])
352
            ->setDisplaysExtra($this->style['displaysExtra'])
353
            ->setTitleSeparator($this->style['titleSeparator']);
354
    }
355
356
    /**
357
     * Return to parent builder
358
     *
359
     * @throws RuntimeException
360
     */
361
    public function end() : CliMenuBuilder
362
    {
363
        if (null === $this->parent) {
364
            throw new RuntimeException('No parent builder to return to');
365
        }
366
367
        return $this->parent;
368
    }
369
370
    /**
371
     * @throws RuntimeException
372
     */
373
    public function getSubMenu(string $id) : CliMenu
374
    {
375
        if (false === $this->isBuilt) {
376
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
377
        }
378
379
        return $this->subMenus[$id];
380
    }
381
382
    private function buildSubMenus(array $items) : array
383
    {
384
        return array_map(function ($item) {
385
            if (!is_string($item)) {
386
                return $item;
387
            }
388
389
            $menuBuilder           = $this->subMenuBuilders[$item];
390
            $this->subMenus[$item] = $menuBuilder->build();
391
392
            return new MenuMenuItem($item, $this->subMenus[$item], $menuBuilder->isMenuDisabled());
393
        }, $items);
394
    }
395
396
    public function build() : CliMenu
397
    {
398
        $this->isBuilt = true;
399
400
        $mergedItems = $this->disableDefaultItems
401
            ? $this->menuItems
402
            : array_merge($this->menuItems, $this->getDefaultItems());
403
404
        $menuItems = $this->buildSubMenus($mergedItems);
405
406
        $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems);
407
408
        $menu = new CliMenu(
409
            $this->menuTitle,
410
            $menuItems,
411
            $this->terminal,
412
            $this->getMenuStyle()
413
        );
414
        
415
        foreach ($this->subMenus as $subMenu) {
416
            $subMenu->setParent($menu);
417
        }
418
419
        return $menu;
420
    }
421
}
422