Completed
Push — master ( 13311a...9ac14b )
by Aydin
14:38 queued 44s
created

CliMenuBuilder::newFromParent()   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 1
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\Terminal\Terminal;
15
use RuntimeException;
16
17
/**
18
 * @author Michael Woodward <[email protected]>
19
 * @author Aydin Hassan <[email protected]>
20
 */
21
class CliMenuBuilder implements Builder
22
{
23
    use BuilderUtils;
24
25
    /**
26
     * @var null|Builder
27
     */
28
    private $parent;
29
    
30
    /**
31
     * @var bool
32
     */
33
    private $isBuilt = false;
34
35
    /**
36
     * @var SplitItemBuilder[]
37
     */
38
    private $splitItemBuilders = [];
39
40
    /**
41
     * @var SplitItem[]
42
     */
43
    private $splitItems = [];
44
45
    /**
46
     * @var string
47
     */
48
    private $goBackButtonText = 'Go Back';
49
50
    /**
51
     * @var string
52
     */
53
    private $exitButtonText = 'Exit';
54
55
    /**
56
     * @var MenuStyle
57
     */
58
    private $style;
59
60
    /**
61
     * @var Terminal
62
     */
63
    private $terminal;
64
65
    /**
66
     * @var string
67
     */
68
    private $menuTitle;
69
70
    /**
71
     * @var bool
72
     */
73
    private $disableDefaultItems = false;
74
75
    /**
76
     * @var bool
77
     */
78
    private $disabled = false;
79
80
    public function __construct(Terminal $terminal = null, Builder $parent = null)
81
    {
82
        $this->terminal = $terminal ?? TerminalFactory::fromSystem();
83
        $this->parent   = $parent;
84
        $this->style = new MenuStyle($this->terminal);
85
    }
86
    
87
    public static function newFromParent(Builder $parent) : self
88
    {
89
        return new self($parent->getTerminal(), $parent);
90
    }
91
92
    public function setTitle(string $title) : self
93
    {
94
        $this->menuTitle = $title;
95
96
        return $this;
97
    }
98
99
    public function addMenuItem(MenuItemInterface $item) : self
100
    {
101
        $this->menuItems[] = $item;
102
103
        return $this;
104
    }
105
106
    public function addItems(array $items) : self
107
    {
108
        foreach ($items as $item) {
109
            $this->addItem(...$item);
0 ignored issues
show
Bug introduced by Aydin Hassan
The call to addItem() misses a required argument $itemCallable.

This check looks for function calls that miss required arguments.

Loading history...
110
        }
111
112
        return $this;
113
    }
114
115
    public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self
116
    {
117
        $this->addMenuItem(new AsciiArtItem($art, $position, $alt));
118
119
        return $this;
120
    }
121
122
    /**
123
     * Add a split item
124
     */
125
    public function addSplitItem() : SplitItemBuilder
126
    {
127
        $this->menuItems[] = [
128
            'type' => 'splititem-placeholder',
129
            'id'   => $id = uniqid('', true),
130
        ];
131
                
132
        $this->splitItemBuilders[$id] = new SplitItemBuilder($this);
133
        return $this->splitItemBuilders[$id];
134
    }
135
136
    /**
137
     * Disable a submenu
138
     *
139
     * @throws \InvalidArgumentException
140
     */
141
    public function disableMenu() : self
142
    {
143
        if (!$this->parent) {
144
            throw new \InvalidArgumentException(
145
                'You can\'t disable the root menu'
146
            );
147
        }
148
149
        $this->disabled = true;
150
151
        return $this;
152
    }
153
154
    public function isMenuDisabled() : bool
155
    {
156
        return $this->disabled;
157
    }
158
159
    public function setGoBackButtonText(string $goBackButtonTest) : self
160
    {
161
        $this->goBackButtonText = $goBackButtonTest;
162
163
        return $this;
164
    }
165
166
    public function setExitButtonText(string $exitButtonText) : self
167
    {
168
        $this->exitButtonText = $exitButtonText;
169
170
        return $this;
171
    }
172
173
    public function setBackgroundColour(string $colour, string $fallback = null) : self
174
    {
175
        $this->style->setBg($colour, $fallback);
176
177
        return $this;
178
    }
179
180
    public function setForegroundColour(string $colour, string $fallback = null) : self
181
    {
182
        $this->style->setFg($colour, $fallback);
183
184
        return $this;
185
    }
186
187
    public function setWidth(int $width) : self
188
    {
189
        $this->style->setWidth($width);
190
191
        return $this;
192
    }
193
194
    public function setPadding(int $topBottom, int $leftRight = null) : self
195
    {
196
        $this->style->setPadding($topBottom, $leftRight);
197
198
        return $this;
199
    }
200
201
    public function setPaddingTopBottom(int $topBottom) : self
202
    {
203
        $this->style->setPaddingTopBottom($topBottom);
204
205
        return $this;
206
    }
207
208
    public function setPaddingLeftRight(int $leftRight) : self
209
    {
210
        $this->style->setPaddingLeftRight($leftRight);
211
212
        return $this;
213
    }
214
215
    public function setMarginAuto() : self
216
    {
217
        $this->style->setMarginAuto();
218
219
        return $this;
220
    }
221
222
    public function setMargin(int $margin) : self
223
    {
224
        $this->style->setMargin($margin);
225
226
        return $this;
227
    }
228
229
    public function setUnselectedMarker(string $marker) : self
230
    {
231
        $this->style->setUnselectedMarker($marker);
232
233
        return $this;
234
    }
235
236
    public function setSelectedMarker(string $marker) : self
237
    {
238
        $this->style->setSelectedMarker($marker);
239
240
        return $this;
241
    }
242
243
    public function setItemExtra(string $extra) : self
244
    {
245
        $this->style->setItemExtra($extra);
246
247
        return $this;
248
    }
249
250
    public function setTitleSeparator(string $separator) : self
251
    {
252
        $this->style->setTitleSeparator($separator);
253
254
        return $this;
255
    }
256
257
    public function setBorder(int $top, $right = null, $bottom = null, $left = null, string $colour = null) : self
258
    {
259
        $this->style->setBorder($top, $right, $bottom, $left, $colour);
260
261
        return $this;
262
    }
263
264
    public function setBorderTopWidth(int $width) : self
265
    {
266
        $this->style->setBorderTopWidth($width);
267
        
268
        return $this;
269
    }
270
271
    public function setBorderRightWidth(int $width) : self
272
    {
273
        $this->style->setBorderRightWidth($width);
274
275
        return $this;
276
    }
277
278
    public function setBorderBottomWidth(int $width) : self
279
    {
280
        $this->style->setBorderBottomWidth($width);
281
282
        return $this;
283
    }
284
285
    public function setBorderLeftWidth(int $width) : self
286
    {
287
        $this->style->setBorderLeftWidth($width);
288
289
        return $this;
290
    }
291
292
    public function setBorderColour(string $colour, $fallback = null) : self
293
    {
294
        $this->style->setBorderColour($colour, $fallback);
295
296
        return $this;
297
    }
298
299
    public function getTerminal() : Terminal
300
    {
301
        return $this->terminal;
302
    }
303
304
    private function getDefaultItems() : array
305
    {
306
        $actions = [];
307
        if ($this->parent) {
308
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
309
        }
310
311
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
312
        return $actions;
313
    }
314
315
    public function disableDefaultItems() : self
316
    {
317
        $this->disableDefaultItems = true;
318
319
        return $this;
320
    }
321
322
    private function itemsHaveExtra(array $items) : bool
323
    {
324
        return !empty(array_filter($items, function (MenuItemInterface $item) {
325
            return $item->showsItemExtra();
326
        }));
327
    }
328
329
    /**
330
     * Recursively drop back to the parents menu style
331
     * when the current menu has a parent and has no changes
332
     */
333
    public function getMenuStyle() : MenuStyle
334
    {
335
        if (null === $this->parent) {
336
            return $this->style;
337
        }
338
339
        if ($this->style->hasChangedFromDefaults()) {
340
            return $this->style;
341
        }
342
343
        return $this->parent->getMenuStyle();
344
    }
345
346
    /**
347
     * @throws RuntimeException
348
     */
349
    public function getSubMenu(string $id) : CliMenu
350
    {
351
        if (false === $this->isBuilt) {
352
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
353
        }
354
355
        return $this->subMenus[$id];
356
    }
357
    
358
    private function buildSplitItems(array $items) : array
359
    {
360
        return array_map(function ($item) {
361
            if (!is_array($item) || $item['type'] !== 'splititem-placeholder') {
362
                return $item;
363
            }
364
365
            $splitItemBuilder        = $this->splitItemBuilders[$item['id']];
366
            $this->splitItems[$item['id']] = $splitItemBuilder->build();
367
368
            return $this->splitItems[$item['id']];
369
        }, $items);
370
    }
371
372
    public function build() : CliMenu
373
    {
374
        $this->isBuilt = true;
375
376
        $mergedItems = $this->disableDefaultItems
377
            ? $this->menuItems
378
            : array_merge($this->menuItems, $this->getDefaultItems());
379
380
        
381
        $menuItems = $this->buildSplitItems($mergedItems);
382
        $menuItems = $this->buildSubMenus($menuItems);
383
384
        $this->style->setDisplaysExtra($this->itemsHaveExtra($menuItems));
385
386
        $menu = new CliMenu(
387
            $this->menuTitle,
388
            $menuItems,
389
            $this->terminal,
390
            $this->getMenuStyle()
391
        );
392
393
        foreach ($this->subMenus as $subMenu) {
394
            $subMenu->setParent($menu);
395
        }
396
        
397
        foreach ($this->splitItemBuilders as $splitItemBuilder) {
398
            $splitItemBuilder->setSubMenuParents($menu);
399
        }
400
401
        return $menu;
402
    }
403
404
    /**
405
     * Return to parent builder
406
     *
407
     * @return CliMenuBuilder|SplitItemBuilder
408
     */
409
    public function end() : ?Builder
410
    {
411
        if (null === $this->parent) {
412
            throw new RuntimeException('No parent builder to return to');
413
        }
414
415
        return $this->parent;
416
    }
417
}
418