Passed
Pull Request — master (#443)
by Théo
02:26
created

Terminal::readFromProcess()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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