Skip to content

Commit

Permalink
feat(console): support dynamic style injections (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
innocenzi authored and brendt committed Nov 14, 2024
1 parent c7fd1ff commit 48b651d
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"Tempest\\Cache\\Tests\\": "src/Tempest/Cache/tests",
"Tempest\\Clock\\Tests\\": "src/Tempest/Clock/tests",
"Tempest\\CommandBus\\Tests\\": "src/Tempest/CommandBus/tests",
"Tempest\\Console\\Tests\\": "src/Tempest/Console/tests",
"Tempest\\Container\\Tests\\": "src/Tempest/Container/tests",
"Tempest\\Core\\Tests\\": "src/Tempest/Core/tests",
"Tempest\\Database\\Tests\\": "src/Tempest/Database/tests",
Expand Down
5 changes: 5 additions & 0 deletions src/Tempest/Console/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"Tempest\\Console\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Tempest\\Console\\Tests\\": "tests"
}
},
"bin": [
"bin/tempest"
]
Expand Down
46 changes: 46 additions & 0 deletions src/Tempest/Console/src/Highlight/DynamicTokenType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Highlight;

use Tempest\Highlight\Themes\TerminalStyle;
use Tempest\Highlight\Tokens\TokenType;
use function Tempest\Support\str;

final readonly class DynamicTokenType implements TokenType
{
public function __construct(
private string $style,
) {
}

public function getStyle(): TerminalStyle
{
$normalizedStyle = str($this->style)
->lower()
->replace(['_', '-'], '');

foreach (TerminalStyle::cases() as $case) {
$normalizedCase = str($case->name)
->lower()
->replace(['_', '-'], '');

if ($normalizedCase->equals($normalizedStyle)) {
return $case;
}
}

return TerminalStyle::RESET;
}

public function getValue(): string
{
return '';
}

public function canContain(TokenType $other): bool
{
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Highlight\TempestConsoleLanguage\Injections;

use Tempest\Console\Highlight\ConsoleTokenType;
use Tempest\Console\Highlight\DynamicTokenType;
use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\ParsedInjection;
use function Tempest\Support\str;

final readonly class DynamicInjection implements Injection
{
public function getTokenType(): ConsoleTokenType
{
return ConsoleTokenType::COMMENT;
}

public function parse(string $content, Highlighter $highlighter): ParsedInjection
{
$pattern = '/(?<match>\<style=\"(?<styles>(?:[a-z-]+\s*)+)\"\>(.|\n)*\<\/style\>)/';

$result = preg_replace_callback(
pattern: $pattern,
callback: function ($matches) use ($highlighter, $pattern) {
$theme = $highlighter->getTheme();
$content = $matches['match'];
$styles = $matches['styles'];
$before = '';
$after = '';

foreach (explode(' ', $styles) as $style) {
$token = new DynamicTokenType($style);
$before .= $theme->before($token);
$after .= $theme->after($token);
}

$result = str_replace(
search: $content,
replace: str($content)
->replaceFirst("<style=\"{$styles}\">", $before)
->replaceLast("</style>", $after)
->toString(),
subject: $matches[0],
);

if (preg_match($pattern, $result)) {
return $this->parse($result, $highlighter)->content;
}

return $result;
},
subject: $content,
);

return new ParsedInjection($result ?? $content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tempest\Console\Highlight\TempestConsoleLanguage;

use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\CommentInjection;
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\DynamicInjection;
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\EmphasizeInjection;
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\ErrorInjection;
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\H1Injection;
Expand Down Expand Up @@ -39,6 +40,7 @@ public function getInjections(): array
new H1Injection(),
new H2Injection(),
new SuccessInjection(),
new DynamicInjection(),
];
}

Expand Down
4 changes: 4 additions & 0 deletions src/Tempest/Console/src/Highlight/TempestTerminalTheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

public function before(TokenType $tokenType): string
{
if ($tokenType instanceof DynamicTokenType) {
return $this->style($tokenType->getStyle());
}

return match ($tokenType) {
TokenTypeEnum::KEYWORD => $this->style(TerminalStyle::FG_DARK_BLUE),
TokenTypeEnum::PROPERTY => $this->style(TerminalStyle::FG_DARK_GREEN),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Tests\TempestConsoleLanguage\Injections;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Tempest\Console\Highlight\TempestConsoleLanguage\Injections\DynamicInjection;
use Tempest\Console\Highlight\TempestTerminalTheme;
use Tempest\Highlight\Highlighter;

/**
* @internal
*/
final class DynamicInjectionTest extends TestCase
{
#[TestWith(['<style="fg-cyan">foo</style>', "\e[96mfoo\e[0m"])]
#[TestWith(['<style="bg-red">foo</style>', "\e[101mfoo\e[0m"])]
#[TestWith(['<style="bold">foo</style>', "\e[1mfoo\e[0m"])]
#[TestWith(['<style="underline">foo</style>', "\e[4mfoo\e[0m"])]
#[TestWith(['<style="reset">foo</style>', "\e[0mfoo\e[0m"])]
#[TestWith(['<style="reverse-text">foo</style>', "\e[7mfoo\e[0m"])]
#[TestWith(['<style="bg-darkcyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
#[TestWith(['<style="bg-dark-cyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
#[TestWith(['<style="fg-cyan"><style="bg-dark-red">foo</style></style>', "\e[96m\e[41mfoo\e[0m\e[0m"])]
#[Test]
public function language(string $content, string $expected): void
{
$highlighter = new Highlighter(new TempestTerminalTheme());

$this->assertSame(
$expected,
(new DynamicInjection())->parse($content, $highlighter)->content,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Console\Highlight\LogLanguage;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Tempest\Console\Highlight\TempestConsoleLanguage\TempestConsoleLanguage;
use Tempest\Console\Highlight\TempestTerminalTheme;
use Tempest\Highlight\Highlighter;

/**
* @internal
*/
final class TempestConsoleLanguageTest extends TestCase
{
#[TestWith(['<style="fg-cyan">foo</style>', "\e[96mfoo\e[0m"])]
#[TestWith(['<style="bg-red">foo</style>', "\e[101mfoo\e[0m"])]
#[TestWith(['<style="bold">foo</style>', "\e[1mfoo\e[0m"])]
#[TestWith(['<style="underline">foo</style>', "\e[4mfoo\e[0m"])]
#[TestWith(['<style="reset">foo</style>', "\e[0mfoo\e[0m"])]
#[TestWith(['<style="reverse-text">foo</style>', "\e[7mfoo\e[0m"])]
#[TestWith(['<style="bg-darkcyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
#[TestWith(['<style="bg-dark-cyan fg-cyan underline">Tempest</style>', "\e[46m\e[96m\e[4mTempest\e[0m\e[0m\e[0m"])]
#[TestWith(['<style="fg-cyan"><style="bg-dark-red">foo</style></style>', "\e[96m\e[41mfoo\e[0m\e[0m"])]
#[Test]
public function language(string $content, string $expected): void
{
$highlighter = new Highlighter(new TempestTerminalTheme());

$this->assertSame(
$expected,
$highlighter->parse($content, new TempestConsoleLanguage())
);
}
}

0 comments on commit 48b651d

Please # to comment.