Skip to content

Commit 7416d45

Browse files
feat(documentator)!: Added \LastDragon_ru\LaraASP\Documentator\Editor\Editor::extract() to extract text by coordinates (#217)
2 parents 528f6d4 + 488645f commit 7416d45

File tree

13 files changed

+795
-334
lines changed

13 files changed

+795
-334
lines changed

packages/documentator/src/Editor/Editor.php

+19-206
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,22 @@
22

33
namespace LastDragon_ru\LaraASP\Documentator\Editor;
44

5+
use LastDragon_ru\LaraASP\Documentator\Editor\Mutators\Extractor;
6+
use LastDragon_ru\LaraASP\Documentator\Editor\Mutators\Mutator;
57
use LastDragon_ru\LaraASP\Documentator\Utils\Text;
68
use Override;
79
use Stringable;
810

9-
use function array_merge;
10-
use function array_push;
11-
use function array_reverse;
12-
use function array_slice;
13-
use function array_splice;
14-
use function array_values;
15-
use function count;
1611
use function implode;
1712
use function is_string;
18-
use function mb_rtrim;
19-
use function mb_substr;
20-
use function usort;
21-
22-
use const PHP_INT_MAX;
2313

2414
readonly class Editor implements Stringable {
2515
/**
2616
* @var list<string>
2717
*/
28-
protected array $lines;
18+
protected array $lines;
19+
protected Mutator $mutator;
20+
protected Extractor $extractor;
2921

3022
/**
3123
* @param list<string>|string $content
@@ -35,7 +27,9 @@ final public function __construct(
3527
protected int $startLine = 0,
3628
protected string $endOfLine = "\n",
3729
) {
38-
$this->lines = is_string($content) ? Text::getLines($content) : $content;
30+
$this->lines = is_string($content) ? Text::getLines($content) : $content;
31+
$this->mutator = new Mutator();
32+
$this->extractor = new Extractor();
3933
}
4034

4135
#[Override]
@@ -44,207 +38,26 @@ public function __toString(): string {
4438
}
4539

4640
/**
47-
* @param iterable<array-key, Coordinate> $location
48-
*/
49-
public function getText(iterable $location): ?string {
50-
// Select
51-
$selected = null;
52-
53-
foreach ($location as $coordinate) {
54-
$number = $coordinate->line - $this->startLine;
55-
56-
if (isset($this->lines[$number])) {
57-
$selected[] = mb_substr($this->lines[$number], $coordinate->offset, $coordinate->length);
58-
} else {
59-
$selected = null;
60-
break;
61-
}
62-
}
63-
64-
if ($selected === null) {
65-
return null;
66-
}
67-
68-
// Return
69-
return implode($this->endOfLine, $selected);
70-
}
71-
72-
/**
73-
* @param iterable<array-key, array{iterable<array-key, Coordinate>, ?string}> $changes
41+
* @param iterable<mixed, iterable<mixed, Coordinate>> $locations
7442
*
7543
* @return new<static>
7644
*/
77-
public function mutate(iterable $changes): static {
78-
// Modify
79-
$lines = $this->lines;
80-
$changes = $this->prepare($changes);
81-
$changes = $this->removeOverlaps($changes);
82-
$changes = $this->expand($changes);
83-
84-
foreach ($changes as [$coordinate, $text]) {
85-
// Append?
86-
if ($coordinate->line === PHP_INT_MAX) {
87-
array_push($lines, ...$text);
88-
continue;
89-
}
90-
91-
// Change
92-
$number = $coordinate->line - $this->startLine;
93-
$line = $lines[$number] ?? '';
94-
$count = count($text);
95-
$prefix = mb_substr($line, 0, $coordinate->offset);
96-
$suffix = $coordinate->length !== null
97-
? mb_substr($line, $coordinate->offset + $coordinate->length)
98-
: '';
99-
$padding = mb_substr($line, 0, $coordinate->padding);
100-
101-
if ($count > 1) {
102-
$insert = [];
103-
104-
for ($t = 0; $t < $count; $t++) {
105-
$insert[] = match (true) {
106-
$t === 0 => mb_rtrim($prefix.$text[$t]),
107-
$t === $count - 1 => mb_rtrim($padding.$text[$t].$suffix),
108-
default => mb_rtrim($padding.$text[$t]),
109-
};
110-
}
111-
112-
array_splice($lines, $number, 1, $insert);
113-
} elseif ($count === 1) {
114-
$lines[$number] = mb_rtrim($prefix.$text[0].$suffix);
115-
} elseif (($prefix !== '' && $prefix !== $padding) || $suffix !== '') {
116-
$lines[$number] = mb_rtrim($prefix.$suffix);
117-
} else {
118-
unset($lines[$number]);
119-
}
120-
}
121-
122-
// Return
123-
return new static(array_values($lines), $this->startLine, $this->endOfLine);
124-
}
125-
126-
/**
127-
* @param iterable<array-key, array{iterable<array-key, Coordinate>, ?string}> $changes
128-
*
129-
* @return list<array{list<Coordinate>, ?string}>
130-
*/
131-
protected function prepare(iterable $changes): array {
132-
$prepared = [];
133-
134-
foreach ($changes as [$location, $text]) {
135-
$coordinates = [];
136-
137-
foreach ($location as $coordinate) {
138-
$coordinates[] = $coordinate;
139-
}
140-
141-
if ($coordinates !== []) {
142-
$prepared[] = [$coordinates, $text];
143-
}
144-
}
145-
146-
return array_reverse($prepared);
147-
}
148-
149-
/**
150-
* @param array<int, array{list<Coordinate>, ?string}> $changes
151-
*
152-
* @return list<array{Coordinate, list<string>}>
153-
*/
154-
protected function expand(array $changes): array {
155-
$expanded = [];
156-
$append = [];
157-
$sort = static function (Coordinate $a, Coordinate $b): int {
158-
$result = $a->line <=> $b->line;
159-
$result = $result === 0
160-
? $a->offset <=> $b->offset
161-
: $result;
162-
163-
return $result;
164-
};
165-
166-
foreach ($changes as [$coordinates, $text]) {
167-
$text = match (true) {
168-
$text === null => [],
169-
$text === '' => [''],
170-
default => Text::getLines($text),
171-
};
45+
public function extract(iterable $locations): static {
46+
$extracted = ($this->extractor)($this->lines, $locations, $this->startLine);
47+
$editor = new static($extracted, $this->startLine, $this->endOfLine);
17248

173-
usort($coordinates, $sort);
174-
175-
for ($i = 0, $c = count($coordinates); $i < $c; $i++) {
176-
$line = $i === $c - 1 ? array_slice($text, $i) : (array) ($text[$i] ?? null);
177-
178-
if ($coordinates[$i]->line === PHP_INT_MAX) {
179-
$append[] = [$coordinates[$i], $line];
180-
} else {
181-
$expanded[] = [$coordinates[$i], $line];
182-
}
183-
}
184-
}
185-
186-
usort($expanded, static fn ($a, $b) => -$sort($a[0], $b[0]));
187-
188-
return array_merge($expanded, array_reverse($append));
49+
return $editor;
18950
}
19051

19152
/**
192-
* @param list<array{list<Coordinate>, ?string}> $changes
53+
* @param iterable<mixed, array{iterable<mixed, Coordinate>, ?string}> $changes
19354
*
194-
* @return array<int, array{list<Coordinate>, ?string}>
195-
*/
196-
protected function removeOverlaps(array $changes): array {
197-
$used = [];
198-
199-
foreach ($changes as $key => [$coordinates]) {
200-
$lines = [];
201-
202-
foreach ($coordinates as $coordinate) {
203-
$lines[$coordinate->line][] = $coordinate;
204-
205-
if ($this->isOverlapped($used, $coordinate)) {
206-
$lines = [];
207-
break;
208-
}
209-
}
210-
211-
if ($lines !== []) {
212-
foreach ($lines as $line => $coords) {
213-
$used[$line] = array_merge($used[$line] ?? [], $coords);
214-
}
215-
} else {
216-
unset($changes[$key]);
217-
}
218-
}
219-
220-
// Return
221-
return $changes;
222-
}
223-
224-
/**
225-
* @param array<int, array<int, Coordinate>> $coordinates
55+
* @return new<static>
22656
*/
227-
private function isOverlapped(array $coordinates, Coordinate $coordinate): bool {
228-
// Append?
229-
if ($coordinate->line === PHP_INT_MAX) {
230-
return false;
231-
}
232-
233-
// Check
234-
$overlapped = false;
235-
236-
foreach ($coordinates[$coordinate->line] ?? [] as $c) {
237-
$aStart = $c->offset;
238-
$aEnd = $aStart + ($c->length ?? PHP_INT_MAX) - 1;
239-
$bStart = $coordinate->offset;
240-
$bEnd = $bStart + ($coordinate->length ?? PHP_INT_MAX) - 1;
241-
$overlapped = !($bEnd < $aStart || $bStart > $aEnd);
242-
243-
if ($overlapped) {
244-
break;
245-
}
246-
}
57+
public function mutate(iterable $changes): static {
58+
$mutated = ($this->mutator)($this->lines, $changes, $this->startLine);
59+
$editor = new static($mutated, $this->startLine, $this->endOfLine);
24760

248-
return $overlapped;
61+
return $editor;
24962
}
25063
}

0 commit comments

Comments
 (0)