Completed
Push — master ( c799ac...64d6d1 )
by Aydin
05:05 queued 02:59
created

CliMenuBuilder::getTerminal()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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 setTerminal(Terminal $terminal) : self
269
    {
270
        $this->terminal = $terminal;
271
        return $this;
272
    }
273
274
    public function getTerminal() : Terminal
275
    {
276
        return $this->terminal;
277
    }
278
279
    private function getDefaultItems() : array
280
    {
281
        $actions = [];
282
        if ($this->parent) {
283
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
284
        }
285
        
286
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
287
        return $actions;
288
    }
289
290
    public function disableDefaultItems() : self
291
    {
292
        $this->disableDefaultItems = true;
293
294
        return $this;
295
    }
296
297
    private function itemsHaveExtra(array $items) : bool
298
    {
299
        return !empty(array_filter($items, function (MenuItemInterface $item) {
300
            return $item->showsItemExtra();
301
        }));
302
    }
303
304
    /**
305
     * Recursively drop back to the parents menu style
306
     * when the current menu has a parent and has no changes
307
     */
308
    private function getMenuStyle() : MenuStyle
309
    {
310
        if (null === $this->parent) {
311
            return $this->buildStyle();
312
        }
313
314
        if ($this->style !== MenuStyle::getDefaultStyleValues()) {
315
            return $this->buildStyle();
316
        }
317
318
        return $this->parent->getMenuStyle();
319
    }
320
321
    private function buildStyle() : MenuStyle
322
    {
323
        return (new MenuStyle($this->terminal))
324
            ->setFg($this->style['fg'])
325
            ->setBg($this->style['bg'])
326
            ->setWidth($this->style['width'])
327
            ->setPadding($this->style['padding'])
328
            ->setMargin($this->style['margin'])
329
            ->setSelectedMarker($this->style['selectedMarker'])
330
            ->setUnselectedMarker($this->style['unselectedMarker'])
331
            ->setItemExtra($this->style['itemExtra'])
332
            ->setDisplaysExtra($this->style['displaysExtra'])
333
            ->setTitleSeparator($this->style['titleSeparator']);
334
    }
335
336
    /**
337
     * Return to parent builder
338
     *
339
     * @throws RuntimeException
340
     */
341
    public function end() : CliMenuBuilder
342
    {
343
        if (null === $this->parent) {
344
            throw new RuntimeException('No parent builder to return to');
345
        }
346
347
        return $this->parent;
348
    }
349
350
    /**
351
     * @throws RuntimeException
352
     */
353
    public function getSubMenu(string $id) : CliMenu
354
    {
355
        if (false === $this->isBuilt) {
356
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
357
        }
358
359
        return $this->subMenus[$id];
360
    }
361
362
    private function buildSubMenus(array $items) : array
363
    {
364
        return array_map(function ($item) {
365
            if (!is_string($item)) {
366
                return $item;
367
            }
368
369
            $menuBuilder           = $this->subMenuBuilders[$item];
370
            $this->subMenus[$item] = $menuBuilder->build();
371
372
            return new MenuMenuItem($item, $this->subMenus[$item], $menuBuilder->isMenuDisabled());
373
        }, $items);
374
    }
375
376
    public function build() : CliMenu
377
    {
378
        $this->isBuilt = true;
379
380
        $mergedItems = $this->disableDefaultItems
381
            ? $this->menuItems
382
            : array_merge($this->menuItems, $this->getDefaultItems());
383
384
        $menuItems = $this->buildSubMenus($mergedItems);
385
386
        $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems);
387
388
        $menu = new CliMenu(
389
            $this->menuTitle,
390
            $menuItems,
391
            $this->terminal,
392
            $this->getMenuStyle()
393
        );
394
        
395
        foreach ($this->subMenus as $subMenu) {
396
            $subMenu->setParent($menu);
397
        }
398
399
        return $menu;
400
    }
401
}
402