diff --git a/src/Contracts/Renderer.php b/src/Contracts/Renderer.php new file mode 100644 index 0000000..2abddd9 --- /dev/null +++ b/src/Contracts/Renderer.php @@ -0,0 +1,16 @@ +ask($question, $autocomplete); } } + +if (! function_exists('Termwind\registerRenderer')) { + /** + * Registers a new renderer + * + * @param string $name + * @param string $renderer + * @return void + * + * @thows InvalidRenderer + */ + function registerRenderer(string $name, string $renderer): void + { + ElementRenderer::register($name, $renderer); + } +} diff --git a/src/Html/CodeRenderer.php b/src/Html/CodeRenderer.php index 6950cfd..7207a14 100644 --- a/src/Html/CodeRenderer.php +++ b/src/Html/CodeRenderer.php @@ -5,13 +5,14 @@ namespace Termwind\Html; use Termwind\Components\Element; +use Termwind\Contracts\Renderer; use Termwind\Termwind; use Termwind\ValueObjects\Node; /** * @internal */ -final class CodeRenderer +final class CodeRenderer implements Renderer { public const TOKEN_DEFAULT = 'token_default'; diff --git a/src/Html/ElementRenderer.php b/src/Html/ElementRenderer.php new file mode 100644 index 0000000..e92d4ff --- /dev/null +++ b/src/Html/ElementRenderer.php @@ -0,0 +1,84 @@ +> + */ + private static array $renderers = [ + 'table' => TableRenderer::class, + 'code' => CodeRenderer::class, + 'pre' => PreRenderer::class, + ]; + + /** + * @return array> + */ + public static function renderers(): array + { + return self::$renderers; + } + + /** + * Checks if there is any renderer registered for the node name + * + * @param string $name + * @return bool + */ + public static function hasRenderer(string $name): bool + { + return array_key_exists($name, self::$renderers); + } + + /** + * Registers a new renderer + * + * @param string $name + * @param string $renderer + * @return void + * + * @thows InvalidRenderer + */ + public static function register(string $name, string $renderer): void + { + if (! is_a($renderer, Renderer::class, true)) { + throw new InvalidRenderer(); + } + + self::$renderers[$name] = $renderer; + } + + /** + * Renders the given Node + * + * @param Node $node + * @return Element + * + * @thows InvalidRenderer + */ + public static function render(Node $node): Element + { + $nodeName = $node->getName(); + + if (! self::hasRenderer($nodeName)) { + throw new InvalidRenderer(); + } + + /** @var Renderer $renderer */ + $renderer = (new self::$renderers[$nodeName]); + + return $renderer->toElement($node); + } +} diff --git a/src/Html/PreRenderer.php b/src/Html/PreRenderer.php index e97048c..c0db88f 100644 --- a/src/Html/PreRenderer.php +++ b/src/Html/PreRenderer.php @@ -5,13 +5,14 @@ namespace Termwind\Html; use Termwind\Components\Element; +use Termwind\Contracts\Renderer; use Termwind\Termwind; use Termwind\ValueObjects\Node; /** * @internal */ -final class PreRenderer +final class PreRenderer implements Renderer { /** * Gets HTML content from a given node and converts to the content element. diff --git a/src/Html/TableRenderer.php b/src/Html/TableRenderer.php index 60b73d4..87906fc 100644 --- a/src/Html/TableRenderer.php +++ b/src/Html/TableRenderer.php @@ -12,6 +12,7 @@ use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Termwind\Components\Element; +use Termwind\Contracts\Renderer; use Termwind\HtmlRenderer; use Termwind\Termwind; use Termwind\ValueObjects\Node; @@ -20,7 +21,7 @@ /** * @internal */ -final class TableRenderer +final class TableRenderer implements Renderer { /** * Symfony table object uses for table generation. diff --git a/src/HtmlRenderer.php b/src/HtmlRenderer.php index 568c946..8161fd7 100644 --- a/src/HtmlRenderer.php +++ b/src/HtmlRenderer.php @@ -6,9 +6,7 @@ use DOMDocument; use DOMNode; -use Termwind\Html\CodeRenderer; -use Termwind\Html\PreRenderer; -use Termwind\Html\TableRenderer; +use Termwind\Html\ElementRenderer; use Termwind\ValueObjects\Node; /** @@ -56,12 +54,8 @@ private function convert(Node $node): Components\Element|string { $children = []; - if ($node->isName('table')) { - return (new TableRenderer)->toElement($node); - } elseif ($node->isName('code')) { - return (new CodeRenderer)->toElement($node); - } elseif ($node->isName('pre')) { - return (new PreRenderer)->toElement($node); + if (ElementRenderer::hasRenderer($node->getName())) { + return ElementRenderer::render($node); } foreach ($node->getChildNodes() as $child) { diff --git a/tests/registerRenderer.php b/tests/registerRenderer.php new file mode 100644 index 0000000..5a30557 --- /dev/null +++ b/tests/registerRenderer.php @@ -0,0 +1,61 @@ +toBeTrue(); +})->with(['code', 'pre', 'table']); + +it('adds valid custom renderer', function () { + expect(ElementRenderer::hasRenderer('custom')) + ->toBeFalse(); + + registerRenderer('custom', CustomRenderer::class); + + expect(ElementRenderer::hasRenderer('custom')) + ->toBeTrue(); +}); + +it('throws exception when adding invalid custom renderer', function () { + $this->expectException(InvalidRenderer::class); + + registerRenderer('foo', 'bar'); +}); + +final class CustomRenderer implements Renderer +{ + public function toElement(Node $node): Element + { + $lines = explode("\n", $node->getHtml()); + if (reset($lines) === '') { + array_shift($lines); + } + + if (end($lines) === '') { + array_pop($lines); + } + + $maxStrLen = array_reduce( + $lines, + static fn (int $max, string $line) => ($max < strlen($line)) ? strlen($line) : $max, + 0 + ); + + $styles = $node->getClassAttribute(); + $html = array_map( + static fn (string $line) => (string) Termwind::div(str_pad($line, $maxStrLen + 3), $styles), + $lines + ); + + return Termwind::raw( + implode('', $html) + ); + } +}