Completed
Push — master ( 627059...4c883f )
by Michael
9s
created

InputIO::collect()   C

Complexity

Conditions 10
Paths 9

Size

Total Lines 50
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 5.7647
c 0
b 0
f 0
cc 10
eloc 33
nc 9
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpSchool\CliMenu\Input;
4
5
use PhpSchool\CliMenu\CliMenu;
6
use PhpSchool\CliMenu\Util\StringUtil;
7
use PhpSchool\Terminal\InputCharacter;
8
use PhpSchool\Terminal\NonCanonicalReader;
9
use PhpSchool\Terminal\Terminal;
10
11
/**
12
 * @author Aydin Hassan <[email protected]>
13
 */
14
class InputIO
15
{
16
    /**
17
     * @var CliMenu
18
     */
19
    private $parentMenu;
20
21
    /**
22
     * @var Terminal
23
     */
24
    private $terminal;
25
26
    /**
27
     * @var callable[][]
28
     */
29
    private $callbacks = [];
30
31
    public function __construct(CliMenu $parentMenu, Terminal $terminal)
32
    {
33
        $this->terminal     = $terminal;
34
        $this->parentMenu   = $parentMenu;
35
    }
36
37
    public function collect(Input $input) : InputResult
38
    {
39
        $this->drawInput($input, $input->getPlaceholderText());
40
41
        $inputValue = $input->getPlaceholderText();
42
        $havePlaceHolderValue = !empty($inputValue);
43
44
        $reader = new NonCanonicalReader($this->terminal);
45
46
        while ($char = $reader->readCharacter()) {
47
            if ($char->isNotControl()) {
48
                if ($havePlaceHolderValue) {
49
                    $inputValue = $char->get();
50
                    $havePlaceHolderValue = false;
51
                } else {
52
                    $inputValue .= $char->get();
53
                }
54
55
                $this->parentMenu->redraw();
56
                $this->drawInput($input, $inputValue);
57
                continue;
58
            }
59
60
            if ($char->isHandledControl()) {
61
                switch ($char->getControl()) {
62
                    case InputCharacter::ENTER:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
63
                        if ($input->validate($inputValue)) {
64
                            $this->parentMenu->redraw();
65
                            return new InputResult($inputValue);
66
                        } else {
67
                            $this->drawInputWithError($input, $inputValue);
68
                            continue 2;
69
                        }
70
71
                    case InputCharacter::BACKSPACE:
72
                        $inputValue = substr($inputValue, 0, -1);
73
                        $this->parentMenu->redraw();
74
                        $this->drawInput($input, $inputValue);
75
                        continue 2;
76
                }
77
78
                if (!empty($this->callbacks[$char->getControl()])) {
79
                    foreach ($this->callbacks[$char->getControl()] as $callback) {
80
                        $inputValue = $callback($inputValue);
81
                        $this->drawInput($input, $inputValue);
82
                    }
83
                }
84
            }
85
        }
86
    }
87
88
    public function registerControlCallback(string $control, callable $callback) : void
89
    {
90
        if (!isset($this->callbacks[$control])) {
91
            $this->callbacks[$control] = [];
92
        }
93
94
        $this->callbacks[$control][] = $callback;
95
    }
96
97
    private function getInputWidth(array $lines)
98
    {
99
        return max(
100
            array_map(
101
                function (string $line) {
102
                    return mb_strlen($line);
103
                },
104
                $lines
105
            )
106
        );
107
    }
108
109
    private function calculateYPosition() : int
110
    {
111
        $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...
112
113
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
114
    }
115
116
    private function calculateYPositionWithError() : int
117
    {
118
        $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...
119
120
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
121
    }
122
123
    private function calculateXPosition(Input $input, string $userInput) : int
124
    {
125
        $width = $this->getInputWidth(
126
            [
127
                $input->getPromptText(),
128
                $input->getValidationFailedText(),
129
                $userInput
130
            ]
131
        );
132
133
        $parentStyle     = $this->parentMenu->getStyle();
134
        $halfWidth       = ($width + ($input->getStyle()->getPadding() * 2)) / 2;
135
        $parentHalfWidth = ceil($parentStyle->getWidth() / 2);
136
137
        return $parentHalfWidth - $halfWidth;
138
    }
139
140
    private function drawLine(Input $input, string $userInput, string $text) : void
141
    {
142
        $this->terminal->moveCursorToColumn($this->calculateXPosition($input, $userInput));
143
144
        $line = sprintf(
145
            "%s%s%s%s%s\n",
146
            $input->getStyle()->getUnselectedSetCode(),
147
            str_repeat(' ', $input->getStyle()->getPadding()),
148
            $text,
149
            str_repeat(' ', $input->getStyle()->getPadding()),
150
            $input->getStyle()->getUnselectedUnsetCode()
151
        );
152
153
        $this->terminal->write($line);
154
    }
155
156
    private function drawCenteredLine(Input $input, string $userInput, string $text) : void
157
    {
158
        $width = $this->getInputWidth(
159
            [
160
                $input->getPromptText(),
161
                $input->getValidationFailedText(),
162
                $userInput
163
            ]
164
        );
165
166
        $textLength = mb_strlen(StringUtil::stripAnsiEscapeSequence($text));
167
        $leftFill   = ($width / 2) - ($textLength / 2);
168
        $rightFill  = ceil($width - $leftFill - $textLength);
169
170
        $this->drawLine(
171
            $input,
172
            $userInput,
173
            sprintf(
174
                '%s%s%s',
175
                str_repeat(' ', $leftFill),
176
                $text,
177
                str_repeat(' ', $rightFill)
178
            )
179
        );
180
    }
181
182
    private function drawEmptyLine(Input $input, string $userInput) : void
183
    {
184
        $width = $this->getInputWidth(
185
            [
186
                $input->getPromptText(),
187
                $input->getValidationFailedText(),
188
                $userInput
189
            ]
190
        );
191
192
        $this->drawLine(
193
            $input,
194
            $userInput,
195
            str_repeat(' ', $width)
196
        );
197
    }
198
199
    private function drawInput(Input $input, string $userInput) : void
200
    {
201
        $this->terminal->moveCursorToRow($this->calculateYPosition());
202
203
        $this->drawEmptyLine($input, $userInput);
204
        $this->drawTitle($input, $userInput);
205
        $this->drawEmptyLine($input, $userInput);
206
        $this->drawInputField($input, $input->filter($userInput));
207
        $this->drawEmptyLine($input, $userInput);
208
    }
209
210
    private function drawInputWithError(Input $input, string $userInput) : void
211
    {
212
        $this->terminal->moveCursorToRow($this->calculateYPositionWithError());
213
214
        $this->drawEmptyLine($input, $userInput);
215
        $this->drawTitle($input, $userInput);
216
        $this->drawEmptyLine($input, $userInput);
217
        $this->drawInputField($input, $input->filter($userInput));
218
        $this->drawEmptyLine($input, $userInput);
219
        $this->drawCenteredLine(
220
            $input,
221
            $userInput,
222
            $input->getValidationFailedText()
223
        );
224
        $this->drawEmptyLine($input, $userInput);
225
    }
226
227
    private function drawTitle(Input $input, string $userInput) : void
228
    {
229
230
        $this->drawCenteredLine(
231
            $input,
232
            $userInput,
233
            $input->getPromptText()
234
        );
235
    }
236
237
    private function drawInputField(Input $input, string $userInput) : void
238
    {
239
        $this->drawCenteredLine(
240
            $input,
241
            $userInput,
242
            sprintf(
243
                '%s%s%s%s%s',
244
                $input->getStyle()->getUnselectedUnsetCode(),
245
                $input->getStyle()->getSelectedSetCode(),
246
                $userInput,
247
                $input->getStyle()->getSelectedUnsetCode(),
248
                $input->getStyle()->getUnselectedSetCode()
249
            )
250
        );
251
    }
252
}
253