Passed
Push — master ( 787040...58cabb )
by Théo
02:43
created

Terminal::hasSttyAvailable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
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::hasSttyAvailable()) {
90
                self::initDimensionsUsingStty();
91
            } elseif (null !== $dimensions = self::getConsoleMode()) {
92
                // extract [w, h] from "wxh"
93
                self::$width = (int) $dimensions[0];
94
                self::$height = (int) $dimensions[1];
95
            }
96
        } else {
97
            self::initDimensionsUsingStty();
98
        }
99
    }
100
101
    private static function initDimensionsUsingStty()
102
    {
103
        if ($sttyString = self::getSttyColumns()) {
104
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
105
                // extract [w, h] from "rows h; columns w;"
106
                self::$width = (int) $matches[2];
107
                self::$height = (int) $matches[1];
108
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
109
                // extract [w, h] from "; h rows; w columns"
110
                self::$width = (int) $matches[2];
111
                self::$height = (int) $matches[1];
112
            }
113
        }
114
    }
115
116
    /**
117
     * Runs and parses mode CON if it's available, suppressing any error output.
118
     *
119
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
120
     */
121
    private static function getConsoleMode()
122
    {
123
        $info = self::readFromProcess('mode CON');
124
125
        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
126
            return null;
127
        }
128
129
        return array((int) $matches[2], (int) $matches[1]);
130
    }
131
132
    /**
133
     * Runs and parses stty -a if it's available, suppressing any error output.
134
     *
135
     * @return string|null
136
     */
137
    private static function getSttyColumns()
138
    {
139
        return self::readFromProcess('stty -a | grep columns');
140
    }
141
142
    /**
143
     * @param string $command
144
     *
145
     * @return string|null
146
     */
147
    private static function readFromProcess($command)
148
    {
149
        if (!\function_exists('proc_open')) {
150
            return null;
151
        }
152
153
        $descriptorspec = array(
154
            1 => array('pipe', 'w'),
155
            2 => array('pipe', 'w'),
156
        );
157
158
        $process = proc_open($command, $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
159
        if (!\is_resource($process)) {
160
            return null;
161
        }
162
163
        $info = stream_get_contents($pipes[1]);
164
        fclose($pipes[1]);
165
        fclose($pipes[2]);
166
        proc_close($process);
167
168
        return $info;
169
    }
170
}
171