Skip to content

Commit 779973e

Browse files
authored
fix(console): handle nested style tags (#726)
1 parent 0bdee91 commit 779973e

File tree

9 files changed

+107
-55
lines changed

9 files changed

+107
-55
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"symfony/uid": "^7.1",
3434
"symfony/var-dumper": "^7.1",
3535
"symfony/var-exporter": "^7.1",
36-
"tempest/highlight": "^2.0",
36+
"tempest/highlight": "^2.11.2",
3737
"vlucas/phpdotenv": "^5.6"
3838
},
3939
"require-dev": {

src/Tempest/Console/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"tempest/core": "dev-main",
1010
"tempest/container": "dev-main",
1111
"tempest/debug": "dev-main",
12-
"tempest/highlight": "^2.0",
12+
"tempest/highlight": "^2.11.2",
1313
"tempest/log": "dev-main",
1414
"tempest/reflection": "dev-main",
1515
"tempest/support": "dev-main",

src/Tempest/Console/src/Highlight/DynamicTokenType.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public function __construct(
1515
) {
1616
}
1717

18-
public function getStyle(): TerminalStyle
18+
public function getBeforeStyle(): TerminalStyle
1919
{
2020
$normalizedStyle = str($this->style)
2121
->lower()
@@ -34,6 +34,56 @@ public function getStyle(): TerminalStyle
3434
return TerminalStyle::RESET;
3535
}
3636

37+
public function getAfterStyle(): TerminalStyle
38+
{
39+
return match ($this->getBeforeStyle()) {
40+
// Mods
41+
TerminalStyle::BOLD => TerminalStyle::RESET_INTENSITY,
42+
TerminalStyle::DIM => TerminalStyle::RESET_INTENSITY,
43+
TerminalStyle::ITALIC => TerminalStyle::RESET_ITALIC,
44+
TerminalStyle::HIDDEN => TerminalStyle::VISIBLE,
45+
TerminalStyle::UNDERLINE => TerminalStyle::RESET_UNDERLINE,
46+
TerminalStyle::OVERLINE => TerminalStyle::RESET_OVERLINE,
47+
TerminalStyle::STRIKETHROUGH => TerminalStyle::RESET_STRIKETHROUGH,
48+
TerminalStyle::REVERSE_TEXT => TerminalStyle::RESET_REVERSE_TEXT,
49+
// Foregrounds
50+
TerminalStyle::FG_BLACK,
51+
TerminalStyle::FG_DARK_RED,
52+
TerminalStyle::FG_DARK_GREEN,
53+
TerminalStyle::FG_DARK_YELLOW,
54+
TerminalStyle::FG_DARK_BLUE,
55+
TerminalStyle::FG_DARK_MAGENTA,
56+
TerminalStyle::FG_DARK_CYAN,
57+
TerminalStyle::FG_LIGHT_GRAY,
58+
TerminalStyle::FG_GRAY,
59+
TerminalStyle::FG_RED,
60+
TerminalStyle::FG_GREEN,
61+
TerminalStyle::FG_YELLOW,
62+
TerminalStyle::FG_BLUE,
63+
TerminalStyle::FG_MAGENTA,
64+
TerminalStyle::FG_CYAN,
65+
TerminalStyle::FG_WHITE => TerminalStyle::RESET_FOREGROUND,
66+
// Backgrounds
67+
TerminalStyle::BG_BLACK,
68+
TerminalStyle::BG_DARK_RED,
69+
TerminalStyle::BG_DARK_GREEN,
70+
TerminalStyle::BG_DARK_YELLOW,
71+
TerminalStyle::BG_DARK_BLUE,
72+
TerminalStyle::BG_DARK_MAGENTA,
73+
TerminalStyle::BG_DARK_CYAN,
74+
TerminalStyle::BG_LIGHT_GRAY,
75+
TerminalStyle::BG_GRAY,
76+
TerminalStyle::BG_RED,
77+
TerminalStyle::BG_GREEN,
78+
TerminalStyle::BG_YELLOW,
79+
TerminalStyle::BG_BLUE,
80+
TerminalStyle::BG_MAGENTA,
81+
TerminalStyle::BG_CYAN,
82+
TerminalStyle::BG_WHITE => TerminalStyle::RESET_BACKGROUND,
83+
default => TerminalStyle::RESET,
84+
};
85+
}
86+
3787
public function getValue(): string
3888
{
3989
return '';

src/Tempest/Console/src/Highlight/TempestConsoleLanguage/Injections/DynamicInjection.php

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,41 +20,33 @@ public function getTokenType(): ConsoleTokenType
2020

2121
public function parse(string $content, Highlighter $highlighter): ParsedInjection
2222
{
23-
$pattern = '/(?<match>\<style=\"(?<styles>(?:[a-z-]+\s*)+)\"\>(.|\n)*\<\/style\>)/';
24-
25-
$result = preg_replace_callback(
26-
pattern: $pattern,
27-
callback: function ($matches) use ($highlighter, $pattern) {
28-
$theme = $highlighter->getTheme();
29-
$content = $matches['match'];
30-
$styles = $matches['styles'];
31-
$before = '';
32-
$after = '';
33-
34-
foreach (explode(' ', $styles) as $style) {
35-
$token = new DynamicTokenType($style);
36-
$before .= $theme->before($token);
37-
$after .= $theme->after($token);
38-
}
39-
40-
$result = str_replace(
41-
search: $content,
42-
replace: str($content)
23+
$pattern = '/(?<match>\<style=\"(?<styles>(?:[a-z-]+\s*)+)\"\>(?:(?!\<style).|\n)*?\<\/style\>)/';
24+
25+
do {
26+
$content = preg_replace_callback(
27+
subject: $content,
28+
pattern: $pattern,
29+
callback: function ($matches) use ($highlighter) {
30+
$theme = $highlighter->getTheme();
31+
$match = $matches['match'];
32+
$styles = $matches['styles'];
33+
$before = '';
34+
$after = '';
35+
36+
foreach (explode(' ', $styles) as $style) {
37+
$token = new DynamicTokenType($style);
38+
$before .= $theme->before($token);
39+
$after .= $theme->after($token);
40+
}
41+
42+
return str($match)
4343
->replaceFirst("<style=\"{$styles}\">", $before)
4444
->replaceLast("</style>", $after)
45-
->toString(),
46-
subject: $matches[0],
47-
);
48-
49-
if (preg_match($pattern, $result)) {
50-
return $this->parse($result, $highlighter)->content;
45+
->toString();
5146
}
47+
);
48+
} while (preg_match($pattern, $content));
5249

53-
return $result;
54-
},
55-
subject: $content,
56-
);
57-
58-
return new ParsedInjection($result ?? $content);
50+
return new ParsedInjection($content);
5951
}
6052
}

src/Tempest/Console/src/Highlight/TempestTerminalTheme.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public function before(TokenType $tokenType): string
1818
{
1919
if ($tokenType instanceof DynamicTokenType) {
20-
return $this->style($tokenType->getStyle());
20+
return $this->style($tokenType->getBeforeStyle());
2121
}
2222

2323
return match ($tokenType) {
@@ -42,6 +42,10 @@ public function before(TokenType $tokenType): string
4242

4343
public function after(TokenType $tokenType): string
4444
{
45+
if ($tokenType instanceof DynamicTokenType) {
46+
return $this->style($tokenType->getAfterStyle());
47+
}
48+
4549
return match ($tokenType) {
4650
ConsoleTokenType::ERROR,
4751
ConsoleTokenType::QUESTION,
@@ -57,7 +61,7 @@ private function style(TerminalStyle ...$styles): string
5761
return implode(
5862
'',
5963
array_map(
60-
fn (TerminalStyle $style) => TerminalStyle::ESC->value . $style->value,
64+
fn (TerminalStyle $style) => TerminalStyle::ESC->value . $style->value,
6165
$styles,
6266
),
6367
);

src/Tempest/Console/tests/TempestConsoleLanguage/Injections/DynamicInjectionTest.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616
*/
1717
final class DynamicInjectionTest extends TestCase
1818
{
19-
#[TestWith(['<style="fg-cyan">foo</style>', "\e[96mfoo\e[0m"])]
20-
#[TestWith(['<style="bg-red">foo</style>', "\e[101mfoo\e[0m"])]
21-
#[TestWith(['<style="bold">foo</style>', "\e[1mfoo\e[0m"])]
22-
#[TestWith(['<style="underline">foo</style>', "\e[4mfoo\e[0m"])]
19+
#[TestWith(['<style="fg-cyan">foo</style>', "\e[96mfoo\e[39m"])]
20+
#[TestWith(['<style="bg-red">foo</style>', "\e[101mfoo\e[49m"])]
21+
#[TestWith(['<style="bold">foo</style>', "\e[1mfoo\e[22m"])]
22+
#[TestWith(['<style="underline">foo</style>', "\e[4mfoo\e[24m"])]
2323
#[TestWith(['<style="reset">foo</style>', "\e[0mfoo\e[0m"])]
24-
#[TestWith(['<style="reverse-text">foo</style>', "\e[7mfoo\e[0m"])]
25-
#[TestWith(['<style="bg-darkcyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
26-
#[TestWith(['<style="bg-dark-cyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
27-
#[TestWith(['<style="fg-cyan"><style="bg-dark-red">foo</style></style>', "\e[96m\e[41mfoo\e[0m\e[0m"])]
24+
#[TestWith(['<style="reverse-text">foo</style>', "\e[7mfoo\e[27m"])]
25+
#[TestWith(['<style="bg-darkcyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[49m\e[39m\e[24m"])]
26+
#[TestWith(['<style="bg-dark-cyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[49m\e[39m\e[24m"])]
27+
#[TestWith(['<style="fg-cyan"><style="bg-dark-red">foo</style></style>', "\e[96m\e[41mfoo\e[49m\e[39m"])]
28+
#[TestWith(['<style="dim"><style="bg-dark-red fg-white">foo</style></style>', "\e[2m\e[41m\e[97mfoo\e[49m\e[39m\e[22m"])]
29+
#[TestWith(['<style="fg-cyan">cyan</style>unstyled<style="bg-dark-red">dark red</style>', "\e[96mcyan\e[39munstyled\e[41mdark red\e[49m"])]
30+
#[TestWith(['<style="dim"><style="fg-gray">dim-gray</style> just-gray</style>', "\e[2m\e[90mdim-gray\e[39m just-gray\e[22m"])]
2831
#[Test]
2932
public function language(string $content, string $expected): void
3033
{

src/Tempest/Debug/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"minimum-stability": "dev",
66
"require": {
77
"php": "^8.3",
8-
"tempest/highlight": "^2.0",
8+
"tempest/highlight": "^2.11.2",
99
"symfony/var-dumper": "^7.1"
1010
},
1111
"autoload": {

src/Tempest/Http/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"tempest/view": "dev-main",
1212
"tempest/mapper": "dev-main",
1313
"tempest/container": "dev-main",
14-
"tempest/highlight": "^2.0",
14+
"tempest/highlight": "^2.11.2",
1515
"laminas/laminas-diactoros": "^3.3",
1616
"psr/http-factory": "^1.0",
1717
"psr/http-message": "^1.0|^2.0",

tests/Integration/Console/Highlight/TempestConsoleLanguage/TempestConsoleLanguageTest.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616
*/
1717
final class TempestConsoleLanguageTest extends TestCase
1818
{
19-
#[TestWith(['<style="fg-cyan">foo</style>', "\e[96mfoo\e[0m"])]
20-
#[TestWith(['<style="bg-red">foo</style>', "\e[101mfoo\e[0m"])]
21-
#[TestWith(['<style="bold">foo</style>', "\e[1mfoo\e[0m"])]
22-
#[TestWith(['<style="underline">foo</style>', "\e[4mfoo\e[0m"])]
19+
#[TestWith(['<style="fg-cyan">foo</style>', "\e[96mfoo\e[39m"])]
20+
#[TestWith(['<style="bg-red">foo</style>', "\e[101mfoo\e[49m"])]
21+
#[TestWith(['<style="bold">foo</style>', "\e[1mfoo\e[22m"])]
22+
#[TestWith(['<style="underline">foo</style>', "\e[4mfoo\e[24m"])]
2323
#[TestWith(['<style="reset">foo</style>', "\e[0mfoo\e[0m"])]
24-
#[TestWith(['<style="reverse-text">foo</style>', "\e[7mfoo\e[0m"])]
25-
#[TestWith(['<style="bg-darkcyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
26-
#[TestWith(['<style="bg-dark-cyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
27-
#[TestWith(['<style="fg-cyan"><style="bg-dark-red">foo</style></style>', "\e[96m\e[41mfoo\e[0m\e[0m"])]
24+
#[TestWith(['<style="reverse-text">foo</style>', "\e[7mfoo\e[27m"])]
25+
#[TestWith(['<style="bg-darkcyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[49m\e[39m\e[24m"])]
26+
#[TestWith(['<style="bg-dark-cyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[49m\e[39m\e[24m"])]
27+
#[TestWith(['<style="fg-cyan"><style="bg-dark-red">foo</style></style>', "\e[96m\e[41mfoo\e[49m\e[39m"])]
28+
#[TestWith(['<style="dim"><style="bg-dark-red fg-white">foo</style></style>', "\e[2m\e[41m\e[97mfoo\e[49m\e[39m\e[22m"])]
29+
#[TestWith(['<style="fg-cyan">cyan</style>unstyled<style="bg-dark-red">dark red</style>', "\e[96mcyan\e[39munstyled\e[41mdark red\e[49m"])]
30+
#[TestWith(['<style="dim"><style="fg-gray">dim-gray</style> just-gray</style>', "\e[2m\e[90mdim-gray\e[39m just-gray\e[22m"])]
2831
#[Test]
2932
public function language(string $content, string $expected): void
3033
{

0 commit comments

Comments
 (0)