Passed
Push — master ( db8d65...17211b )
by Alec
02:28
created

AbstractTerminal   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 70
dl 0
loc 166
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
wmc 28

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getHeight() 0 11 4
A initDimensionsWindows() 0 12 5
A initDimensions() 0 6 3
A getConsoleMode() 0 23 5
A initDimensionsUnix() 0 10 3
A getWidth() 0 11 4
A getSttyColumns() 0 29 4
1
<?php declare(strict_types=1);
2
3
namespace AlecRabbit\ConsoleColour\Core;
4
5
/**
6
 * Class Terminal
7
 *
8
 * Reference: Symfony\Component\Console\Terminal::class
9
 * https://github.com/symfony/console
10
 *
11
 * @author Fabien Potencier <[email protected]>
12
 *
13
 * @author AlecRabbit
14
 */
15
abstract class AbstractTerminal extends AbstractColorSupportingTerminal
16
{
17
    protected const DEFAULT_WIDTH = 80;
18
    protected const DEFAULT_HEIGHT = 50;
19
20
    protected const ENV_COLUMNS = 'COLUMNS';
21
    protected const ENV_LINES = 'LINES';
22
23
    /** @var null|int */
24
    protected static $width;
25
26
    /** @var null|int */
27
    protected static $height;
28
29
    /**
30
     * @codeCoverageIgnore
31
     */
32
    protected static function initDimensions(): void
33
    {
34
        if (static::onWindows()) {
35
            self::initDimensionsWindows();
36
        } elseif ($sttyString = static::getSttyColumns()) {
37
            self::initDimensionsUnix($sttyString);
38
        }
39
    }
40
41
    /**
42
     * @codeCoverageIgnore
43
     */
44
    protected static function initDimensionsWindows(): void
45
    {
46
        if ((false !== $term = getenv(static::ENV_ANSICON)) &&
47
            preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($term), $matches)) {
48
            // extract [w, H] from "wxh (WxH)"
49
            // or [w, h] from "wxh"
50
            static::$width = (int)$matches[1];
51
            static::$height = isset($matches[4]) ? (int)$matches[4] : (int)$matches[2];
52
        } elseif (null !== $dimensions = static::getConsoleMode()) {
53
            // extract [w, h] from "wxh"
54
            static::$width = (int)$dimensions[0];
55
            static::$height = (int)$dimensions[1];
56
        }
57
    }
58
59
    /**
60
     * @codeCoverageIgnore
61
     *
62
     * Runs and parses mode CON if it's available, suppressing any error output.
63
     *
64
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
65
     */
66
    protected static function getConsoleMode(): ?array
67
    {
68
        if (!\function_exists('proc_open')) {
69
            return null;
70
        }
71
72
        $descriptorSpec = [
73
            1 => ['pipe', 'w'],
74
            2 => ['pipe', 'w'],
75
        ];
76
        $process =
77
            proc_open('mode CON', $descriptorSpec, $pipes, null, null, ['suppress_errors' => true]);
78
        if (\is_resource($process)) {
79
            $info = stream_get_contents($pipes[1]);
80
            fclose($pipes[1]);
81
            fclose($pipes[2]);
82
            proc_close($process);
83
84
            if (false !== $info && preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
85
                return [(int)$matches[2], (int)$matches[1]];
86
            }
87
        }
88
        return null;
89
    }
90
91
    /**
92
     * @codeCoverageIgnore
93
     *
94
     * Runs and parses stty -a if it's available, suppressing any error output.
95
     *
96
     * @return string|null
97
     */
98
    protected static function getSttyColumns(): ?string
99
    {
100
        if (!\function_exists('proc_open')) {
101
            return null;
102
        }
103
104
        $descriptorSpec = [
105
            1 => ['pipe', 'w'],
106
            2 => ['pipe', 'w'],
107
        ];
108
109
        $process = proc_open(
110
            'stty -a | grep columns',
111
            $descriptorSpec,
112
            $pipes,
113
            null,
114
            null,
115
            ['suppress_errors' => true]
116
        );
117
118
        if (\is_resource($process)) {
119
            $info = stream_get_contents($pipes[1]);
120
            fclose($pipes[1]);
121
            fclose($pipes[2]);
122
            proc_close($process);
123
124
            return $info ?: null;
125
        }
126
        return null;
127
    }
128
129
    /**
130
     * @codeCoverageIgnore
131
     * @param string $sttyString
132
     */
133
    protected static function initDimensionsUnix(string $sttyString): void
134
    {
135
        if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
136
            // extract [w, h] from "rows h; columns w;"
137
            static::$width = (int)$matches[2];
138
            static::$height = (int)$matches[1];
139
        } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
140
            // extract [w, h] from "; h rows; w columns"
141
            static::$width = (int)$matches[2];
142
            static::$height = (int)$matches[1];
143
        }
144
    }
145
146
    /**
147
     * Gets the terminal width.
148
     *
149
     * @return int
150
     */
151 2
    protected function getWidth(): int
152
    {
153 2
        $width = getenv(static::ENV_COLUMNS);
154 2
        if (false !== $width) {
155 2
            return (int)trim($width);
156
        }
157
        // @codeCoverageIgnoreStart
158
        if (null === static::$width) {
159
            static::initDimensions();
160
        }
161
        return static::$width ?: static::DEFAULT_WIDTH;
162
        // @codeCoverageIgnoreEnd
163
    }
164
165
    /**
166
     * Gets the terminal height.
167
     *
168
     * @return int
169
     */
170 2
    protected function getHeight(): int
171
    {
172 2
        $height = getenv(static::ENV_LINES);
173 2
        if (false !== $height) {
174 2
            return (int)trim($height);
175
        }
176
        // @codeCoverageIgnoreStart
177
        if (null === static::$height) {
178
            static::initDimensions();
179
        }
180
        return static::$height ?: static::DEFAULT_HEIGHT;
181
        // @codeCoverageIgnoreEnd
182
    }
183
}
184