Passed
Push — master ( 543a09...efdac2 )
by Alec
02:50
created

Terminal::hasColorSupport()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 31
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 14
nc 10
nop 0
dl 0
loc 31
ccs 4
cts 4
cp 1
crap 10
rs 7.6666
c 0
b 0
f 0

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 declare(strict_types=1);
2
/*
3
 * This class based on
4
 * `Symfony\Component\Console\Terminal::class`
5
 * from `symfony\console` package.
6
 *
7
 * hasColorSupport() based on function
8
 * `Symfony\Component\Console\Output\StreamOutput::hasColorSupport()`
9
 *
10
 * @author Fabien Potencier <[email protected]>
11
 */
12
13
namespace AlecRabbit\ConsoleColour;
14
15
/**
16
 * Class Terminal
17
 * @author AlecRabbit
18
 */
19
class Terminal
20
{
21
    public const DEFAULT_WIDTH = 80;
22
    public const DEFAULT_HEIGHT = 50;
23
24
    /** @var null|int */
25
    protected static $width;
26
27
    /** @var null|int */
28
    protected static $height;
29
30
    /** @var null|bool */
31
    protected static $supports256Color;
32
33
    /** @var null|bool */
34
    protected static $supportsColor;
35
36
    /**
37
     * @param bool $recheck
38
     * @return int
39
     */
40 2
    public function width(bool $recheck = false): int
41
    {
42 2
        if (null !== static::$width && true !== $recheck) {
43 2
            return static::$width;
44
        }
45
        return
46 2
            static::$width = $this->getWidth();
47
    }
48
49
    /**
50
     * Gets the terminal width.
51
     *
52
     * @return int
53
     */
54 2
    protected function getWidth(): int
55
    {
56 2
        $width = \getenv('COLUMNS');
57 2
        if (false !== $width) {
58 2
            return (int)\trim($width);
59
        }
60
        // @codeCoverageIgnoreStart
61
        if (null === static::$width) {
62
            static::initDimensions();
63
        }
64
        return static::$width ?: static::DEFAULT_WIDTH;
65
        // @codeCoverageIgnoreEnd
66
    }
67
68
    /**
69
     * @codeCoverageIgnore
70
     */
71
    protected static function initDimensions(): void
72
    {
73
        if (static::onWindows()) {
74
            self::initDimensionsWindows();
75
        } elseif ($sttyString = static::getSttyColumns()) {
76
            self::initDimensionsUnix($sttyString);
77
        }
78
    }
79
80
    /**
81
     * @return bool
82
     */
83 1
    protected static function onWindows(): bool
84
    {
85 1
        return '\\' === \DIRECTORY_SEPARATOR;
86
    }
87
88
    /**
89
     * @codeCoverageIgnore
90
     */
91
    protected static function initDimensionsWindows(): void
92
    {
93
        if ((false !== $term = \getenv('ANSICON')) &&
94
            \preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', \trim($term), $matches)) {
95
            // extract [w, H] from "wxh (WxH)"
96
            // or [w, h] from "wxh"
97
            static::$width = (int)$matches[1];
98
            static::$height = isset($matches[4]) ? (int)$matches[4] : (int)$matches[2];
99
        } elseif (null !== $dimensions = static::getConsoleMode()) {
100
            // extract [w, h] from "wxh"
101
            static::$width = (int)$dimensions[0];
102
            static::$height = (int)$dimensions[1];
103
        }
104
    }
105
106
    /**
107
     * @codeCoverageIgnore
108
     *
109
     * Runs and parses mode CON if it's available, suppressing any error output.
110
     *
111
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
112
     */
113
    protected static function getConsoleMode(): ?array
114
    {
115
        if (!\function_exists('proc_open')) {
116
            return null;
117
        }
118
119
        $descriptorSpec = [
120
            1 => ['pipe', 'w'],
121
            2 => ['pipe', 'w'],
122
        ];
123
        $process =
124
            \proc_open('mode CON', $descriptorSpec, $pipes, null, null, ['suppress_errors' => true]);
125
        if (\is_resource($process)) {
126
            $info = \stream_get_contents($pipes[1]);
127
            \fclose($pipes[1]);
128
            \fclose($pipes[2]);
129
            \proc_close($process);
130
131
            if (false !== $info && \preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
132
                return [(int)$matches[2], (int)$matches[1]];
133
            }
134
        }
135
        return null;
136
    }
137
138
    /**
139
     * @codeCoverageIgnore
140
     *
141
     * Runs and parses stty -a if it's available, suppressing any error output.
142
     *
143
     * @return string|null
144
     */
145
    protected static function getSttyColumns(): ?string
146
    {
147
        if (!\function_exists('proc_open')) {
148
            return null;
149
        }
150
151
        $descriptorSpec = [
152
            1 => ['pipe', 'w'],
153
            2 => ['pipe', 'w'],
154
        ];
155
156
        $process = \proc_open(
157
            'stty -a | grep columns',
158
            $descriptorSpec,
159
            $pipes,
160
            null,
161
            null,
162
            ['suppress_errors' => true]
163
        );
164
165
        if (\is_resource($process)) {
166
            $info = \stream_get_contents($pipes[1]);
167
            \fclose($pipes[1]);
168
            \fclose($pipes[2]);
169
            \proc_close($process);
170
171
            return $info ?: null;
172
        }
173
        return null;
174
    }
175
176
    /**
177
     * @codeCoverageIgnore
178
     * @param string $sttyString
179
     */
180
    protected static function initDimensionsUnix(string $sttyString): void
181
    {
182
        if (\preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
183
            // extract [w, h] from "rows h; columns w;"
184
            static::$width = (int)$matches[2];
185
            static::$height = (int)$matches[1];
186
        } elseif (\preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
187
            // extract [w, h] from "; h rows; w columns"
188
            static::$width = (int)$matches[2];
189
            static::$height = (int)$matches[1];
190
        }
191
    }
192
193
    /**
194
     * @param bool $recheck
195
     * @return int
196
     */
197 2
    public function height(bool $recheck = false): int
198
    {
199 2
        if (null !== static::$height && true !== $recheck) {
200 2
            return static::$height;
201
        }
202
        return
203 2
            static::$height = $this->getHeight();
204
    }
205
206
    /**
207
     * Gets the terminal height.
208
     *
209
     * @return int
210
     */
211 2
    protected function getHeight(): int
212
    {
213 2
        $height = \getenv('LINES');
214 2
        if (false !== $height) {
215 2
            return (int)\trim($height);
216
        }
217
        // @codeCoverageIgnoreStart
218
        if (null === static::$height) {
219
            static::initDimensions();
220
        }
221
        return static::$height ?: static::DEFAULT_HEIGHT;
222
        // @codeCoverageIgnoreEnd
223
    }
224
225
    /**
226
     * @return bool
227
     */
228 72
    public function supports256Color(): bool
229
    {
230 72
        if (null !== static::$supports256Color) {
231 71
            return static::$supports256Color;
232
        }
233
        return
234 1
            static::$supports256Color = $this->check256ColorSupport();
235
    }
236
237
    /**
238
     * @return bool
239
     */
240 1
    protected function check256ColorSupport(): bool
241
    {
242 1
        if (!$this->supportsColor() || !$term = \getenv('TERM')) {
243
            // @codeCoverageIgnoreStart
244
            return false;
245
            // @codeCoverageIgnoreEnd
246
        }
247 1
        return \strpos($term, '256color') !== false;
248
    }
249
250
    /**
251
     * @return bool
252
     */
253 72
    public function supportsColor(): bool
254
    {
255 72
        if (null !== static::$supportsColor) {
256 72
            return static::$supportsColor;
257
        }
258
        return
259 1
            static::$supportsColor = $this->hasColorSupport();
260
    }
261
262
    /**
263
     * Returns true if the stream supports colorization.
264
     *
265
     * Colorization is disabled if not supported by the stream:
266
     *
267
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
268
     * terminals via named pipes, so we can only check the environment.
269
     *
270
     * Reference: Composer\XdebugHandler\Process::supportsColor
271
     * https://github.com/composer/xdebug-handler
272
     *
273
     * @return bool true if the stream supports colorization, false otherwise
274
     */
275 1
    protected function hasColorSupport(): bool
276
    {
277 1
        if ('Hyper' === \getenv('TERM_PROGRAM')) {
278
            // @codeCoverageIgnoreStart
279
            return true;
280
            // @codeCoverageIgnoreEnd
281
        }
282
283
        // @codeCoverageIgnoreStart
284
        if (static::onWindows()) {
285
            return (\function_exists('sapi_windows_vt100_support')
286
                    && @\sapi_windows_vt100_support(STDOUT))
287
                || false !== \getenv('ANSICON')
288
                || 'ON' === \getenv('ConEmuANSI')
289
                || 'xterm' === \getenv('TERM');
290
        }
291
        // @codeCoverageIgnoreEnd
292
293 1
        if (\function_exists('stream_isatty')) {
294 1
            return @\stream_isatty(STDOUT);
295
        }
296
297
        // @codeCoverageIgnoreStart
298
        if (\function_exists('posix_isatty')) {
299
            /** @noinspection PhpComposerExtensionStubsInspection */
300
            return @\posix_isatty(STDOUT);
301
        }
302
303
        $stat = @\fstat(STDOUT);
304
        // Check if formatted mode is S_IFCHR
305
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
306
        // @codeCoverageIgnoreEnd
307
    }
308
}
309