Skip to content

Commit 96d5f59

Browse files
authored
Merge pull request #1121 from phpDocumentor/task/menu-automatic
[FEATURE] Add option to create an automatic menu
2 parents 5d2bbf1 + 9978ef1 commit 96d5f59

File tree

52 files changed

+1652
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1652
-8
lines changed

packages/guides-cli/resources/schema/guides.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<xsd:attribute name="theme" type="xsd:string"/>
3232
<xsd:attribute name="default-code-language" type="xsd:string"/>
3333
<xsd:attribute name="links-are-relative" type="xsd:string"/>
34+
<xsd:attribute name="automatic-menu" type="xsd:string"/>
3435
<xsd:attribute name="max-menu-depth" type="xsd:int"/>
3536

3637
</xsd:complexType>

packages/guides-markdown/src/Markdown/MarkupLanguageParser.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use phpDocumentor\Guides\Nodes\DocumentNode;
2525
use phpDocumentor\Guides\Nodes\Node;
2626
use phpDocumentor\Guides\ParserContext;
27+
use phpDocumentor\Guides\Settings\ProjectSettings;
28+
use phpDocumentor\Guides\Settings\SettingsManager;
2729
use Psr\Log\LoggerInterface;
2830
use RuntimeException;
2931

@@ -40,16 +42,21 @@ final class MarkupLanguageParser implements MarkupLanguageParserInterface
4042

4143
private DocumentNode|null $document = null;
4244

45+
private SettingsManager $settingsManager;
46+
4347
/** @param iterable<ParserInterface<Node>> $parsers */
4448
public function __construct(
4549
private readonly LoggerInterface $logger,
4650
private readonly iterable $parsers,
51+
SettingsManager|null $settingsManager,
4752
) {
4853
$cmEnvironment = new CommonMarkEnvironment(['html_input' => 'strip']);
4954
$cmEnvironment->addExtension(new CommonMarkCoreExtension());
5055
$cmEnvironment->addExtension(new TableExtension());
5156
$cmEnvironment->addExtension(new AutolinkExtension());
5257
$this->markdownParser = new MarkdownParser($cmEnvironment);
58+
// if for backward compatibility reasons no settings manager was passed, use the defaults
59+
$this->settingsManager = $settingsManager ?? new SettingsManager(new ProjectSettings());
5360
}
5461

5562
public function supports(string $inputFormat): bool
@@ -69,7 +76,7 @@ public function parse(ParserContext $parserContext, string $contents): DocumentN
6976
private function parseDocument(NodeWalker $walker, string $hash): DocumentNode
7077
{
7178
$document = new DocumentNode($hash, ltrim($this->getParserContext()->getCurrentAbsolutePath(), '/'));
72-
$document->setOrphan(true);
79+
$document->setOrphan(!$this->settingsManager->getProjectSettings()->isAutomaticMenu());
7380
$this->document = $document;
7481

7582
while ($event = $walker->next()) {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Compiler\NodeTransformers\MenuNodeTransformers;
15+
16+
use phpDocumentor\Guides\Compiler\CompilerContextInterface;
17+
use phpDocumentor\Guides\Compiler\NodeTransformer;
18+
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
19+
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
20+
use phpDocumentor\Guides\Nodes\Menu\TocNode;
21+
use phpDocumentor\Guides\Nodes\Node;
22+
use phpDocumentor\Guides\Settings\SettingsManager;
23+
use Psr\Log\LoggerInterface;
24+
25+
/** @implements NodeTransformer<MenuNode> */
26+
final class TocNodeReplacementTransformer implements NodeTransformer
27+
{
28+
public function __construct(
29+
private readonly LoggerInterface $logger,
30+
private readonly SettingsManager $settingsManager,
31+
) {
32+
}
33+
34+
public function enterNode(Node $node, CompilerContextInterface $compilerContext): Node
35+
{
36+
return $node;
37+
}
38+
39+
public function leaveNode(Node $node, CompilerContextInterface $compilerContext): Node|null
40+
{
41+
if (!$node instanceof TocNode) {
42+
return $node;
43+
}
44+
45+
if (!$this->settingsManager->getProjectSettings()->isAutomaticMenu()) {
46+
return $node;
47+
}
48+
49+
if ($node->hasOption('hidden')) {
50+
$this->logger->warning('The `.. toctree::` directive with option `:hidden:` is not supported in automatic-menu mode. ', $compilerContext->getLoggerInformation());
51+
52+
return null;
53+
}
54+
55+
$this->logger->warning('The `.. toctree::` directive is not supported in automatic-menu mode. Use `.. menu::` instead. ', $compilerContext->getLoggerInformation());
56+
$menuNode = new NavMenuNode($node->getMenuEntries());
57+
$menuNode = $menuNode->withOptions($node->getOptions());
58+
$menuNode = $menuNode->withCaption($node->getCaption());
59+
60+
return $menuNode;
61+
}
62+
63+
public function supports(Node $node): bool
64+
{
65+
return $node instanceof TocNode;
66+
}
67+
68+
public function getPriority(): int
69+
{
70+
return 20_000;
71+
}
72+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Compiler\Passes;
15+
16+
use phpDocumentor\Guides\Compiler\CompilerContextInterface;
17+
use phpDocumentor\Guides\Compiler\CompilerPass;
18+
use phpDocumentor\Guides\Nodes\DocumentNode;
19+
use phpDocumentor\Guides\Settings\SettingsManager;
20+
21+
final class AutomaticMenuPass implements CompilerPass
22+
{
23+
public function __construct(
24+
private readonly SettingsManager $settingsManager,
25+
) {
26+
}
27+
28+
public function getPriority(): int
29+
{
30+
return 20; // must be run very late
31+
}
32+
33+
/**
34+
* @param DocumentNode[] $documents
35+
*
36+
* @return DocumentNode[]
37+
*/
38+
public function run(array $documents, CompilerContextInterface $compilerContext): array
39+
{
40+
if (!$this->settingsManager->getProjectSettings()->isAutomaticMenu()) {
41+
return $documents;
42+
}
43+
44+
$projectNode = $compilerContext->getProjectNode();
45+
$rootDocumentEntry = $projectNode->getRootDocumentEntry();
46+
foreach ($documents as $documentNode) {
47+
if ($documentNode->isOrphan()) {
48+
// Do not add orphans to the automatic menu
49+
continue;
50+
}
51+
52+
if ($documentNode->isRoot()) {
53+
continue;
54+
}
55+
56+
$documentEntry = $projectNode->getDocumentEntry($documentNode->getFilePath());
57+
$documentEntry->setParent($rootDocumentEntry);
58+
$rootDocumentEntry->addChild($documentEntry);
59+
}
60+
61+
return $documents;
62+
}
63+
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
use function array_map;
3232
use function assert;
33+
use function count;
3334

3435
use const PHP_INT_MAX;
3536

@@ -56,7 +57,7 @@ public function run(array $documents, CompilerContextInterface $compilerContext)
5657
try {
5758
$rootDocumentEntry = $projectNode->getRootDocumentEntry();
5859
} catch (Throwable) {
59-
// Todo: Functional tests have not root document entry
60+
// Todo: Functional tests have no root document entry
6061
return $documents;
6162
}
6263

@@ -78,11 +79,27 @@ public function run(array $documents, CompilerContextInterface $compilerContext)
7879
$menuNodes[] = $menuNode->withCaption($tocNode->getCaption());
7980
}
8081

82+
if ($this->settingsManager->getProjectSettings()->isAutomaticMenu() && count($menuNodes) === 0) {
83+
$menuNodes[] = $this->getNavMenuNodeFromDocumentEntries($compilerContext);
84+
}
85+
8186
$projectNode->setGlobalMenues($menuNodes);
8287

8388
return $documents;
8489
}
8590

91+
private function getNavMenuNodeFromDocumentEntries(CompilerContextInterface $compilerContext): NavMenuNode
92+
{
93+
$menuEntries = [];
94+
$rootDocumentEntry = $compilerContext->getProjectNode()->getRootDocumentEntry();
95+
foreach ($rootDocumentEntry->getChildren() as $documentEntryNode) {
96+
$newMenuEntry = new InternalMenuEntryNode($documentEntryNode->getFile(), $documentEntryNode->getTitle(), [], false, 1);
97+
$menuEntries[] = $newMenuEntry;
98+
}
99+
100+
return new NavMenuNode($menuEntries);
101+
}
102+
86103
private function getNavMenuNodefromTocNode(CompilerContextInterface $compilerContext, TocNode $tocNode, string|null $menuType = null): NavMenuNode
87104
{
88105
$self = $this;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@
1818
use phpDocumentor\Guides\Nodes\DocumentNode;
1919
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
2020
use phpDocumentor\Guides\Nodes\ProjectNode;
21+
use phpDocumentor\Guides\Settings\ProjectSettings;
22+
use phpDocumentor\Guides\Settings\SettingsManager;
2123
use Psr\Log\LoggerInterface;
2224

2325
final class ToctreeValidationPass implements CompilerPass
2426
{
27+
private SettingsManager $settingsManager;
28+
2529
public function __construct(
2630
private readonly LoggerInterface $logger,
31+
SettingsManager|null $settingsManager = null,
2732
) {
33+
// if for backward compatibility reasons no settings manager was passed, use the defaults
34+
$this->settingsManager = $settingsManager ?? new SettingsManager(new ProjectSettings());
2835
}
2936

3037
public function getPriority(): int
@@ -39,6 +46,10 @@ public function getPriority(): int
3946
*/
4047
public function run(array $documents, CompilerContextInterface $compilerContext): array
4148
{
49+
if ($this->settingsManager->getProjectSettings()->isAutomaticMenu()) {
50+
return $documents;
51+
}
52+
4253
$projectNode = $compilerContext->getProjectNode();
4354

4455
foreach ($documents as $document) {

packages/guides/src/DependencyInjection/GuidesExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ static function ($value) {
143143
->scalarNode('show_progress')->end()
144144
->scalarNode('links_are_relative')->end()
145145
->scalarNode('max_menu_depth')->end()
146+
->scalarNode('automatic_menu')->end()
146147
->arrayNode('base_template_paths')
147148
->defaultValue([])
148149
->scalarPrototype()->end()
@@ -278,6 +279,10 @@ public function load(array $configs, ContainerBuilder $container): void
278279
$projectSettings->setMaxMenuDepth((int) $config['max_menu_depth']);
279280
}
280281

282+
if (isset($config['automatic_menu'])) {
283+
$projectSettings->setAutomaticMenu((bool) $config['automatic_menu']);
284+
}
285+
281286
if (isset($config['default_code_language'])) {
282287
$projectSettings->setDefaultCodeLanguage((string) $config['default_code_language']);
283288
}

packages/guides/src/Settings/ProjectSettings.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ final class ProjectSettings
3535
private bool $linksRelative = false;
3636
private string $defaultCodeLanguage = '';
3737
private int $maxMenuDepth = 0;
38+
private bool $automaticMenu = false;
3839

3940
/** @var string[] */
4041
private array $ignoredDomains = [];
@@ -243,4 +244,16 @@ public function setIndexName(string $indexName): ProjectSettings
243244

244245
return $this;
245246
}
247+
248+
public function isAutomaticMenu(): bool
249+
{
250+
return $this->automaticMenu;
251+
}
252+
253+
public function setAutomaticMenu(bool $automaticMenu): ProjectSettings
254+
{
255+
$this->automaticMenu = $automaticMenu;
256+
257+
return $this;
258+
}
246259
}

0 commit comments

Comments
 (0)