Skip to content

Commit b3a8056

Browse files
authored
Merge pull request #455 from phpDocumentor/menu-3
Introduce Transformer for Content Menus
2 parents 9564b93 + 1c29d1d commit b3a8056

22 files changed

+258
-56
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\Directives;
66

7-
use phpDocumentor\Guides\Nodes\ContentMenuNode;
7+
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
88
use phpDocumentor\Guides\Nodes\Node;
99
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
1010
use phpDocumentor\Guides\RestructuredText\Parser\DocumentParserContext;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Guides\Compiler\NodeTransformers;
6+
7+
use phpDocumentor\Guides\Compiler\CompilerContext;
8+
use phpDocumentor\Guides\Compiler\NodeTransformer;
9+
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
10+
use phpDocumentor\Guides\Nodes\DocumentTree\SectionEntryNode;
11+
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
12+
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
13+
use phpDocumentor\Guides\Nodes\Menu\TocNode;
14+
use phpDocumentor\Guides\Nodes\Node;
15+
16+
use function assert;
17+
18+
use const PHP_INT_MAX;
19+
20+
/** @implements NodeTransformer<TocNode> */
21+
class ContentMenuNodeWithSectionEntryTransformer implements NodeTransformer
22+
{
23+
public function enterNode(Node $node, CompilerContext $compilerContext): Node
24+
{
25+
return $node;
26+
}
27+
28+
public function leaveNode(Node $node, CompilerContext $compilerContext): Node|null
29+
{
30+
if (!$node instanceof ContentMenuNode) {
31+
return $node;
32+
}
33+
34+
$depth = (int) $node->getOption('depth', PHP_INT_MAX);
35+
$documentEntry = $compilerContext->getDocumentNode()->getDocumentEntry();
36+
37+
$menuEntries = [];
38+
foreach ($documentEntry->getSections() as $section) {
39+
// We do not add the main section as it repeats the document title
40+
foreach ($section->getChildren() as $subSectionEntryNode) {
41+
assert($subSectionEntryNode instanceof SectionEntryNode);
42+
$sectionMenuEntry = new MenuEntryNode(
43+
$documentEntry->getFile(),
44+
$subSectionEntryNode->getTitle(),
45+
[],
46+
false,
47+
1,
48+
$subSectionEntryNode->getId(),
49+
);
50+
$menuEntries[] = $sectionMenuEntry;
51+
$this->addSubSections($sectionMenuEntry, $subSectionEntryNode, $documentEntry, 1, $depth);
52+
}
53+
}
54+
55+
$node = $node->withMenuEntries($menuEntries);
56+
57+
return $node;
58+
}
59+
60+
private function addSubSections(
61+
MenuEntryNode $sectionMenuEntry,
62+
SectionEntryNode $sectionEntryNode,
63+
DocumentEntryNode $documentEntry,
64+
int $currentLevel,
65+
int $maxLevel,
66+
): void {
67+
if ($currentLevel >= $maxLevel) {
68+
return;
69+
}
70+
71+
foreach ($sectionEntryNode->getChildren() as $subSectionEntryNode) {
72+
$subSectionMenuEntry = new MenuEntryNode(
73+
$documentEntry->getFile(),
74+
$subSectionEntryNode->getTitle(),
75+
[],
76+
false,
77+
$currentLevel,
78+
$subSectionEntryNode->getId(),
79+
);
80+
$sectionMenuEntry->addSection($subSectionMenuEntry);
81+
$this->addSubSections(
82+
$subSectionMenuEntry,
83+
$subSectionEntryNode,
84+
$documentEntry,
85+
$currentLevel + 1,
86+
$maxLevel,
87+
);
88+
}
89+
}
90+
91+
public function supports(Node $node): bool
92+
{
93+
return $node instanceof ContentMenuNode;
94+
}
95+
96+
public function getPriority(): int
97+
{
98+
// After DocumentEntryTransformer
99+
return 4500;
100+
}
101+
}

packages/guides/src/Compiler/NodeTransformers/DocumentEntryRegistrationTransformer.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
3737

3838
$entry = new DocumentEntryNode($node->getFilePath(), $node->getTitle() ?? TitleNode::emptyNode());
3939
$compilerContext->getProjectNode()->addDocumentEntry($entry);
40-
$node->setDocumentEntry($entry);
4140

42-
return $node;
41+
return $node->setDocumentEntry($entry);
4342
}
4443

4544
public function supports(Node $node): bool

packages/guides/src/Compiler/NodeTransformers/SectionEntryRegistrationTransformer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function enterNode(Node $node, CompilerContext $compilerContext): Node
2929

3030
$sectionEntryNode = new SectionEntryNode($node->getTitle());
3131
if (count($this->sectionStack) === 0) {
32-
$compilerContext->getDocumentNode()->getDocumentEntry()?->addSection($sectionEntryNode);
32+
$compilerContext->getDocumentNode()->getDocumentEntry()->addSection($sectionEntryNode);
3333
} else {
3434
$parentSection = end($this->sectionStack);
3535
assert($parentSection instanceof SectionEntryNode);

packages/guides/src/Compiler/NodeTransformers/TocNodeWithDocumentEntryTransformer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
8080
private function addSubSections(MenuEntryNode $sectionMenuEntry, SectionEntryNode $sectionEntryNode, DocumentEntryNode $documentEntry, int $currentLevel): void
8181
{
8282
foreach ($sectionEntryNode->getChildren() as $subSectionEntryNode) {
83-
$sectionMenuEntry = new MenuEntryNode($documentEntry->getFile(), $subSectionEntryNode->getTitle(), [], false, $currentLevel, $subSectionEntryNode->getId());
84-
$sectionMenuEntry->addSection($sectionMenuEntry);
83+
$subSectionMenuEntry = new MenuEntryNode($documentEntry->getFile(), $subSectionEntryNode->getTitle(), [], false, $currentLevel + 1, $subSectionEntryNode->getId());
84+
$sectionMenuEntry->addSection($subSectionMenuEntry);
8585
}
8686
}
8787

8888
public function supports(Node $node): bool
8989
{
90-
return $node instanceof MenuNode;
90+
return $node instanceof TocNode;
9191
}
9292

9393
public function getPriority(): int

packages/guides/src/NodeRenderers/Html/MenuNodeRenderer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace phpDocumentor\Guides\NodeRenderers\Html;
1515

1616
use phpDocumentor\Guides\NodeRenderers\NodeRenderer;
17-
use phpDocumentor\Guides\Nodes\ContentMenuNode;
17+
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
1818
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
1919
use phpDocumentor\Guides\Nodes\Menu\TocNode;
2020
use phpDocumentor\Guides\Nodes\Node;

packages/guides/src/Nodes/DocumentNode.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace phpDocumentor\Guides\Nodes;
1515

16+
use Exception;
1617
use phpDocumentor\Guides\Meta\FootnoteTarget;
1718
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
1819
use phpDocumentor\Guides\Nodes\DocumentTree\SectionEntryNode;
@@ -245,14 +246,20 @@ public function getFootnoteTargetAnonymous(): FootnoteTarget|null
245246
return null;
246247
}
247248

248-
public function getDocumentEntry(): DocumentEntryNode|null
249+
public function getDocumentEntry(): DocumentEntryNode
249250
{
251+
if ($this->documentEntry === null) {
252+
throw new Exception('DocumentEntry may not be accessed before initialization');
253+
}
254+
250255
return $this->documentEntry;
251256
}
252257

253-
public function setDocumentEntry(DocumentEntryNode $documentEntry): void
258+
public function setDocumentEntry(DocumentEntryNode $documentEntry): DocumentNode
254259
{
255260
$this->documentEntry = $documentEntry;
261+
262+
return $this;
256263
}
257264

258265
public function getRootSectionEntry(): SectionEntryNode|null

packages/guides/src/Nodes/ContentMenuNode.php renamed to packages/guides/src/Nodes/Menu/ContentMenuNode.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
* @link https://phpdoc.org
1212
*/
1313

14-
namespace phpDocumentor\Guides\Nodes;
15-
16-
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
14+
namespace phpDocumentor\Guides\Nodes\Menu;
1715

1816
use function is_scalar;
1917

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Guides\Nodes\Menu;
6+
7+
use phpDocumentor\Guides\Nodes\AbstractNode;
8+
use phpDocumentor\Guides\Nodes\TitleNode;
9+
10+
/** @extends AbstractNode<TitleNode> */
11+
final class MenuEntry extends AbstractNode
12+
{
13+
/** @param MenuEntry[] $children */
14+
public function __construct(
15+
private readonly string $url,
16+
TitleNode $title,
17+
private readonly array $children = [],
18+
private readonly bool $isDocumentRoot = false,
19+
private readonly int $level = 1,
20+
) {
21+
$this->value = $title;
22+
}
23+
24+
public function getUrl(): string
25+
{
26+
return $this->url;
27+
}
28+
29+
/** @return MenuEntry[] */
30+
public function getChildren(): array
31+
{
32+
return $this->children;
33+
}
34+
35+
/** @return MenuEntry[] */
36+
public function getEntries(): array
37+
{
38+
return $this->children;
39+
}
40+
41+
public function isDocumentRoot(): bool
42+
{
43+
return $this->isDocumentRoot;
44+
}
45+
46+
public function getLevel(): int
47+
{
48+
return $this->level;
49+
}
50+
}

packages/guides/tests/unit/Compiler/NodeTransformers/SectionEntryRegistrationTransformerTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private static function getCompilerContext(string $path): CompilerContext
2626
{
2727
$context = new CompilerContext(new ProjectNode());
2828
$document = new DocumentNode('123', $path);
29-
$document->setDocumentEntry(new DocumentEntryNode($path, TitleNode::emptyNode()));
29+
$document = $document->setDocumentEntry(new DocumentEntryNode($path, TitleNode::emptyNode()));
3030

3131
return $context->withShadowTree($document);
3232
}
@@ -42,7 +42,7 @@ public function testSectionGetsRegistered(): void
4242
$transformer->enterNode($node2, $this->context);
4343
$transformer->leaveNode($node2, $this->context);
4444
$transformer->leaveNode($node, $this->context);
45-
self::assertCount(1, $this->context->getDocumentNode()->getDocumentEntry()?->getSections() ?? []);
46-
self::assertInstanceOf(SectionEntryNode::class, $this->context->getDocumentNode()->getDocumentEntry()?->getSections()[0]);
45+
self::assertCount(1, $this->context->getDocumentNode()->getDocumentEntry()->getSections());
46+
self::assertInstanceOf(SectionEntryNode::class, $this->context->getDocumentNode()->getDocumentEntry()->getSections()[0]);
4747
}
4848
}

0 commit comments

Comments
 (0)