Terminal   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 32
eloc 58
dl 0
loc 150
rs 9.84
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A initDimensionsUsingStty() 0 11 4
A hasVt100Support() 0 3 2
A hasSttyAvailable() 0 14 3
A getSttyColumns() 0 3 1
A getHeight() 0 12 4
A getConsoleMode() 0 9 3
B initDimensions() 0 19 8
A readFromProcess() 0 22 3
A getWidth() 0 12 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\RequirementChecker;
16
17
use function exec;
18
use function fclose;
19
use function fopen;
20
use function function_exists;
21
use function getenv;
22
use function is_resource;
23
use function preg_match;
24
use function proc_close;
25
use function proc_open;
26
use function sapi_windows_vt100_support;
27
use function stream_get_contents;
28
use function trim;
29
use const DIRECTORY_SEPARATOR;
30
31
/**
32
 * This file is copy/pasted from the Symfony project to avoid a dependency on `symfony/console` which would be too big for just using this
33
 * class.
34
 *
35
 * @license MIT (c) Fabien Potencier <[email protected]>
36
 */
37
class Terminal
38
{
39
    private static $width;
40
    private static $height;
41
    private static $stty;
42
43
    /**
44
     * Gets the terminal width.
45
     */
46
    public function getWidth(): int
47
    {
48
        $width = getenv('COLUMNS');
49
        if (false !== $width) {
50
            return (int) trim($width);
51
        }
52
53
        if (!isset(self::$width)) {
54
            self::initDimensions();
55
        }
56
57
        return self::$width ?: 80;
58
    }
59
60
    /**
61
     * Gets the terminal height.
62
     */
63
    public function getHeight(): int
64
    {
65
        $height = getenv('LINES');
66
        if (false !== $height) {
67
            return (int) trim($height);
68
        }
69
70
        if (!isset(self::$height)) {
71
            self::initDimensions();
72
        }
73
74
        return self::$height ?: 50;
75
    }
76
77
    /**
78
     * @internal
79
     */
80
    public static function hasSttyAvailable(): bool
81
    {
82
        if (isset(self::$stty)) {
83
            return self::$stty;
84
        }
85
86
        // skip check if exec function is disabled
87
        if (!function_exists('exec')) {
88
            return false;
89
        }
90
91
        exec('stty 2>&1', $output, $exitcode);
92
93
        return self::$stty = 0 === $exitcode;
94
    }
95
96
    private static function initDimensions(): void
97
    {
98
        if ('\\' === DIRECTORY_SEPARATOR) {
99
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON') ?: ''), $matches)) {
100
                // extract [w, H] from "wxh (WxH)"
101
                // or [w, h] from "wxh"
102
                self::$width = (int) $matches[1];
103
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
104
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
105
                // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
106
                // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
107
                self::initDimensionsUsingStty();
108
            } elseif (null !== $dimensions = self::getConsoleMode()) {
109
                // extract [w, h] from "wxh"
110
                self::$width = (int) $dimensions[0];
111
                self::$height = (int) $dimensions[1];
112
            }
113
        } else {
114
            self::initDimensionsUsingStty();
115
        }
116
    }
117
118
    /**
119
     * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
120
     */
121
    private static function hasVt100Support(): bool
122
    {
123
        return function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'wb'));
124
    }
125
126
    private static function initDimensionsUsingStty(): void
127
    {
128
        if ($sttyString = self::getSttyColumns()) {
129
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
130
                // extract [w, h] from "rows h; columns w;"
131
                self::$width = (int) $matches[2];
132
                self::$height = (int) $matches[1];
133
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
134
                // extract [w, h] from "; h rows; w columns"
135
                self::$width = (int) $matches[2];
136
                self::$height = (int) $matches[1];
137
            }
138
        }
139
    }
140
141
    /**
142
     * Runs and parses mode CON if it's available, suppressing any error output.
143
     *
144
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
145
     */
146
    private static function getConsoleMode(): ?array
147
    {
148
        $info = self::readFromProcess('mode CON');
149
150
        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
151
            return null;
152
        }
153
154
        return [(int) $matches[2], (int) $matches[1]];
155
    }
156
157
    /**
158
     * Runs and parses stty -a if it's available, suppressing any error output.
159
     */
160
    private static function getSttyColumns(): ?string
161
    {
162
        return self::readFromProcess('stty -a | grep columns');
163
    }
164
165
    private static function readFromProcess(string $command): ?string
166
    {
167
        if (!function_exists('proc_open')) {
168
            return null;
169
        }
170
171
        $descriptorspec = [
172
            1 => ['pipe', 'w'],
173
            2 => ['pipe', 'w'],
174
        ];
175
176
        $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
177
        if (!is_resource($process)) {
178
            return null;
179
        }
180
181
        $info = stream_get_contents($pipes[1]);
182
        fclose($pipes[1]);
183
        fclose($pipes[2]);
184
        proc_close($process);
185
186
        return $info;
187
    }
188
}
189