Skip to content

Commit

Permalink
perf(documentator)!: Minor Markdown mutations improvements (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
LastDragon-ru authored Aug 16, 2024
2 parents 324ed60 + 56ce044 commit 460883e
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 64 deletions.
4 changes: 2 additions & 2 deletions packages/documentator/src/Markdown/Contracts/Mutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

interface Mutation {
/**
* @return array<array-key, array{Location, ?string}>
* @return iterable<array-key, array{Location, ?string}>
*/
public function __invoke(Document $document, DocumentNode $node): array;
public function __invoke(Document $document, DocumentNode $node): iterable;
}
11 changes: 9 additions & 2 deletions packages/documentator/src/Markdown/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Lines;
use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Coordinate;
use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location;
use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Composite;
use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\FootnotesPrefix;
use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\FootnotesRemove;
use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\ReferencesInline;
Expand Down Expand Up @@ -140,7 +141,10 @@ public function mutate(Mutation ...$mutations): static {
* @return new<static>
*/
public function toInlinable(string $seed): static {
return $this->mutate(new FootnotesPrefix($seed), new ReferencesPrefix($seed));
return $this->mutate(new Composite(
new FootnotesPrefix($seed),
new ReferencesPrefix($seed),
));
}

/**
Expand All @@ -151,7 +155,10 @@ public function toInlinable(string $seed): static {
* @return new<static>
*/
public function toSplittable(): static {
return $this->mutate(new FootnotesRemove(), new ReferencesInline());
return $this->mutate(new Composite(
new FootnotesRemove(),
new ReferencesInline(),
));
}

protected function setContent(string $content): static {
Expand Down
48 changes: 32 additions & 16 deletions packages/documentator/src/Markdown/Editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ public function getText(Location|Coordinate $location): ?string {
}

/**
* @param array<array-key, array{Location, ?string}> $changes
* @param iterable<array-key, array{Location, ?string}> $changes
*
* @return new<static>
*/
public function mutate(array $changes): static {
public function mutate(iterable $changes): static {
// Modify
$lines = $this->lines;
$changes = $this->prepare($changes);
$changes = $this->removeOverlaps($changes);
$changes = $this->expand($changes);
$paddings = [];
Expand Down Expand Up @@ -134,7 +135,23 @@ public function mutate(array $changes): static {
}

/**
* @param array<array-key, array{Location, ?string}> $changes
* @param iterable<array-key, array{Location, ?string}> $changes
*
* @return list<array{array<array-key, Coordinate>, ?string}>
*/
protected function prepare(iterable $changes): array {
$prepared = [];

foreach ($changes as $change) {
[$location, $text] = $change;
$prepared[] = [iterator_to_array($location), $text];
}

return array_reverse($prepared);
}

/**
* @param array<array-key, array{array<array-key, Coordinate>, ?string}> $changes
*
* @return list<array{Coordinate, ?string}>
*/
Expand All @@ -144,11 +161,10 @@ protected function expand(array $changes): array {
return $a->line <=> $b->line ?: $a->offset <=> $b->offset;
};

foreach (array_reverse($changes, true) as $change) {
[$location, $text] = $change;
$coordinates = iterator_to_array($location);
$text = $text ? Text::getLines($text) : [];
$line = 0;
foreach ($changes as $change) {
[$coordinates, $text] = $change;
$text = $text ? Text::getLines($text) : [];
$line = 0;

usort($coordinates, $sort);

Expand All @@ -168,20 +184,20 @@ protected function expand(array $changes): array {
}

/**
* @param array<array-key, array{Location, ?string}> $changes
* @param array<array-key, array{array<array-key, Coordinate>, ?string}> $changes
*
* @return array<array-key, array{Location, ?string}>
* @return array<array-key, array{array<array-key, Coordinate>, ?string}>
*/
protected function removeOverlaps(array $changes): array {
$used = [];
$sort = static function (Coordinate $a, Coordinate $b): int {
return $b->line <=> $a->line;
};

foreach (array_reverse($changes, true) as $key => $change) {
[$location] = $change;
$coordinates = iterator_to_array($location);
foreach ($changes as $key => $change) {
[$coordinates] = $change;

usort($coordinates, static function (Coordinate $a, Coordinate $b): int {
return $b->line <=> $a->line;
});
usort($coordinates, $sort);

foreach ($coordinates as $coordinate) {
if ($this->isOverlapped($used, $coordinate)) {
Expand Down
56 changes: 41 additions & 15 deletions packages/documentator/src/Markdown/EditorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use Override;
use PHPUnit\Framework\Attributes\CoversClass;

use function iterator_to_array;

/**
* @internal
*/
Expand Down Expand Up @@ -62,6 +64,30 @@ public function testMutate(): void {
self::assertSame($expected, $actual->getLines());
}

public function testPrepare(): void {
$editor = new class([]) extends Editor {
/**
* @inheritDoc
*/
#[Override]
public function prepare(iterable $changes): array {
return parent::prepare($changes);
}
};
$changes = [
[new Location(10, 10, 15, 10), 'a'],
[new Location(10, 10, 10, null), 'b'],
[new Location(12, 15, 5, 10), 'c'],
];
$expected = [
[iterator_to_array(new Location(12, 15, 5, 10)), 'c'],
[iterator_to_array(new Location(10, 10, 10, null)), 'b'],
[iterator_to_array(new Location(10, 10, 15, 10)), 'a'],
];

self::assertEquals($expected, $editor->prepare($changes));
}

public function testRemoveOverlaps(): void {
$editor = new class([]) extends Editor {
/**
Expand All @@ -73,19 +99,19 @@ public function removeOverlaps(array $changes): array {
}
};
$changes = [
0 => [new Location(10, 10, 15, 10), 'a'],
1 => [new Location(10, 10, 10, null), 'b'],
2 => [new Location(12, 15, 5, 10), 'c'],
3 => [new Location(14, 15, 5, 10), 'd'],
4 => [new Location(17, 17, 5, 10), 'e'],
5 => [new Location(17, 17, 11, 10), 'f'],
6 => [new Location(18, 18, 5, 10), 'g'],
0 => [iterator_to_array(new Location(18, 18, 5, 10)), 'g'],
1 => [iterator_to_array(new Location(17, 17, 11, 10)), 'f'],
2 => [iterator_to_array(new Location(17, 17, 5, 10)), 'e'],
3 => [iterator_to_array(new Location(14, 15, 5, 10)), 'd'],
4 => [iterator_to_array(new Location(12, 15, 5, 10)), 'c'],
5 => [iterator_to_array(new Location(10, 10, 10, null)), 'b'],
6 => [iterator_to_array(new Location(10, 10, 15, 10)), 'a'],
];
$expected = [
1 => [new Location(10, 10, 10, null), 'b'],
3 => [new Location(14, 15, 5, 10), 'd'],
5 => [new Location(17, 17, 11, 10), 'f'],
6 => [new Location(18, 18, 5, 10), 'g'],
0 => [iterator_to_array(new Location(18, 18, 5, 10)), 'g'],
1 => [iterator_to_array(new Location(17, 17, 11, 10)), 'f'],
3 => [iterator_to_array(new Location(14, 15, 5, 10)), 'd'],
5 => [iterator_to_array(new Location(10, 10, 10, null)), 'b'],
];

self::assertEquals($expected, $editor->removeOverlaps($changes));
Expand All @@ -102,10 +128,10 @@ public function expand(array $changes): array {
}
};
$changes = [
[new Location(1, 1, 5, 10), 'text'],
[new Location(2, 3, 5, null), 'text'],
[new Location(4, 5, 5, 5, 1), "text a\ntext b"],
[new Location(6, 6, 5, 10, 2), "text a\ntext b"],
[iterator_to_array(new Location(6, 6, 5, 10, 2)), "text a\ntext b"],
[iterator_to_array(new Location(4, 5, 5, 5, 1)), "text a\ntext b"],
[iterator_to_array(new Location(2, 3, 5, null)), 'text'],
[iterator_to_array(new Location(1, 1, 5, 10)), 'text'],
];
$expected = [
[new Coordinate(6, 7, 10, 2), 'text a'],
Expand Down
36 changes: 36 additions & 0 deletions packages/documentator/src/Markdown/Mutations/Composite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Markdown\Mutations;

use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation;
use LastDragon_ru\LaraASP\Documentator\Markdown\Document;
use League\CommonMark\Node\Block\Document as DocumentNode;
use Override;

/**
* Merges all mutations into one.
*/
readonly class Composite implements Mutation {
/**
* @var array<array-key, Mutation>
*/
protected array $mutations;

public function __construct(Mutation ...$mutations) {
$this->mutations = $mutations;
}

/**
* @inheritDoc
*/
#[Override]
public function __invoke(Document $document, DocumentNode $node): iterable {
// Just in case
yield from [];

// Process all
foreach ($this->mutations as $mutation) {
yield from $mutation($document, $node);
}
}
}
55 changes: 55 additions & 0 deletions packages/documentator/src/Markdown/Mutations/CompositeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Markdown\Mutations;

use LastDragon_ru\LaraASP\Core\Utils\Cast;
use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation;
use LastDragon_ru\LaraASP\Documentator\Markdown\Document;
use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location;
use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase;
use League\CommonMark\Node\Block\Document as DocumentNode;
use Mockery;
use PHPUnit\Framework\Attributes\CoversClass;
use ReflectionProperty;

use function array_merge;

/**
* @internal
*/
#[CoversClass(Composite::class)]
final class CompositeTest extends TestCase {
public function testInvoke(): void {
$document = new Document('');
$node = Cast::to(DocumentNode::class, (new ReflectionProperty($document, 'node'))->getValue($document));
$aChanges = [
[new Location(1, 1, 10), 'a'],
];
$bChanges = [
[new Location(2, 3, 0), 'b'],
];
$aMutation = Mockery::mock(Mutation::class);
$aMutation
->shouldReceive('__invoke')
->with($document, $node)
->once()
->andReturn($aChanges);
$bMutation = Mockery::mock(Mutation::class);
$bMutation
->shouldReceive('__invoke')
->with($document, $node)
->once()
->andReturn($bChanges);

$mutation = new Composite($aMutation, $bMutation);
$changes = $mutation($document, $node);
$expected = array_merge($aChanges, $bChanges);
$actual = [];

foreach ($changes as $change) {
$actual[] = $change;
}

self::assertEquals($expected, $actual);
}
}
10 changes: 5 additions & 5 deletions packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ public function __construct(
* @inheritDoc
*/
#[Override]
public function __invoke(Document $document, DocumentNode $node): array {
$changes = [];
public function __invoke(Document $document, DocumentNode $node): iterable {
// Just in case
yield from [];

// Process
foreach ($node->iterator() as $child) {
// Footnote?
if (!($child instanceof FootnoteRef) && !($child instanceof Footnote)) {
Expand All @@ -42,11 +44,9 @@ public function __invoke(Document $document, DocumentNode $node): array {
$location = $label ? $this->getLabelLocation($child, $label) : null;

if ($location) {
$changes[] = [$location, "{$this->prefix}-{$label}"];
yield [$location, "{$this->prefix}-{$label}"];
}
}

return $changes;
}

private function getLabel(Document $document, Footnote|FootnoteRef $footnote): ?string {
Expand Down
13 changes: 7 additions & 6 deletions packages/documentator/src/Markdown/Mutations/FootnotesRemove.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation;
use LastDragon_ru\LaraASP\Documentator\Markdown\Document;
use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location;
use LastDragon_ru\LaraASP\Documentator\Markdown\Utils;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
Expand All @@ -19,23 +20,23 @@ public function __construct() {
}

/**
* @inheritDoc
* @return iterable<array-key, array{Location, ?string}>
*/
#[Override]
public function __invoke(Document $document, DocumentNode $node): array {
$changes = [];
public function __invoke(Document $document, DocumentNode $node): iterable {
// Just in case
yield from [];

// Process
foreach ($node->iterator() as $child) {
$location = match (true) {
$child instanceof FootnoteRef, $child instanceof Footnote => Utils::getLocation($child),
default => null,
};

if ($location) {
$changes[] = [$location, null];
yield [$location, null];
}
}

return $changes;
}
}
Loading

0 comments on commit 460883e

Please sign in to comment.