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": "
\n

CRUD\u00b6

\n

CRUD stands for: \u201ccreate, read, update, delete\u201d.

\n

Or 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": "
\n

Dashboards\u00b6

\n

A 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": "
\n

Design\u00b6

\n

Something that should not be left to most programmers\nto try to do.

\n
\n

Section 1\u00b6

\n

The 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
\n

Some subsection\u00b6

\n

This is a subsection of the first section. That\u2019s all.

\n
\n
\n

Some subsection\u00b6

\n

This 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
\n

Section 2\u00b6

\n

However, 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
\n

Some subsection\u00b6

\n

This 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": "
\n

Design Sub-Page\u00b6

\n

Hi! 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": "
\n

Fields\u00b6

\n

I 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": "
\n

JSON Generation Test\u00b6

\n

In the toctree below, \u201cdesign\u201d also has a toctree, which affects\nhow these will be parsed.

\n
\n\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": "
\n

Orphan\u00b6

\n

I\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!