From df8739633dd8d13dd438f62fd0151017ad1158e8 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:33:03 +0400 Subject: [PATCH] Removed `\LastDragon_ru\LaraASP\Documentator\Markdown\Document::getTitle()`, `\LastDragon_ru\LaraASP\Documentator\Markdown\Document::getSummary()`, `\LastDragon_ru\LaraASP\Documentator\Markdown\Document::getBody()`. The new extractions will be used instead. --- .../documentator/src/Markdown/Document.php | 123 +-------- .../src/Markdown/DocumentTest.php | 248 ------------------ .../Markdown/Mutations/Heading/Renumber.php | 28 +- packages/documentator/src/Markdown/Utils.php | 26 ++ .../documentator/src/Markdown/UtilsTest.php | 50 ++++ .../IncludeDocBlock/Instruction.php | 6 +- .../IncludeDocumentList/Instruction.php | 14 +- .../IncludeDocumentList/Template/Document.php | 2 +- .../IncludePackageList/Instruction.php | 13 +- 9 files changed, 110 insertions(+), 400 deletions(-) diff --git a/packages/documentator/src/Markdown/Document.php b/packages/documentator/src/Markdown/Document.php index ed636cded..68f1bc7ca 100644 --- a/packages/documentator/src/Markdown/Document.php +++ b/packages/documentator/src/Markdown/Document.php @@ -2,42 +2,28 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown; -use Closure; use LastDragon_ru\LaraASP\Core\Path\FilePath; use LastDragon_ru\LaraASP\Documentator\Editor\Coordinate; use LastDragon_ru\LaraASP\Documentator\Editor\Editor; -use LastDragon_ru\LaraASP\Documentator\Editor\Locations\Location; use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Extraction; use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Markdown; use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Lines; -use League\CommonMark\Extension\CommonMark\Node\Block\Heading; -use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; -use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document as DocumentNode; -use League\CommonMark\Node\Block\Paragraph; -use League\CommonMark\Node\Node; use Override; use Stringable; use function array_key_first; -use function array_key_last; use function array_values; use function count; use function implode; -use function is_int; -use function mb_ltrim; use function mb_trim; -use function str_ends_with; -use function str_starts_with; // todo(documentator): There is no way to convert AST back to Markdown yet // https://github.com/thephpleague/commonmark/issues/419 class Document implements Stringable { - private ?Editor $editor = null; - private ?string $title = null; - private ?string $summary = null; + private ?Editor $editor = null; public function __construct( protected readonly Markdown $markdown, @@ -51,50 +37,6 @@ public function isEmpty(): bool { return !$this->node->hasChildren() && count($this->node->getReferenceMap()) === 0; } - /** - * Returns the first `# Header` if present. - */ - public function getTitle(): ?string { - if ($this->title === null) { - $title = $this->getFirstNode(Heading::class, static fn ($n) => $n->getLevel() === 1); - $title = $this->getBlockText($title) ?? ''; - $title = mb_trim(mb_ltrim("{$title}", '#')); - $this->title = $title; - } - - return $this->title !== '' ? $this->title : null; - } - - /** - * Returns the first paragraph if present. - */ - public function getSummary(): ?string { - if ($this->summary === null) { - $summary = $this->getSummaryNode(); - $summary = $this->getBlockText($summary); - $summary = mb_trim("{$summary}"); - $this->summary = $summary; - } - - return $this->summary !== '' ? $this->summary : null; - } - - /** - * Returns the rest of the document text after the summary. - */ - public function getBody(): ?string { - $summary = $this->getSummaryNode(); - $start = $summary?->getEndLine(); - $end = array_key_last($this->getLines()); - $body = $start !== null && is_int($end) - ? $this->getText(new Location($start + 1, $end)) - : null; - $body = mb_trim((string) $body); - $body = $body !== '' ? $body : null; - - return $body; - } - /** * @param iterable $location */ @@ -133,69 +75,6 @@ protected function getEditor(): Editor { return $this->editor; } - /** - * @template T of Node - * - * @param class-string $class - * @param Closure(T): bool|null $filter - * @param Closure(Node): bool|null $skip - * - * @return ?T - */ - private function getFirstNode(string $class, ?Closure $filter = null, ?Closure $skip = null): ?Node { - $node = null; - - foreach ($this->node->children() as $child) { - // Comment? - if ( - $child instanceof HtmlBlock - && str_starts_with($child->getLiteral(), '') - ) { - continue; - } - - // Skipped? - if ($skip !== null && $skip($child)) { - continue; - } - - // Wanted? - if ($child instanceof $class) { - if ($filter === null || $filter($child)) { - $node = $child; - } - - break; - } - - // End - break; - } - - return $node; - } - - private function getBlockText(?AbstractBlock $node): ?string { - $startLine = $node?->getStartLine(); - $endLine = $node?->getEndLine(); - $location = $startLine !== null && $endLine !== null - ? new Location($startLine, $endLine) - : null; - $text = $location !== null - ? $this->getText($location) - : null; - - return $text; - } - - private function getSummaryNode(): ?Paragraph { - $skip = static fn ($node) => $node instanceof Heading && $node->getLevel() === 1; - $node = $this->getFirstNode(Paragraph::class, skip: $skip); - - return $node; - } - #[Override] public function __toString(): string { $lines = $this->getLines(); diff --git a/packages/documentator/src/Markdown/DocumentTest.php b/packages/documentator/src/Markdown/DocumentTest.php index 7bbdee98c..fc74e0916 100644 --- a/packages/documentator/src/Markdown/DocumentTest.php +++ b/packages/documentator/src/Markdown/DocumentTest.php @@ -20,33 +20,6 @@ final class DocumentTest extends TestCase { // // ========================================================================= - #[DataProvider('dataProviderGetTitle')] - public function testGetTitle(?string $expected, string $content): void { - $markdown = $this->app()->make(Markdown::class); - $document = $markdown->parse($content); - $actual = $document->getTitle(); - - self::assertSame($expected, $actual); - } - - #[DataProvider('dataProviderGetSummary')] - public function testGetSummary(?string $expected, string $content): void { - $markdown = $this->app()->make(Markdown::class); - $document = $markdown->parse($content); - $actual = $document->getSummary(); - - self::assertSame($expected, $actual); - } - - #[DataProvider('dataProviderGetBody')] - public function testGetBody(?string $expected, string $content): void { - $markdown = $this->app()->make(Markdown::class); - $document = $markdown->parse($content); - $actual = $document->getBody(); - - self::assertSame($expected, $actual); - } - #[DataProvider('dataProviderIsEmpty')] public function testIsEmpty(bool $expected, string $content): void { $markdown = $this->app()->make(Markdown::class); @@ -106,227 +79,6 @@ public function testToString(string $expected, string $content, ?Mutation $mutat // // ========================================================================= - /** - * @return array - */ - public static function dataProviderGetTitle(): array { - return [ - 'No #' => [ - null, - <<<'MARKDOWN' - ## Header A - # Header B - MARKDOWN, - ], - 'The # is not first' => [ - null, - <<<'MARKDOWN' - fsdfsdfsdf - - # Header - MARKDOWN, - ], - 'The # is empty' => [ - null, - <<<'MARKDOWN' - # - - fsdfsdfsdf - MARKDOWN, - ], - 'Empty line before #' => [ - 'Header', - <<<'MARKDOWN' - - # Header - - fsdfsdfsdf - MARKDOWN, - ], - 'Comment before #' => [ - 'Header', - <<<'MARKDOWN' - - - # Header - - fsdfsdfsdf - MARKDOWN, - ], - ]; - } - - /** - * @return array - */ - public static function dataProviderGetSummary(): array { - return [ - 'The # is not first' => [ - null, - <<<'MARKDOWN' - ## Header A - # Header B - - sdfsdfsdf - MARKDOWN, - ], - 'Summary is the first node' => [ - 'fsdfsdfsdf', - <<<'MARKDOWN' - fsdfsdfsdf - - # Header - - sdfsdfsdf - MARKDOWN, - ], - 'Quote before #' => [ - null, - <<<'MARKDOWN' - # Header - - > Not a paragraph - - fsdfsdfsdf - MARKDOWN, - ], - 'Empty #' => [ - 'fsdfsdfsdf', - <<<'MARKDOWN' - # - - fsdfsdfsdf - MARKDOWN, - ], - 'Multiline' => [ - <<<'TEXT' - fsdfsdfsdf - fsdfsdfsdf - TEXT, - <<<'MARKDOWN' - - # Header - - fsdfsdfsdf - fsdfsdfsdf - MARKDOWN, - ], - 'Comments should be ignored' => [ - <<<'TEXT' - fsdfsdfsdf - fsdfsdfsdf - TEXT, - <<<'MARKDOWN' - - - # Header - - - - fsdfsdfsdf - fsdfsdfsdf - MARKDOWN, - ], - ]; - } - - /** - * @return array - */ - public static function dataProviderGetBody(): array { - return [ - 'The # is not first' => [ - null, - <<<'MARKDOWN' - ## Header A - # Header B - - sdfsdfsdf - MARKDOWN, - ], - 'Summary is the first node' => [ - <<<'TEXT' - # Header - - sdfsdfsdf - TEXT, - <<<'MARKDOWN' - fsdfsdfsdf - - # Header - - sdfsdfsdf - MARKDOWN, - ], - 'Quote before #' => [ - null, - <<<'MARKDOWN' - # Header - - > Not a paragraph - - fsdfsdfsdf - - text text text - MARKDOWN, - ], - 'Empty #' => [ - <<<'TEXT' - text text text - - text text text - TEXT, - <<<'MARKDOWN' - # - - fsdfsdfsdf - - text text text - - text text text - MARKDOWN, - ], - 'Multiline summary' => [ - <<<'TEXT' - text text text - - text text text - TEXT, - <<<'MARKDOWN' - - # Header - - fsdfsdfsdf - fsdfsdfsdf - - text text text - - text text text - MARKDOWN, - ], - 'Comments should be ignored' => [ - <<<'TEXT' - - - text text text - TEXT, - <<<'MARKDOWN' - - - # Header - - - - summary - - - - text text text - MARKDOWN, - ], - ]; - } - /** * @return array */ diff --git a/packages/documentator/src/Markdown/Mutations/Heading/Renumber.php b/packages/documentator/src/Markdown/Mutations/Heading/Renumber.php index 40893561d..9d5a53441 100644 --- a/packages/documentator/src/Markdown/Mutations/Heading/Renumber.php +++ b/packages/documentator/src/Markdown/Mutations/Heading/Renumber.php @@ -5,19 +5,14 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Location as LocationData; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; -use LastDragon_ru\LaraASP\Documentator\Utils\Text; +use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use Override; -use function array_map; -use function array_slice; -use function implode; -use function mb_ltrim; use function mb_rtrim; use function mb_strlen; -use function mb_trim; use function min; use function str_repeat; -use function str_starts_with; +use function str_replace; /** * Updates all headings levels. @@ -52,23 +47,18 @@ public function __invoke(Document $document): iterable { foreach ($nodes as $node) { $location = LocationData::get($node); $heading = $document->getText($location); - $setext = $this->isSetext($heading); + $setext = Utils::isHeadingSetext($heading); $level = min(static::MaxLevel, $node->getLevel() + $diff); $eols = str_repeat("\n", mb_strlen($heading) - mb_strlen(mb_rtrim($heading, "\n"))); - $text = mb_trim($heading); - $lines = $setext - ? array_slice(Text::getLines($text), 0, -1) - : [mb_trim($text, '#')]; - $lines = array_map(mb_trim(...), $lines); + $text = Utils::getHeadingText($heading); $prefix = ''; $suffix = ''; if ($setext && $level <= 2) { - $text = implode("\n", $lines); $suffix = "\n".str_repeat($level === 1 ? '=' : '-', 5); } else { $prefix = str_repeat('#', $level).' '; - $text = implode(' ', $lines); + $text = str_replace("\n", ' ', $text); } yield [$location, $prefix.$text.$suffix.$eols]; @@ -89,12 +79,4 @@ protected function nodes(Document $document, int &$initial = 0): iterable { return $nodes; } - - private function isAtx(string $heading): bool { - return str_starts_with(mb_ltrim($heading), '#'); - } - - private function isSetext(string $heading): bool { - return !$this->isAtx($heading); - } } diff --git a/packages/documentator/src/Markdown/Utils.php b/packages/documentator/src/Markdown/Utils.php index 563e30d55..8cf699d7d 100644 --- a/packages/documentator/src/Markdown/Utils.php +++ b/packages/documentator/src/Markdown/Utils.php @@ -3,12 +3,18 @@ namespace LastDragon_ru\LaraASP\Documentator\Markdown; use LastDragon_ru\LaraASP\Core\Path\FilePath; +use LastDragon_ru\LaraASP\Documentator\Utils\Text; use League\CommonMark\Extension\Table\TableCell; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Node; use League\CommonMark\Util\UrlEncoder; +use function array_map; +use function array_slice; use function filter_var; +use function implode; +use function mb_ltrim; +use function mb_trim; use function parse_url; use function preg_match; use function str_contains; @@ -127,4 +133,24 @@ public static function isPathToSelf(Document $document, FilePath|string $path): return $is; } + + public static function isHeadingAtx(string $heading): bool { + return str_starts_with(mb_ltrim($heading), '#'); + } + + public static function isHeadingSetext(string $heading): bool { + return !static::isHeadingAtx($heading); + } + + public static function getHeadingText(string $heading): string { + $heading = mb_trim($heading); + $setext = static::isHeadingSetext($heading); + $lines = $setext + ? array_slice(Text::getLines($heading), 0, -1) + : [mb_trim($heading, '#')]; + $lines = array_map(mb_trim(...), $lines); + $text = implode($setext ? "\n" : '', $lines); + + return $text; + } } diff --git a/packages/documentator/src/Markdown/UtilsTest.php b/packages/documentator/src/Markdown/UtilsTest.php index ba7bf3936..447b3e8a4 100644 --- a/packages/documentator/src/Markdown/UtilsTest.php +++ b/packages/documentator/src/Markdown/UtilsTest.php @@ -52,4 +52,54 @@ public function testIsPathToSelf(): void { self::assertTrue(Utils::isPathToSelf($a, new FilePath('a.md'))); self::assertTrue(Utils::isPathToSelf($a, new FilePath('../to/a.md'))); } + + public function testIsHeadingAtx(): void { + self::assertTrue(Utils::isHeadingAtx('# Header')); + self::assertFalse( + Utils::isHeadingAtx( + <<<'MARKDOWN' + Header + ------ + MARKDOWN, + ), + ); + } + + public function testIsHeadingSetext(): void { + self::assertFalse(Utils::isHeadingSetext('# Header')); + self::assertTrue( + Utils::isHeadingSetext( + <<<'MARKDOWN' + Header + ------ + MARKDOWN, + ), + ); + } + + public function testGetHeadingText(): void { + self::assertSame( + 'Header', + Utils::getHeadingText( + <<<'MARKDOWN' + # Header + + MARKDOWN, + ), + ); + self::assertSame( + <<<'TEXT' + Header + line b + TEXT, + Utils::getHeadingText( + <<<'MARKDOWN' + Header + line b + ====== + + MARKDOWN, + ), + ); + } } diff --git a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocBlock/Instruction.php b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocBlock/Instruction.php index dfc532e8d..15eecd6b8 100644 --- a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocBlock/Instruction.php +++ b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocBlock/Instruction.php @@ -5,6 +5,8 @@ use Generator; use LastDragon_ru\LaraASP\Core\Utils\Cast; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Document\Body; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Document\Summary; use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Dependency; use LastDragon_ru\LaraASP\Documentator\Processor\Dependencies\FileReference; use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File; @@ -59,8 +61,8 @@ public function __invoke(Context $context, InstructionParameters $parameters): G // Parse $result = match (true) { $parameters->summary && $parameters->description => $document, - $parameters->summary => $context->toSplittable($document)->getSummary() ?? '', - $parameters->description => $context->toSplittable($document)->getBody() ?? '', + $parameters->summary => (string) $context->toSplittable($document)->mutate(new Summary()), + $parameters->description => (string) $context->toSplittable($document)->mutate(new Body()), default => '', }; diff --git a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Instruction.php b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Instruction.php index 358c005e2..b5d04e565 100644 --- a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Instruction.php +++ b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Instruction.php @@ -5,6 +5,10 @@ use Generator; use Iterator; use LastDragon_ru\LaraASP\Core\Utils\Cast; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Document\Summary; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Document\Title; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Link\Unlink; +use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use LastDragon_ru\LaraASP\Documentator\PackageViewer; use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Dependency; use LastDragon_ru\LaraASP\Documentator\Processor\Dependencies\FileIterator; @@ -23,7 +27,9 @@ use function array_filter; use function max; +use function mb_trim; use function min; +use function str_replace; use function usort; /** @@ -85,10 +91,14 @@ public function __invoke(Context $context, InstructionParameters $parameters): G // Add $document = $context->toSplittable($document); + $summary = mb_trim((string) $document->mutate(new Summary())); + $title = mb_trim((string) $document->mutate(new Title(), new Unlink())); + $title = mb_trim(str_replace("\n", ' ', Utils::getHeadingText($title))); + $title = $title === '' ? Text::getPathTitle($file->getName()) : $title; $documents[] = new TemplateDocument( $context->file->getRelativePath($file), - $document->getTitle() ?? Text::getPathTitle($file->getName()), - $document->getSummary(), + $title, + $summary, ); } diff --git a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Template/Document.php b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Template/Document.php index bbe68b780..69768e531 100644 --- a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Template/Document.php +++ b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludeDocumentList/Template/Document.php @@ -8,7 +8,7 @@ public function __construct( public FilePath $path, public string $title, - public ?string $summary, + public string $summary, ) { // empty } diff --git a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludePackageList/Instruction.php b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludePackageList/Instruction.php index 75ac1f470..9ed110970 100644 --- a/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludePackageList/Instruction.php +++ b/packages/documentator/src/Processor/Tasks/Preprocess/Instructions/IncludePackageList/Instruction.php @@ -5,6 +5,10 @@ use Generator; use Iterator; use LastDragon_ru\LaraASP\Core\Utils\Cast; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Document\Summary; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Document\Title; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Link\Unlink; +use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use LastDragon_ru\LaraASP\Documentator\Package; use LastDragon_ru\LaraASP\Documentator\PackageViewer; use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Dependency; @@ -25,6 +29,8 @@ use LastDragon_ru\LaraASP\Documentator\Utils\Text; use Override; +use function mb_trim; +use function str_replace; use function trigger_deprecation; use function usort; @@ -98,10 +104,13 @@ public function __invoke(Context $context, InstructionParameters $parameters): G $content = $context->toSplittable($content); $upgrade = $package->getFilePath('UPGRADE.md'); $upgrade = Cast::toNullable(File::class, yield new Optional(new FileReference($upgrade))); + $title = mb_trim((string) $content->mutate(new Title(), new Unlink())); + $title = mb_trim(str_replace("\n", ' ', Utils::getHeadingText($title))); + $title = $title === '' ? Text::getPathTitle($package->getName()) : $title; $packages[] = [ 'path' => $context->file->getRelativePath($readme), - 'title' => $content->getTitle() ?? Text::getPathTitle("{$package->getName()}.md"), - 'summary' => $content->getSummary(), + 'title' => $title, + 'summary' => mb_trim((string) $content->mutate(new Summary())), 'upgrade' => $upgrade !== null ? $context->file->getRelativePath($upgrade) : null,