Completed
Pull Request — master (#104)
by
unknown
02:11
created

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