Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | 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) {
139
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
140
                // extract [w, H] from "wxh (WxH)"
141
                // or [w, h] from "wxh"
142
                self::$width = (int) $matches[1];
143
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
144
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
145
                // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
146
                // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
147
                self::initDimensionsUsingStty();
148
            } elseif (null !== $dimensions = self::getConsoleMode()) {
149
                // extract [w, h] from "wxh"
150
                self::$width = (int) $dimensions[0];
151
                self::$height = (int) $dimensions[1];
152
            }
153
        } else {
154
            self::initDimensionsUsingStty();
155
        }
156
    }
157
 
158
    /**
159
     * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
160
     */
161
    private static function hasVt100Support(): bool
162
    {
163
        return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
164
    }
165
 
166
    /**
167
     * Initializes dimensions using the output of an stty columns line.
168
     */
169
    private static function initDimensionsUsingStty()
170
    {
171
        if ($sttyString = self::getSttyColumns()) {
172
            if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) {
173
                // extract [w, h] from "rows h; columns w;"
174
                self::$width = (int) $matches[2];
175
                self::$height = (int) $matches[1];
176
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) {
177
                // extract [w, h] from "; h rows; w columns"
178
                self::$width = (int) $matches[2];
179
                self::$height = (int) $matches[1];
180
            }
181
        }
182
    }
183
 
184
    /**
185
     * Runs and parses mode CON if it's available, suppressing any error output.
186
     *
187
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
188
     */
189
    private static function getConsoleMode(): ?array
190
    {
191
        $info = self::readFromProcess('mode CON');
192
 
193
        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
194
            return null;
195
        }
196
 
197
        return [(int) $matches[2], (int) $matches[1]];
198
    }
199
 
200
    /**
201
     * Runs and parses stty -a if it's available, suppressing any error output.
202
     */
203
    private static function getSttyColumns(): ?string
204
    {
205
        return self::readFromProcess(['stty', '-a']);
206
    }
207
 
208
    private static function readFromProcess(string|array $command): ?string
209
    {
210
        if (!\function_exists('proc_open')) {
211
            return null;
212
        }
213
 
214
        $descriptorspec = [
215
            1 => ['pipe', 'w'],
216
            2 => ['pipe', 'w'],
217
        ];
218
 
219
        $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
220
        if (!\is_resource($process)) {
221
            return null;
222
        }
223
 
224
        $info = stream_get_contents($pipes[1]);
225
        fclose($pipes[1]);
226
        fclose($pipes[2]);
227
        proc_close($process);
228
 
229
        return $info;
230
    }
231
}