Skip to content

Commit

Permalink
!!![FEATURE] add document iterator
Browse files Browse the repository at this point in the history
The newly added iterators do iterate the toc tree to find the order of rendering
of documents. This is required to have single page outputs in the correct order.
The downside of this new approach is that only documents that are in a toc-tree
are rendered.
  • Loading branch information
jaapio committed Nov 20, 2023
1 parent ed94404 commit 0adf582
Show file tree
Hide file tree
Showing 13 changed files with 474 additions and 34 deletions.
14 changes: 10 additions & 4 deletions packages/guides-cli/src/Command/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Monolog\Logger;
use phpDocumentor\Guides\Cli\Logger\SpyProcessor;
use phpDocumentor\Guides\Compiler\CompilerContext;
use phpDocumentor\Guides\Event\PostRenderDocument;
use phpDocumentor\Guides\Handlers\CompileDocumentsCommand;
use phpDocumentor\Guides\Handlers\ParseDirectoryCommand;
use phpDocumentor\Guides\Handlers\ParseFileCommand;
Expand All @@ -29,6 +30,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

use function array_pop;
use function count;
Expand All @@ -49,6 +51,7 @@ public function __construct(
private readonly Logger $logger,
private readonly ThemeManager $themeManager,
private readonly SettingsManager $settingsManager,
private readonly EventDispatcher $eventDispatcher,
) {
parent::__construct('run');

Expand Down Expand Up @@ -223,18 +226,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$outputFormats = $settings->getOutputFormats();

$progressBar = null;

if ($output instanceof ConsoleOutputInterface && $settings->isShowProgressBar()) {
$progressBar = new ProgressBar($output->section());
$progressBar = new ProgressBar($output->section(), count($documents));
$this->eventDispatcher->addListener(
PostRenderDocument::class,
static function (PostRenderDocument $event) use ($progressBar): void {
$progressBar->advance();
},
);
}

foreach ($outputFormats as $format) {
$this->commandBus->handle(
new RenderCommand(
$format,
$documents,
$progressBar === null ? $documents : $progressBar->iterate($documents),
$sourceFileSystem,
$destinationFileSystem,
$projectNode,
Expand Down
20 changes: 13 additions & 7 deletions packages/guides/src/Handlers/RenderCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@
use League\Flysystem\FilesystemInterface;
use phpDocumentor\Guides\Nodes\DocumentNode;
use phpDocumentor\Guides\Nodes\ProjectNode;
use phpDocumentor\Guides\Renderer\DocumentListIterator;
use phpDocumentor\Guides\Renderer\DocumentTreeIterator;

final class RenderCommand
{
/**
* @param DocumentNode[] $documentArray
* @param iterable<DocumentNode> $documentIterator
*/
private DocumentListIterator $documentIterator;

/** @param DocumentNode[] $documentArray */
public function __construct(
private readonly string $outputFormat,
private readonly array $documentArray,
private readonly iterable $documentIterator,
private readonly FilesystemInterface $origin,
private readonly FilesystemInterface $destination,
private readonly ProjectNode $projectNode,
private readonly string $destinationPath = '/',
) {
$this->documentIterator = new DocumentListIterator(
new DocumentTreeIterator(
[$this->projectNode->getRootDocumentEntry()],
$this->documentArray,
),
$this->documentArray,
);
}

public function getOutputFormat(): string
Expand All @@ -36,8 +43,7 @@ public function getDocumentArray(): array
return $this->documentArray;
}

/** @return iterable<DocumentNode> $documentIterator */
public function getDocumentIterator(): iterable
public function getDocumentIterator(): DocumentListIterator
{
return $this->documentIterator;
}
Expand Down
15 changes: 15 additions & 0 deletions packages/guides/src/RenderContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class RenderContext
/** @var DocumentNode[] */
private array $allDocuments;

private Renderer\DocumentListIterator $iterator;

private function __construct(
private readonly string $destinationPath,
private readonly string|null $currentFileName,
Expand Down Expand Up @@ -77,6 +79,14 @@ public function withDocument(DocumentNode $documentNode): self
);
}

public function withIterator(Renderer\DocumentListIterator $iterator): self
{
$that = clone $this;
$that->iterator = $iterator;

return $that;
}

/** @param DocumentNode[] $allDocumentNodes */
public static function forProject(
ProjectNode $projectNode,
Expand Down Expand Up @@ -192,4 +202,9 @@ public function getOutputFormat(): string
{
return $this->outputFormat;
}

public function getIterator(): Renderer\DocumentListIterator
{
return $this->iterator;
}
}
21 changes: 11 additions & 10 deletions packages/guides/src/Renderer/BaseTypeRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ public function __construct(protected readonly CommandBus $commandBus)

public function render(RenderCommand $renderCommand): void
{
foreach ($renderCommand->getDocumentIterator() as $document) {
$context = RenderContext::forProject(
$renderCommand->getProjectNode(),
$renderCommand->getDocumentArray(),
$renderCommand->getOrigin(),
$renderCommand->getDestination(),
$renderCommand->getDestinationPath(),
$renderCommand->getOutputFormat(),
)->withIterator($renderCommand->getDocumentIterator());

foreach ($context->getIterator() as $document) {
$this->commandBus->handle(
new RenderDocumentCommand(
$document,
RenderContext::forDocument(
$document,
$renderCommand->getDocumentArray(),
$renderCommand->getOrigin(),
$renderCommand->getDestination(),
$renderCommand->getDestinationPath(),
$renderCommand->getOutputFormat(),
$renderCommand->getProjectNode(),
),
$context->withDocument($document),
),
);
}
Expand Down
123 changes: 123 additions & 0 deletions packages/guides/src/Renderer/DocumentListIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Guides\Renderer;

use AppendIterator;
use Generator;
use Iterator;
use phpDocumentor\Guides\Nodes\DocumentNode;
use RecursiveIteratorIterator;
use WeakMap;
use WeakReference;

/** @implements Iterator<array-key, DocumentNode> */
final class DocumentListIterator implements Iterator
{
/** @var WeakReference<DocumentNode>|null */
private WeakReference|null $previousDocument;

/** @var WeakReference<DocumentNode>|null */
private WeakReference|null $nextDocument;

/** @var WeakMap<DocumentNode, bool> */
private WeakMap $unseenDocuments;

/** @var Iterator<array-key, DocumentNode> */
private Iterator $innerIterator;

/** @param DocumentNode[] $documents */
public function __construct(
DocumentTreeIterator $iterator,
array $documents,
) {
$this->unseenDocuments = new WeakMap();
$this->previousDocument = null;
$this->nextDocument = null;
$this->innerIterator = new AppendIterator();
$this->innerIterator->append(
new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST),
);
$this->innerIterator->append($this->unseenIterator());
foreach ($documents as $document) {
$this->unseenDocuments[$document] = true;
}
}

public function next(): void
{
if ($this->innerIterator->valid()) {
$this->previousDocument = WeakReference::create($this->current());
} else {
$this->previousDocument = null;
}

if ($this->nextDocument === null) {
$this->innerIterator->next();
}

$this->nextDocument = null;
}

public function previousNode(): DocumentNode|null
{
return $this->previousDocument?->get();
}

public function valid(): bool
{
if ($this->nextDocument !== null) {
return true;
}

return $this->innerIterator->valid();
}

public function nextNode(): DocumentNode|null
{
$this->innerIterator->next();

if ($this->innerIterator->valid()) {
$this->nextDocument = WeakReference::create($this->current());
}

return $this->nextDocument?->get();
}

public function current(): mixed
{
$document = $this->innerIterator->current();
if ($document instanceof DocumentNode) {
$this->unseenDocuments[$document] = false;
}

return $document;
}

public function key(): mixed
{
return $this->innerIterator->key();
}

public function rewind(): void
{
foreach ($this->unseenDocuments as $document => $seen) {
$this->unseenDocuments[$document] = true;
}

$this->innerIterator->rewind();
}

/** @return Generator<DocumentNode> */
private function unseenIterator(): Generator
{
foreach ($this->unseenDocuments as $document => $seen) {
if ($seen === false) {
continue;
}

yield $document;
}
}
}
73 changes: 73 additions & 0 deletions packages/guides/src/Renderer/DocumentTreeIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Guides\Renderer;

use LogicException;
use phpDocumentor\Guides\Nodes\DocumentNode;
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
use RecursiveIterator;

/**
* Iterates over the document tree and returns the documents in the table of contents order.
*
* @internal This class is not part of the public API of this package and should not be used outside of this package.
*
* @implements RecursiveIterator<int, DocumentNode>
*/
final class DocumentTreeIterator implements RecursiveIterator
{
private int $position = 0;

/**
* @param DocumentEntryNode[] $levelNodes
* @param DocumentNode[] $documents
*/
public function __construct(
private readonly array $levelNodes,
private readonly array $documents,
) {
}

public function current(): DocumentNode
{
foreach ($this->documents as $document) {
if ($document->getDocumentEntry() === $this->levelNodes[$this->position]) {
return $document;
}
}

throw new LogicException('Could not find document for node');
}

public function next(): void
{
++$this->position;
}

public function key(): int
{
return $this->position;
}

public function valid(): bool
{
return isset($this->levelNodes[$this->position]);
}

public function rewind(): void
{
$this->position = 0;
}

public function hasChildren(): bool
{
return empty($this->levelNodes[$this->position]->getChildren()) === false;
}

public function getChildren(): self|null
{
return new self($this->levelNodes[$this->position]->getChildren(), $this->documents);
}
}
4 changes: 2 additions & 2 deletions packages/guides/src/Renderer/LatexRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function render(RenderCommand $renderCommand): void
$renderCommand->getDestination(),
$renderCommand->getDestinationPath(),
'tex',
);
)->withIterator($renderCommand->getDocumentIterator());

$context->getDestination()->put(
$renderCommand->getDestinationPath() . '/index.tex',
Expand All @@ -34,7 +34,7 @@ public function render(RenderCommand $renderCommand): void
'structure/project.tex.twig',
[
'project' => $projectNode,
'documents' => $renderCommand->getDocumentIterator(),
'documents' => $context->getIterator(),
],
),
);
Expand Down
Loading

0 comments on commit 0adf582

Please sign in to comment.