Skip to content

Commit c29676e

Browse files
committed
[FEATURE] Support Menu entries with overriden titles and external urls
These are also supported by sphinx
1 parent 534f96c commit c29676e

File tree

27 files changed

+398
-136
lines changed

27 files changed

+398
-136
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace phpDocumentor\Guides\RestructuredText\Directives;
66

77
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
8-
use phpDocumentor\Guides\Nodes\Menu\ParsedMenuEntryNode;
8+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
99
use phpDocumentor\Guides\Nodes\Node;
1010
use phpDocumentor\Guides\ReferenceResolvers\DocumentNameResolverInterface;
1111
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
@@ -39,7 +39,7 @@ public function process(
3939
$blockContext->getDocumentParserContext()->getContext()->getCurrentFileName(),
4040
);
4141

42-
return (new ContentMenuNode([new ParsedMenuEntryNode($absoluteUrl)]))
42+
return (new ContentMenuNode([new MenuDefinitionLineNode($absoluteUrl)]))
4343
->withOptions($this->optionsToArray($options))
4444
->withCaption($directive->getDataNode());
4545
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\Directives;
66

7+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
78
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
8-
use phpDocumentor\Guides\Nodes\Menu\ParsedMenuEntryNode;
99
use phpDocumentor\Guides\Nodes\Node;
1010
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
1111
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
@@ -43,13 +43,13 @@ public function process(
4343
$options['titlesonly'] = new DirectiveOption('titlesonly', false);
4444
$options['globExclude'] ??= new DirectiveOption('globExclude', 'index,Index');
4545

46-
$toctreeFiles = $this->toctreeBuilder->buildToctreeFiles(
46+
$toctreeFiles = $this->toctreeBuilder->buildToctreeEntries(
4747
$parserContext,
4848
$blockContext->getDocumentIterator(),
4949
$options,
5050
);
5151
if (count($toctreeFiles) === 0) {
52-
$toctreeFiles[] = new ParsedMenuEntryNode('/*');
52+
$toctreeFiles[] = new MenuDefinitionLineNode('/*');
5353
}
5454

5555
return (new NavMenuNode($toctreeFiles))->withOptions($this->optionsToArray($options));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function process(
4545
$options = $directive->getOptions();
4646
$options['globExclude'] ??= new DirectiveOption('globExclude', 'index,Index');
4747

48-
$toctreeFiles = $this->toctreeBuilder->buildToctreeFiles(
48+
$toctreeFiles = $this->toctreeBuilder->buildToctreeEntries(
4949
$parserContext,
5050
$blockContext->getDocumentIterator(),
5151
$options,

packages/guides-restructured-text/src/RestructuredText/Toc/ToctreeBuilder.php

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,49 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\Toc;
66

7-
use phpDocumentor\Guides\Nodes\Menu\ParsedMenuEntryNode;
7+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
88
use phpDocumentor\Guides\ParserContext;
9+
use phpDocumentor\Guides\RestructuredText\Parser\EmbeddedUriParser;
910
use phpDocumentor\Guides\RestructuredText\Parser\LinesIterator;
1011

12+
use function array_filter;
1113
use function array_map;
1214

1315
class ToctreeBuilder
1416
{
17+
use EmbeddedUriParser;
18+
1519
/**
1620
* @param mixed[] $options
1721
*
18-
* @return ParsedMenuEntryNode[]
22+
* @return MenuDefinitionLineNode[]
1923
*/
20-
public function buildToctreeFiles(
24+
public function buildToctreeEntries(
2125
ParserContext $parserContext,
2226
LinesIterator $lines,
2327
array $options,
2428
): array {
25-
$toctreeFiles = [];
29+
$toctreeEntries = [];
2630

27-
foreach ($this->parseToctreeFiles($lines) as $file) {
28-
$toctreeFiles[] = $file;
31+
foreach ($this->parseToctreeEntryLines($lines) as $entry) {
32+
$toctreeEntries[] = $entry;
2933
}
3034

31-
return $toctreeFiles;
35+
return $toctreeEntries;
3236
}
3337

34-
/** @return ParsedMenuEntryNode[] */
35-
private function parseToctreeFiles(LinesIterator $lines): array
38+
/** @return MenuDefinitionLineNode[] */
39+
private function parseToctreeEntryLines(LinesIterator $lines): array
3640
{
37-
$linesArray = $lines->toArray();
38-
$trimmedLines = array_map('trim', $linesArray);
41+
$linesArray = array_filter(
42+
array_map('trim', $lines->toArray()),
43+
static fn (string $file): bool => $file !== '',
44+
);
3945

4046
$result = [];
41-
foreach ($trimmedLines as $file) {
42-
if ($file === '') {
43-
continue;
44-
}
45-
46-
$result[] = new ParsedMenuEntryNode($file);
47+
foreach ($linesArray as $line) {
48+
$parsed = $this->extractEmbeddedUri($line);
49+
$result[] = new MenuDefinitionLineNode($parsed['uri'], $parsed['text']);
4750
}
4851

4952
return $result;

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
1010
use phpDocumentor\Guides\Nodes\DocumentTree\SectionEntryNode;
1111
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
12-
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
12+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1313
use phpDocumentor\Guides\Nodes\Menu\TocNode;
1414
use phpDocumentor\Guides\Nodes\Node;
1515

@@ -40,7 +40,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
4040
// We do not add the main section as it repeats the document title
4141
foreach ($section->getChildren() as $subSectionEntryNode) {
4242
assert($subSectionEntryNode instanceof SectionEntryNode);
43-
$sectionMenuEntry = new MenuEntryNode(
43+
$sectionMenuEntry = new InternalMenuEntryNode(
4444
$documentEntry->getFile(),
4545
$subSectionEntryNode->getTitle(),
4646
[],
@@ -59,7 +59,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
5959
}
6060

6161
private function addSubSections(
62-
MenuEntryNode $sectionMenuEntry,
62+
InternalMenuEntryNode $sectionMenuEntry,
6363
SectionEntryNode $sectionEntryNode,
6464
DocumentEntryNode $documentEntry,
6565
int $currentLevel,
@@ -70,7 +70,7 @@ private function addSubSections(
7070
}
7171

7272
foreach ($sectionEntryNode->getChildren() as $subSectionEntryNode) {
73-
$subSectionMenuEntry = new MenuEntryNode(
73+
$subSectionMenuEntry = new InternalMenuEntryNode(
7474
$documentEntry->getFile(),
7575
$subSectionEntryNode->getTitle(),
7676
[],

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

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,31 @@
88
use phpDocumentor\Guides\Compiler\NodeTransformer;
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
1010
use phpDocumentor\Guides\Nodes\DocumentTree\SectionEntryNode;
11-
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
11+
use phpDocumentor\Guides\Nodes\Menu\ExternalMenuEntryNode;
12+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
13+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
1214
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
1315
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
14-
use phpDocumentor\Guides\Nodes\Menu\ParsedMenuEntryNode;
1516
use phpDocumentor\Guides\Nodes\Menu\TocNode;
1617
use phpDocumentor\Guides\Nodes\Node;
18+
use phpDocumentor\Guides\Nodes\TitleNode;
1719
use Psr\Log\LoggerInterface;
1820

1921
use function array_pop;
2022
use function array_unique;
2123
use function assert;
2224
use function explode;
25+
use function filter_var;
2326
use function implode;
2427
use function in_array;
28+
use function is_string;
2529
use function preg_match;
2630
use function sprintf;
2731
use function str_replace;
2832
use function str_starts_with;
2933

34+
use const FILTER_VALIDATE_URL;
35+
3036
/** @implements NodeTransformer<MenuNode> */
3137
class MenuNodeAddEntryTransformer implements NodeTransformer
3238
{
@@ -56,6 +62,15 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
5662
$menuEntries = [];
5763

5864
foreach ($parsedMenuEntryNodes as $parsedMenuEntryNode) {
65+
if (filter_var($parsedMenuEntryNode->getReference(), FILTER_VALIDATE_URL) !== false) {
66+
$menuEntry = new ExternalMenuEntryNode(
67+
$parsedMenuEntryNode->getReference(),
68+
TitleNode::fromString($parsedMenuEntryNode->getTitle() ?? $parsedMenuEntryNode->getReference()),
69+
);
70+
$menuEntries[] = $menuEntry;
71+
continue;
72+
}
73+
5974
foreach ($documentEntries as $documentEntry) {
6075
if (
6176
!self::isEqualAbsolutePath($documentEntry->getFile(), $parsedMenuEntryNode, $currentPath, $glob, $globExclude)
@@ -70,9 +85,9 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
7085
}
7186

7287
$documentEntriesInTree[] = $documentEntry;
73-
$menuEntry = new MenuEntryNode(
88+
$menuEntry = new InternalMenuEntryNode(
7489
$documentEntry->getFile(),
75-
$documentEntry->getTitle(),
90+
is_string($parsedMenuEntryNode->getTitle()) ? TitleNode::fromString($parsedMenuEntryNode->getTitle()) : $documentEntry->getTitle(),
7691
[],
7792
false,
7893
1,
@@ -117,10 +132,10 @@ private function isCurrent(DocumentEntryNode $menuEntry, string $currentPath): b
117132
return $menuEntry->getFile() === $currentPath;
118133
}
119134

120-
private function addSubSections(MenuEntryNode $sectionMenuEntry, SectionEntryNode $sectionEntryNode, DocumentEntryNode $documentEntry, int $currentLevel): void
135+
private function addSubSections(InternalMenuEntryNode $sectionMenuEntry, SectionEntryNode $sectionEntryNode, DocumentEntryNode $documentEntry, int $currentLevel): void
121136
{
122137
foreach ($sectionEntryNode->getChildren() as $subSectionEntryNode) {
123-
$subSectionMenuEntry = new MenuEntryNode($documentEntry->getFile(), $subSectionEntryNode->getTitle(), [], false, $currentLevel + 1, $subSectionEntryNode->getId());
138+
$subSectionMenuEntry = new InternalMenuEntryNode($documentEntry->getFile(), $subSectionEntryNode->getTitle(), [], false, $currentLevel + 1, $subSectionEntryNode->getId());
124139
$sectionMenuEntry->addSection($subSectionMenuEntry);
125140
}
126141
}
@@ -137,7 +152,7 @@ public function getPriority(): int
137152
}
138153

139154
/** @param String[] $globExclude */
140-
private static function isEqualAbsolutePath(string $actualFile, ParsedMenuEntryNode $parsedMenuEntryNode, string $currentFile, bool $glob, array $globExclude): bool
155+
private static function isEqualAbsolutePath(string $actualFile, MenuDefinitionLineNode $parsedMenuEntryNode, string $currentFile, bool $glob, array $globExclude): bool
141156
{
142157
$expectedFile = $parsedMenuEntryNode->getReference();
143158
if (!self::isAbsoluteFile($expectedFile)) {
@@ -152,7 +167,7 @@ private static function isEqualAbsolutePath(string $actualFile, ParsedMenuEntryN
152167
}
153168

154169
/** @param String[] $globExclude */
155-
private static function isEqualRelativePath(string $actualFile, ParsedMenuEntryNode $parsedMenuEntryNode, string $currentFile, bool $glob, array $globExclude): bool
170+
private static function isEqualRelativePath(string $actualFile, MenuDefinitionLineNode $parsedMenuEntryNode, string $currentFile, bool $glob, array $globExclude): bool
156171
{
157172
$expectedFile = $parsedMenuEntryNode->getReference();
158173
if (self::isAbsoluteFile($expectedFile)) {
@@ -228,14 +243,14 @@ private function attachDocumentEntriesToParents(
228243
}
229244
}
230245

231-
private function addSubSectionsToMenuEntries(DocumentEntryNode $documentEntry, MenuEntryNode $menuEntry): void
246+
private function addSubSectionsToMenuEntries(DocumentEntryNode $documentEntry, InternalMenuEntryNode $menuEntry): void
232247
{
233248
foreach ($documentEntry->getSections() as $section) {
234249
// We do not add the main section as it repeats the document title
235250
foreach ($section->getChildren() as $subSectionEntryNode) {
236251
assert($subSectionEntryNode instanceof SectionEntryNode);
237252
$currentLevel = $menuEntry->getLevel() + 1;
238-
$sectionMenuEntry = new MenuEntryNode(
253+
$sectionMenuEntry = new InternalMenuEntryNode(
239254
$documentEntry->getFile(),
240255
$subSectionEntryNode->getTitle(),
241256
[],

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use phpDocumentor\Guides\Compiler\CompilerContext;
88
use phpDocumentor\Guides\Compiler\NodeTransformer;
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
10-
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
10+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1111
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
1212
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
1313
use phpDocumentor\Guides\Nodes\Menu\TocNode;
@@ -34,6 +34,10 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
3434

3535

3636
foreach ($node->getMenuEntries() as $menuEntry) {
37+
if (!$menuEntry instanceof InternalMenuEntryNode) {
38+
continue;
39+
}
40+
3741
$documentEntryOfMenuEntry = $compilerContext->getProjectNode()->getDocumentEntry($menuEntry->getUrl());
3842
$this->addSubEntries($compilerContext, $menuEntry, $documentEntryOfMenuEntry, $menuEntry->getLevel() + 1, $maxDepth);
3943
}
@@ -43,7 +47,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
4347

4448
private function addSubEntries(
4549
CompilerContext $compilerContext,
46-
MenuEntryNode $sectionMenuEntry,
50+
InternalMenuEntryNode $sectionMenuEntry,
4751
DocumentEntryNode $documentEntry,
4852
int $currentLevel,
4953
int $maxDepth,
@@ -53,7 +57,7 @@ private function addSubEntries(
5357
}
5458

5559
foreach ($documentEntry->getChildren() as $subDocumentEntryNode) {
56-
$subMenuEntry = new MenuEntryNode(
60+
$subMenuEntry = new InternalMenuEntryNode(
5761
$subDocumentEntryNode->getFile(),
5862
$subDocumentEntryNode->getTitle(),
5963
[],

packages/guides/src/Compiler/Passes/GlobalMenuPass.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use phpDocumentor\Guides\Compiler\CompilerPass;
99
use phpDocumentor\Guides\Nodes\DocumentNode;
1010
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
11+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1112
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
1213
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
1314
use phpDocumentor\Guides\Nodes\Menu\TocNode;
@@ -93,10 +94,14 @@ private function getNavMenuNodefromTocNode(CompilerContext $compilerContext, Toc
9394

9495
private function getMenuEntryWithChildren(CompilerContext $compilerContext, MenuEntryNode $menuEntry): MenuEntryNode
9596
{
97+
if (!$menuEntry instanceof InternalMenuEntryNode) {
98+
return $menuEntry;
99+
}
100+
101+
$newMenuEntry = new InternalMenuEntryNode($menuEntry->getUrl(), $menuEntry->getValue(), [], false, 2);
96102
$maxdepth = $this->settingsManager->getProjectSettings()->getMaxMenuDepth();
97103
$maxdepth = $maxdepth < 1 ? PHP_INT_MAX : $maxdepth + 1;
98104
$documentEntryOfMenuEntry = $compilerContext->getProjectNode()->getDocumentEntry($menuEntry->getUrl());
99-
$newMenuEntry = new MenuEntryNode($menuEntry->getUrl(), $menuEntry->getValue(), [], false, 2);
100105
$this->addSubEntries($compilerContext, $newMenuEntry, $documentEntryOfMenuEntry, 3, $maxdepth);
101106

102107
return $newMenuEntry;
@@ -113,8 +118,12 @@ private function addSubEntries(
113118
return;
114119
}
115120

121+
if (!$sectionMenuEntry instanceof InternalMenuEntryNode) {
122+
return;
123+
}
124+
116125
foreach ($documentEntry->getChildren() as $subDocumentEntryNode) {
117-
$subMenuEntry = new MenuEntryNode(
126+
$subMenuEntry = new InternalMenuEntryNode(
118127
$subDocumentEntryNode->getFile(),
119128
$subDocumentEntryNode->getTitle(),
120129
[],

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use phpDocumentor\Guides\NodeRenderers\NodeRenderer;
88
use phpDocumentor\Guides\Nodes\BreadCrumbNode;
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
10+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1011
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
1112
use phpDocumentor\Guides\Nodes\Node;
1213
use phpDocumentor\Guides\RenderContext;
@@ -97,7 +98,7 @@ private function buildBreadcrumb(
9798
int $level,
9899
bool $isCurrent,
99100
): array {
100-
$entry = new MenuEntryNode(
101+
$entry = new InternalMenuEntryNode(
101102
$documentEntry->getFile(),
102103
$documentEntry->getTitle(),
103104
[],

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ public function supports(string $nodeFqcn): bool
2929

3030
public function render(Node $node, RenderContext $renderContext): string
3131
{
32+
$url = $this->urlGenerator->generateCanonicalOutputUrl($renderContext, $node->getUrl(), $node->getValue()->getId());
33+
3234
return $this->renderer->renderTemplate(
3335
$renderContext,
3436
'body/menu/menu-item.html.twig',
3537
[
36-
'url' => $this->urlGenerator->generateCanonicalOutputUrl($renderContext, $node->getUrl(), $node->getValue()->getId()),
38+
'url' => $url,
3739
'node' => $node,
3840
],
3941
);

0 commit comments

Comments
 (0)