Passed
Push — master ( 538d07...120b0a )
by Théo
03:15
created

Terminal::hasVt100Support()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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