Skip to content

Commit a992c60

Browse files
committed
Properly fixing next/prev/parents in the JSON files
This was researched specifically using Sphinx behavior
1 parent 1d75582 commit a992c60

File tree

9 files changed

+240
-104
lines changed

9 files changed

+240
-104
lines changed

src/BuildResult.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
class BuildResult
1717
{
1818
private $errors;
19-
private $metas;
19+
private $builder;
2020
private $jsonResults = [];
2121

22-
public function __construct(array $errors, Metas $metas)
22+
public function __construct(array $errors, Builder $builder)
2323
{
2424
$this->errors = $errors;
25-
$this->metas = $metas;
25+
$this->builder = $builder;
2626
}
2727

2828
public function appendError(string $errorMessage): void
@@ -47,7 +47,19 @@ public function getErrors(): array
4747

4848
public function getMetas(): Metas
4949
{
50-
return $this->metas;
50+
return $this->builder->getMetas();
51+
}
52+
53+
/**
54+
* Returns the "master document": the first file whose toctree is parsed.
55+
*
56+
* Unless customized, this is "index" (i.e. file index.rst).
57+
*
58+
* @return string
59+
*/
60+
public function getMasterDocumentFilename(): string
61+
{
62+
return $this->builder->getIndexName();
5163
}
5264

5365
/**

src/DocBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function build(BuildConfig $config): BuildResult
2727

2828
$buildResult = new BuildResult(
2929
$builder->getErrorManager()->getErrors(),
30-
$builder->getMetas()
30+
$builder
3131
);
3232

3333
$missingFilesChecker = new MissingFilesChecker($config);
@@ -47,7 +47,7 @@ public function build(BuildConfig $config): BuildResult
4747
$htmlForPdfGenerator->generateHtmlForPdf();
4848
} else {
4949
$jsonGenerator = new JsonGenerator($metas, $config);
50-
$buildResult->setJsonResults($jsonGenerator->generateJson());
50+
$buildResult->setJsonResults($jsonGenerator->generateJson($builder->getIndexName()));
5151
}
5252

5353
return $buildResult;

src/Generator/JsonGenerator.php

Lines changed: 130 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -40,31 +40,46 @@ public function __construct(Metas $metas, BuildConfig $buildConfig)
4040
/**
4141
* Returns an array of each JSON file string, keyed by the input filename
4242
*
43+
* @param string $masterDocument The file whose toctree should be read first
4344
* @return string[]
4445
*/
45-
public function generateJson(): array
46+
public function generateJson(string $masterDocument = 'index'): array
4647
{
4748
$fs = new Filesystem();
4849

4950
$progressBar = new ProgressBar($this->output ?: new NullOutput());
5051
$progressBar->setMaxSteps(\count($this->metas->getAll()));
5152

53+
$walkedFiles = [];
54+
$tocTreeHierarchy = $this->walkTocTreeAndReturnHierarchy(
55+
$masterDocument,
56+
$walkedFiles
57+
);
58+
// for purposes of prev/next/parents, the "master document"
59+
// behaves as if it's the first item in the toctree
60+
$tocTreeHierarchy = [$masterDocument => []] + $tocTreeHierarchy;
61+
$flattenedTocTree = $this->flattenTocTree($tocTreeHierarchy);
62+
5263
$fJsonFiles = [];
5364
foreach ($this->metas->getAll() as $filename => $metaEntry) {
5465
$parserFilename = $filename;
5566
$jsonFilename = $this->buildConfig->getOutputDir().'/'.$filename.'.fjson';
5667

5768
$crawler = new Crawler(file_get_contents($this->buildConfig->getOutputDir().'/'.$filename.'.html'));
5869

70+
$next = $this->determineNext($parserFilename, $flattenedTocTree, $masterDocument);
71+
$prev = $this->determinePrev($parserFilename, $flattenedTocTree);
5972
$data = [
6073
'title' => $metaEntry->getTitle(),
74+
'parents' => $this->determineParents($parserFilename, $tocTreeHierarchy) ?: [],
6175
'current_page_name' => $parserFilename,
6276
'toc' => $this->generateToc($metaEntry, current($metaEntry->getTitles())[1]),
63-
'next' => $this->guessNext($parserFilename),
64-
'prev' => $this->guessPrev($parserFilename),
77+
'next' => $next,
78+
'prev' => $prev,
6579
'rellinks' => [
66-
$this->guessNext($parserFilename),
67-
$this->guessPrev($parserFilename),
80+
// probably these shouldn't be straight copies of next/prev
81+
$next,
82+
$prev,
6883
],
6984
'body' => $crawler->filter('body')->html(),
7085
];
@@ -109,128 +124,162 @@ private function generateToc(MetaEntry $metaEntry, ?array $titles): array
109124
return $tocTree;
110125
}
111126

112-
private function guessNext(string $parserFilename): ?array
127+
private function determineNext(string $parserFilename, array $flattenedTocTree): ?array
113128
{
114-
$meta = $this->getMetaEntry($parserFilename, true);
115-
116-
$parentFile = $meta->getParent();
129+
$foundCurrentFile = false;
130+
$nextFileName = null;
117131

118-
// if current file is an index, next is the first chapter
119-
if ('index' === $parentFile && 1 === \count($tocs = $meta->getTocs()) && \count($tocs[0]) > 0) {
120-
$firstChapterMeta = $this->getMetaEntry($tocs[0][0]);
132+
foreach ($flattenedTocTree as $filename) {
133+
if ($foundCurrentFile) {
134+
$nextFileName = $filename;
121135

122-
if (null === $firstChapterMeta) {
123-
return null;
136+
break;
124137
}
125138

126-
return [
127-
'title' => $firstChapterMeta->getTitle(),
128-
'link' => $firstChapterMeta->getUrl(),
129-
];
139+
if ($filename === $parserFilename) {
140+
$foundCurrentFile = true;
141+
}
130142
}
131143

132-
[$toc, $indexCurrentFile] = $this->getNextPrevInformation($parserFilename);
133-
134-
if (!isset($toc[$indexCurrentFile + 1])) {
144+
// no next document found!
145+
if (null === $nextFileName) {
135146
return null;
136147
}
137148

138-
$nextFileName = $toc[$indexCurrentFile + 1];
139-
140-
$nextMeta = $this->getMetaEntry($nextFileName);
141-
142-
if (null === $nextMeta) {
143-
return null;
144-
}
149+
$meta = $this->getMetaEntry($nextFileName);
145150

146151
return [
147-
'title' => $nextMeta->getTitle(),
148-
'link' => $nextMeta->getUrl(),
152+
'title' => $meta->getTitle(),
153+
'link' => $meta->getUrl(),
149154
];
150155
}
151156

152-
private function guessPrev(string $parserFilename): ?array
157+
private function determinePrev(string $parserFilename, array $flattenedTocTree): ?array
153158
{
154-
$meta = $this->getMetaEntry($parserFilename, true);
155-
$parentFile = $meta->getParent();
156-
157-
[$toc, $indexCurrentFile] = $this->getNextPrevInformation($parserFilename);
158-
159-
// if current file is the first one of the chapter, prev is the direct parent
160-
if (0 === $indexCurrentFile) {
161-
$parentMeta = $this->getMetaEntry($parentFile);
162-
163-
if (null === $parentMeta) {
164-
return null;
159+
$previousFileName = null;
160+
$foundCurrentFile = false;
161+
foreach ($flattenedTocTree as $filename) {
162+
if ($filename === $parserFilename) {
163+
$foundCurrentFile = true;
164+
break;
165165
}
166166

167-
return [
168-
'title' => $parentMeta->getTitle(),
169-
'link' => $parentMeta->getUrl(),
170-
];
167+
$previousFileName = $filename;
171168
}
172169

173-
if (!isset($toc[$indexCurrentFile - 1])) {
170+
// no previous document found!
171+
if (null === $previousFileName || !$foundCurrentFile) {
174172
return null;
175173
}
176174

177-
$prevFileName = $toc[$indexCurrentFile - 1];
178-
179-
$prevMeta = $this->getMetaEntry($prevFileName);
180-
181-
if (null === $prevMeta) {
182-
return null;
183-
}
175+
$meta = $this->getMetaEntry($previousFileName);
184176

185177
return [
186-
'title' => $prevMeta->getTitle(),
187-
'link' => $prevMeta->getUrl(),
178+
'title' => $meta->getTitle(),
179+
'link' => $meta->getUrl(),
188180
];
189181
}
190182

191-
private function getNextPrevInformation(string $parserFilename): ?array
183+
private function getMetaEntry(string $parserFilename, bool $throwOnMissing = false): ?MetaEntry
192184
{
193-
$meta = $this->getMetaEntry($parserFilename, true);
194-
$parentFile = $meta->getParent();
185+
$metaEntry = $this->metas->get($parserFilename);
195186

196-
if (!$parentFile) {
197-
return [null, null];
198-
}
187+
// this is possible if there are invalid references
188+
if (null === $metaEntry) {
189+
$message = sprintf('Could not find MetaEntry for file "%s"', $parserFilename);
199190

200-
$metaParent = $this->getMetaEntry($parentFile);
191+
if ($throwOnMissing) {
192+
throw new \Exception($message);
193+
}
201194

202-
if (null === $metaParent || !$metaParent->getTocs() || 1 !== \count($metaParent->getTocs())) {
203-
return [null, null];
195+
if ($this->output) {
196+
$this->output->note($message);
197+
}
204198
}
205199

206-
$toc = current($metaParent->getTocs());
200+
return $metaEntry;
201+
}
207202

208-
if (\count($toc) < 2 || !isset(array_flip($toc)[$parserFilename])) {
209-
return [null, null];
210-
}
203+
/**
204+
* Creates a hierarchy of documents by crawling the toctree's
205+
*
206+
* This looks at the
207+
* toc tree of the master document, following the first entry
208+
* like a link, then repeating the process on the next document's
209+
* toc tree (if it has one). When it hits a dead end, it would
210+
* go back to the master document and click the second link.
211+
* But, it skips any links that have been seen before. This
212+
* is the logic behind how the prev/next parent information is created.
213+
*
214+
* Example result:
215+
* [
216+
* 'dashboards' => [],
217+
* 'design' => [
218+
* 'crud' => [],
219+
* 'design/sub-page' => [],
220+
* ],
221+
* 'fields' => []
222+
* ]
223+
*
224+
* See the JsonIntegrationTest for a test case.
225+
*/
226+
private function walkTocTreeAndReturnHierarchy(string $filename, array &$walkedFiles): array
227+
{
228+
$hierarchy = [];
229+
foreach ($this->getMetaEntry($filename)->getTocs() as $toc) {
230+
foreach ($toc as $tocFilename) {
231+
// only walk a file one time, the first time you see it
232+
if (in_array($tocFilename, $walkedFiles, true)) {
233+
continue;
234+
}
211235

212-
$indexCurrentFile = array_flip($toc)[$parserFilename];
236+
$walkedFiles[] = $tocFilename;
237+
238+
$hierarchy[$tocFilename] = $this->walkTocTreeAndReturnHierarchy($tocFilename, $walkedFiles);
239+
}
240+
}
213241

214-
return [$toc, $indexCurrentFile];
242+
return $hierarchy;
215243
}
216244

217-
private function getMetaEntry(string $parserFilename, bool $throwOnMissing = false): ?MetaEntry
245+
/**
246+
* Takes the structure from walkTocTreeAndReturnHierarchy() and flattens it.
247+
*
248+
* For example:
249+
*
250+
* [dashboards, design, crud, design/sub-page, fields]
251+
*
252+
* @return string[]
253+
*/
254+
private function flattenTocTree(array $tocTreeHierarchy): array
218255
{
219-
$metaEntry = $this->metas->get($parserFilename);
256+
$files = [];
220257

221-
// this is possible if there are invalid references
222-
if (null === $metaEntry) {
223-
$message = sprintf('Could not find MetaEntry for file "%s"', $parserFilename);
258+
foreach ($tocTreeHierarchy as $filename => $tocTree) {
259+
$files[] = $filename;
224260

225-
if ($throwOnMissing) {
226-
throw new \Exception($message);
261+
$files = array_merge($files, $this->flattenTocTree($tocTree));
262+
}
263+
264+
return $files;
265+
}
266+
267+
private function determineParents(string $parserFilename, array $tocTreeHierarchy, array $parents = []): ?array
268+
{
269+
foreach ($tocTreeHierarchy as $filename => $tocTree) {
270+
if ($filename === $parserFilename) {
271+
return $parents;
227272
}
228273

229-
if ($this->output) {
230-
$this->output->note($message);
274+
$subParents = $this->determineParents($parserFilename, $tocTree, $parents + [$filename]);
275+
276+
if (null !== $subParents) {
277+
// the item WAS found and the parents were returned
278+
return $subParents;
231279
}
232280
}
233281

234-
return $metaEntry;
282+
// item was not found
283+
return null;
235284
}
236285
}

0 commit comments

Comments
 (0)