Skip to content

Commit e402c66

Browse files
linawolfjaapio
authored andcommitted
[FEATURE] Introduce table directive
1 parent 481cc03 commit e402c66

File tree

14 files changed

+256
-23
lines changed

14 files changed

+256
-23
lines changed

packages/guides-restructured-text/resources/config/guides-restructured-text.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
use phpDocumentor\Guides\RestructuredText\Directives\SeeAlsoDirective;
4343
use phpDocumentor\Guides\RestructuredText\Directives\SidebarDirective;
4444
use phpDocumentor\Guides\RestructuredText\Directives\SubDirective;
45+
use phpDocumentor\Guides\RestructuredText\Directives\TableDirective;
4546
use phpDocumentor\Guides\RestructuredText\Directives\TipDirective;
4647
use phpDocumentor\Guides\RestructuredText\Directives\TitleDirective;
4748
use phpDocumentor\Guides\RestructuredText\Directives\ToctreeDirective;
@@ -197,6 +198,7 @@
197198
->set(RoleDirective::class)
198199
->set(SeeAlsoDirective::class)
199200
->set(SidebarDirective::class)
201+
->set(TableDirective::class)
200202
->set(TipDirective::class)
201203
->set(TitleDirective::class)
202204
->set(ToctreeDirective::class)

packages/guides-restructured-text/src/RestructuredText/Directives/BaseDirective.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function process(
6060
): Node|null {
6161
return $this->processNode($blockContext, $directive)
6262
// Ensure options are always available
63-
->withOptions($this->optionsToArray($directive->getOptions()));
63+
->withDefaultOptions($this->optionsToArray($directive->getOptions()));
6464
}
6565

6666
/**

packages/guides-restructured-text/src/RestructuredText/Directives/CsvTableDirective.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
use Psr\Log\LoggerInterface;
1818

1919
use function array_filter;
20+
use function array_map;
2021
use function count;
22+
use function explode;
2123
use function implode;
24+
use function strval;
2225
use function trim;
2326

2427
/**
@@ -47,6 +50,7 @@ public function processNode(
4750
BlockContext $blockContext,
4851
Directive $directive,
4952
): Node {
53+
$options = $this->optionsToArray($directive->getOptions());
5054
if ($directive->hasOption('file')) {
5155
$csvStream = $blockContext->getDocumentParserContext()
5256
->getContext()
@@ -96,7 +100,17 @@ public function processNode(
96100
$rows[] = $tableRow;
97101
}
98102

99-
return new TableNode($rows, array_filter([$header]));
103+
$tableNode = new TableNode($rows, array_filter([$header]));
104+
if (isset($options['widths']) && $options['widths'] !== 'auto' && $options['widths'] !== 'grid') {
105+
$colWidths = array_map('intval', explode(',', strval($options['widths'])));
106+
// A list of integers is used instead of the input column widths. Implies "grid".
107+
$options['widths'] = 'grid';
108+
$tableNode = $tableNode->withColumnWidth($colWidths);
109+
}
110+
111+
$tableNode = $tableNode->withOptions($options);
112+
113+
return $tableNode;
100114
}
101115

102116
private function buildColumn(

packages/guides-restructured-text/src/RestructuredText/Directives/SubDirective.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ final public function process(
4444
return null;
4545
}
4646

47-
return $node->withOptions($this->optionsToArray($directive->getOptions()));
47+
return $node->withDefaultOptions($this->optionsToArray($directive->getOptions()));
4848
}
4949

5050
/** @return Rule<CollectionNode> */
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Guides\RestructuredText\Directives;
6+
7+
use phpDocumentor\Guides\Nodes\Node;
8+
use phpDocumentor\Guides\Nodes\TableNode;
9+
use phpDocumentor\Guides\RestructuredText\Nodes\CollectionNode;
10+
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
11+
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
12+
use Psr\Log\LoggerInterface;
13+
14+
use function array_map;
15+
use function count;
16+
use function explode;
17+
use function sprintf;
18+
use function strval;
19+
20+
/**
21+
* Applies more options to a table
22+
*
23+
* .. table:: Table Title
24+
* :widths: 30,70
25+
* :align: center
26+
* :class: custom-table
27+
*
28+
* +-----------------+-----------------+
29+
* | Header 1 | Header 2 |
30+
* +=================+=================+
31+
* | Row 1, Column 1 | Row 1, Column 2 |
32+
* +-----------------+-----------------+
33+
* | Row 2, Column 1 | Row 2, Column 2 |
34+
* +-----------------+-----------------+
35+
*/
36+
final class TableDirective extends SubDirective
37+
{
38+
public function __construct(
39+
protected Rule $startingRule,
40+
private LoggerInterface $logger,
41+
) {
42+
parent::__construct($startingRule);
43+
}
44+
45+
public function getName(): string
46+
{
47+
return 'table';
48+
}
49+
50+
/** {@inheritDoc} */
51+
protected function processSub(
52+
CollectionNode $collectionNode,
53+
Directive $directive,
54+
): Node|null {
55+
if (count($collectionNode->getChildren()) !== 1) {
56+
$this->logger->warning(sprintf('The table directive may contain exactly one table. %s children found', count($collectionNode->getChildren())));
57+
58+
return $collectionNode;
59+
}
60+
61+
$tableNode = $collectionNode->getChildren()[0];
62+
if (!$tableNode instanceof TableNode) {
63+
$this->logger->warning(sprintf('The table directive may contain exactly one table. A node of type %s was found. ', $tableNode::class));
64+
65+
return $collectionNode;
66+
}
67+
68+
$options = $this->optionsToArray($directive->getOptions());
69+
if (isset($options['widths']) && $options['widths'] !== 'auto' && $options['widths'] !== 'grid') {
70+
$colWidths = array_map('intval', explode(',', strval($options['widths'])));
71+
// A list of integers is used instead of the input column widths. Implies "grid".
72+
$options['widths'] = 'grid';
73+
$tableNode = $tableNode->withColumnWidth($colWidths);
74+
}
75+
76+
return $tableNode->withOptions($options);
77+
}
78+
}

packages/guides-restructured-text/tests/unit/Parser/DummyNode.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public function withOptions(array $options): Node
3636
return $this;
3737
}
3838

39+
/** {@inheritDoc} */
40+
public function withDefaultOptions(array $options): Node
41+
{
42+
return $this;
43+
}
44+
3945
public function hasOption(string $name): bool
4046
{
4147
return false;

packages/guides/resources/template/html/body/table.html.twig

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,43 @@
1-
<table{% if tableNode.classes %} class="{{ tableNode.classesString }}"{% endif %}>
1+
{# Define a variable to store the classes #}
2+
{% set tableClasses = '' %}
3+
4+
{# Check and add classes conditionally #}
5+
{% if tableNode.classes %}
6+
{% set tableClasses = tableClasses ~ tableNode.classesString ~ ' ' %}
7+
{% endif %}
8+
{% if tableNode.hasOption('align') %}
9+
{% set tableClasses = tableClasses ~ 'align-' ~ tableNode.option('align') ~ ' ' %}
10+
{% endif %}
11+
{% if tableNode.hasOption('widths') %}
12+
{% set tableClasses = tableClasses ~ 'colwidths-' ~ tableNode.option('widths') ~ ' ' %}
13+
{% endif %}
14+
{% if tableNode.hasOption('grid') == 'grid' %}
15+
{% set tableClasses = tableClasses ~ 'grid-' ~ tableNode.option('grid') ~ ' ' %}
16+
{% endif %}
17+
18+
{% set tableStyle = '' %}
19+
{% if tableNode.hasOption('width') %}
20+
{% set tableStyle = tableStyle ~ 'width: ' ~ tableNode.option('width') ~ '; ' %}
21+
{% endif %}
22+
23+
<table
24+
{%- if tableClasses|trim %} class="{{ tableClasses|trim }}"{% endif %}
25+
{%- if tableStyle|trim %} style="{{ tableStyle|trim }}"{% endif %}
26+
>
27+
{% if tableNode.hasOption('caption') %}
28+
<caption>{{ tableNode.option('caption') }}</caption>
29+
{% endif %}
30+
{% if tableNode.columnWidth %}
31+
<colgroup>
32+
{% for width in tableNode.columnWidth %}
33+
{% if width<=0 %}
34+
<col style="width: auto">
35+
{% else %}
36+
<col style="width: {{ width }}%">
37+
{% endif %}
38+
{% endfor %}
39+
</colgroup>
40+
{% endif %}
241
{% if tableHeaderRows is not empty %}
342
<thead>
443
{% for tableHeaderRow in tableHeaderRows %}

packages/guides/src/Nodes/AbstractNode.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@ public function withOptions(array $options): Node
8989
return $result;
9090
}
9191

92+
/**
93+
* Adds $options as default options without overriding any options already set.
94+
*
95+
* @param array<string, scalar|null> $options
96+
*
97+
* @return static
98+
*/
99+
public function withDefaultOptions(array $options): Node
100+
{
101+
$result = clone $this;
102+
$result->options = [...$options, ...$result->options];
103+
104+
return $result;
105+
}
106+
92107
public function hasOption(string $name): bool
93108
{
94109
return isset($this->options[$name]);

packages/guides/src/Nodes/Node.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public function getOptions(): array;
2121
/** @param array<string, scalar|null> $options */
2222
public function withOptions(array $options): Node;
2323

24+
/** @param array<string, scalar|null> $options */
25+
public function withDefaultOptions(array $options): Node;
26+
2427
public function hasOption(string $name): bool;
2528

2629
public function setValue(mixed $value): void;

packages/guides/src/Nodes/TableNode.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class TableNode extends CompoundNode
2424
/**
2525
* @param TableRow[] $data
2626
* @param TableRow[] $headers
27+
* @param int[] $columnWidth
2728
*/
28-
public function __construct(protected array $data, protected array $headers = [])
29+
public function __construct(protected array $data, protected array $headers = [], protected array $columnWidth = [])
2930
{
3031
parent::__construct();
3132
}
@@ -56,4 +57,19 @@ public function getHeaders(): array
5657
{
5758
return $this->headers;
5859
}
60+
61+
/** @return int[] */
62+
public function getColumnWidth(): array
63+
{
64+
return $this->columnWidth;
65+
}
66+
67+
/** @param int[] $columnWidth */
68+
public function withColumnWidth(array $columnWidth): TableNode
69+
{
70+
$table = clone $this;
71+
$table->columnWidth = $columnWidth;
72+
73+
return $table;
74+
}
5975
}

0 commit comments

Comments
 (0)