From caaa368533f6cda17bac9c84255d5d979439c513 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 26 Aug 2023 13:42:18 +0400 Subject: [PATCH] feat(documentator): `IncludeExample` can include raw markdown output via `raw` tag. --- .../documentator/docs/commands/preprocess.md | 4 ++ .../Instructions/IncludeExample.php | 65 +++++++++++++++---- .../Instructions/IncludeExampleTest.php | 64 +++++++++++++++++- 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/packages/documentator/docs/commands/preprocess.md b/packages/documentator/docs/commands/preprocess.md index 064cdabeb..9cea34285 100644 --- a/packages/documentator/docs/commands/preprocess.md +++ b/packages/documentator/docs/commands/preprocess.md @@ -35,6 +35,10 @@ Includes contents of the `` file as an example wrapped into ` ```code block``` `. It also searches for `.run` file, execute it if found, and include its result right after the code block. +By default, output of `.run` will be included as ` ```plain text``` ` +block. You can wrap the output into `text` tags to +insert it as is. + #### `[include:exec]: ` * `` - Path to the executable. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php index 216ae3293..ad98bac3b 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -10,13 +10,17 @@ use function dirname; use function is_file; use function pathinfo; +use function preg_match; use function preg_match_all; +use function preg_replace_callback; use function trim; use const PATHINFO_EXTENSION; +use const PREG_UNMATCHED_AS_NULL; class IncludeExample extends IncludeFile { - public const Limit = 20; + public const Limit = 20; + protected const MarkdownRegexp = '/^\<(?Pmarkdown)\>(?P.*?)\<\/(?P=tag)\>$/msu'; public function __construct( protected readonly Process $process, @@ -33,6 +37,10 @@ public static function getDescription(): string { Includes contents of the `` file as an example wrapped into ` ```code block``` `. It also searches for `.run` file, execute it if found, and include its result right after the code block. + + By default, output of `.run` will be included as ` ```plain text``` ` + block. You can wrap the output into `text` tags to + insert it as is. DESC; } @@ -51,30 +59,61 @@ public function process(string $path, string $target): string { $command = $this->getCommand($path, $target); if ($command) { + // Call try { $output = $this->process->run($command, dirname($path)); + $output = trim($output); } catch (Exception $exception) { throw new TargetExecFailed($path, $target, $exception); } - if (preg_match_all('/\R/u', $output) > static::Limit) { + // Markdown? + $isMarkdown = (bool) preg_match(static::MarkdownRegexp, $output); + + if ($isMarkdown) { + $output = trim( + (string) preg_replace_callback( + pattern : static::MarkdownRegexp, + callback: static function (array $matches): string { + return $matches['markdown']; + }, + subject : $output, + flags : PREG_UNMATCHED_AS_NULL, + ), + ); + } + + // Format + $isTooLong = preg_match_all('/\R/u', $output) > static::Limit; + + if ($isMarkdown && $isTooLong) { + $output = <<Example output + + {$output} + + + CODE; + } elseif ($isMarkdown) { + // as is + } elseif ($isTooLong) { $output = <<Output +
Example output - ```plain - $output - ``` + ```plain + {$output} + ``` -
- CODE; + + CODE; } else { $output = <<Output +
Example output ```plain {$output} @@ -106,4 +106,64 @@ public function testProcessLongOutput(): void { $instance->process($path, $file), ); } + + public function testProcessMarkdown(): void { + $path = self::getTestData()->path('~runnable.md'); + $file = basename(self::getTestData()->path('~runnable.md')); + $expected = trim(self::getTestData()->content('~runnable.md')); + $output = 'command output'; + $process = Mockery::mock(Process::class); + $process + ->shouldReceive('run') + ->with([self::getTestData()->path('~runnable.run')], dirname($path)) + ->once() + ->andReturn("{$output}"); + + $instance = $this->app->make(IncludeExample::class, [ + 'process' => $process, + ]); + + self::assertEquals( + <<process($path, $file), + ); + } + + public function testProcessMarkdownLongOutput(): void { + $path = self::getTestData()->path('~runnable.md'); + $file = self::getTestData()->path('~runnable.md'); + $expected = trim(self::getTestData()->content('~runnable.md')); + $output = implode("\n", range(0, IncludeExample::Limit + 1)); + $process = Mockery::mock(Process::class); + $process + ->shouldReceive('run') + ->with([self::getTestData()->path('~runnable.run')], dirname($path)) + ->once() + ->andReturn("{$output}"); + + $instance = $this->app->make(IncludeExample::class, [ + 'process' => $process, + ]); + + self::assertEquals( + <<Example output + + {$output} + +
+ EXPECTED, + $instance->process($path, $file), + ); + } }