Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

!!![FEATURE] add document iterator #682

Merged
merged 1 commit into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -30,6 +31,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 @@ -51,6 +53,7 @@ public function __construct(
private readonly ThemeManager $themeManager,
private readonly SettingsManager $settingsManager,
private readonly ClockInterface $clock,
private readonly EventDispatcher $eventDispatcher,
) {
parent::__construct('run');

Expand Down Expand Up @@ -226,18 +229,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