Completed
Push — master ( e920ad...bc045d )
by Aydin
01:58
created

src/MenuItem/SplitItem.php (4 issues)

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
The type PhpSchool\CliMenu\CliMenuBuilder was not found. Maybe you did not declare it correctly or list all dependencies?

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:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 ignore-type  annotation

144
                            /** @scrutinizer ignore-type */ $length,
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 ignore-type  annotation

148
                    /** @scrutinizer ignore-type */ $length,
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 ignore-type  annotation

156
            /** @scrutinizer ignore-type */ $length
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