AbstractTerminal   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getWidth() 0 11 4
A initDimensionsUnix() 0 10 3
A getHeight() 0 11 4
A initDimensionsWindows() 0 12 5
A getSttyColumns() 0 28 4
A initDimensions() 0 6 3
A getConsoleMode() 0 23 5
1
<?php declare(strict_types=1);
2
3
namespace AlecRabbit\Cli\Tools\Core;
4
5
use const AlecRabbit\ENV_ANSICON;
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
25
    /** @var null|int */
26
    protected static $width;
27
28
    /** @var null|int */
29
    protected static $height;
30
31
    /**
32
     * @codeCoverageIgnore
33
     */
34
    protected static function initDimensions(): void
35
    {
36
        if ('\\' === \DIRECTORY_SEPARATOR) {
37
            self::initDimensionsWindows();
38
        } elseif ($sttyString = static::getSttyColumns()) {
39
            self::initDimensionsUnix($sttyString);
40
        }
41
    }
42
43
    /**
44
     * @codeCoverageIgnore
45
     */
46
    protected static function initDimensionsWindows(): void
47
    {
48
        if ((false !== $term = getenv(ENV_ANSICON)) &&
49
            preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($term), $matches)) {
50
            // extract [w, H] from "wxh (WxH)"
51
            // or [w, h] from "wxh"
52
            static::$width = (int)$matches[1];
53
            static::$height = isset($matches[4]) ? (int)$matches[4] : (int)$matches[2];
54
        } elseif (null !== $dimensions = static::getConsoleMode()) {
55
            // extract [w, h] from "wxh"
56
            static::$width = (int)$dimensions[0];
57
            static::$height = (int)$dimensions[1];
58
        }
59
    }
60
61
    /**
62
     * @codeCoverageIgnore
63
     *
64
     * Runs and parses mode CON if it's available, suppressing any error output.
65
     *
66
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
67
     */
68
    protected static function getConsoleMode(): ?array
69
    {
70
        if (!\function_exists('proc_open')) {
71
            return null;
72
        }
73
74
        $descriptorSpec = [
75
            1 => ['pipe', 'w'],
76
            2 => ['pipe', 'w'],
77
        ];
78
        $process =
79
            proc_open('mode CON', $descriptorSpec, $pipes, null, null, ['suppress_errors' => true]);
80
        if (\is_resource($process)) {
81
            $info = stream_get_contents($pipes[1]);
82
            fclose($pipes[1]);
83
            fclose($pipes[2]);
84
            proc_close($process);
85
86
            if (false !== $info && preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
87
                return [(int)$matches[2], (int)$matches[1]];
88
            }
89
        }
90
        return null;
91
    }
92
93
    /**
94
     * @codeCoverageIgnore
95
     *
96
     * Runs and parses stty -a if it's available, suppressing any error output.
97
     *
98
     * @return string|null
99
     */
100
    protected static function getSttyColumns(): ?string
101
    {
102
        if (!\function_exists('proc_open')) {
103
            return null;
104
        }
105
106
        $descriptorSpec = [
107
            1 => ['pipe', 'w'],
108
            2 => ['pipe', 'w'],
109
        ];
110
111
        $process = proc_open(
112
            'stty -a | grep columns',
113
            $descriptorSpec,
114
            $pipes,
115
            null,
116
            null,
117
            ['suppress_errors' => true]
118
        );
119
120
        if (\is_resource($process)) {
121
            $info = stream_get_contents($pipes[1]);
122
            fclose($pipes[1]);
123
            fclose($pipes[2]);
124
            proc_close($process);
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 static 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 static 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