Passed
Push — master ( 05cc9e...6196c7 )
by Alec
02:39
created

AbstractTerminal::onWindows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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