Passed
Pull Request — master (#1)
by Aydin
02:24
created

UnixTerminal::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
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 = false;
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 string
40
     */
41
    private $details;
0 ignored issues
show
introduced by
The private property $details is not used, and could be removed.
Loading history...
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->input = $input;
62
        $this->output = $output;
63
    }
64
65
    public function getWidth() : int
66
    {
67
        return $this->width ?: $this->width = (int) exec('tput cols');
68
    }
69
70
    public function getHeight() : int
71
    {
72
        return $this->height ?: $this->height = (int) exec('tput lines');
73
    }
74
75
    private function getOriginalConfiguration() : string
76
    {
77
        return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g');
78
    }
79
80
    /**
81
     * Disables echoing every character back to the terminal. This means
82
     * we do not have to clear the line when reading.
83
     */
84
    public function disableEchoBack() : void
85
    {
86
        exec('stty -echo');
87
        $this->echoBack = false;
88
    }
89
90
    /**
91
     * Enable echoing back every character input to the terminal.
92
     */
93
    public function enableEchoBack() : void
94
    {
95
        exec('stty echo');
96
        $this->echoBack = true;
97
    }
98
99
    /**
100
     * Is echo back mode enabled
101
     */
102
    public function isEchoBack() : bool
103
    {
104
        return $this->echoBack;
105
    }
106
107
    /**
108
     * Disable canonical input (allow each key press for reading, rather than the whole line)
109
     *
110
     * @see https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
111
     */
112
    public function disableCanonicalMode() : void
113
    {
114
        if ($this->isCanonical) {
115
            exec('stty -icanon -echo');
116
            $this->isCanonical = false;
117
        }
118
    }
119
120
    /**
121
     * Enable canonical input - read input by line
122
     *
123
     * @see https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
124
     */
125
    public function enableCanonicalMode() : void
126
    {
127
        if (!$this->isCanonical) {
128
            exec('stty canon echo' . $this->getOriginalConfiguration());
129
            $this->isCanonical = true;
130
        }
131
    }
132
133
    /**
134
     * Is canonical mode enabled or not
135
     */
136
    public function isCanonicalMode() : bool
137
    {
138
        return $this->isCanonical;
139
    }
140
141
    /**
142
     * Restore the original terminal configuration
143
     */
144
    public function restoreOriginalConfiguration() : void
145
    {
146
        exec('stty ' . $this->getOriginalConfiguration());
147
    }
148
149
    /**
150
     * Check if the Input & Output streams are interactive. Eg - they are
151
     * connected to a terminal.
152
     *
153
     * @return bool
154
     */
155
    public function isInteractive() : bool
156
    {
157
        return $this->input->isInteractive() && $this->output->isInteractive();
158
    }
159
160
    /**
161
     * Assert that both the Input & Output streams are interactive. Throw
162
     * `NotInteractiveTerminal` if not.
163
     */
164
    public function mustBeInteractive() : void
165
    {
166
        if (!$this->input->isInteractive()) {
167
            throw NotInteractiveTerminal::inputNotInteractive();
168
        }
169
170
        if (!$this->output->isInteractive()) {
171
            throw NotInteractiveTerminal::outputNotInteractive();
172
        }
173
    }
174
175
    /**
176
     * @see https://github.com/symfony/Console/blob/master/Output/StreamOutput.php#L95-L102
177
     */
178
    public function supportsColour() : bool
179
    {
180
        if (DIRECTORY_SEPARATOR === '\\') {
181
            return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
182
        }
183
184
        return $this->isInteractive();
185
    }
186
187
    public function clear() : void
188
    {
189
        $this->output->write("\033[2J");
190
    }
191
192
    public function clearLine() : void
193
    {
194
        $this->output->write(sprintf("\033[%dD\033[K", $this->getWidth()));
195
    }
196
197
    public function clean() : void
198
    {
199
        foreach (range(0, $this->getHeight()) as $rowNum) {
200
            $this->moveCursorToRow($rowNum);
201
            $this->clearLine();
202
        }
203
    }
204
205
    public function enableCursor() : void
206
    {
207
        $this->output->write("\033[?25h");
208
    }
209
210
    public function disableCursor() : void
211
    {
212
        $this->output->write("\033[?25l");
213
    }
214
215
    public function moveCursorToTop() : void
216
    {
217
        $this->output->write("\033[H");
218
    }
219
220
    public function moveCursorToRow(int $rowNumber) : void
221
    {
222
        $this->output->write(sprintf("\033[%d;0H", $rowNumber));
223
    }
224
225
    public function moveCursorToColumn(int $column) : void
226
    {
227
        $this->output->write(sprintf("\033[%dC", $column));
228
    }
229
230
    /**
231
     * Read bytes from the input stream
232
     */
233
    public function read(int $bytes): string
234
    {
235
        $buffer = '';
236
        $this->input->read($bytes, function ($data) use (&$buffer) {
237
            $buffer .= $data;
238
        });
239
        return $buffer;
240
    }
241
242
    /**
243
     * Write to the output stream
244
     */
245
    public function write(string $buffer): void
246
    {
247
        $this->output->write($buffer);
248
    }
249
250
    /**
251
     * Restore the original terminal configuration on shutdown.
252
     */
253
    public function __destruct()
254
    {
255
        $this->restoreOriginalConfiguration();
256
    }
257
}
258