diff --git a/lib/sphinx/out/crud.fjson b/lib/sphinx/out/crud.fjson
new file mode 100644
index 000000000..8e8919ceb
--- /dev/null
+++ b/lib/sphinx/out/crud.fjson
@@ -0,0 +1 @@
+{"parents": [], "prev": {"link": "../dashboards/", "title": "Dashboards"}, "next": {"link": "../design/", "title": "Design"}, "title": "CRUD", "meta": null, "body": "\nCRUD
\nCRUD stands for: \u201ccreate, read, update, delete\u201d.
\nOr it might be: \u201cCrazy runner\u2019s utter delight\u201d (which would\nbe, of course, a warm spring morning).
\n\n", "metatags": "\n", "rellinks": [["genindex", "General Index", "I", "index"], ["design", "Design", "N", "next"], ["dashboards", "Dashboards", "P", "previous"]], "sourcename": "crud.rst.txt", "toc": "
\n", "display_toc": false, "page_source_suffix": ".rst", "current_page_name": "crud", "sidebars": ["about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html"], "customsidebar": null, "alabaster_version": "0.7.13"}
\ No newline at end of file
diff --git a/lib/sphinx/out/dashboards.fjson b/lib/sphinx/out/dashboards.fjson
new file mode 100644
index 000000000..879a32773
--- /dev/null
+++ b/lib/sphinx/out/dashboards.fjson
@@ -0,0 +1 @@
+{"parents": [], "prev": {"link": "../", "title": "JSON Generation Test"}, "next": {"link": "../crud/", "title": "CRUD"}, "title": "Dashboards", "meta": null, "body": "\nDashboards
\nA file about dashboards\u2026 probably the one in an admin area\u2026 but\nmaybe also the ones found in a car!
\n\n", "metatags": "\n", "rellinks": [["genindex", "General Index", "I", "index"], ["crud", "CRUD", "N", "next"], ["index", "JSON Generation Test", "P", "previous"]], "sourcename": "dashboards.rst.txt", "toc": "\n", "display_toc": false, "page_source_suffix": ".rst", "current_page_name": "dashboards", "sidebars": ["about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html"], "customsidebar": null, "alabaster_version": "0.7.13"}
\ No newline at end of file
diff --git a/lib/sphinx/out/design.fjson b/lib/sphinx/out/design.fjson
new file mode 100644
index 000000000..e77597e69
--- /dev/null
+++ b/lib/sphinx/out/design.fjson
@@ -0,0 +1 @@
+{"parents": [], "prev": {"link": "../crud/", "title": "CRUD"}, "next": {"link": "sub-page/", "title": "Design Sub-Page"}, "title": "Design", "meta": null, "body": "\nDesign
\nSomething that should not be left to most programmers\nto try to do.
\n\nSection 1
\nThe toctree below should affects the next/prev. The\nfirst entry is effectively ignored, as it was already\nincluded by the toctree in index.rst (which is parsed first).
\n\nSome subsection
\nThis is a subsection of the first section. That\u2019s all.
\n\n\nSome subsection
\nThis sub-section uses the same title as before to test that the tool\nnever generated two or more headings with the same ID.
\n\n\n\nSection 2
\nHowever, crud (which is ALSO included in the toctree in index.rst),\nWILL be read here, as the \u201ccrud\u201d in index.rst has not been read\nyet (design comes first). Also, design/sub-page WILL be considered.
\n\nSome subsection
\nThis sub-section also uses the same title as in the previous section\nto test that the tool never generated two or more headings with the same ID.
\n\n\n\n\n", "metatags": "\n", "rellinks": [["genindex", "General Index", "I", "index"], ["design/sub-page", "Design Sub-Page", "N", "next"], ["crud", "CRUD", "P", "previous"]], "sourcename": "design.rst.txt", "toc": "\n", "display_toc": true, "page_source_suffix": ".rst", "current_page_name": "design", "sidebars": ["about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html"], "customsidebar": null, "alabaster_version": "0.7.13"}
\ No newline at end of file
diff --git a/lib/sphinx/out/design/sub-page.fjson b/lib/sphinx/out/design/sub-page.fjson
new file mode 100644
index 000000000..cab8e0de9
--- /dev/null
+++ b/lib/sphinx/out/design/sub-page.fjson
@@ -0,0 +1 @@
+{"parents": [{"link": "../", "title": "Design"}], "prev": {"link": "../", "title": "Design"}, "next": {"link": "../../fields/", "title": "Fields"}, "title": "Design Sub-Page", "meta": {}, "body": "\nDesign Sub-Page
\nHi! I\u2019m a design sub-page, to determine if the next/previous\nfunctionality works correctly. Have a designy day!
\n\n", "metatags": "\n", "rellinks": [["genindex", "General Index", "I", "index"], ["fields", "Fields", "N", "next"], ["design", "Design", "P", "previous"]], "sourcename": "design/sub-page.rst.txt", "toc": "\n", "display_toc": false, "page_source_suffix": ".rst", "current_page_name": "design/sub-page", "sidebars": ["about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html"], "customsidebar": null, "alabaster_version": "0.7.13"}
\ No newline at end of file
diff --git a/lib/sphinx/out/fields.fjson b/lib/sphinx/out/fields.fjson
new file mode 100644
index 000000000..cad5cf3dd
--- /dev/null
+++ b/lib/sphinx/out/fields.fjson
@@ -0,0 +1 @@
+{"parents": [], "prev": {"link": "../design/sub-page/", "title": "Design Sub-Page"}, "next": null, "title": "Fields", "meta": null, "body": "\nFields
\nI love fields: big open prairies, grass fields, corn fields\u2026 really,\nany type of field, I\u2019m a fan.
\n\n", "metatags": "\n", "rellinks": [["genindex", "General Index", "I", "index"], ["design/sub-page", "Design Sub-Page", "P", "previous"]], "sourcename": "fields.rst.txt", "toc": "\n", "display_toc": false, "page_source_suffix": ".rst", "current_page_name": "fields", "sidebars": ["about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html"], "customsidebar": null, "alabaster_version": "0.7.13"}
\ No newline at end of file
diff --git a/lib/sphinx/out/index.fjson b/lib/sphinx/out/index.fjson
new file mode 100644
index 000000000..f62381904
--- /dev/null
+++ b/lib/sphinx/out/index.fjson
@@ -0,0 +1 @@
+{"parents": [], "prev": null, "next": {"link": "dashboards/", "title": "Dashboards"}, "title": "JSON Generation Test", "meta": null, "body": "\nJSON Generation Test
\nIn the toctree below, \u201cdesign\u201d also has a toctree, which affects\nhow these will be parsed.
\n\n\n", "metatags": "\n", "rellinks": [["genindex", "General Index", "I", "index"], ["dashboards", "Dashboards", "N", "next"]], "sourcename": "index.rst.txt", "toc": "\n", "display_toc": false, "page_source_suffix": ".rst", "current_page_name": "index", "sidebars": ["about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html"], "customsidebar": null, "alabaster_version": "0.7.13"}
\ No newline at end of file
diff --git a/lib/sphinx/out/orphan.fjson b/lib/sphinx/out/orphan.fjson
new file mode 100644
index 000000000..1039b1cfc
--- /dev/null
+++ b/lib/sphinx/out/orphan.fjson
@@ -0,0 +1,26 @@
+{
+ "parents": [],
+ "prev": null,
+ "next": null,
+ "title": "Orphan",
+ "meta": {},
+ "body": "\nOrphan
\nI\u2019m a little lonely, because nobody has included me in their toctree!
\n\n",
+ "metatags": "\n",
+ "rellinks": [
+ ["genindex", "General Index", "I", "index"]
+ ],
+ "sourcename": "orphan.rst.txt",
+ "toc": "\n",
+ "display_toc": false,
+ "page_source_suffix": ".rst",
+ "current_page_name": "orphan",
+ "sidebars": [
+ "about.html",
+ "navigation.html",
+ "relations.html",
+ "searchbox.html",
+ "donate.html"
+ ],
+ "customsidebar": null,
+ "alabaster_version": "0.7.13"
+}
diff --git a/lib/symfony-extension/config/renderer.php b/lib/symfony-extension/config/renderer.php
index 2f1d33f1f..e14522869 100644
--- a/lib/symfony-extension/config/renderer.php
+++ b/lib/symfony-extension/config/renderer.php
@@ -15,6 +15,7 @@
use SymfonyTools\GuidesExtension\NodeRenderer\CodeNodeRenderer;
use SymfonyTools\GuidesExtension\NodeRenderer\MenuEntryRenderer;
use SymfonyTools\GuidesExtension\Node\ExternalLinkNode;
+use SymfonyTools\GuidesExtension\Renderer\JsonRenderer;
use SymfonyTools\GuidesExtension\Twig\CodeExtension;
use SymfonyTools\GuidesExtension\Twig\UrlExtension;
use Twig\Extension\ExtensionInterface;
@@ -42,5 +43,9 @@
->set(SymfonyHighlighter::class)
->decorate(Highlighter::class)
+
+ ->set(JsonRenderer::class)
+ ->arg('$nodeRendererFactory', service('phpdoc.guides.noderenderer.factory.json'))
+ ->tag('phpdoc.renderer.typerenderer', ['format' => 'json', 'noderender_tag' => 'phpdoc.guides.noderenderer.html'])
;
};
diff --git a/lib/symfony-extension/config/services.php b/lib/symfony-extension/config/services.php
index 1a0c4b3c4..bbd992efb 100644
--- a/lib/symfony-extension/config/services.php
+++ b/lib/symfony-extension/config/services.php
@@ -23,7 +23,7 @@
->set(EventDispatcherInterface::class, EventDispatcher::class)
- ->set(BuildConfig::class)
+ ->set(BuildConfig::class)->public()
->set(DocBuilder::class)->public()
;
diff --git a/lib/symfony-extension/src/Build/BuildConfig.php b/lib/symfony-extension/src/Build/BuildConfig.php
index 337b60242..f84e92144 100644
--- a/lib/symfony-extension/src/Build/BuildConfig.php
+++ b/lib/symfony-extension/src/Build/BuildConfig.php
@@ -39,11 +39,21 @@ public function getSymfonyRepositoryUrl(): string
return str_replace('{symfonyVersion}', $this->getSymfonyVersion(), self::SYMFONY_REPOSITORY_URL);
}
- public function getFormat(): string
+ public function setOutputFormat(string $format): void
+ {
+ $this->format = $format;
+ }
+
+ public function getOutputFormat(): string
{
return $this->format;
}
+ public function getFormat(): string
+ {
+ return 'fjson' === $this->format ? 'html' : $this->format;
+ }
+
public function createProjectNode(): ProjectNode
{
return new ProjectNode('Symfony', $this->symfonyVersion);
diff --git a/lib/symfony-extension/src/DocBuilder.php b/lib/symfony-extension/src/DocBuilder.php
index d827f70fc..738a3647c 100644
--- a/lib/symfony-extension/src/DocBuilder.php
+++ b/lib/symfony-extension/src/DocBuilder.php
@@ -15,9 +15,11 @@
use phpDocumentor\Guides\Compiler\CompilerContext;
use phpDocumentor\Guides\Handlers\CompileDocumentsCommand;
use phpDocumentor\Guides\Handlers\ParseDirectoryCommand;
+use phpDocumentor\Guides\Handlers\RenderCommand;
use phpDocumentor\Guides\Handlers\RenderDocumentCommand;
use phpDocumentor\Guides\Nodes\DocumentNode;
use phpDocumentor\Guides\RenderContext;
+use phpDocumentor\Guides\Renderer\TypeRendererFactory;
use phpDocumentor\Guides\Twig\Theme\ThemeManager;
use SymfonyTools\GuidesExtension\Build\BuildConfig;
use SymfonyTools\GuidesExtension\Build\BuildEnvironment;
@@ -27,6 +29,7 @@ final class DocBuilder
{
public function __construct(
private CommandBus $commandBus,
+ private TypeRendererFactory $rendererFactory,
private ThemeManager $themeManager,
private BuildConfig $buildConfig,
) {
@@ -43,20 +46,15 @@ public function build(BuildEnvironment $buildEnvironment): void
$documents = $this->commandBus->handle(new CompileDocumentsCommand($documents, new CompilerContext($projectNode)));
- foreach ($documents as $document) {
- $this->commandBus->handle(new RenderDocumentCommand(
- $document,
- RenderContext::forDocument(
- $document,
- $documents,
- $buildEnvironment->getSourceFilesystem(),
- $buildEnvironment->getOutputFilesystem(),
- '/',
- 'html',
- $projectNode
- )
- ));
- }
+ $this->rendererFactory->getRenderSet($this->buildConfig->getOutputFormat())->render(
+ new RenderCommand(
+ $this->buildConfig->getFormat(),
+ $documents,
+ $buildEnvironment->getSourceFilesystem(),
+ $buildEnvironment->getOutputFilesystem(),
+ $projectNode
+ )
+ );
}
public function buildString(string $contents): string
diff --git a/lib/symfony-extension/src/Renderer/JsonRenderer.php b/lib/symfony-extension/src/Renderer/JsonRenderer.php
new file mode 100644
index 000000000..be2d8f853
--- /dev/null
+++ b/lib/symfony-extension/src/Renderer/JsonRenderer.php
@@ -0,0 +1,67 @@
+getProjectNode(),
+ $renderCommand->getDocumentArray(),
+ $renderCommand->getOrigin(),
+ $renderCommand->getDestination(),
+ $renderCommand->getDestinationPath(),
+ $renderCommand->getOutputFormat(),
+ )->withIterator($renderCommand->getDocumentIterator());
+
+ foreach ($projectRenderContext->getIterator() as $documentNode) {
+ $context = $projectRenderContext->withDocument($documentNode);
+ $html = implode(
+ "\n",
+ array_map(fn (Node $node): string => $this->nodeRendererFactory->get($node)->render($node, $context), $documentNode->getChildren())
+ );
+
+ $prevDocument = $context->getIterator()->previousNode();
+ $nextDocument = $context->getIterator()->nextNode();
+ $context->getDestination()->put(
+ $context->getDestinationPath().'/'.$context->getCurrentFileName().'.fjson',
+ json_encode([
+ 'parents' => [],
+ 'prev' => $prevDocument ? [
+ 'title' => $prevDocument->getTitle()?->toString() ?? '',
+ 'link' => substr($this->urlGenerator->createFileUrl($context, $prevDocument->getFilePath()), 0, -4).'html',
+ ] : null,
+ 'next' => $nextDocument ? [
+ 'title' => $nextDocument->getTitle()?->toString() ?? '',
+ 'link' => substr($this->urlGenerator->createFileUrl($context, $nextDocument->getFilePath()), 0, -4).'html',
+ ] : null,
+ 'title' => $documentNode->getTitle()?->toString() ?? '',
+ 'body' => $html,
+ ], \JSON_PRETTY_PRINT)
+ );
+ }
+ }
+}
diff --git a/lib/symfony-extension/tests/integration/JsonIntegrationTest.php b/lib/symfony-extension/tests/integration/JsonIntegrationTest.php
new file mode 100644
index 000000000..67b0bf899
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/JsonIntegrationTest.php
@@ -0,0 +1,190 @@
+get(BuildConfig::class)->setOutputFormat('json');
+
+ $buildEnvironment = new DynamicBuildEnvironment(new Local(__DIR__.'/fixtures/source/json'));
+ $kernel->get(DocBuilder::class)->build($buildEnvironment);
+
+ $actualFileData = json_decode($buildEnvironment->getOutputFilesystem()->read($filename.'.fjson'), true);
+ $this->assertSame($expectedData, array_intersect_key($actualFileData, $expectedData), sprintf('Invalid data in file "%s"', $filename));
+ foreach ($expectedData as $key => $expectedKeyData) {
+ $this->assertArrayHasKey($key, $actualFileData, sprintf('Missing key "%s" in file "%s"', $key, $filename));
+ }
+ }
+
+ public function getJsonTests()
+ {
+ yield 'index' => [
+ 'file' => 'index',
+ 'data' => [
+ 'parents' => [],
+ 'prev' => null,
+ 'next' => [
+ 'title' => 'Dashboards',
+ 'link' => 'dashboards.html',
+ ],
+ 'title' => 'JSON Generation Test',
+ ]
+ ];
+
+ yield 'dashboards' => [
+ 'file' => 'dashboards',
+ 'data' => [
+ 'parents' => [],
+ 'prev' => [
+ 'title' => 'JSON Generation Test',
+ 'link' => 'index.html',
+ ],
+ 'next' => [
+ 'title' => 'CRUD',
+ 'link' => 'crud.html',
+ ],
+ 'title' => 'Dashboards',
+ ]
+ ];
+
+ yield 'design' => [
+ 'file' => 'design',
+ 'data' => [
+ 'parents' => [],
+ 'prev' => [
+ 'title' => 'CRUD',
+ 'link' => 'crud.html',
+ ],
+ 'next' => [
+ 'title' => 'Design Sub-Page',
+ 'link' => 'design/sub-page.html',
+ ],
+ 'title' => 'Design',
+ 'toc_options' => [
+ 'maxDepth' => 2,
+ 'numVisibleItems' => 5,
+ 'size' => 'md'
+ ],
+ 'toc' => [
+ [
+ 'level' => 1,
+ 'url' => 'design.html#section-1',
+ 'page' => 'design',
+ 'fragment' => 'section-1',
+ 'title' => 'Section 1',
+ 'children' => [
+ [
+ 'level' => 2,
+ 'url' => 'design.html#some-subsection',
+ 'page' => 'design',
+ 'fragment' => 'some-subsection',
+ 'title' => 'Some subsection',
+ 'children' => [],
+ ],
+ [
+ 'level' => 2,
+ 'url' => 'design.html#some-subsection-1',
+ 'page' => 'design',
+ 'fragment' => 'some-subsection-1',
+ 'title' => 'Some subsection',
+ 'children' => [],
+ ],
+ ],
+ ],
+ [
+ 'level' => 1,
+ 'url' => 'design.html#section-2',
+ 'page' => 'design',
+ 'fragment' => 'section-2',
+ 'title' => 'Section 2',
+ 'children' => [
+ [
+ 'level' => 2,
+ 'url' => 'design.html#some-subsection-2',
+ 'page' => 'design',
+ 'fragment' => 'some-subsection-2',
+ 'title' => 'Some subsection',
+ 'children' => [],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ yield 'crud' => [
+ 'file' => 'crud',
+ 'data' => [
+ 'parents' => [],
+ 'prev' => [
+ 'title' => 'Dashboards',
+ 'link' => 'dashboard.html',
+ ],
+ 'next' => [
+ 'title' => 'Design',
+ 'link' => 'design.html',
+ ],
+ 'title' => 'CRUD',
+ ]
+ ];
+
+ yield 'design/sub-page' => [
+ 'file' => 'design/sub-page',
+ 'data' => [
+ 'parents' => [
+ [
+ 'title' => 'Design',
+ 'link' => '../design.html',
+ ],
+ ],
+ 'prev' => [
+ 'title' => 'Design',
+ 'link' => '../design.html',
+ ],
+ 'next' => [
+ 'title' => 'Fields',
+ 'link' => '../fields.html',
+ ],
+ 'title' => 'Design Sub-Page',
+ ]
+ ];
+
+ yield 'fields' => [
+ 'file' => 'fields',
+ 'data' => [
+ 'parents' => [],
+ 'prev' => [
+ 'title' => 'Design Sub-Page',
+ 'link' => 'design/sub-page.html',
+ ],
+ 'next' => null,
+ 'title' => 'Fields',
+ ]
+ ];
+
+ yield 'orphan' => [
+ 'file' => 'orphan',
+ 'data' => [
+ 'parents' => [],
+ 'prev' => null,
+ 'next' => null,
+ 'title' => 'Orphan',
+ ]
+ ];
+ }
+}
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/crud.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/crud.rst
new file mode 100644
index 000000000..b2712fe10
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/crud.rst
@@ -0,0 +1,7 @@
+CRUD
+====
+
+CRUD stands for: "create, read, update, delete".
+
+Or it might be: "Crazy runner's utter delight" (which would
+be, of course, a warm spring morning).
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/dashboards.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/dashboards.rst
new file mode 100644
index 000000000..c8cee47c3
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/dashboards.rst
@@ -0,0 +1,5 @@
+Dashboards
+==========
+
+A file about dashboards... probably the one in an admin area... but
+maybe also the ones found in a car!
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/design.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/design.rst
new file mode 100644
index 000000000..2711ac4ad
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/design.rst
@@ -0,0 +1,43 @@
+Design
+======
+
+Something that should not be left to most programmers
+to try to do.
+
+Section 1
+---------
+
+The toctree below should affects the next/prev. The
+first entry is effectively ignored, as it was already
+included by the toctree in index.rst (which is parsed first).
+
+Some subsection
+~~~~~~~~~~~~~~~
+
+This is a subsection of the first section. That's all.
+
+Some subsection
+~~~~~~~~~~~~~~~
+
+This sub-section uses the same title as before to test that the tool
+never generated two or more headings with the same ID.
+
+Section 2
+---------
+
+However, crud (which is ALSO included in the toctree in index.rst),
+WILL be read here, as the "crud" in index.rst has not been read
+yet (design comes first). Also, design/sub-page WILL be considered.
+
+Some subsection
+~~~~~~~~~~~~~~~
+
+This sub-section also uses the same title as in the previous section
+to test that the tool never generated two or more headings with the same ID.
+
+.. toctree::
+ :maxdepth: 1
+
+ dashboards
+ crud
+ design/sub-page
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/design/sub-page.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/design/sub-page.rst
new file mode 100644
index 000000000..192953eef
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/design/sub-page.rst
@@ -0,0 +1,5 @@
+Design Sub-Page
+===============
+
+Hi! I'm a design sub-page, to determine if the next/previous
+functionality works correctly. Have a designy day!
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/fields.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/fields.rst
new file mode 100644
index 000000000..7120aa905
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/fields.rst
@@ -0,0 +1,5 @@
+Fields
+======
+
+I love fields: big open prairies, grass fields, corn fields... really,
+any type of field, I'm a fan.
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/index.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/index.rst
new file mode 100644
index 000000000..3765d9d62
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/index.rst
@@ -0,0 +1,13 @@
+JSON Generation Test
+====================
+
+In the toctree below, "design" also has a toctree, which affects
+how these will be parsed.
+
+.. toctree::
+ :maxdepth: 1
+
+ dashboards
+ crud
+ design
+ fields
diff --git a/lib/symfony-extension/tests/integration/fixtures/source/json/orphan.rst b/lib/symfony-extension/tests/integration/fixtures/source/json/orphan.rst
new file mode 100644
index 000000000..a11d392ca
--- /dev/null
+++ b/lib/symfony-extension/tests/integration/fixtures/source/json/orphan.rst
@@ -0,0 +1,4 @@
+Orphan
+======
+
+I'm a little lonely, because nobody has included me in their toctree!