Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\Console;
13
 
14
use Symfony\Component\Console\Output\AnsiColorMode;
15
 
16
class Terminal
17
{
18
    public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4;
19
 
20
    private static ?AnsiColorMode $colorMode = null;
21
    private static ?int $width = null;
22
    private static ?int $height = null;
23
    private static ?bool $stty = null;
24
 
25
    /**
26
     * About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
27
     * For more information about true color support with terminals https://github.com/termstandard/colors/.
28
     */
29
    public static function getColorMode(): AnsiColorMode
30
    {
31
        // Use Cache from previous run (or user forced mode)
32
        if (null !== self::$colorMode) {
33
            return self::$colorMode;
34
        }
35
 
36
        // Try with $COLORTERM first
37
        if (\is_string($colorterm = getenv('COLORTERM'))) {
38
            $colorterm = strtolower($colorterm);
39
 
40
            if (str_contains($colorterm, 'truecolor')) {
41
                self::setColorMode(AnsiColorMode::Ansi24);
42
 
43
                return self::$colorMode;
44
            }
45
 
46
            if (str_contains($colorterm, '256color')) {
47
                self::setColorMode(AnsiColorMode::Ansi8);
48
 
49
                return self::$colorMode;
50
            }
51
        }
52
 
53
        // Try with $TERM
54
        if (\is_string($term = getenv('TERM'))) {
55
            $term = strtolower($term);
56
 
57
            if (str_contains($term, 'truecolor')) {
58
                self::setColorMode(AnsiColorMode::Ansi24);
59
 
60
                return self::$colorMode;
61
            }
62
 
63
            if (str_contains($term, '256color')) {
64
                self::setColorMode(AnsiColorMode::Ansi8);
65
 
66
                return self::$colorMode;
67
            }
68
        }
69
 
70
        self::setColorMode(self::DEFAULT_COLOR_MODE);
71
 
72
        return self::$colorMode;
73
    }
74
 
75
    /**
76
     * Force a terminal color mode rendering.
77
     */
78
    public static function setColorMode(?AnsiColorMode $colorMode): void
79
    {
80
        self::$colorMode = $colorMode;
81
    }
82
 
83
    /**
84
     * Gets the terminal width.
85
     */
86
    public function getWidth(): int
87
    {
88
        $width = getenv('COLUMNS');
89
        if (false !== $width) {
90
            return (int) trim($width);
91
        }
92
 
93
        if (null === self::$width) {
94
            self::initDimensions();
95
        }
96
 
97
        return self::$width ?: 80;
98
    }
99
 
100
    /**
101
     * Gets the terminal height.
102
     */
103
    public function getHeight(): int
104
    {
105
        $height = getenv('LINES');
106
        if (false !== $height) {
107
            return (int) trim($height);
108
        }
109
 
110
        if (null === self::$height) {
111
            self::initDimensions();
112
        }
113
 
114
        return self::$height ?: 50;
115
    }
116
 
117
    /**
118
     * @internal
119
     */
120
    public static function hasSttyAvailable(): bool
121
    {
122
        if (null !== self::$stty) {
123
            return self::$stty;
124
        }
125
 
126
        // skip check if exec function is disabled
127
        if (!\function_exists('exec')) {
128
            return false;
129
        }
130
 
131
        exec('stty 2>&1', $output, $exitcode);
132
 
133
        return self::$stty = 0 === $exitcode;
134
    }
135
 
136
    private static function initDimensions()
137
    {
138
        if ('\\' === \DIRECTORY_SEPARATOR) {
1663 lars 139
            $ansicon = getenv('ANSICON');
140
            if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) {
148 lars 141
                // extract [w, H] from "wxh (WxH)"
142
                // or [w, h] from "wxh"
143
                self::$width = (int) $matches[1];
144
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
145
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
146
                // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
147
                // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
148
                self::initDimensionsUsingStty();
149
            } elseif (null !== $dimensions = self::getConsoleMode()) {
150
                // extract [w, h] from "wxh"
151
                self::$width = (int) $dimensions[0];
152
                self::$height = (int) $dimensions[1];
153
            }
154
        } else {
155
            self::initDimensionsUsingStty();
156
        }
157
    }
158
 
159
    /**
160
     * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
161
     */
162
    private static function hasVt100Support(): bool
163
    {
164
        return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
165
    }
166
 
167
    /**
168
     * Initializes dimensions using the output of an stty columns line.
169
     */
170
    private static function initDimensionsUsingStty()
171
    {
172
        if ($sttyString = self::getSttyColumns()) {
173
            if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) {
174
                // extract [w, h] from "rows h; columns w;"
175
                self::$width = (int) $matches[2];
176
                self::$height = (int) $matches[1];
177
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) {
178
                // extract [w, h] from "; h rows; w columns"
179
                self::$width = (int) $matches[2];
180
                self::$height = (int) $matches[1];
181
            }
182
        }
183
    }
184
 
185
    /**
186
     * Runs and parses mode CON if it's available, suppressing any error output.
187
     *
188
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
189
     */
190
    private static function getConsoleMode(): ?array
191
    {
192
        $info = self::readFromProcess('mode CON');
193
 
194
        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
195
            return null;
196
        }
197
 
198
        return [(int) $matches[2], (int) $matches[1]];
199
    }
200
 
201
    /**
202
     * Runs and parses stty -a if it's available, suppressing any error output.
203
     */
204
    private static function getSttyColumns(): ?string
205
    {
206
        return self::readFromProcess(['stty', '-a']);
207
    }
208
 
209
    private static function readFromProcess(string|array $command): ?string
210
    {
211
        if (!\function_exists('proc_open')) {
212
            return null;
213
        }
214
 
215
        $descriptorspec = [
216
            1 => ['pipe', 'w'],
217
            2 => ['pipe', 'w'],
218
        ];
219
 
1663 lars 220
        $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0;
221
 
148 lars 222
        $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
223
        if (!\is_resource($process)) {
224
            return null;
225
        }
226
 
227
        $info = stream_get_contents($pipes[1]);
228
        fclose($pipes[1]);
229
        fclose($pipes[2]);
230
        proc_close($process);
231
 
1663 lars 232
        if ($cp) {
233
            sapi_windows_cp_set($cp);
234
        }
235
 
148 lars 236
        return $info;
237
    }
238
}