1 | <?php |
||||
2 | |||||
3 | namespace PhpSchool\CliMenu\MenuItem; |
||||
4 | |||||
5 | use Assert\Assertion; |
||||
6 | use PhpSchool\CliMenu\CliMenu; |
||||
7 | use PhpSchool\CliMenu\CliMenuBuilder; |
||||
0 ignored issues
–
show
|
|||||
8 | use PhpSchool\CliMenu\MenuStyle; |
||||
9 | use PhpSchool\CliMenu\Util\StringUtil; |
||||
10 | |||||
11 | /** |
||||
12 | * @author Michael Woodward <[email protected]> |
||||
13 | */ |
||||
14 | class SplitItem implements MenuItemInterface |
||||
15 | { |
||||
16 | /** |
||||
17 | * @var array |
||||
18 | */ |
||||
19 | private $items = []; |
||||
20 | |||||
21 | /** |
||||
22 | * @var int|null |
||||
23 | */ |
||||
24 | private $selectedItemIndex; |
||||
25 | |||||
26 | /** |
||||
27 | * @var bool |
||||
28 | */ |
||||
29 | private $canBeSelected = true; |
||||
30 | |||||
31 | /** |
||||
32 | * @var int |
||||
33 | */ |
||||
34 | private $gutter = 2; |
||||
35 | |||||
36 | /** |
||||
37 | * @var array |
||||
38 | */ |
||||
39 | private static $blacklistedItems = [ |
||||
40 | \PhpSchool\CliMenu\MenuItem\AsciiArtItem::class, |
||||
41 | \PhpSchool\CliMenu\MenuItem\LineBreakItem::class, |
||||
42 | \PhpSchool\CliMenu\MenuItem\SplitItem::class, |
||||
43 | ]; |
||||
44 | |||||
45 | public function __construct(array $items = []) |
||||
46 | { |
||||
47 | $this->addItems($items); |
||||
48 | $this->setDefaultSelectedItem(); |
||||
49 | } |
||||
50 | |||||
51 | public function setGutter(int $gutter) : void |
||||
52 | { |
||||
53 | Assertion::greaterOrEqualThan($gutter, 0); |
||||
54 | $this->gutter = $gutter; |
||||
55 | } |
||||
56 | |||||
57 | public function addItem(MenuItemInterface $item) : self |
||||
58 | { |
||||
59 | foreach (self::$blacklistedItems as $bl) { |
||||
60 | if ($item instanceof $bl) { |
||||
61 | throw new \InvalidArgumentException("Cannot add a $bl to a SplitItem"); |
||||
62 | } |
||||
63 | } |
||||
64 | $this->items[] = $item; |
||||
65 | $this->setDefaultSelectedItem(); |
||||
66 | return $this; |
||||
67 | } |
||||
68 | |||||
69 | public function addItems(array $items) : self |
||||
70 | { |
||||
71 | foreach ($items as $item) { |
||||
72 | $this->addItem($item); |
||||
73 | } |
||||
74 | |||||
75 | return $this; |
||||
76 | } |
||||
77 | |||||
78 | public function setItems(array $items) : self |
||||
79 | { |
||||
80 | $this->items = []; |
||||
81 | $this->addItems($items); |
||||
82 | return $this; |
||||
83 | } |
||||
84 | |||||
85 | /** |
||||
86 | * Select default item |
||||
87 | */ |
||||
88 | private function setDefaultSelectedItem() : void |
||||
89 | { |
||||
90 | foreach ($this->items as $index => $item) { |
||||
91 | if ($item->canSelect()) { |
||||
92 | $this->canBeSelected = true; |
||||
93 | $this->selectedItemIndex = $index; |
||||
94 | return; |
||||
95 | } |
||||
96 | } |
||||
97 | |||||
98 | $this->canBeSelected = false; |
||||
99 | $this->selectedItemIndex = null; |
||||
100 | } |
||||
101 | |||||
102 | /** |
||||
103 | * The output text for the item |
||||
104 | */ |
||||
105 | public function getRows(MenuStyle $style, bool $selected = false) : array |
||||
106 | { |
||||
107 | $numberOfItems = count($this->items); |
||||
108 | |||||
109 | if ($numberOfItems === 0) { |
||||
110 | throw new \RuntimeException(sprintf('There should be at least one item added to: %s', __CLASS__)); |
||||
111 | } |
||||
112 | |||||
113 | if (!$selected) { |
||||
114 | $this->setDefaultSelectedItem(); |
||||
115 | } |
||||
116 | |||||
117 | $length = $style->getDisplaysExtra() |
||||
118 | ? floor($style->getContentWidth() / $numberOfItems) - (mb_strlen($style->getItemExtra()) + 2) |
||||
119 | : floor($style->getContentWidth() / $numberOfItems); |
||||
120 | |||||
121 | $length -= $this->gutter; |
||||
122 | |||||
123 | $missingLength = $style->getContentWidth() % $numberOfItems; |
||||
124 | |||||
125 | return $this->buildRows( |
||||
126 | array_map(function ($index, $item) use ($selected, $length, $style) { |
||||
127 | $isSelected = $selected && $index === $this->selectedItemIndex; |
||||
128 | $marker = $item->canSelect() |
||||
129 | ? sprintf('%s', $style->getMarker($isSelected)) |
||||
130 | : ''; |
||||
131 | |||||
132 | $itemExtra = ''; |
||||
133 | if ($style->getDisplaysExtra()) { |
||||
134 | $itemExtra = $item->showsItemExtra() |
||||
135 | ? sprintf(' %s', $style->getItemExtra()) |
||||
136 | : sprintf(' %s', str_repeat(' ', mb_strlen($style->getItemExtra()))); |
||||
137 | } |
||||
138 | |||||
139 | return $this->buildCell( |
||||
140 | explode( |
||||
141 | "\n", |
||||
142 | StringUtil::wordwrap( |
||||
143 | sprintf('%s%s', $marker, $item->getText()), |
||||
144 | $length, |
||||
1 ignored issue
–
show
$length of type double is incompatible with the type integer expected by parameter $width of PhpSchool\CliMenu\Util\StringUtil::wordwrap() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
145 | sprintf("\n%s", str_repeat(' ', mb_strlen($marker))) |
||||
146 | ) |
||||
147 | ), |
||||
148 | $length, |
||||
1 ignored issue
–
show
$length of type double is incompatible with the type integer expected by parameter $length of PhpSchool\CliMenu\MenuItem\SplitItem::buildCell() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
149 | $style, |
||||
150 | $isSelected, |
||||
151 | $itemExtra |
||||
152 | ); |
||||
153 | }, array_keys($this->items), $this->items), |
||||
154 | $style, |
||||
155 | $missingLength, |
||||
156 | $length |
||||
1 ignored issue
–
show
$length of type double is incompatible with the type integer expected by parameter $length of PhpSchool\CliMenu\MenuItem\SplitItem::buildRows() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
157 | ); |
||||
158 | } |
||||
159 | |||||
160 | private function buildRows(array $cells, MenuStyle $style, int $missingLength, int $length) : array |
||||
161 | { |
||||
162 | $extraPadLength = $style->getDisplaysExtra() ? 2 + mb_strlen($style->getItemExtra()) : 0; |
||||
163 | |||||
164 | return array_map( |
||||
165 | function ($i) use ($cells, $length, $missingLength, $extraPadLength) { |
||||
166 | return $this->buildRow($cells, $i, $length, $missingLength, $extraPadLength); |
||||
167 | }, |
||||
168 | range(0, max(array_map('count', $cells)) - 1) |
||||
169 | ); |
||||
170 | } |
||||
171 | |||||
172 | private function buildRow(array $cells, int $index, int $length, int $missingLength, int $extraPadLength) : string |
||||
173 | { |
||||
174 | return sprintf( |
||||
175 | '%s%s', |
||||
176 | implode( |
||||
177 | '', |
||||
178 | array_map( |
||||
179 | function ($cell) use ($index, $length, $extraPadLength) { |
||||
180 | return $cell[$index] ?? str_repeat(' ', $length + $this->gutter + $extraPadLength); |
||||
181 | }, |
||||
182 | $cells |
||||
183 | ) |
||||
184 | ), |
||||
185 | str_repeat(' ', $missingLength) |
||||
186 | ); |
||||
187 | } |
||||
188 | |||||
189 | private function buildCell( |
||||
190 | array $content, |
||||
191 | int $length, |
||||
192 | MenuStyle $style, |
||||
193 | bool $isSelected, |
||||
194 | string $itemExtra |
||||
195 | ) : array { |
||||
196 | return array_map(function ($row, $index) use ($length, $style, $isSelected, $itemExtra) { |
||||
197 | $invertedColoursSetCode = $isSelected |
||||
198 | ? $style->getInvertedColoursSetCode() |
||||
199 | : ''; |
||||
200 | $invertedColoursUnsetCode = $isSelected |
||||
201 | ? $style->getInvertedColoursUnsetCode() |
||||
202 | : ''; |
||||
203 | |||||
204 | return sprintf( |
||||
205 | '%s%s%s%s%s%s', |
||||
206 | $invertedColoursSetCode, |
||||
207 | $row, |
||||
208 | str_repeat(' ', $length - mb_strlen($row)), |
||||
209 | $index === 0 ? $itemExtra : str_repeat(' ', mb_strlen($itemExtra)), |
||||
210 | $invertedColoursUnsetCode, |
||||
211 | str_repeat(' ', $this->gutter) |
||||
212 | ); |
||||
213 | }, $content, array_keys($content)); |
||||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * Is there an item with this index and can it be |
||||
218 | * selected? |
||||
219 | */ |
||||
220 | public function canSelectIndex(int $index) : bool |
||||
221 | { |
||||
222 | return isset($this->items[$index]) && $this->items[$index]->canSelect(); |
||||
223 | } |
||||
224 | |||||
225 | /** |
||||
226 | * Set the item index which should be selected. If the item does |
||||
227 | * not exist then throw an exception. |
||||
228 | */ |
||||
229 | public function setSelectedItemIndex(int $index) : void |
||||
230 | { |
||||
231 | if (!isset($this->items[$index])) { |
||||
232 | throw new \InvalidArgumentException(sprintf('Index: "%s" does not exist', $index)); |
||||
233 | } |
||||
234 | |||||
235 | $this->selectedItemIndex = $index; |
||||
236 | } |
||||
237 | |||||
238 | /** |
||||
239 | * Get the currently select item index. |
||||
240 | * May be null in case of no selectable item. |
||||
241 | */ |
||||
242 | public function getSelectedItemIndex() : ?int |
||||
243 | { |
||||
244 | return $this->selectedItemIndex; |
||||
245 | } |
||||
246 | |||||
247 | /** |
||||
248 | * Get the currently selected item - if no items are selectable |
||||
249 | * then throw an exception. |
||||
250 | */ |
||||
251 | public function getSelectedItem() : MenuItemInterface |
||||
252 | { |
||||
253 | if (null === $this->selectedItemIndex) { |
||||
254 | throw new \RuntimeException('No item is selected'); |
||||
255 | } |
||||
256 | |||||
257 | return $this->items[$this->selectedItemIndex]; |
||||
258 | } |
||||
259 | |||||
260 | public function getItems() : array |
||||
261 | { |
||||
262 | return $this->items; |
||||
263 | } |
||||
264 | |||||
265 | /** |
||||
266 | * Can the item be selected |
||||
267 | * In this case, it indicates if at least 1 item inside the SplitItem can be selected |
||||
268 | */ |
||||
269 | public function canSelect() : bool |
||||
270 | { |
||||
271 | return $this->canBeSelected; |
||||
272 | } |
||||
273 | |||||
274 | /** |
||||
275 | * Execute the items callable if required |
||||
276 | */ |
||||
277 | public function getSelectAction() : ?callable |
||||
278 | { |
||||
279 | return null; |
||||
280 | } |
||||
281 | |||||
282 | /** |
||||
283 | * Whether or not the menu item is showing the menustyle extra value |
||||
284 | */ |
||||
285 | public function showsItemExtra() : bool |
||||
286 | { |
||||
287 | return false; |
||||
288 | } |
||||
289 | |||||
290 | /** |
||||
291 | * Enable showing item extra |
||||
292 | */ |
||||
293 | public function showItemExtra() : void |
||||
294 | { |
||||
295 | //noop |
||||
296 | } |
||||
297 | |||||
298 | /** |
||||
299 | * Disable showing item extra |
||||
300 | */ |
||||
301 | public function hideItemExtra() : void |
||||
302 | { |
||||
303 | //noop |
||||
304 | } |
||||
305 | |||||
306 | /** |
||||
307 | * Nothing to return with SplitItem |
||||
308 | */ |
||||
309 | public function getText() : string |
||||
310 | { |
||||
311 | throw new \BadMethodCallException(sprintf('Not supported on: %s', __CLASS__)); |
||||
312 | } |
||||
313 | } |
||||
314 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths