Completed
Push — master ( a82150...06534f )
by Aydin
11s
created

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