Completed
Pull Request — master (#78)
by Aydin
02:26
created

InputIO::drawCenteredLine()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 16
nc 1
nop 3
1
<?php
2
3
namespace PhpSchool\CliMenu\Input;
4
5
use PhpSchool\CliMenu\CliMenu;
6
use PhpSchool\CliMenu\MenuStyle;
7
use PhpSchool\CliMenu\Terminal\TerminalInterface;
8
use PhpSchool\CliMenu\Util\StringUtil;
9
10
/**
11
 * @author Aydin Hassan <[email protected]>
12
 */
13
class InputIO
14
{
15
    /**
16
     * @var MenuStyle
17
     */
18
    private $style;
19
20
    /**
21
     * @var CliMenu
22
     */
23
    private $parentMenu;
24
25
    /**
26
     * @var TerminalInterface
27
     */
28
    private $terminal;
29
30
    /**
31
     * @var array
32
     */
33
    private $inputMap = [
34
        "\n"   => 'enter',
35
        "\r"   => 'enter',
36
        "\177" => 'backspace'
37
    ];
38
39
    /**
40
     * @var callable[][]
41
     */
42
    private $callbacks = [];
43
44
    public function __construct(CliMenu $parentMenu, MenuStyle $menuStyle, TerminalInterface $terminal)
45
    {
46
        $this->style        = $menuStyle;
47
        $this->terminal     = $terminal;
48
        $this->parentMenu   = $parentMenu;
49
    }
50
51
    public function collect(Input $input) : InputResult
52
    {
53
        $this->drawInput($input, $input->getPlaceholderText());
54
55
        $inputValue = $input->getPlaceholderText();
56
57
        while (($userInput = $this->terminal->getKeyedInput($this->inputMap)) !== false) {
0 ignored issues
show
Unused Code introduced by
The call to TerminalInterface::getKeyedInput() has too many arguments starting with $this->inputMap.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
58
            $this->parentMenu->redraw();
59
            $this->drawInput($input, $inputValue);
60
61
            if ($userInput === 'enter') {
62
                if ($input->validate($inputValue)) {
63
                    $this->parentMenu->redraw();
64
                    return new InputResult($inputValue);
65
                } else {
66
                    $this->drawInputWithError($input, $inputValue);
67
                    continue;
68
                }
69
            }
70
71
            if ($userInput === 'backspace') {
72
                $inputValue = substr($inputValue, 0, -1);
73
                $this->drawInput($input, $inputValue);
74
                continue;
75
            }
76
77
            if (!empty($this->callbacks[$userInput])) {
78
                foreach ($this->callbacks[$userInput] as $callback) {
79
                    $inputValue = $callback($this, $inputValue);
80
                    $this->drawInput($input, $inputValue);
81
                }
82
                continue;
83
            }
84
85
            $inputValue .= $userInput;
86
            $this->drawInput($input, $inputValue);
87
        }
88
    }
89
90
    public function registerInputMap(string $input, string $mapTo) : void
91
    {
92
        $this->inputMap[$input] = $mapTo;
93
    }
94
95
    public function registerControlCallback(string $control, callable $callback) : void
96
    {
97
        if (!isset($this->callbacks[$control])) {
98
            $this->callbacks[$control] = [];
99
        }
100
101
        $this->callbacks[$control][] = $callback;
102
    }
103
104
    private function getInputWidth(array $lines)
105
    {
106
        return max(
107
            array_map(
108
                function (string $line) {
109
                    return mb_strlen($line);
110
                },
111
                $lines
112
            )
113
        );
114
    }
115
116
    private function calculateYPosition(Input $input) : int
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
117
    {
118
        $lines = 5; //1. empty 2. prompt text 3. empty 4. input 5. empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
119
120
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
121
    }
122
123
    private function calculateYPositionWithError() : int
124
    {
125
        $lines = 7; //1. empty 2. prompt text 3. empty 4. input 5. empty 6. error 7. empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
126
127
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
128
    }
129
130
    private function calculateXPosition(Input $input, string $userInput) : int
131
    {
132
        $width = $this->getInputWidth(
133
            [
134
                $input->getPromptText(),
135
                $input->getValidationFailedText(),
136
                $userInput
137
            ]
138
        );
139
140
        $parentStyle     = $this->parentMenu->getStyle();
141
        $halfWidth       = ($width + ($this->style->getPadding() * 2)) / 2;
142
        $parentHalfWidth = ceil($parentStyle->getWidth() / 2);
143
144
        return $parentHalfWidth - $halfWidth;
145
    }
146
147
    private function drawLine(Input $input, string $userInput, string $text) : void
148
    {
149
        $this->terminal->moveCursorToColumn($this->calculateXPosition($input, $userInput));
150
151
        printf(
152
            "%s%s%s%s%s\n",
153
            $this->style->getUnselectedSetCode(),
154
            str_repeat(' ', $this->style->getPadding()),
155
            $text,
156
            str_repeat(' ', $this->style->getPadding()),
157
            $this->style->getUnselectedUnsetCode()
158
        );
159
    }
160
161
    private function drawCenteredLine(Input $input, string $userInput, string $text) : void
162
    {
163
        $width = $this->getInputWidth(
164
            [
165
                $input->getPromptText(),
166
                $input->getValidationFailedText(),
167
                $userInput
168
            ]
169
        );
170
171
        $textLength = mb_strlen(StringUtil::stripAnsiEscapeSequence($text));
172
        $leftFill   = ($width / 2) - ($textLength / 2);
173
        $rightFill  = ceil($width - $leftFill - $textLength);
174
175
        $this->drawLine(
176
            $input,
177
            $userInput,
178
            sprintf(
179
                '%s%s%s',
180
                str_repeat(' ', $leftFill),
181
                $text,
182
                str_repeat(' ', $rightFill)
183
            )
184
        );
185
    }
186
187
    private function drawEmptyLine(Input $input, string $userInput) : void
188
    {
189
        $width = $this->getInputWidth(
190
            [
191
                $input->getPromptText(),
192
                $input->getValidationFailedText(),
193
                $userInput
194
            ]
195
        );
196
197
        $this->drawLine(
198
            $input,
199
            $userInput,
200
            str_repeat(' ', $width)
201
        );
202
    }
203
204
    private function drawInput(Input $input, string $userInput) : void
205
    {
206
        $this->terminal->moveCursorToRow($this->calculateYPosition($input));
207
208
        $this->drawEmptyLine($input, $userInput);
209
        $this->drawTitle($input, $userInput);
210
        $this->drawEmptyLine($input, $userInput);
211
        $this->drawInputField($input, $input->format($userInput));
212
        $this->drawEmptyLine($input, $userInput);
213
    }
214
215
    private function drawInputWithError(Input $input, string $userInput) : void
216
    {
217
        $this->terminal->moveCursorToRow($this->calculateYPositionWithError($input));
0 ignored issues
show
Unused Code introduced by
The call to InputIO::calculateYPositionWithError() has too many arguments starting with $input.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
218
219
        $this->drawEmptyLine($input, $userInput);
220
        $this->drawTitle($input, $userInput);
221
        $this->drawEmptyLine($input, $userInput);
222
        $this->drawInputField($input, $input->format($userInput));
223
        $this->drawEmptyLine($input, $userInput);
224
        $this->drawCenteredLine(
225
            $input,
226
            $userInput,
227
            sprintf(
228
                '%s',
229
                $input->getValidationFailedText()
230
            )
231
        );
232
        $this->drawEmptyLine($input, $userInput);
233
    }
234
235
    private function drawTitle(Input $input, string $userInput) : void
236
    {
237
238
        $this->drawCenteredLine(
239
            $input,
240
            $userInput,
241
            $input->getPromptText()
242
        );
243
    }
244
245
    private function drawInputField(Input $input, string $userInput) : void
246
    {
247
        $this->drawCenteredLine(
248
            $input,
249
            $userInput,
250
            sprintf(
251
                '%s%s%s%s%s',
252
                $this->style->getUnselectedUnsetCode(),
253
                $this->style->getSelectedSetCode(),
254
                $userInput,
255
                $this->style->getSelectedUnsetCode(),
256
                $this->style->getUnselectedSetCode()
257
            )
258
        );
259
    }
260
}
261