Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
54 / 54 |
|
89.47% |
51 / 57 |
|
8.49% |
9 / 106 |
|
20.00% |
1 / 5 |
CRAP | |
0.00% |
0 / 1 |
| Markup | |
100.00% |
54 / 54 |
|
89.47% |
51 / 57 |
|
8.49% |
9 / 106 |
|
100.00% |
5 / 5 |
358.94 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
3 / 3 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
1.12 | |||
| render | |
100.00% |
35 / 35 |
|
93.55% |
29 / 31 |
|
2.44% |
2 / 82 |
|
100.00% |
1 / 1 |
145.72 | |||
| sgr | |
100.00% |
7 / 7 |
|
86.67% |
13 / 15 |
|
17.65% |
3 / 17 |
|
100.00% |
1 / 1 |
12.94 | |||
| escape | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| width | |
100.00% |
7 / 7 |
|
83.33% |
5 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Console; |
| 6 | |
| 7 | use ValueError; |
| 8 | |
| 9 | /** |
| 10 | * Renders the inline console markup to ANSI escape codes. |
| 11 | * |
| 12 | * Style tags are `<strong>`, `<em>`, `<dim>`, and `<u>`; color tags are |
| 13 | * the ANSI names — `<red>`, `<bright-red>`, `<gray>` — and the same set |
| 14 | * with a `bg-` prefix for backgrounds. Truecolor hex tags take a |
| 15 | * lowercase six-digit code: `<#ff7313>`, `<bg-#ff7313>`. Tags compose |
| 16 | * by nesting, the innermost tag wins on conflict. |
| 17 | * |
| 18 | * Only exact known tags are parsed; everything else — `<info@example.com>`, |
| 19 | * generics, unknown names — passes through untouched. A backslash renders |
| 20 | * a known tag literally: `\<green>`. Structural mistakes (a mismatched, |
| 21 | * dangling, or unclosed tag) throw a ValueError, also when colors are |
| 22 | * disabled, so they surface in tests. |
| 23 | * |
| 24 | * @internal |
| 25 | */ |
| 26 | final class Markup |
| 27 | { |
| 28 | private const string RESET = "\033[0m"; |
| 29 | |
| 30 | /** SGR codes by tag name. */ |
| 31 | private const array TAGS = [ |
| 32 | 'strong' => '1', |
| 33 | 'em' => '3', |
| 34 | 'dim' => '2', |
| 35 | 'u' => '4', |
| 36 | 'black' => '30', |
| 37 | 'red' => '31', |
| 38 | 'green' => '32', |
| 39 | 'yellow' => '33', |
| 40 | 'blue' => '34', |
| 41 | 'magenta' => '35', |
| 42 | 'cyan' => '36', |
| 43 | 'white' => '37', |
| 44 | 'gray' => '90', |
| 45 | 'bright-black' => '90', |
| 46 | 'bright-red' => '91', |
| 47 | 'bright-green' => '92', |
| 48 | 'bright-yellow' => '93', |
| 49 | 'bright-blue' => '94', |
| 50 | 'bright-magenta' => '95', |
| 51 | 'bright-cyan' => '96', |
| 52 | 'bright-white' => '97', |
| 53 | 'bg-black' => '40', |
| 54 | 'bg-red' => '41', |
| 55 | 'bg-green' => '42', |
| 56 | 'bg-yellow' => '43', |
| 57 | 'bg-blue' => '44', |
| 58 | 'bg-magenta' => '45', |
| 59 | 'bg-cyan' => '46', |
| 60 | 'bg-white' => '47', |
| 61 | 'bg-gray' => '100', |
| 62 | 'bg-bright-black' => '100', |
| 63 | 'bg-bright-red' => '101', |
| 64 | 'bg-bright-green' => '102', |
| 65 | 'bg-bright-yellow' => '103', |
| 66 | 'bg-bright-blue' => '104', |
| 67 | 'bg-bright-magenta' => '105', |
| 68 | 'bg-bright-cyan' => '106', |
| 69 | 'bg-bright-white' => '107', |
| 70 | ]; |
| 71 | |
| 72 | /** @var non-empty-string */ |
| 73 | private readonly string $split; |
| 74 | |
| 75 | /** @var non-empty-string */ |
| 76 | private readonly string $tag; |
| 77 | |
| 78 | public function __construct() |
| 79 | { |
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 81 | $this->split = "#(\\\\?</?(?:{$names})>)#"; |
| 82 | $this->tag = "#^\\\\?</?(?:{$names})>$#"; |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * Renders the markup as escape codes, or strips it without `$colors`. |
| 87 | */ |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 91 | return $text; |
| 92 | } |
| 93 | |
| 94 | /** @var list<string> $parts */ |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 150 | } |
| 151 | |
| 152 | return $out; |
| 153 | } |
| 154 | |
| 155 | /** The SGR code for a tag name: a named lookup or a truecolor hex tag. */ |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 159 | return self::TAGS[$name]; |
| 160 | } |
| 161 | |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Escapes known tags and strips control characters so the text |
| 172 | * prints literally. |
| 173 | * |
| 174 | * Everything C0 except newline and tab is removed, DEL included, so |
| 175 | * arbitrary text cannot inject terminal escape sequences (ESC, BEL, |
| 176 | * carriage returns, ...). |
| 177 | */ |
| 178 | public function escape(string $text): string |
| 179 | { |
| 180 | $text = (string) preg_replace('/[\x00-\x08\x0B-\x1F\x7F]/', replacement: '', subject: $text); |
| 181 | |
| 182 | return (string) preg_replace($this->split, replacement: '\\\\$0', subject: $text); |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * The visible width of the text: tags collapse to nothing, an |
| 187 | * escaped tag to the tag without its backslash. |
| 188 | */ |
| 189 | public function width(string $text): int |
| 190 | { |
| 191 | if (str_contains($text, '<')) { |
| 192 | $text = (string) preg_replace_callback( |
| 193 | $this->split, |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 195 | $text, |
| 196 | ); |
| 197 | } |
| 198 | |
| 199 | return mb_strwidth($text); |
| 200 | } |
| 201 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 81 | $this->split = "#(\\\\?</?(?:{$names})>)#"; |
| 82 | $this->tag = "#^\\\\?</?(?:{$names})>$#"; |
| 83 | } |
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 80 | $names = implode('|', array_keys(self::TAGS)) . '|(?:bg-)?\#[0-9a-f]{6}'; |
| 81 | $this->split = "#(\\\\?</?(?:{$names})>)#"; |
| 82 | $this->tag = "#^\\\\?</?(?:{$names})>$#"; |
| 83 | } |
| 178 | public function escape(string $text): string |
| 179 | { |
| 180 | $text = (string) preg_replace('/[\x00-\x08\x0B-\x1F\x7F]/', replacement: '', subject: $text); |
| 181 | |
| 182 | return (string) preg_replace($this->split, replacement: '\\\\$0', subject: $text); |
| 183 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 91 | return $text; |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 91 | return $text; |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 124 | if ($last !== $name) { |
| 128 | if ($colors) { |
| 137 | continue; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 110 | if ($part[0] === '\\') { |
| 116 | if ($part[1] === '/') { |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 149 | throw new ValueError("Unclosed markup tag '<{$stack[array_key_last($stack)]}>'"); |
| 88 | public function render(string $text, bool $colors): string |
| 89 | { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 90 | if (!str_contains($text, '<')) { |
| 95 | $parts = (array) preg_split( |
| 96 | $this->split, |
| 97 | $text, |
| 98 | flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, |
| 99 | ); |
| 100 | $out = ''; |
| 101 | $stack = []; |
| 102 | |
| 103 | foreach ($parts as $part) { |
| 103 | foreach ($parts as $part) { |
| 104 | if (preg_match($this->tag, $part) !== 1) { |
| 105 | $out .= $part; |
| 106 | |
| 107 | continue; |
| 108 | } |
| 109 | |
| 110 | if ($part[0] === '\\') { |
| 111 | $out .= substr($part, offset: 1); |
| 112 | |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | if ($part[1] === '/') { |
| 117 | $name = substr($part, offset: 2, length: -1); |
| 118 | $last = array_pop($stack); |
| 119 | |
| 120 | if ($last === null) { |
| 121 | throw new ValueError("Markup tag '</{$name}>' has no opening tag"); |
| 122 | } |
| 123 | |
| 124 | if ($last !== $name) { |
| 125 | throw new ValueError("Markup tag '<{$last}>' closed by '</{$name}>'"); |
| 126 | } |
| 127 | |
| 128 | if ($colors) { |
| 129 | // A blanket reset, then re-apply the enclosing tags. |
| 130 | $out .= self::RESET; |
| 131 | |
| 132 | foreach ($stack as $open) { |
| 133 | $out .= "\033[" . $this->sgr($open) . 'm'; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | continue; |
| 138 | } |
| 139 | |
| 140 | $name = substr($part, offset: 1, length: -1); |
| 141 | $stack[] = $name; |
| 142 | |
| 143 | if ($colors) { |
| 144 | $out .= "\033[" . $this->sgr($name) . 'm'; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | if ($stack !== []) { |
| 152 | return $out; |
| 153 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 159 | return self::TAGS[$name]; |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 156 | private function sgr(string $name): string |
| 157 | { |
| 158 | if (array_key_exists($name, self::TAGS)) { |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 162 | $bg = str_starts_with($name, 'bg-'); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 163 | $hex = substr($name, offset: $bg ? 4 : 1); |
| 164 | |
| 165 | return ( |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 166 | ($bg ? '48' : '38') . ';2;' . implode(';', array_map(hexdec(...), str_split($hex, length: 2))) |
| 167 | ); |
| 168 | } |
| 189 | public function width(string $text): int |
| 190 | { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 192 | $text = (string) preg_replace_callback( |
| 193 | $this->split, |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 195 | $text, |
| 196 | ); |
| 197 | } |
| 198 | |
| 199 | return mb_strwidth($text); |
| 199 | return mb_strwidth($text); |
| 200 | } |
| 189 | public function width(string $text): int |
| 190 | { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 199 | return mb_strwidth($text); |
| 200 | } |
| 189 | public function width(string $text): int |
| 190 | { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 192 | $text = (string) preg_replace_callback( |
| 193 | $this->split, |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 195 | $text, |
| 196 | ); |
| 197 | } |
| 198 | |
| 199 | return mb_strwidth($text); |
| 199 | return mb_strwidth($text); |
| 200 | } |
| 189 | public function width(string $text): int |
| 190 | { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 191 | if (str_contains($text, '<')) { |
| 199 | return mb_strwidth($text); |
| 200 | } |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |
| 194 | static fn(array $match): string => $match[0][0] === '\\' ? substr($match[0], offset: 1) : '', |