Subversion-Projekte lars-tiefland.laravel_shop

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\Formatter;
13
 
14
use Symfony\Component\Console\Exception\InvalidArgumentException;
15
 
16
/**
17
 * Formatter class for console output.
18
 *
19
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
20
 * @author Roland Franssen <franssen.roland@gmail.com>
21
 */
22
class OutputFormatter implements WrappableOutputFormatterInterface
23
{
24
    private bool $decorated;
25
    private array $styles = [];
26
    private OutputFormatterStyleStack $styleStack;
27
 
28
    public function __clone()
29
    {
30
        $this->styleStack = clone $this->styleStack;
31
        foreach ($this->styles as $key => $value) {
32
            $this->styles[$key] = clone $value;
33
        }
34
    }
35
 
36
    /**
37
     * Escapes "<" and ">" special chars in given text.
38
     */
39
    public static function escape(string $text): string
40
    {
41
        $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);
42
 
43
        return self::escapeTrailingBackslash($text);
44
    }
45
 
46
    /**
47
     * Escapes trailing "\" in given text.
48
     *
49
     * @internal
50
     */
51
    public static function escapeTrailingBackslash(string $text): string
52
    {
53
        if (str_ends_with($text, '\\')) {
54
            $len = \strlen($text);
55
            $text = rtrim($text, '\\');
56
            $text = str_replace("\0", '', $text);
57
            $text .= str_repeat("\0", $len - \strlen($text));
58
        }
59
 
60
        return $text;
61
    }
62
 
63
    /**
64
     * Initializes console output formatter.
65
     *
66
     * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
67
     */
68
    public function __construct(bool $decorated = false, array $styles = [])
69
    {
70
        $this->decorated = $decorated;
71
 
72
        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
73
        $this->setStyle('info', new OutputFormatterStyle('green'));
74
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
75
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
76
 
77
        foreach ($styles as $name => $style) {
78
            $this->setStyle($name, $style);
79
        }
80
 
81
        $this->styleStack = new OutputFormatterStyleStack();
82
    }
83
 
84
    public function setDecorated(bool $decorated)
85
    {
86
        $this->decorated = $decorated;
87
    }
88
 
89
    public function isDecorated(): bool
90
    {
91
        return $this->decorated;
92
    }
93
 
94
    public function setStyle(string $name, OutputFormatterStyleInterface $style)
95
    {
96
        $this->styles[strtolower($name)] = $style;
97
    }
98
 
99
    public function hasStyle(string $name): bool
100
    {
101
        return isset($this->styles[strtolower($name)]);
102
    }
103
 
104
    public function getStyle(string $name): OutputFormatterStyleInterface
105
    {
106
        if (!$this->hasStyle($name)) {
107
            throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name));
108
        }
109
 
110
        return $this->styles[strtolower($name)];
111
    }
112
 
113
    public function format(?string $message): ?string
114
    {
115
        return $this->formatAndWrap($message, 0);
116
    }
117
 
118
    public function formatAndWrap(?string $message, int $width)
119
    {
120
        if (null === $message) {
121
            return '';
122
        }
123
 
124
        $offset = 0;
125
        $output = '';
126
        $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
127
        $closeTagRegex = '[a-z][^<>]*+';
128
        $currentLineLength = 0;
129
        preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
130
        foreach ($matches[0] as $i => $match) {
131
            $pos = $match[1];
132
            $text = $match[0];
133
 
134
            if (0 != $pos && '\\' == $message[$pos - 1]) {
135
                continue;
136
            }
137
 
138
            // add the text up to the next tag
139
            $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
140
            $offset = $pos + \strlen($text);
141
 
142
            // opening tag?
143
            if ($open = '/' !== $text[1]) {
144
                $tag = $matches[1][$i][0];
145
            } else {
146
                $tag = $matches[3][$i][0] ?? '';
147
            }
148
 
149
            if (!$open && !$tag) {
150
                // </>
151
                $this->styleStack->pop();
152
            } elseif (null === $style = $this->createStyleFromString($tag)) {
153
                $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
154
            } elseif ($open) {
155
                $this->styleStack->push($style);
156
            } else {
157
                $this->styleStack->pop($style);
158
            }
159
        }
160
 
161
        $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
162
 
163
        return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
164
    }
165
 
166
    public function getStyleStack(): OutputFormatterStyleStack
167
    {
168
        return $this->styleStack;
169
    }
170
 
171
    /**
172
     * Tries to create new style instance from string.
173
     */
174
    private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
175
    {
176
        if (isset($this->styles[$string])) {
177
            return $this->styles[$string];
178
        }
179
 
180
        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
181
            return null;
182
        }
183
 
184
        $style = new OutputFormatterStyle();
185
        foreach ($matches as $match) {
186
            array_shift($match);
187
            $match[0] = strtolower($match[0]);
188
 
189
            if ('fg' == $match[0]) {
190
                $style->setForeground(strtolower($match[1]));
191
            } elseif ('bg' == $match[0]) {
192
                $style->setBackground(strtolower($match[1]));
193
            } elseif ('href' === $match[0]) {
194
                $url = preg_replace('{\\\\([<>])}', '$1', $match[1]);
195
                $style->setHref($url);
196
            } elseif ('options' === $match[0]) {
197
                preg_match_all('([^,;]+)', strtolower($match[1]), $options);
198
                $options = array_shift($options);
199
                foreach ($options as $option) {
200
                    $style->setOption($option);
201
                }
202
            } else {
203
                return null;
204
            }
205
        }
206
 
207
        return $style;
208
    }
209
 
210
    /**
211
     * Applies current style from stack to text, if must be applied.
212
     */
213
    private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
214
    {
215
        if ('' === $text) {
216
            return '';
217
        }
218
 
219
        if (!$width) {
220
            return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
221
        }
222
 
223
        if (!$currentLineLength && '' !== $current) {
224
            $text = ltrim($text);
225
        }
226
 
227
        if ($currentLineLength) {
228
            $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
229
            $text = substr($text, $i);
230
        } else {
231
            $prefix = '';
232
        }
233
 
234
        preg_match('~(\\n)$~', $text, $matches);
235
        $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
236
        $text = rtrim($text, "\n").($matches[1] ?? '');
237
 
238
        if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) {
239
            $text = "\n".$text;
240
        }
241
 
242
        $lines = explode("\n", $text);
243
 
244
        foreach ($lines as $line) {
245
            $currentLineLength += \strlen($line);
246
            if ($width <= $currentLineLength) {
247
                $currentLineLength = 0;
248
            }
249
        }
250
 
251
        if ($this->isDecorated()) {
252
            foreach ($lines as $i => $line) {
253
                $lines[$i] = $this->styleStack->getCurrent()->apply($line);
254
            }
255
        }
256
 
257
        return implode("\n", $lines);
258
    }
259
}