From 8efaab74b4c098127b524f7b8148ed6561d64c42 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 17 Mar 2021 15:01:21 +0100 Subject: [PATCH 1/8] Fixed the generation of the "prev" option in JSON generator --- src/Generator/JsonGenerator.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Generator/JsonGenerator.php b/src/Generator/JsonGenerator.php index 029a614..773c516 100644 --- a/src/Generator/JsonGenerator.php +++ b/src/Generator/JsonGenerator.php @@ -145,11 +145,6 @@ private function guessPrev(string $parserFilename): ?array $meta = $this->getMetaEntry($parserFilename, true); $parentFile = $meta->getParent(); - // no prev if parent is an index - if ('index' === $parentFile) { - return null; - } - [$toc, $indexCurrentFile] = $this->getNextPrevInformation($parserFilename); // if current file is the first one of the chapter, prev is the direct parent From ee6407f17a1415390991171842f161e2dde545c6 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 17 Mar 2021 12:59:12 -0400 Subject: [PATCH 2/8] simplifying test code --- tests/IntegrationTest.php | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index a3a9c8e..8604c68 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -27,12 +27,6 @@ class IntegrationTest extends TestCase { - public function setUp(): void - { - $fs = new Filesystem(); - $fs->remove(__DIR__.'/../var'); - } - /** * @dataProvider integrationProvider */ @@ -72,16 +66,9 @@ public function testIntegration(string $folder) ); } - /* - * TODO - get this from the Builder when it is exposed - * https://github.com/doctrine/rst-parser/pull/97 - */ - $metas = new Metas(); - $cachedMetasLoader = new CachedMetasLoader(); - $cachedMetasLoader->loadCachedMetaEntries(__DIR__.'/_output', $metas); - + $metas = $builder->getMetas(); $jsonGenerator = new JsonGenerator($metas, $buildConfig); - $jsonGenerator->generateJson(new ProgressBar(new NullOutput())); + $jsonGenerator->generateJson(); foreach ($finder as $htmlFile) { $relativePath = $htmlFile->getRelativePathname(); @@ -287,8 +274,9 @@ private function createBuildConfig(string $sourceDir): BuildConfig return (new BuildConfig()) ->setSymfonyVersion('4.0') ->setContentDir($sourceDir) + ->disableBuildCache() ->setOutputDir(__DIR__.'/_output') - ->setCacheDir(__DIR__.'/_cache'); + ; } private function createIndenter(): Indenter From 2851650c5661e2e6420c3534f3641a7127b6a9b4 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 18 Mar 2021 08:33:53 -0400 Subject: [PATCH 3/8] Returning JSON from JsonGenerator - can be used for tests --- src/Generator/JsonGenerator.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Generator/JsonGenerator.php b/src/Generator/JsonGenerator.php index 773c516..6678d76 100644 --- a/src/Generator/JsonGenerator.php +++ b/src/Generator/JsonGenerator.php @@ -37,13 +37,19 @@ public function __construct(Metas $metas, BuildConfig $buildConfig) $this->buildConfig = $buildConfig; } - public function generateJson() + /** + * Returns an array of each JSON file string, keyed by the input filename + * + * @return string[] + */ + public function generateJson(): array { $fs = new Filesystem(); $progressBar = new ProgressBar($this->output ?: new NullOutput()); $progressBar->setMaxSteps(\count($this->metas->getAll())); + $fJsonFiles = []; foreach ($this->metas->getAll() as $filename => $metaEntry) { $parserFilename = $filename; $jsonFilename = $this->buildConfig->getOutputDir().'/'.$filename.'.fjson'; @@ -67,11 +73,14 @@ public function generateJson() $jsonFilename, json_encode($data, JSON_PRETTY_PRINT) ); + $fJsonFiles[$filename] = json_encode($data, JSON_PRETTY_PRINT); $progressBar->advance(); } $progressBar->finish(); + + return $fJsonFiles; } public function setOutput(SymfonyStyle $output) From 632ce06cc895d27a7a4b988d28dbbd97036f6238 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 18 Mar 2021 09:15:33 -0400 Subject: [PATCH 4/8] Simplfying IntegrationTest --- composer.json | 5 ++++ src/BuildResult.php | 26 +++++++++++++++++- src/DocBuilder.php | 9 ++++--- tests/AbstractIntegrationTest.php | 30 +++++++++++++++++++++ tests/IntegrationTest.php | 44 +++++-------------------------- 5 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 tests/AbstractIntegrationTest.php diff --git a/composer.json b/composer.json index b40331d..e8b4fd4 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,11 @@ "SymfonyDocsBuilder\\": "src" } }, + "autoload-dev": { + "psr-4": { + "SymfonyDocsBuilder\\Tests\\": "tests" + } + }, "require": { "ext-json": "*", "ext-curl": "*", diff --git a/src/BuildResult.php b/src/BuildResult.php index 2d4dccf..48c85f9 100644 --- a/src/BuildResult.php +++ b/src/BuildResult.php @@ -3,6 +3,7 @@ namespace SymfonyDocsBuilder; use Doctrine\RST\Builder; +use Doctrine\RST\Meta\Metas; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Filesystem\Filesystem; use SymfonyDocsBuilder\BuildConfig; @@ -15,10 +16,13 @@ class BuildResult { private $errors; + private $metas; + private $jsonResults = []; - public function __construct(array $errors) + public function __construct(array $errors, Metas $metas) { $this->errors = $errors; + $this->metas = $metas; } public function appendError(string $errorMessage): void @@ -40,4 +44,24 @@ public function getErrors(): array { return $this->errors; } + + public function getMetas(): Metas + { + return $this->metas; + } + + /** + * Returns the JSON data generated for each file, keyed by the source filename. + * + * @return string[] + */ + public function getJsonResults(): array + { + return $this->jsonResults; + } + + public function setJsonResults(array $jsonResults): void + { + $this->jsonResults = $jsonResults; + } } diff --git a/src/DocBuilder.php b/src/DocBuilder.php index 83eb81e..2608a4b 100644 --- a/src/DocBuilder.php +++ b/src/DocBuilder.php @@ -25,7 +25,10 @@ public function build(BuildConfig $config): BuildResult $builder = new Builder(KernelFactory::createKernel($config)); $builder->build($config->getContentDir(), $config->getOutputDir()); - $buildResult = new BuildResult($builder->getErrorManager()->getErrors()); + $buildResult = new BuildResult( + $builder->getErrorManager()->getErrors(), + $builder->getMetas() + ); $missingFilesChecker = new MissingFilesChecker($config); $missingFiles = $missingFilesChecker->getMissingFiles(); @@ -38,13 +41,13 @@ public function build(BuildConfig $config): BuildResult $filesystem->dumpFile($config->getOutputDir().'/build_errors.txt', implode("\n", $buildResult->getErrors())); } - $metas = $builder->getMetas(); + $metas = $buildResult->getMetas(); if ($config->getSubdirectoryToBuild()) { $htmlForPdfGenerator = new HtmlForPdfGenerator($metas, $config); $htmlForPdfGenerator->generateHtmlForPdf(); } else { $jsonGenerator = new JsonGenerator($metas, $config); - $jsonGenerator->generateJson(); + $buildResult->setJsonResults($jsonGenerator->generateJson()); } return $buildResult; diff --git a/tests/AbstractIntegrationTest.php b/tests/AbstractIntegrationTest.php new file mode 100644 index 0000000..f390054 --- /dev/null +++ b/tests/AbstractIntegrationTest.php @@ -0,0 +1,30 @@ +setSymfonyVersion('4.0') + ->setContentDir($sourceDir) + ->disableBuildCache() + ->setOutputDir(__DIR__.'/_output') + ; + } + + /** + * @after + */ + public function cleanUpOutput() + { + $filesystem = new Filesystem(); + $filesystem->remove(__DIR__.'/_output'); + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 8604c68..d0e892f 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -9,43 +9,25 @@ namespace SymfonyDocsBuilder\Tests; -use Doctrine\RST\Builder; use Doctrine\RST\Configuration; -use Doctrine\RST\Meta\CachedMetasLoader; -use Doctrine\RST\Meta\Metas; use Doctrine\RST\Parser; use Gajus\Dindent\Indenter; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Helper\ProgressBar; -use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\DomCrawler\Crawler; -use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; -use SymfonyDocsBuilder\BuildConfig; +use SymfonyDocsBuilder\DocBuilder; use SymfonyDocsBuilder\Generator\JsonGenerator; use SymfonyDocsBuilder\KernelFactory; -class IntegrationTest extends TestCase +class IntegrationTest extends AbstractIntegrationTest { /** * @dataProvider integrationProvider */ public function testIntegration(string $folder) { - $fs = new Filesystem(); - $fs->remove([__DIR__.'/_output', __DIR__.'/_cache']); - $fs->mkdir([__DIR__.'/_output', __DIR__.'/_cache']); - $buildConfig = $this->createBuildConfig(sprintf('%s/fixtures/source/%s', __DIR__, $folder)); - - $builder = new Builder( - KernelFactory::createKernel($buildConfig) - ); - - $builder->build( - sprintf('%s/fixtures/source/%s', __DIR__, $folder), - __DIR__.'/_output' - ); + $builder = new DocBuilder(); + $buildResult = $builder->build($buildConfig); $finder = new Finder(); $finder->in(sprintf('%s/fixtures/expected/%s', __DIR__, $folder)) @@ -55,7 +37,7 @@ public function testIntegration(string $folder) $indenter = $this->createIndenter(); foreach ($finder as $expectedFile) { $relativePath = $expectedFile->getRelativePathname(); - $actualFilename = __DIR__.'/_output/'.$relativePath; + $actualFilename = $buildConfig->getOutputDir().'/'.$relativePath; $this->assertFileExists($actualFilename); $this->assertSame( @@ -66,13 +48,9 @@ public function testIntegration(string $folder) ); } - $metas = $builder->getMetas(); - $jsonGenerator = new JsonGenerator($metas, $buildConfig); - $jsonGenerator->generateJson(); - foreach ($finder as $htmlFile) { $relativePath = $htmlFile->getRelativePathname(); - $actualFilename = __DIR__.'/_output/'.str_replace('.html', '.fjson', $relativePath); + $actualFilename = $buildConfig->getOutputDir().'/'.str_replace('.html', '.fjson', $relativePath); $this->assertFileExists($actualFilename); $jsonData = json_decode(file_get_contents($actualFilename), true); @@ -269,16 +247,6 @@ public function parserUnitBlockProvider() ]; } - private function createBuildConfig(string $sourceDir): BuildConfig - { - return (new BuildConfig()) - ->setSymfonyVersion('4.0') - ->setContentDir($sourceDir) - ->disableBuildCache() - ->setOutputDir(__DIR__.'/_output') - ; - } - private function createIndenter(): Indenter { $indenter = new Indenter(); From c02be8a3f4cec3a5216cae12b74f7317081fa073 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 18 Mar 2021 09:44:33 -0400 Subject: [PATCH 5/8] Adding a (nearly passing) fjson integration test --- src/BuildResult.php | 2 +- src/Generator/JsonGenerator.php | 2 +- tests/AbstractIntegrationTest.php | 12 +-- tests/JsonIntegrationTest.php | 93 +++++++++++++++++++++++ tests/fixtures/source/json/crud.rst | 7 ++ tests/fixtures/source/json/dashboards.rst | 5 ++ tests/fixtures/source/json/design.rst | 5 ++ tests/fixtures/source/json/index.rst | 9 +++ 8 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 tests/JsonIntegrationTest.php create mode 100644 tests/fixtures/source/json/crud.rst create mode 100644 tests/fixtures/source/json/dashboards.rst create mode 100644 tests/fixtures/source/json/design.rst create mode 100644 tests/fixtures/source/json/index.rst diff --git a/src/BuildResult.php b/src/BuildResult.php index 48c85f9..4e2610c 100644 --- a/src/BuildResult.php +++ b/src/BuildResult.php @@ -53,7 +53,7 @@ public function getMetas(): Metas /** * Returns the JSON data generated for each file, keyed by the source filename. * - * @return string[] + * @return array[] */ public function getJsonResults(): array { diff --git a/src/Generator/JsonGenerator.php b/src/Generator/JsonGenerator.php index 6678d76..99ab79a 100644 --- a/src/Generator/JsonGenerator.php +++ b/src/Generator/JsonGenerator.php @@ -73,7 +73,7 @@ public function generateJson(): array $jsonFilename, json_encode($data, JSON_PRETTY_PRINT) ); - $fJsonFiles[$filename] = json_encode($data, JSON_PRETTY_PRINT); + $fJsonFiles[$filename] = $data; $progressBar->advance(); } diff --git a/tests/AbstractIntegrationTest.php b/tests/AbstractIntegrationTest.php index f390054..d6719da 100644 --- a/tests/AbstractIntegrationTest.php +++ b/tests/AbstractIntegrationTest.php @@ -11,6 +11,9 @@ class AbstractIntegrationTest extends TestCase { protected function createBuildConfig(string $sourceDir): BuildConfig { + $filesystem = new Filesystem(); + $filesystem->remove(__DIR__.'/_output'); + return (new BuildConfig()) ->setSymfonyVersion('4.0') ->setContentDir($sourceDir) @@ -18,13 +21,4 @@ protected function createBuildConfig(string $sourceDir): BuildConfig ->setOutputDir(__DIR__.'/_output') ; } - - /** - * @after - */ - public function cleanUpOutput() - { - $filesystem = new Filesystem(); - $filesystem->remove(__DIR__.'/_output'); - } } diff --git a/tests/JsonIntegrationTest.php b/tests/JsonIntegrationTest.php new file mode 100644 index 0000000..74a6868 --- /dev/null +++ b/tests/JsonIntegrationTest.php @@ -0,0 +1,93 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SymfonyDocsBuilder\Tests; + +use SymfonyDocsBuilder\DocBuilder; + +class JsonIntegrationTest extends AbstractIntegrationTest +{ + /** + * @dataProvider getJsonTests + */ + public function testJsonGeneration(string $filename, array $expectedData) + { + $buildConfig = $this->createBuildConfig(__DIR__ . '/fixtures/source/json'); + $builder = new DocBuilder(); + $buildResult = $builder->build($buildConfig); + $fJsons = $buildResult->getJsonResults(); + + $actualFileData = $fJsons[$filename]; + foreach ($expectedData as $key => $expectedKeyData) { + $this->assertArrayHasKey($key, $actualFileData, sprintf('Missing key "%s" in file "%s"', $key, $filename)); + $this->assertSame($expectedData[$key], $actualFileData[$key], sprintf('Invalid data for key "%s" in file "%s"', $key, $filename)); + } + } + + public function getJsonTests() + { + yield 'index' => [ + 'file' => 'index', + 'data' => [ + //'parents' => [], + 'prev' => null, + 'next' => [ + 'link' => 'dashboards/', + 'title' => 'Dashboards' + ], + '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 'crud' => [ + 'file' => 'crud', + 'data' => [ + //'parents' => [], + 'prev' => [ + 'title' => 'Dashboards', + 'link' => 'dashboards.html', + ], + 'next' => [ + 'title' => 'Design', + 'link' => 'design.html', + ], + 'title' => 'CRUD', + ] + ]; + + yield 'design' => [ + 'file' => 'design', + 'data' => [ + //'parents' => [], + 'prev' => [ + 'title' => 'CRUD', + 'link' => 'crud.html', + ], + 'next' => null, + 'title' => 'Design', + ] + ]; + } +} diff --git a/tests/fixtures/source/json/crud.rst b/tests/fixtures/source/json/crud.rst new file mode 100644 index 0000000..b2712fe --- /dev/null +++ b/tests/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/tests/fixtures/source/json/dashboards.rst b/tests/fixtures/source/json/dashboards.rst new file mode 100644 index 0000000..c8cee47 --- /dev/null +++ b/tests/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/tests/fixtures/source/json/design.rst b/tests/fixtures/source/json/design.rst new file mode 100644 index 0000000..0eb46bb --- /dev/null +++ b/tests/fixtures/source/json/design.rst @@ -0,0 +1,5 @@ +Design +====== + +Something that should not be left to most programmers +to try to do. diff --git a/tests/fixtures/source/json/index.rst b/tests/fixtures/source/json/index.rst new file mode 100644 index 0000000..04ff4e6 --- /dev/null +++ b/tests/fixtures/source/json/index.rst @@ -0,0 +1,9 @@ +JSON Generation Test +==================== + +.. toctree:: + :maxdepth: 1 + + dashboards + crud + design From caa5c4aa71e6373634bc4fdf3c98e0e449bbb959 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 18 Mar 2021 16:36:43 -0400 Subject: [PATCH 6/8] Properly fixing next/prev/parents in the JSON files This was researched specifically using Sphinx behavior --- src/BuildResult.php | 20 +- src/DocBuilder.php | 4 +- src/Generator/JsonGenerator.php | 211 +++++++++++------- tests/JsonIntegrationTest.php | 74 ++++-- tests/fixtures/source/json/design.rst | 15 ++ .../fixtures/source/json/design/sub-page.rst | 5 + tests/fixtures/source/json/fields.rst | 5 + tests/fixtures/source/json/index.rst | 6 +- tests/fixtures/source/json/orphan.rst | 4 + 9 files changed, 240 insertions(+), 104 deletions(-) create mode 100644 tests/fixtures/source/json/design/sub-page.rst create mode 100644 tests/fixtures/source/json/fields.rst create mode 100644 tests/fixtures/source/json/orphan.rst diff --git a/src/BuildResult.php b/src/BuildResult.php index 4e2610c..ca24423 100644 --- a/src/BuildResult.php +++ b/src/BuildResult.php @@ -16,13 +16,13 @@ class BuildResult { private $errors; - private $metas; + private $builder; private $jsonResults = []; - public function __construct(array $errors, Metas $metas) + public function __construct(array $errors, Builder $builder) { $this->errors = $errors; - $this->metas = $metas; + $this->builder = $builder; } public function appendError(string $errorMessage): void @@ -47,7 +47,19 @@ public function getErrors(): array public function getMetas(): Metas { - return $this->metas; + return $this->builder->getMetas(); + } + + /** + * Returns the "master document": the first file whose toctree is parsed. + * + * Unless customized, this is "index" (i.e. file index.rst). + * + * @return string + */ + public function getMasterDocumentFilename(): string + { + return $this->builder->getIndexName(); } /** diff --git a/src/DocBuilder.php b/src/DocBuilder.php index 2608a4b..703be83 100644 --- a/src/DocBuilder.php +++ b/src/DocBuilder.php @@ -27,7 +27,7 @@ public function build(BuildConfig $config): BuildResult $buildResult = new BuildResult( $builder->getErrorManager()->getErrors(), - $builder->getMetas() + $builder ); $missingFilesChecker = new MissingFilesChecker($config); @@ -47,7 +47,7 @@ public function build(BuildConfig $config): BuildResult $htmlForPdfGenerator->generateHtmlForPdf(); } else { $jsonGenerator = new JsonGenerator($metas, $config); - $buildResult->setJsonResults($jsonGenerator->generateJson()); + $buildResult->setJsonResults($jsonGenerator->generateJson($builder->getIndexName())); } return $buildResult; diff --git a/src/Generator/JsonGenerator.php b/src/Generator/JsonGenerator.php index 99ab79a..73dc60b 100644 --- a/src/Generator/JsonGenerator.php +++ b/src/Generator/JsonGenerator.php @@ -40,15 +40,26 @@ public function __construct(Metas $metas, BuildConfig $buildConfig) /** * Returns an array of each JSON file string, keyed by the input filename * + * @param string $masterDocument The file whose toctree should be read first * @return string[] */ - public function generateJson(): array + public function generateJson(string $masterDocument = 'index'): array { $fs = new Filesystem(); $progressBar = new ProgressBar($this->output ?: new NullOutput()); $progressBar->setMaxSteps(\count($this->metas->getAll())); + $walkedFiles = []; + $tocTreeHierarchy = $this->walkTocTreeAndReturnHierarchy( + $masterDocument, + $walkedFiles + ); + // for purposes of prev/next/parents, the "master document" + // behaves as if it's the first item in the toctree + $tocTreeHierarchy = [$masterDocument => []] + $tocTreeHierarchy; + $flattenedTocTree = $this->flattenTocTree($tocTreeHierarchy); + $fJsonFiles = []; foreach ($this->metas->getAll() as $filename => $metaEntry) { $parserFilename = $filename; @@ -56,15 +67,19 @@ public function generateJson(): array $crawler = new Crawler(file_get_contents($this->buildConfig->getOutputDir().'/'.$filename.'.html')); + $next = $this->determineNext($parserFilename, $flattenedTocTree, $masterDocument); + $prev = $this->determinePrev($parserFilename, $flattenedTocTree); $data = [ 'title' => $metaEntry->getTitle(), + 'parents' => $this->determineParents($parserFilename, $tocTreeHierarchy) ?: [], 'current_page_name' => $parserFilename, 'toc' => $this->generateToc($metaEntry, current($metaEntry->getTitles())[1]), - 'next' => $this->guessNext($parserFilename), - 'prev' => $this->guessPrev($parserFilename), + 'next' => $next, + 'prev' => $prev, 'rellinks' => [ - $this->guessNext($parserFilename), - $this->guessPrev($parserFilename), + // probably these shouldn't be straight copies of next/prev + $next, + $prev, ], 'body' => $crawler->filter('body')->html(), ]; @@ -109,128 +124,162 @@ private function generateToc(MetaEntry $metaEntry, ?array $titles): array return $tocTree; } - private function guessNext(string $parserFilename): ?array + private function determineNext(string $parserFilename, array $flattenedTocTree): ?array { - $meta = $this->getMetaEntry($parserFilename, true); - - $parentFile = $meta->getParent(); + $foundCurrentFile = false; + $nextFileName = null; - // if current file is an index, next is the first chapter - if ('index' === $parentFile && 1 === \count($tocs = $meta->getTocs()) && \count($tocs[0]) > 0) { - $firstChapterMeta = $this->getMetaEntry($tocs[0][0]); + foreach ($flattenedTocTree as $filename) { + if ($foundCurrentFile) { + $nextFileName = $filename; - if (null === $firstChapterMeta) { - return null; + break; } - return [ - 'title' => $firstChapterMeta->getTitle(), - 'link' => $firstChapterMeta->getUrl(), - ]; + if ($filename === $parserFilename) { + $foundCurrentFile = true; + } } - [$toc, $indexCurrentFile] = $this->getNextPrevInformation($parserFilename); - - if (!isset($toc[$indexCurrentFile + 1])) { + // no next document found! + if (null === $nextFileName) { return null; } - $nextFileName = $toc[$indexCurrentFile + 1]; - - $nextMeta = $this->getMetaEntry($nextFileName); - - if (null === $nextMeta) { - return null; - } + $meta = $this->getMetaEntry($nextFileName); return [ - 'title' => $nextMeta->getTitle(), - 'link' => $nextMeta->getUrl(), + 'title' => $meta->getTitle(), + 'link' => $meta->getUrl(), ]; } - private function guessPrev(string $parserFilename): ?array + private function determinePrev(string $parserFilename, array $flattenedTocTree): ?array { - $meta = $this->getMetaEntry($parserFilename, true); - $parentFile = $meta->getParent(); - - [$toc, $indexCurrentFile] = $this->getNextPrevInformation($parserFilename); - - // if current file is the first one of the chapter, prev is the direct parent - if (0 === $indexCurrentFile) { - $parentMeta = $this->getMetaEntry($parentFile); - - if (null === $parentMeta) { - return null; + $previousFileName = null; + $foundCurrentFile = false; + foreach ($flattenedTocTree as $filename) { + if ($filename === $parserFilename) { + $foundCurrentFile = true; + break; } - return [ - 'title' => $parentMeta->getTitle(), - 'link' => $parentMeta->getUrl(), - ]; + $previousFileName = $filename; } - if (!isset($toc[$indexCurrentFile - 1])) { + // no previous document found! + if (null === $previousFileName || !$foundCurrentFile) { return null; } - $prevFileName = $toc[$indexCurrentFile - 1]; - - $prevMeta = $this->getMetaEntry($prevFileName); - - if (null === $prevMeta) { - return null; - } + $meta = $this->getMetaEntry($previousFileName); return [ - 'title' => $prevMeta->getTitle(), - 'link' => $prevMeta->getUrl(), + 'title' => $meta->getTitle(), + 'link' => $meta->getUrl(), ]; } - private function getNextPrevInformation(string $parserFilename): ?array + private function getMetaEntry(string $parserFilename, bool $throwOnMissing = false): ?MetaEntry { - $meta = $this->getMetaEntry($parserFilename, true); - $parentFile = $meta->getParent(); + $metaEntry = $this->metas->get($parserFilename); - if (!$parentFile) { - return [null, null]; - } + // this is possible if there are invalid references + if (null === $metaEntry) { + $message = sprintf('Could not find MetaEntry for file "%s"', $parserFilename); - $metaParent = $this->getMetaEntry($parentFile); + if ($throwOnMissing) { + throw new \Exception($message); + } - if (null === $metaParent || !$metaParent->getTocs() || 1 !== \count($metaParent->getTocs())) { - return [null, null]; + if ($this->output) { + $this->output->note($message); + } } - $toc = current($metaParent->getTocs()); + return $metaEntry; + } - if (\count($toc) < 2 || !isset(array_flip($toc)[$parserFilename])) { - return [null, null]; - } + /** + * Creates a hierarchy of documents by crawling the toctree's + * + * This looks at the + * toc tree of the master document, following the first entry + * like a link, then repeating the process on the next document's + * toc tree (if it has one). When it hits a dead end, it would + * go back to the master document and click the second link. + * But, it skips any links that have been seen before. This + * is the logic behind how the prev/next parent information is created. + * + * Example result: + * [ + * 'dashboards' => [], + * 'design' => [ + * 'crud' => [], + * 'design/sub-page' => [], + * ], + * 'fields' => [] + * ] + * + * See the JsonIntegrationTest for a test case. + */ + private function walkTocTreeAndReturnHierarchy(string $filename, array &$walkedFiles): array + { + $hierarchy = []; + foreach ($this->getMetaEntry($filename)->getTocs() as $toc) { + foreach ($toc as $tocFilename) { + // only walk a file one time, the first time you see it + if (in_array($tocFilename, $walkedFiles, true)) { + continue; + } - $indexCurrentFile = array_flip($toc)[$parserFilename]; + $walkedFiles[] = $tocFilename; + + $hierarchy[$tocFilename] = $this->walkTocTreeAndReturnHierarchy($tocFilename, $walkedFiles); + } + } - return [$toc, $indexCurrentFile]; + return $hierarchy; } - private function getMetaEntry(string $parserFilename, bool $throwOnMissing = false): ?MetaEntry + /** + * Takes the structure from walkTocTreeAndReturnHierarchy() and flattens it. + * + * For example: + * + * [dashboards, design, crud, design/sub-page, fields] + * + * @return string[] + */ + private function flattenTocTree(array $tocTreeHierarchy): array { - $metaEntry = $this->metas->get($parserFilename); + $files = []; - // this is possible if there are invalid references - if (null === $metaEntry) { - $message = sprintf('Could not find MetaEntry for file "%s"', $parserFilename); + foreach ($tocTreeHierarchy as $filename => $tocTree) { + $files[] = $filename; - if ($throwOnMissing) { - throw new \Exception($message); + $files = array_merge($files, $this->flattenTocTree($tocTree)); + } + + return $files; + } + + private function determineParents(string $parserFilename, array $tocTreeHierarchy, array $parents = []): ?array + { + foreach ($tocTreeHierarchy as $filename => $tocTree) { + if ($filename === $parserFilename) { + return $parents; } - if ($this->output) { - $this->output->note($message); + $subParents = $this->determineParents($parserFilename, $tocTree, $parents + [$filename]); + + if (null !== $subParents) { + // the item WAS found and the parents were returned + return $subParents; } } - return $metaEntry; + // item was not found + return null; } } diff --git a/tests/JsonIntegrationTest.php b/tests/JsonIntegrationTest.php index 74a6868..c9217c1 100644 --- a/tests/JsonIntegrationTest.php +++ b/tests/JsonIntegrationTest.php @@ -35,11 +35,11 @@ public function getJsonTests() yield 'index' => [ 'file' => 'index', 'data' => [ - //'parents' => [], + 'parents' => [], 'prev' => null, 'next' => [ - 'link' => 'dashboards/', - 'title' => 'Dashboards' + 'title' => 'Dashboards', + 'link' => 'dashboards.html', ], 'title' => 'JSON Generation Test', ] @@ -48,46 +48,88 @@ public function getJsonTests() yield 'dashboards' => [ 'file' => 'dashboards', 'data' => [ - //'parents' => [], + 'parents' => [], 'prev' => [ 'title' => 'JSON Generation Test', 'link' => 'index.html', ], + 'next' => [ + 'title' => 'Design', + 'link' => 'design.html', + ], + 'title' => 'Dashboards', + ] + ]; + + yield 'design' => [ + 'file' => 'design', + 'data' => [ + 'parents' => [], + 'prev' => [ + 'title' => 'Dashboards', + 'link' => 'dashboards.html', + ], 'next' => [ 'title' => 'CRUD', 'link' => 'crud.html', ], - 'title' => 'Dashboards', + 'title' => 'Design', ] ]; yield 'crud' => [ 'file' => 'crud', 'data' => [ - //'parents' => [], + 'parents' => ['design'], 'prev' => [ - 'title' => 'Dashboards', - 'link' => 'dashboards.html', - ], - 'next' => [ 'title' => 'Design', 'link' => 'design.html', ], + 'next' => [ + 'title' => 'Design Sub-Page', + 'link' => 'design/sub-page.html', + ], 'title' => 'CRUD', ] ]; - yield 'design' => [ - 'file' => 'design', + yield 'design/sub-page' => [ + 'file' => 'design/sub-page', + 'data' => [ + 'parents' => ['design'], + 'prev' => [ + 'title' => 'CRUD', + 'link' => 'crud.html', + ], + 'next' => [ + 'title' => 'Fields', + 'link' => 'fields.html', + ], + 'title' => 'Design Sub-Page', + ] + ]; + + yield 'fields' => [ + 'file' => 'fields', 'data' => [ - //'parents' => [], + 'parents' => [], 'prev' => [ - 'title' => 'CRUD', - 'link' => 'crud.html', + 'title' => 'Design Sub-Page', + 'link' => 'design/sub-page.html', ], 'next' => null, - 'title' => 'Design', + 'title' => 'Fields', ] ]; + + yield 'orphan' => [ + 'file' => 'orphan', + 'data' => [ + 'parents' => [], + 'prev' => null, + 'next' => null, + 'title' => 'Orphan', + ] + ]; } } diff --git a/tests/fixtures/source/json/design.rst b/tests/fixtures/source/json/design.rst index 0eb46bb..fd8feb9 100644 --- a/tests/fixtures/source/json/design.rst +++ b/tests/fixtures/source/json/design.rst @@ -3,3 +3,18 @@ Design Something that should not be left to most programmers to try to do. + +The toctree below should affects the next/prev. The +first entry is effectively ignored, as it wasa already +included by the toctree in index.rst (which is parsed first). + +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. + +.. toctree:: + :maxdepth: 1 + + dashboards + crud + design/sub-page diff --git a/tests/fixtures/source/json/design/sub-page.rst b/tests/fixtures/source/json/design/sub-page.rst new file mode 100644 index 0000000..192953e --- /dev/null +++ b/tests/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/tests/fixtures/source/json/fields.rst b/tests/fixtures/source/json/fields.rst new file mode 100644 index 0000000..bfb069f --- /dev/null +++ b/tests/fixtures/source/json/fields.rst @@ -0,0 +1,5 @@ +Fields +====== + +I love fields: big open prairies, grass fields, corn fields... reall, +any type of field, I'm a fan. diff --git a/tests/fixtures/source/json/index.rst b/tests/fixtures/source/json/index.rst index 04ff4e6..03c3f6f 100644 --- a/tests/fixtures/source/json/index.rst +++ b/tests/fixtures/source/json/index.rst @@ -1,9 +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 + crud + fields diff --git a/tests/fixtures/source/json/orphan.rst b/tests/fixtures/source/json/orphan.rst new file mode 100644 index 0000000..a11d392 --- /dev/null +++ b/tests/fixtures/source/json/orphan.rst @@ -0,0 +1,4 @@ +Orphan +====== + +I'm a little lonely, because nobody has included me in their toctree! From f518593bd732b7040f14131838ac3095880b712e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 18 Mar 2021 16:53:56 -0400 Subject: [PATCH 7/8] updating dependencies and fixing last tests --- composer.lock | 11 ++++++----- tests/IntegrationTest.php | 3 +-- tests/fixtures/expected/build-pdf/book.html | 4 ++-- tests/fixtures/expected/main/index.html | 6 +++--- tests/fixtures/expected/toctree/index.html | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/composer.lock b/composer.lock index 2081a58..14cabce 100644 --- a/composer.lock +++ b/composer.lock @@ -106,12 +106,12 @@ "source": { "type": "git", "url": "https://github.com/doctrine/rst-parser.git", - "reference": "7907706178f02198a423a907d5aa83dc2a356b70" + "reference": "9f9887b282307c7cc8ff92a2c98aec39346696d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/rst-parser/zipball/7907706178f02198a423a907d5aa83dc2a356b70", - "reference": "7907706178f02198a423a907d5aa83dc2a356b70", + "url": "https://api.github.com/repos/doctrine/rst-parser/zipball/9f9887b282307c7cc8ff92a2c98aec39346696d9", + "reference": "9f9887b282307c7cc8ff92a2c98aec39346696d9", "shasum": "" }, "require": { @@ -130,6 +130,7 @@ "phpstan/phpstan-strict-rules": "^0.12", "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -164,9 +165,9 @@ ], "support": { "issues": "https://github.com/doctrine/rst-parser/issues", - "source": "https://github.com/doctrine/rst-parser/tree/0.3.x" + "source": "https://github.com/doctrine/rst-parser/tree/0.3.1" }, - "time": "2021-03-08T20:11:41+00:00" + "time": "2021-03-15T18:19:47+00:00" }, { "name": "psr/container", diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index d0e892f..79cdf69 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -15,7 +15,6 @@ use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\Finder\Finder; use SymfonyDocsBuilder\DocBuilder; -use SymfonyDocsBuilder\Generator\JsonGenerator; use SymfonyDocsBuilder\KernelFactory; class IntegrationTest extends AbstractIntegrationTest @@ -27,7 +26,7 @@ public function testIntegration(string $folder) { $buildConfig = $this->createBuildConfig(sprintf('%s/fixtures/source/%s', __DIR__, $folder)); $builder = new DocBuilder(); - $buildResult = $builder->build($buildConfig); + $builder->build($buildConfig); $finder = new Finder(); $finder->in(sprintf('%s/fixtures/expected/%s', __DIR__, $folder)) diff --git a/tests/fixtures/expected/build-pdf/book.html b/tests/fixtures/expected/build-pdf/book.html index a4e1d46..deb53f8 100644 --- a/tests/fixtures/expected/build-pdf/book.html +++ b/tests/fixtures/expected/build-pdf/book.html @@ -8,8 +8,8 @@

Book

Here is a link to the main index

diff --git a/tests/fixtures/expected/main/index.html b/tests/fixtures/expected/main/index.html index 3eda13c..57fc68e 100644 --- a/tests/fixtures/expected/main/index.html +++ b/tests/fixtures/expected/main/index.html @@ -3,14 +3,14 @@ - +

Some Test Docs!

- + From d35a62e981879fb43034def24aeba414b9b302b1 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 19 Mar 2021 12:41:38 -0400 Subject: [PATCH 8/8] Fixes from feedback --- src/BuildResult.php | 14 +++++--------- src/DocBuilder.php | 7 ++----- src/Generator/JsonGenerator.php | 5 ----- tests/fixtures/source/json/fields.rst | 2 +- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/BuildResult.php b/src/BuildResult.php index ca24423..a31a71a 100644 --- a/src/BuildResult.php +++ b/src/BuildResult.php @@ -15,14 +15,14 @@ class BuildResult { - private $errors; private $builder; + private $errors; private $jsonResults = []; - public function __construct(array $errors, Builder $builder) + public function __construct(Builder $builder) { - $this->errors = $errors; $this->builder = $builder; + $this->errors = $builder->getErrorManager()->getErrors(); } public function appendError(string $errorMessage): void @@ -45,7 +45,7 @@ public function getErrors(): array return $this->errors; } - public function getMetas(): Metas + public function getMetadata(): Metas { return $this->builder->getMetas(); } @@ -54,8 +54,6 @@ public function getMetas(): Metas * Returns the "master document": the first file whose toctree is parsed. * * Unless customized, this is "index" (i.e. file index.rst). - * - * @return string */ public function getMasterDocumentFilename(): string { @@ -63,9 +61,7 @@ public function getMasterDocumentFilename(): string } /** - * Returns the JSON data generated for each file, keyed by the source filename. - * - * @return array[] + * Returns the JSON array data generated for each file, keyed by the source filename. */ public function getJsonResults(): array { diff --git a/src/DocBuilder.php b/src/DocBuilder.php index 703be83..1f1b079 100644 --- a/src/DocBuilder.php +++ b/src/DocBuilder.php @@ -25,10 +25,7 @@ public function build(BuildConfig $config): BuildResult $builder = new Builder(KernelFactory::createKernel($config)); $builder->build($config->getContentDir(), $config->getOutputDir()); - $buildResult = new BuildResult( - $builder->getErrorManager()->getErrors(), - $builder - ); + $buildResult = new BuildResult($builder); $missingFilesChecker = new MissingFilesChecker($config); $missingFiles = $missingFilesChecker->getMissingFiles(); @@ -41,7 +38,7 @@ public function build(BuildConfig $config): BuildResult $filesystem->dumpFile($config->getOutputDir().'/build_errors.txt', implode("\n", $buildResult->getErrors())); } - $metas = $buildResult->getMetas(); + $metas = $buildResult->getMetadata(); if ($config->getSubdirectoryToBuild()) { $htmlForPdfGenerator = new HtmlForPdfGenerator($metas, $config); $htmlForPdfGenerator->generateHtmlForPdf(); diff --git a/src/Generator/JsonGenerator.php b/src/Generator/JsonGenerator.php index 73dc60b..b24ecc7 100644 --- a/src/Generator/JsonGenerator.php +++ b/src/Generator/JsonGenerator.php @@ -76,11 +76,6 @@ public function generateJson(string $masterDocument = 'index'): array 'toc' => $this->generateToc($metaEntry, current($metaEntry->getTitles())[1]), 'next' => $next, 'prev' => $prev, - 'rellinks' => [ - // probably these shouldn't be straight copies of next/prev - $next, - $prev, - ], 'body' => $crawler->filter('body')->html(), ]; diff --git a/tests/fixtures/source/json/fields.rst b/tests/fixtures/source/json/fields.rst index bfb069f..7120aa9 100644 --- a/tests/fixtures/source/json/fields.rst +++ b/tests/fixtures/source/json/fields.rst @@ -1,5 +1,5 @@ Fields ====== -I love fields: big open prairies, grass fields, corn fields... reall, +I love fields: big open prairies, grass fields, corn fields... really, any type of field, I'm a fan.