These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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\CliMenu\Util\ColourUtil; |
||
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 implements Builder |
||
23 | { |
||
24 | use BuilderUtils; |
||
25 | |||
26 | /** |
||
27 | * @var bool |
||
28 | */ |
||
29 | private $isBuilt = false; |
||
30 | |||
31 | /** |
||
32 | * @var SplitItemBuilder[] |
||
33 | */ |
||
34 | private $splitItemBuilders = []; |
||
35 | |||
36 | /** |
||
37 | * @var SplitItem[] |
||
38 | */ |
||
39 | private $splitItems = []; |
||
40 | |||
41 | /** |
||
42 | * @var string |
||
43 | */ |
||
44 | private $goBackButtonText = 'Go Back'; |
||
45 | |||
46 | /** |
||
47 | * @var string |
||
48 | */ |
||
49 | private $exitButtonText = 'Exit'; |
||
50 | |||
51 | /** |
||
52 | * @var array |
||
53 | */ |
||
54 | private $style; |
||
55 | |||
56 | /** |
||
57 | * @var Terminal |
||
58 | */ |
||
59 | private $terminal; |
||
60 | |||
61 | /** |
||
62 | * @var string |
||
63 | */ |
||
64 | private $menuTitle; |
||
65 | |||
66 | /** |
||
67 | * @var bool |
||
68 | */ |
||
69 | private $disableDefaultItems = false; |
||
70 | |||
71 | /** |
||
72 | * @var bool |
||
73 | */ |
||
74 | private $disabled = false; |
||
75 | |||
76 | public function __construct(Builder $parent = null) |
||
77 | { |
||
78 | $this->parent = $parent; |
||
79 | $this->terminal = $this->parent !== null |
||
80 | ? $this->parent->getTerminal() |
||
81 | : TerminalFactory::fromSystem(); |
||
82 | $this->style = MenuStyle::getDefaultStyleValues(); |
||
83 | } |
||
84 | |||
85 | public function setTitle(string $title) : self |
||
86 | { |
||
87 | $this->menuTitle = $title; |
||
88 | |||
89 | return $this; |
||
90 | } |
||
91 | |||
92 | public function addMenuItem(MenuItemInterface $item) : self |
||
93 | { |
||
94 | $this->menuItems[] = $item; |
||
95 | |||
96 | return $this; |
||
97 | } |
||
98 | |||
99 | public function addItems(array $items) : self |
||
100 | { |
||
101 | foreach ($items as $item) { |
||
102 | $this->addItem(...$item); |
||
103 | } |
||
104 | |||
105 | return $this; |
||
106 | } |
||
107 | |||
108 | public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self |
||
109 | { |
||
110 | $this->addMenuItem(new AsciiArtItem($art, $position, $alt)); |
||
111 | |||
112 | return $this; |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Add a split item |
||
117 | */ |
||
118 | public function addSplitItem() : SplitItemBuilder |
||
119 | { |
||
120 | $this->menuItems[] = $id = uniqid('splititem-placeholder-', true); |
||
121 | |||
122 | $this->splitItemBuilders[$id] = new SplitItemBuilder($this); |
||
123 | return $this->splitItemBuilders[$id]; |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Disable a submenu |
||
128 | * |
||
129 | * @throws \InvalidArgumentException |
||
130 | */ |
||
131 | public function disableMenu() : self |
||
132 | { |
||
133 | if (!$this->parent) { |
||
134 | throw new \InvalidArgumentException( |
||
135 | 'You can\'t disable the root menu' |
||
136 | ); |
||
137 | } |
||
138 | |||
139 | $this->disabled = true; |
||
140 | |||
141 | return $this; |
||
142 | } |
||
143 | |||
144 | public function isMenuDisabled() : bool |
||
145 | { |
||
146 | return $this->disabled; |
||
147 | } |
||
148 | |||
149 | public function setGoBackButtonText(string $goBackButtonTest) : self |
||
150 | { |
||
151 | $this->goBackButtonText = $goBackButtonTest; |
||
152 | |||
153 | return $this; |
||
154 | } |
||
155 | |||
156 | public function setExitButtonText(string $exitButtonText) : self |
||
157 | { |
||
158 | $this->exitButtonText = $exitButtonText; |
||
159 | |||
160 | return $this; |
||
161 | } |
||
162 | |||
163 | public function setBackgroundColour(string $colour, string $fallback = null) : self |
||
164 | { |
||
165 | $this->style['bg'] = ColourUtil::validateColour( |
||
166 | $this->terminal, |
||
167 | $colour, |
||
168 | $fallback |
||
169 | ); |
||
170 | |||
171 | return $this; |
||
172 | } |
||
173 | |||
174 | View Code Duplication | public function setForegroundColour(string $colour, string $fallback = null) : self |
|
0 ignored issues
–
show
|
|||
175 | { |
||
176 | $this->style['fg'] = ColourUtil::validateColour( |
||
177 | $this->terminal, |
||
178 | $colour, |
||
179 | $fallback |
||
180 | ); |
||
181 | |||
182 | return $this; |
||
183 | } |
||
184 | |||
185 | public function setWidth(int $width) : self |
||
186 | { |
||
187 | $this->style['width'] = $width; |
||
188 | |||
189 | return $this; |
||
190 | } |
||
191 | |||
192 | View Code Duplication | public function setPadding(int $topBottom, int $leftRight = null) : self |
|
0 ignored issues
–
show
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...
|
|||
193 | { |
||
194 | if ($leftRight === null) { |
||
195 | $leftRight = $topBottom; |
||
196 | } |
||
197 | |||
198 | $this->setPaddingTopBottom($topBottom); |
||
199 | $this->setPaddingLeftRight($leftRight); |
||
200 | |||
201 | return $this; |
||
202 | } |
||
203 | |||
204 | public function setPaddingTopBottom(int $topBottom) : self |
||
205 | { |
||
206 | $this->style['paddingTopBottom'] = $topBottom; |
||
207 | |||
208 | return $this; |
||
209 | } |
||
210 | |||
211 | public function setPaddingLeftRight(int $leftRight) : self |
||
212 | { |
||
213 | $this->style['paddingLeftRight'] = $leftRight; |
||
214 | |||
215 | return $this; |
||
216 | } |
||
217 | |||
218 | public function setMarginAuto() : self |
||
219 | { |
||
220 | $this->style['marginAuto'] = true; |
||
221 | |||
222 | return $this; |
||
223 | } |
||
224 | |||
225 | public function setMargin(int $margin) : self |
||
226 | { |
||
227 | $this->style['marginAuto'] = false; |
||
228 | $this->style['margin'] = $margin; |
||
229 | |||
230 | return $this; |
||
231 | } |
||
232 | |||
233 | public function setUnselectedMarker(string $marker) : self |
||
234 | { |
||
235 | $this->style['unselectedMarker'] = $marker; |
||
236 | |||
237 | return $this; |
||
238 | } |
||
239 | |||
240 | public function setSelectedMarker(string $marker) : self |
||
241 | { |
||
242 | $this->style['selectedMarker'] = $marker; |
||
243 | |||
244 | return $this; |
||
245 | } |
||
246 | |||
247 | public function setItemExtra(string $extra) : self |
||
248 | { |
||
249 | $this->style['itemExtra'] = $extra; |
||
250 | |||
251 | return $this; |
||
252 | } |
||
253 | |||
254 | public function setTitleSeparator(string $separator) : self |
||
255 | { |
||
256 | $this->style['titleSeparator'] = $separator; |
||
257 | |||
258 | return $this; |
||
259 | } |
||
260 | |||
261 | public function setBorder( |
||
262 | int $topWidth, |
||
263 | $rightWidth = null, |
||
264 | $bottomWidth = null, |
||
265 | $leftWidth = null, |
||
266 | string $colour = null |
||
267 | ) : self { |
||
268 | View Code Duplication | if (!is_int($rightWidth)) { |
|
0 ignored issues
–
show
This code seems to be duplicated across 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...
|
|||
269 | $colour = $rightWidth; |
||
270 | $rightWidth = $bottomWidth = $leftWidth = $topWidth; |
||
271 | } elseif (!is_int($bottomWidth)) { |
||
272 | $colour = $bottomWidth; |
||
273 | $bottomWidth = $topWidth; |
||
274 | $leftWidth = $rightWidth; |
||
275 | } elseif (!is_int($leftWidth)) { |
||
276 | $colour = $leftWidth; |
||
277 | $leftWidth = $rightWidth; |
||
278 | } |
||
279 | |||
280 | $this->style['borderTopWidth'] = $topWidth; |
||
281 | $this->style['borderRightWidth'] = $rightWidth; |
||
282 | $this->style['borderBottomWidth'] = $bottomWidth; |
||
283 | $this->style['borderLeftWidth'] = $leftWidth; |
||
284 | |||
285 | if (is_string($colour)) { |
||
286 | $this->style['borderColour'] = $colour; |
||
287 | } elseif ($colour !== null) { |
||
288 | throw new \InvalidArgumentException('Invalid colour'); |
||
289 | } |
||
290 | |||
291 | return $this; |
||
292 | } |
||
293 | |||
294 | public function setBorderTopWidth(int $width) : self |
||
295 | { |
||
296 | $this->style['borderTopWidth'] = $width; |
||
297 | |||
298 | return $this; |
||
299 | } |
||
300 | |||
301 | public function setBorderRightWidth(int $width) : self |
||
302 | { |
||
303 | $this->style['borderRightWidth'] = $width; |
||
304 | |||
305 | return $this; |
||
306 | } |
||
307 | |||
308 | public function setBorderBottomWidth(int $width) : self |
||
309 | { |
||
310 | $this->style['borderBottomWidth'] = $width; |
||
311 | |||
312 | return $this; |
||
313 | } |
||
314 | |||
315 | public function setBorderLeftWidth(int $width) : self |
||
316 | { |
||
317 | $this->style['borderLeftWidth'] = $width; |
||
318 | |||
319 | return $this; |
||
320 | } |
||
321 | |||
322 | public function setBorderColour(string $colour, $fallback = null) : self |
||
323 | { |
||
324 | $this->style['borderColour'] = $colour; |
||
325 | $this->style['borderColourFallback'] = $fallback; |
||
326 | |||
327 | return $this; |
||
328 | } |
||
329 | |||
330 | public function setTerminal(Terminal $terminal) : self |
||
331 | { |
||
332 | $this->terminal = $terminal; |
||
333 | return $this; |
||
334 | } |
||
335 | |||
336 | public function getTerminal() : Terminal |
||
337 | { |
||
338 | return $this->terminal; |
||
339 | } |
||
340 | |||
341 | private function getDefaultItems() : array |
||
342 | { |
||
343 | $actions = []; |
||
344 | if ($this->parent) { |
||
345 | $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction); |
||
346 | } |
||
347 | |||
348 | $actions[] = new SelectableItem($this->exitButtonText, new ExitAction); |
||
349 | return $actions; |
||
350 | } |
||
351 | |||
352 | public function disableDefaultItems() : self |
||
353 | { |
||
354 | $this->disableDefaultItems = true; |
||
355 | |||
356 | return $this; |
||
357 | } |
||
358 | |||
359 | private function itemsHaveExtra(array $items) : bool |
||
360 | { |
||
361 | return !empty(array_filter($items, function (MenuItemInterface $item) { |
||
362 | return $item->showsItemExtra(); |
||
363 | })); |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * Recursively drop back to the parents menu style |
||
368 | * when the current menu has a parent and has no changes |
||
369 | */ |
||
370 | public function getMenuStyle() : MenuStyle |
||
371 | { |
||
372 | if (null === $this->parent) { |
||
373 | return $this->buildStyle(); |
||
374 | } |
||
375 | |||
376 | if ($this->style !== MenuStyle::getDefaultStyleValues()) { |
||
377 | return $this->buildStyle(); |
||
378 | } |
||
379 | |||
380 | return $this->parent->getMenuStyle(); |
||
381 | } |
||
382 | |||
383 | private function buildStyle() : MenuStyle |
||
384 | { |
||
385 | $style = (new MenuStyle($this->terminal)) |
||
386 | ->setFg($this->style['fg']) |
||
387 | ->setBg($this->style['bg']) |
||
388 | ->setWidth($this->style['width']) |
||
389 | ->setPaddingTopBottom($this->style['paddingTopBottom']) |
||
390 | ->setPaddingLeftRight($this->style['paddingLeftRight']) |
||
391 | ->setSelectedMarker($this->style['selectedMarker']) |
||
392 | ->setUnselectedMarker($this->style['unselectedMarker']) |
||
393 | ->setItemExtra($this->style['itemExtra']) |
||
394 | ->setDisplaysExtra($this->style['displaysExtra']) |
||
395 | ->setTitleSeparator($this->style['titleSeparator']) |
||
396 | ->setBorderTopWidth($this->style['borderTopWidth']) |
||
397 | ->setBorderRightWidth($this->style['borderRightWidth']) |
||
398 | ->setBorderBottomWidth($this->style['borderBottomWidth']) |
||
399 | ->setBorderLeftWidth($this->style['borderLeftWidth']) |
||
400 | ->setBorderColour($this->style['borderColour'], $this->style['borderColourFallback']); |
||
401 | |||
402 | $this->style['marginAuto'] ? $style->setMarginAuto() : $style->setMargin($this->style['margin']); |
||
403 | |||
404 | return $style; |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * @throws RuntimeException |
||
409 | */ |
||
410 | public function getSubMenu(string $id) : CliMenu |
||
411 | { |
||
412 | if (false === $this->isBuilt) { |
||
413 | throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id)); |
||
414 | } |
||
415 | |||
416 | return $this->subMenus['submenu-placeholder-' . $id]; |
||
417 | } |
||
418 | |||
419 | private function buildSplitItems(array $items) : array |
||
420 | { |
||
421 | return array_map(function ($item) { |
||
422 | if (!is_string($item) || 0 !== strpos($item, 'splititem-placeholder-')) { |
||
423 | return $item; |
||
424 | } |
||
425 | |||
426 | $splitItemBuilder = $this->splitItemBuilders[$item]; |
||
427 | $this->splitItems[$item] = $splitItemBuilder->build(); |
||
428 | |||
429 | return $this->splitItems[$item]; |
||
430 | }, $items); |
||
431 | } |
||
432 | |||
433 | public function build() : CliMenu |
||
434 | { |
||
435 | $this->isBuilt = true; |
||
436 | |||
437 | $mergedItems = $this->disableDefaultItems |
||
438 | ? $this->menuItems |
||
439 | : array_merge($this->menuItems, $this->getDefaultItems()); |
||
440 | |||
441 | |||
442 | $menuItems = $this->buildSplitItems($mergedItems); |
||
443 | $menuItems = $this->buildSubMenus($menuItems); |
||
444 | |||
445 | $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems); |
||
446 | |||
447 | $menu = new CliMenu( |
||
448 | $this->menuTitle, |
||
449 | $menuItems, |
||
450 | $this->terminal, |
||
451 | $this->getMenuStyle() |
||
452 | ); |
||
453 | |||
454 | foreach ($this->subMenus as $subMenu) { |
||
455 | $subMenu->setParent($menu); |
||
456 | } |
||
457 | |||
458 | foreach ($this->splitItemBuilders as $splitItemBuilder) { |
||
459 | $splitItemBuilder->setSubMenuParents($menu); |
||
460 | } |
||
461 | |||
462 | return $menu; |
||
463 | } |
||
464 | } |
||
465 |
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.