Completed
Push — master ( f98c61...7d4983 )
by Aydin
10s
created

UnixTerminal   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
dl 0
loc 273
rs 8.3157
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A getWidth() 0 3 2
A getHeight() 0 3 2
A __construct() 0 6 1
A getOriginalCanonicalMode() 0 4 1
A moveCursorToRow() 0 3 1
A enableCanonicalMode() 0 5 2
A moveCursorToColumn() 0 3 1
A getColourSupport() 0 3 2
A isCanonicalMode() 0 3 1
A moveCursorToTop() 0 3 1
A isEchoBack() 0 3 1
A showSecondaryScreen() 0 3 1
A supportsColour() 0 7 4
A __destruct() 0 3 1
A clearDown() 0 3 1
A disableCanonicalMode() 0 5 2
A clean() 0 5 2
A enableCursor() 0 3 1
A mustBeInteractive() 0 8 3
A getOriginalConfiguration() 0 3 2
A enableEchoBack() 0 4 1
A write() 0 3 1
A isInteractive() 0 3 2
A clear() 0 3 1
A showPrimaryScreen() 0 3 1
A restoreOriginalConfiguration() 0 3 1
A clearLine() 0 3 1
A read() 0 7 1
A disableCursor() 0 3 1
A disableEchoBack() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like UnixTerminal often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UnixTerminal, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpSchool\Terminal;
4
5
use PhpSchool\Terminal\Exception\NotInteractiveTerminal;
6
use PhpSchool\Terminal\IO\InputStream;
7
use PhpSchool\Terminal\IO\OutputStream;
8
9
/**
10
 * @author Michael Woodward <[email protected]>
11
 * @author Aydin Hassan <[email protected]>
12
 */
13
class UnixTerminal implements Terminal
14
{
15
    /**
16
     * @var bool
17
     */
18
    private $isCanonical;
19
20
    /**
21
     * Whether terminal echo back is enabled or not.
22
     * Eg. user key presses and the terminal immediately shows it.
23
     *
24
     * @var bool
25
     */
26
    private $echoBack = true;
27
28
    /**
29
     * @var int
30
     */
31
    private $width;
32
33
    /**
34
     * @var int
35
     */
36
    private $height;
37
    
38
    /**
39
     * @var int;
40
     */
41
    private $colourSupport;
42
43
    /**
44
     * @var string
45
     */
46
    private $originalConfiguration;
47
48
    /**
49
     * @var InputStream
50
     */
51
    private $input;
52
53
    /**
54
     * @var OutputStream
55
     */
56
    private $output;
57
58
    public function __construct(InputStream $input, OutputStream $output)
59
    {
60
        $this->getOriginalConfiguration();
61
        $this->getOriginalCanonicalMode();
62
        $this->input = $input;
63
        $this->output = $output;
64
    }
65
66
    private function getOriginalCanonicalMode() : void
67
    {
68
        exec('stty -a', $output);
69
        $this->isCanonical = (strpos(implode("\n", $output), ' icanon') !== false);
70
    }
71
72
    public function getWidth() : int
73
    {
74
        return $this->width ?: $this->width = (int) exec('tput cols');
75
    }
76
77
    public function getHeight() : int
78
    {
79
        return $this->height ?: $this->height = (int) exec('tput lines');
80
    }
81
82
    public function getColourSupport() : int
83
    {
84
        return $this->colourSupport ?: $this->colourSupport = (int) exec('tput colors');
85
    }
86
87
    private function getOriginalConfiguration() : string
88
    {
89
        return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g');
90
    }
91
92
    /**
93
     * Disables echoing every character back to the terminal. This means
94
     * we do not have to clear the line when reading.
95
     */
96
    public function disableEchoBack() : void
97
    {
98
        exec('stty -echo');
99
        $this->echoBack = false;
100
    }
101
102
    /**
103
     * Enable echoing back every character input to the terminal.
104
     */
105
    public function enableEchoBack() : void
106
    {
107
        exec('stty echo');
108
        $this->echoBack = true;
109
    }
110
111
    /**
112
     * Is echo back mode enabled
113
     */
114
    public function isEchoBack() : bool
115
    {
116
        return $this->echoBack;
117
    }
118
119
    /**
120
     * Disable canonical input (allow each key press for reading, rather than the whole line)
121
     *
122
     * @see https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
123
     */
124
    public function disableCanonicalMode() : void
125
    {
126
        if ($this->isCanonical) {
127
            exec('stty -icanon');
128
            $this->isCanonical = false;
129
        }
130
    }
131
132
    /**
133
     * Enable canonical input - read input by line
134
     *
135
     * @see https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
136
     */
137
    public function enableCanonicalMode() : void
138
    {
139
        if (!$this->isCanonical) {
140
            exec('stty canon');
141
            $this->isCanonical = true;
142
        }
143
    }
144
145
    /**
146
     * Is canonical mode enabled or not
147
     */
148
    public function isCanonicalMode() : bool
149
    {
150
        return $this->isCanonical;
151
    }
152
153
    /**
154
     * Restore the original terminal configuration
155
     */
156
    public function restoreOriginalConfiguration() : void
157
    {
158
        exec('stty ' . $this->getOriginalConfiguration());
159
    }
160
161
    /**
162
     * Check if the Input & Output streams are interactive. Eg - they are
163
     * connected to a terminal.
164
     *
165
     * @return bool
166
     */
167
    public function isInteractive() : bool
168
    {
169
        return $this->input->isInteractive() && $this->output->isInteractive();
170
    }
171
172
    /**
173
     * Assert that both the Input & Output streams are interactive. Throw
174
     * `NotInteractiveTerminal` if not.
175
     */
176
    public function mustBeInteractive() : void
177
    {
178
        if (!$this->input->isInteractive()) {
179
            throw NotInteractiveTerminal::inputNotInteractive();
180
        }
181
182
        if (!$this->output->isInteractive()) {
183
            throw NotInteractiveTerminal::outputNotInteractive();
184
        }
185
    }
186
187
    /**
188
     * @see https://github.com/symfony/Console/blob/master/Output/StreamOutput.php#L95-L102
189
     */
190
    public function supportsColour() : bool
191
    {
192
        if (DIRECTORY_SEPARATOR === '\\') {
193
            return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
194
        }
195
196
        return $this->isInteractive();
197
    }
198
199
    public function clear() : void
200
    {
201
        $this->output->write("\033[2J");
202
    }
203
204
    public function clearLine() : void
205
    {
206
        $this->output->write("\033[2K");
207
    }
208
209
    /**
210
     * Erase screen from the current line down to the bottom of the screen
211
     */
212
    public function clearDown() : void
213
    {
214
        $this->output->write("\033[J");
215
    }
216
217
    public function clean() : void
218
    {
219
        foreach (range(0, $this->getHeight()) as $rowNum) {
220
            $this->moveCursorToRow($rowNum);
221
            $this->clearLine();
222
        }
223
    }
224
225
    public function enableCursor() : void
226
    {
227
        $this->output->write("\033[?25h");
228
    }
229
230
    public function disableCursor() : void
231
    {
232
        $this->output->write("\033[?25l");
233
    }
234
235
    public function moveCursorToTop() : void
236
    {
237
        $this->output->write("\033[H");
238
    }
239
240
    public function moveCursorToRow(int $rowNumber) : void
241
    {
242
        $this->output->write(sprintf("\033[%d;0H", $rowNumber));
243
    }
244
245
    public function moveCursorToColumn(int $column) : void
246
    {
247
        $this->output->write(sprintf("\033[%dC", $column));
248
    }
249
250
    public function showSecondaryScreen() : void
251
    {
252
        $this->output->write("\033[?47h");
253
    }
254
255
    public function showPrimaryScreen() : void
256
    {
257
        $this->output->write("\033[?47l");
258
    }
259
260
    /**
261
     * Read bytes from the input stream
262
     */
263
    public function read(int $bytes): string
264
    {
265
        $buffer = '';
266
        $this->input->read($bytes, function ($data) use (&$buffer) {
267
            $buffer .= $data;
268
        });
269
        return $buffer;
270
    }
271
272
    /**
273
     * Write to the output stream
274
     */
275
    public function write(string $buffer): void
276
    {
277
        $this->output->write($buffer);
278
    }
279
280
    /**
281
     * Restore the original terminal configuration on shutdown.
282
     */
283
    public function __destruct()
284
    {
285
        $this->restoreOriginalConfiguration();
286
    }
287
}
288