Passed
Push — master ( 220960...7c8c93 )
by Alec
02:41
created

AbstractTerminal   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 72
dl 0
loc 176
ccs 10
cts 10
cp 1
rs 10
c 0
b 0
f 0
wmc 29

8 Methods

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