From bafb537e3da7f6281affe9dd32fbf6ac6f4d7986 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 22 Nov 2024 14:08:08 +0100 Subject: [PATCH] Render tables to rst --- .../resources/config/guides-theme-rst.php | 3 + .../template/rst/body/table.rst.twig | 2 +- .../src/RstTheme/Twig/RstExtension.php | 82 +++++++++++++++-- .../table-md-to-rst/expected/index.rst | 87 ++++++++++++------- 4 files changed, 133 insertions(+), 41 deletions(-) diff --git a/packages/guides-theme-rst/resources/config/guides-theme-rst.php b/packages/guides-theme-rst/resources/config/guides-theme-rst.php index 34b3fe095..291b4f635 100644 --- a/packages/guides-theme-rst/resources/config/guides-theme-rst.php +++ b/packages/guides-theme-rst/resources/config/guides-theme-rst.php @@ -6,6 +6,8 @@ use phpDocumentor\Guides\RstTheme\Twig\RstExtension; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + return static function (ContainerConfigurator $container): void { $container->services() ->defaults() @@ -29,6 +31,7 @@ ) ->set(RstExtension::class) + ->arg('$nodeRenderer', service('phpdoc.guides.output_node_renderer')) ->tag('twig.extension') ->autowire(); }; diff --git a/packages/guides-theme-rst/resources/template/rst/body/table.rst.twig b/packages/guides-theme-rst/resources/template/rst/body/table.rst.twig index fc4a32dd3..a794551ae 100644 --- a/packages/guides-theme-rst/resources/template/rst/body/table.rst.twig +++ b/packages/guides-theme-rst/resources/template/rst/body/table.rst.twig @@ -1,2 +1,2 @@ -{{ renderRstTable(node) }} \ No newline at end of file +{{ renderRstTable(node) | raw }} diff --git a/packages/guides-theme-rst/src/RstTheme/Twig/RstExtension.php b/packages/guides-theme-rst/src/RstTheme/Twig/RstExtension.php index 5c3cfb5e5..c0b697f29 100644 --- a/packages/guides-theme-rst/src/RstTheme/Twig/RstExtension.php +++ b/packages/guides-theme-rst/src/RstTheme/Twig/RstExtension.php @@ -13,8 +13,12 @@ namespace phpDocumentor\Guides\RstTheme\Twig; +use phpDocumentor\Guides\NodeRenderers\NodeRenderer; +use phpDocumentor\Guides\Nodes\Table\TableColumn; +use phpDocumentor\Guides\Nodes\Table\TableRow; use phpDocumentor\Guides\Nodes\TableNode; use phpDocumentor\Guides\Nodes\TitleNode; +use phpDocumentor\Guides\RenderContext; use phpDocumentor\Guides\RstTheme\Configuration\HeaderSyntax; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -23,6 +27,9 @@ use function array_map; use function explode; use function implode; +use function max; +use function mb_str_pad; +use function mb_strlen; use function min; use function preg_replace; use function rtrim; @@ -31,12 +38,17 @@ final class RstExtension extends AbstractExtension { + public function __construct( + private NodeRenderer $nodeRenderer, + ) { + } + /** @return TwigFunction[] */ public function getFunctions(): array { return [ new TwigFunction('renderRstTitle', $this->renderRstTitle(...), ['is_safe' => ['rst'], 'needs_context' => false]), - new TwigFunction('renderRstTable', $this->renderRstTable(...), ['is_safe' => ['rst'], 'needs_context' => false]), + new TwigFunction('renderRstTable', $this->renderRstTable(...), ['is_safe' => ['rst'], 'needs_context' => true]), new TwigFunction('renderRstIndent', $this->renderRstIndent(...), ['is_safe' => ['rst'], 'needs_context' => false]), ]; } @@ -77,23 +89,75 @@ public function renderRstTitle(TitleNode $node, string $content): string $ret .= $content . "\n" . str_repeat($headerSyntax->delimiter(), strlen($content)); - return $ret; + return $ret . "\n"; + } + + /** @param array{env: RenderContext} $context */ + public function renderRstTable(array $context, TableNode $node): string + { + $columnWidths = []; + + $this->determineMaxLenght($node->getHeaders(), $context['env'], $columnWidths); + $this->determineMaxLenght($node->getData(), $context['env'], $columnWidths); + + $ret = $this->renderTableRowEnd($columnWidths); + $ret .= $this->renderRows($node->getHeaders(), $context['env'], $columnWidths, '='); + $ret .= $this->renderRows($node->getData(), $context['env'], $columnWidths); + + return $ret . "\n"; + } + + private function renderCellContent(RenderContext $env, TableColumn $column): string + { + return implode('', array_map(fn ($node) => $this->nodeRenderer->render($node, $env), $column->getValue())); + } + + /** + * @param TableRow[] $rows + * @param int[] &$columnWidths + */ + private function determineMaxLenght(array $rows, RenderContext $env, array &$columnWidths): void + { + foreach ($rows as $row) { + foreach ($row->getColumns() as $index => $column) { + $content = $this->renderCellContent($env, $column); + + $columnWidths[$index] = max(mb_strlen($content) + 2, $columnWidths[$index] ?? 0); + } + } } - public function renderRstTable(TableNode $node): string + /** + * @param TableRow[] $rows + * @param int[] $columnWidths + */ + private function renderRows(array $rows, RenderContext $env, array $columnWidths, string $separator = '-'): string { $ret = ''; + foreach ($rows as $row) { + $ret .= '|'; + foreach ($row->getColumns() as $index => $column) { + $content = $this->renderCellContent($env, $column); - foreach ($node->getHeaders() as $header) { - foreach ($header->getColumns() as $column) { - $ret .= $column->getContent() . ' '; + $ret .= ' ' . mb_str_pad($content, $columnWidths[$index] - 2) . ' |'; } - $ret .= "header \n"; + + $ret .= "\n" . $this->renderTableRowEnd($columnWidths, $separator); } - foreach ($node->getData() as $row) { - $ret .= 'row'. "\n"; + + return $ret; + } + + /** @param int[] $columnWidths */ + private function renderTableRowEnd(array $columnWidths, string $char = '-'): string + { + $ret = ''; + foreach ($columnWidths as $width) { + $ret .= '+' . str_repeat($char, $width); } + $ret .= '+' . "\n"; + return $ret; } } diff --git a/tests/Integration/tests-full/md-to-rst/table-md-to-rst/expected/index.rst b/tests/Integration/tests-full/md-to-rst/table-md-to-rst/expected/index.rst index 07595b132..1ea6d02e3 100644 --- a/tests/Integration/tests-full/md-to-rst/table-md-to-rst/expected/index.rst +++ b/tests/Integration/tests-full/md-to-rst/table-md-to-rst/expected/index.rst @@ -1,31 +1,56 @@ -==================== -Markdown Blockquotes -==================== - - This is a blockquote. It can span multiple lines. - -Blockquotes with Multiple Paragraphs -==================================== - - Dorothy followed her through many of the beautiful rooms in her castle. - - The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. - -Blockquotes with Other Elements -=============================== - - The quarterly results look great! - - * Revenue was off the chart. - * Profits were higher than ever. - - *Everything* is going according to **plan**. - -Blockquotes Best Practices -========================== - -Try to put a blank line before... - - This is a blockquote - -...and after a blockquote. +=============== +Markdown Tables +=============== + +Simple Table +============ + ++------------+-----+---------------+ +| Name | Age | City | ++============+=====+===============+ +| John Doe | 29 | New York | ++------------+-----+---------------+ +| Jane Smith | 34 | San Francisco | ++------------+-----+---------------+ +| Sam Green | 22 | Boston | ++------------+-----+---------------+ + +Table 1 +======= + ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| Method name | Description | Parameters | Default | ++==============================================+===================================================================================================================================================================+=======================================================+============================================================================================+ +| `setIcon` | icon file, or existing icon identifier | `string $icon` | `'EXT:container/Resources/Public/Icons/Extension.svg'` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setBackendTemplate` | Template for backend view | `string $backendTemplate` | `null'` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setGridTemplate` | Template for grid | `string $gridTemplate` | `'EXT:container/Resources/Private/Templates/Container.html'` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setGridPartialPaths` / `addGridPartialPath` | Partial root paths for grid | `array $gridPartialPaths` / `string $gridPartialPath` | `['EXT:backend/Resources/Private/Partials/', 'EXT:container/Resources/Private/Partials/']` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setGridLayoutPaths` | Layout root paths for grid | `array $gridLayoutPaths` | `[]` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setSaveAndCloseInNewContentElementWizard` | saveAndClose for new content element wizard | `bool $saveAndCloseInNewContentElementWizard` | `true` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setRegisterInNewContentElementWizard` | register in new content element wizard | `bool $registerInNewContentElementWizard` | `true` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setGroup` | Custom Group (used as optgroup for CType select, and as tab in New Content Element Wizard). If empty "container" is used as tab and no optgroup in CType is used. | `string $group` | `'container'` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ +| `setDefaultValues` | Default values for the newContentElement.wizardItems | `array $defaultValues` | `[]` | ++----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------------------------------+ + +Table 2 +======= + ++-----------------------------+---------------------------------------------------------------------------------------------------------+------------------------------------------------------------+-----------+ +| Option | Description | Default | Parameter | ++=============================+=========================================================================================================+============================================================+===========+ +| `contentId` | id of container to to process | current uid of content element `$cObj->data['uid']` | `?int` | ++-----------------------------+---------------------------------------------------------------------------------------------------------+------------------------------------------------------------+-----------+ +| `colPos` | colPos of children to to process | empty, all children are processed (as `children_`) | `?int` | ++-----------------------------+---------------------------------------------------------------------------------------------------------+------------------------------------------------------------+-----------+ +| `as` | variable to use for proceesedData (only if `colPos` is set) | `children` | `?string` | ++-----------------------------+---------------------------------------------------------------------------------------------------------+------------------------------------------------------------+-----------+ +| `skipRenderingChildContent` | do not call `ContentObjectRenderer->render()` for children, (`renderedContent` in child will not exist) | empty | `?int` | ++-----------------------------+---------------------------------------------------------------------------------------------------------+------------------------------------------------------------+-----------+