Skip to content

Commit

Permalink
Methods to mutate \LastDragon_ru\LaraASP\Documentator\Editor\Editor
Browse files Browse the repository at this point in the history
… moved into new `\LastDragon_ru\LaraASP\Documentator\Editor\Mutators\Mutator` class.
  • Loading branch information
LastDragon-ru committed Jan 15, 2025
1 parent e9bc2b9 commit 488645f
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 282 deletions.
190 changes: 7 additions & 183 deletions packages/documentator/src/Editor/Editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,20 @@
namespace LastDragon_ru\LaraASP\Documentator\Editor;

use LastDragon_ru\LaraASP\Documentator\Editor\Mutators\Extractor;
use LastDragon_ru\LaraASP\Documentator\Editor\Mutators\Mutator;
use LastDragon_ru\LaraASP\Documentator\Utils\Text;
use Override;
use Stringable;

use function array_merge;
use function array_push;
use function array_reverse;
use function array_slice;
use function array_splice;
use function array_values;
use function count;
use function implode;
use function is_string;
use function mb_rtrim;
use function mb_substr;
use function usort;

use const PHP_INT_MAX;

readonly class Editor implements Stringable {
/**
* @var list<string>
*/
protected array $lines;
protected Mutator $mutator;
protected Extractor $extractor;

/**
Expand All @@ -38,6 +28,7 @@ final public function __construct(
protected string $endOfLine = "\n",
) {
$this->lines = is_string($content) ? Text::getLines($content) : $content;
$this->mutator = new Mutator();
$this->extractor = new Extractor();
}

Expand All @@ -59,181 +50,14 @@ public function extract(iterable $locations): static {
}

/**
* @param iterable<array-key, array{iterable<array-key, Coordinate>, ?string}> $changes
* @param iterable<mixed, array{iterable<mixed, Coordinate>, ?string}> $changes
*
* @return new<static>
*/
public function mutate(iterable $changes): static {
// Modify
$lines = $this->lines;
$changes = $this->prepare($changes);
$changes = $this->removeOverlaps($changes);
$changes = $this->expand($changes);

foreach ($changes as [$coordinate, $text]) {
// Append?
if ($coordinate->line === PHP_INT_MAX) {
array_push($lines, ...$text);
continue;
}

// Change
$number = $coordinate->line - $this->startLine;
$line = $lines[$number] ?? '';
$count = count($text);
$prefix = mb_substr($line, 0, $coordinate->offset);
$suffix = $coordinate->length !== null
? mb_substr($line, $coordinate->offset + $coordinate->length)
: '';
$padding = mb_substr($line, 0, $coordinate->padding);

if ($count > 1) {
$insert = [];

for ($t = 0; $t < $count; $t++) {
$insert[] = match (true) {
$t === 0 => mb_rtrim($prefix.$text[$t]),
$t === $count - 1 => mb_rtrim($padding.$text[$t].$suffix),
default => mb_rtrim($padding.$text[$t]),
};
}

array_splice($lines, $number, 1, $insert);
} elseif ($count === 1) {
$lines[$number] = mb_rtrim($prefix.$text[0].$suffix);
} elseif (($prefix !== '' && $prefix !== $padding) || $suffix !== '') {
$lines[$number] = mb_rtrim($prefix.$suffix);
} else {
unset($lines[$number]);
}
}

// Return
return new static(array_values($lines), $this->startLine, $this->endOfLine);
}

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

foreach ($changes as [$location, $text]) {
$coordinates = [];

foreach ($location as $coordinate) {
$coordinates[] = $coordinate;
}

if ($coordinates !== []) {
$prepared[] = [$coordinates, $text];
}
}

return array_reverse($prepared);
}

/**
* @param array<int, array{list<Coordinate>, ?string}> $changes
*
* @return list<array{Coordinate, list<string>}>
*/
protected function expand(array $changes): array {
$expanded = [];
$append = [];
$sort = static function (Coordinate $a, Coordinate $b): int {
$result = $a->line <=> $b->line;
$result = $result === 0
? $a->offset <=> $b->offset
: $result;

return $result;
};

foreach ($changes as [$coordinates, $text]) {
$text = match (true) {
$text === null => [],
$text === '' => [''],
default => Text::getLines($text),
};
$mutated = ($this->mutator)($this->lines, $changes, $this->startLine);
$editor = new static($mutated, $this->startLine, $this->endOfLine);

usort($coordinates, $sort);

for ($i = 0, $c = count($coordinates); $i < $c; $i++) {
$line = $i === $c - 1 ? array_slice($text, $i) : (array) ($text[$i] ?? null);

if ($coordinates[$i]->line === PHP_INT_MAX) {
$append[] = [$coordinates[$i], $line];
} else {
$expanded[] = [$coordinates[$i], $line];
}
}
}

usort($expanded, static fn ($a, $b) => -$sort($a[0], $b[0]));

return array_merge($expanded, array_reverse($append));
}

/**
* @param list<array{list<Coordinate>, ?string}> $changes
*
* @return array<int, array{list<Coordinate>, ?string}>
*/
protected function removeOverlaps(array $changes): array {
$used = [];

foreach ($changes as $key => [$coordinates]) {
$lines = [];

foreach ($coordinates as $coordinate) {
$lines[$coordinate->line][] = $coordinate;

if ($this->isOverlapped($used, $coordinate)) {
$lines = [];
break;
}
}

if ($lines !== []) {
foreach ($lines as $line => $coords) {
$used[$line] = array_merge($used[$line] ?? [], $coords);
}
} else {
unset($changes[$key]);
}
}

// Return
return $changes;
}

/**
* @param array<int, array<int, Coordinate>> $coordinates
*/
private function isOverlapped(array $coordinates, Coordinate $coordinate): bool {
// Append?
if ($coordinate->line === PHP_INT_MAX) {
return false;
}

// Check
$overlapped = false;

foreach ($coordinates[$coordinate->line] ?? [] as $c) {
$aStart = $c->offset;
$aEnd = $aStart + ($c->length ?? PHP_INT_MAX) - 1;
$bStart = $coordinate->offset;
$bEnd = $bStart + ($coordinate->length ?? PHP_INT_MAX) - 1;
$overlapped = !($bEnd < $aStart || $bStart > $aEnd);

if ($overlapped) {
break;
}
}

return $overlapped;
return $editor;
}
}
99 changes: 0 additions & 99 deletions packages/documentator/src/Editor/EditorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@

namespace LastDragon_ru\LaraASP\Documentator\Editor;

use LastDragon_ru\LaraASP\Documentator\Editor\Locations\Append;
use LastDragon_ru\LaraASP\Documentator\Editor\Locations\Location;
use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase;
use Override;
use PHPUnit\Framework\Attributes\CoversClass;

use function array_values;
use function iterator_to_array;

use const PHP_INT_MAX;

/**
Expand Down Expand Up @@ -125,98 +120,4 @@ public function getLines(): array {
self::assertEquals($lines, $editor->getLines());
self::assertSame($expected, $actual->getLines());
}

public function testPrepare(): void {
$editor = new readonly class(['L1', 'L2']) 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 readonly class([]) extends Editor {
/**
* @inheritDoc
*/
#[Override]
public function removeOverlaps(array $changes): array {
return parent::removeOverlaps($changes);
}
};
$changes = [
0 => [array_values(iterator_to_array(new Location(18, 18, 5, 10))), 'a'],
1 => [array_values(iterator_to_array(new Location(17, 17, 11, 10))), 'b'],
2 => [array_values(iterator_to_array(new Location(17, 17, 5, 10))), 'c'],
3 => [array_values(iterator_to_array(new Location(14, 15, 5, 10))), 'd'],
4 => [array_values(iterator_to_array(new Location(12, 15, 5, 10))), 'e'],
5 => [array_values(iterator_to_array(new Location(10, 10, 10, null))), 'f'],
6 => [array_values(iterator_to_array(new Location(10, 10, 15, 10))), 'g'],
7 => [array_values(iterator_to_array(new Location(9, 9, 39, 11))), 'h'],
8 => [array_values(iterator_to_array(new Location(9, 9, 50, null))), 'i'],
9 => [array_values(iterator_to_array(new Location(9, 9, 40, 10))), 'j'],
10 => [array_values(iterator_to_array(new Location(PHP_INT_MAX, PHP_INT_MAX))), 'k'],
11 => [array_values(iterator_to_array(new Append())), 'l'],
];
$expected = [
0 => [iterator_to_array(new Location(18, 18, 5, 10)), 'a'],
1 => [iterator_to_array(new Location(17, 17, 11, 10)), 'b'],
3 => [iterator_to_array(new Location(14, 15, 5, 10)), 'd'],
5 => [iterator_to_array(new Location(10, 10, 10, null)), 'f'],
7 => [iterator_to_array(new Location(9, 9, 39, 11)), 'h'],
8 => [iterator_to_array(new Location(9, 9, 50, null)), 'i'],
10 => [iterator_to_array(new Location(PHP_INT_MAX, PHP_INT_MAX)), 'k'],
11 => [iterator_to_array(new Append()), 'l'],
];

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

public function testExpand(): void {
$editor = new readonly class([]) extends Editor {
/**
* @inheritDoc
*/
#[Override]
public function expand(array $changes): array {
return parent::expand($changes);
}
};
$changes = [
[array_values(iterator_to_array(new Location(PHP_INT_MAX, PHP_INT_MAX))), "new line aa\nnew line ab"],
[array_values(iterator_to_array(new Location(6, 6, 5, 10, 2))), "text aa\ntext ab"],
[array_values(iterator_to_array(new Location(4, 5, 5, 5, 1))), "text ba\ntext bb"],
[array_values(iterator_to_array(new Location(2, 3, 5, null))), 'text c'],
[array_values(iterator_to_array(new Location(1, 1, 5, 10))), "text da\ntext db\ntext dc"],
[array_values(iterator_to_array(new Append())), "new line ba\nnew line bb"],
];
$expected = [
[new Coordinate(6, 7, 10, 2), ['text aa', 'text ab']],
[new Coordinate(5, 1, 5, 1), ['text bb']],
[new Coordinate(4, 6, null, 1), ['text ba']],
[new Coordinate(3, 0, null, 0), []],
[new Coordinate(2, 5, null, 0), ['text c']],
[new Coordinate(1, 5, 10, 0), ['text da', 'text db', 'text dc']],
[new Coordinate(PHP_INT_MAX, 0, null, 0), ['new line ba', 'new line bb']],
[new Coordinate(PHP_INT_MAX, 0, null, 0), ['new line aa', 'new line ab']],
];

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

0 comments on commit 488645f

Please sign in to comment.