Skip to content

Commit 6847a79

Browse files
authored
feat(console): support dynamic style injections (#703)
1 parent c8a31db commit 6847a79

File tree

8 files changed

+194
-0
lines changed

8 files changed

+194
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
"Tempest\\Cache\\Tests\\": "src/Tempest/Cache/tests",
118118
"Tempest\\Clock\\Tests\\": "src/Tempest/Clock/tests",
119119
"Tempest\\CommandBus\\Tests\\": "src/Tempest/CommandBus/tests",
120+
"Tempest\\Console\\Tests\\": "src/Tempest/Console/tests",
120121
"Tempest\\Container\\Tests\\": "src/Tempest/Container/tests",
121122
"Tempest\\Core\\Tests\\": "src/Tempest/Core/tests",
122123
"Tempest\\Database\\Tests\\": "src/Tempest/Database/tests",

src/Tempest/Console/composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"Tempest\\Console\\": "src"
2222
}
2323
},
24+
"autoload-dev": {
25+
"psr-4": {
26+
"Tempest\\Console\\Tests\\": "tests"
27+
}
28+
},
2429
"bin": [
2530
"bin/tempest"
2631
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console\Highlight;
6+
7+
use Tempest\Highlight\Themes\TerminalStyle;
8+
use Tempest\Highlight\Tokens\TokenType;
9+
use function Tempest\Support\str;
10+
11+
final readonly class DynamicTokenType implements TokenType
12+
{
13+
public function __construct(
14+
private string $style,
15+
) {
16+
}
17+
18+
public function getStyle(): TerminalStyle
19+
{
20+
$normalizedStyle = str($this->style)
21+
->lower()
22+
->replace(['_', '-'], '');
23+
24+
foreach (TerminalStyle::cases() as $case) {
25+
$normalizedCase = str($case->name)
26+
->lower()
27+
->replace(['_', '-'], '');
28+
29+
if ($normalizedCase->equals($normalizedStyle)) {
30+
return $case;
31+
}
32+
}
33+
34+
return TerminalStyle::RESET;
35+
}
36+
37+
public function getValue(): string
38+
{
39+
return '';
40+
}
41+
42+
public function canContain(TokenType $other): bool
43+
{
44+
return false;
45+
}
46+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console\Highlight\TempestConsoleLanguage\Injections;
6+
7+
use Tempest\Console\Highlight\ConsoleTokenType;
8+
use Tempest\Console\Highlight\DynamicTokenType;
9+
use Tempest\Highlight\Highlighter;
10+
use Tempest\Highlight\Injection;
11+
use Tempest\Highlight\ParsedInjection;
12+
use function Tempest\Support\str;
13+
14+
final readonly class DynamicInjection implements Injection
15+
{
16+
public function getTokenType(): ConsoleTokenType
17+
{
18+
return ConsoleTokenType::COMMENT;
19+
}
20+
21+
public function parse(string $content, Highlighter $highlighter): ParsedInjection
22+
{
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)
43+
->replaceFirst("<style=\"{$styles}\">", $before)
44+
->replaceLast("</style>", $after)
45+
->toString(),
46+
subject: $matches[0],
47+
);
48+
49+
if (preg_match($pattern, $result)) {
50+
return $this->parse($result, $highlighter)->content;
51+
}
52+
53+
return $result;
54+
},
55+
subject: $content,
56+
);
57+
58+
return new ParsedInjection($result ?? $content);
59+
}
60+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Tempest\Console\Highlight\TempestConsoleLanguage;
66

77
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\CommentInjection;
8+
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\DynamicInjection;
89
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\EmphasizeInjection;
910
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\ErrorInjection;
1011
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\H1Injection;
@@ -39,6 +40,7 @@ public function getInjections(): array
3940
new H1Injection(),
4041
new H2Injection(),
4142
new SuccessInjection(),
43+
new DynamicInjection(),
4244
];
4345
}
4446

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
public function before(TokenType $tokenType): string
1818
{
19+
if ($tokenType instanceof DynamicTokenType) {
20+
return $this->style($tokenType->getStyle());
21+
}
22+
1923
return match ($tokenType) {
2024
TokenTypeEnum::KEYWORD => $this->style(TerminalStyle::FG_DARK_BLUE),
2125
TokenTypeEnum::PROPERTY => $this->style(TerminalStyle::FG_DARK_GREEN),
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console\Tests\TempestConsoleLanguage\Injections;
6+
7+
use PHPUnit\Framework\Attributes\Test;
8+
use PHPUnit\Framework\Attributes\TestWith;
9+
use PHPUnit\Framework\TestCase;
10+
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\DynamicInjection;
11+
use Tempest\Console\Highlight\TempestTerminalTheme;
12+
use Tempest\Highlight\Highlighter;
13+
14+
/**
15+
* @internal
16+
*/
17+
final class DynamicInjectionTest extends TestCase
18+
{
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"])]
23+
#[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"])]
28+
#[Test]
29+
public function language(string $content, string $expected): void
30+
{
31+
$highlighter = new Highlighter(new TempestTerminalTheme());
32+
33+
$this->assertSame(
34+
$expected,
35+
(new DynamicInjection())->parse($content, $highlighter)->content,
36+
);
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Integration\Console\Highlight\LogLanguage;
6+
7+
use PHPUnit\Framework\Attributes\Test;
8+
use PHPUnit\Framework\Attributes\TestWith;
9+
use PHPUnit\Framework\TestCase;
10+
use Tempest\Console\Highlight\TempestConsoleLanguage\TempestConsoleLanguage;
11+
use Tempest\Console\Highlight\TempestTerminalTheme;
12+
use Tempest\Highlight\Highlighter;
13+
14+
/**
15+
* @internal
16+
*/
17+
final class TempestConsoleLanguageTest extends TestCase
18+
{
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"])]
23+
#[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"])]
28+
#[Test]
29+
public function language(string $content, string $expected): void
30+
{
31+
$highlighter = new Highlighter(new TempestTerminalTheme());
32+
33+
$this->assertSame(
34+
$expected,
35+
$highlighter->parse($content, new TempestConsoleLanguage())
36+
);
37+
}
38+
}

0 commit comments

Comments
 (0)