diff --git a/packages/dev/composer.json b/packages/dev/composer.json
index 799068231..ca79e4fe8 100644
--- a/packages/dev/composer.json
+++ b/packages/dev/composer.json
@@ -19,15 +19,14 @@
},
"require": {
"php": "^8.1|^8.2|^8.3",
- "illuminate/console": "^10.34.0|^11.0.0",
"illuminate/contracts": "^10.34.0|^11.0.0",
"illuminate/support": "^10.34.0|^11.0.0",
"larastan/larastan": "^2.8.1",
"lastdragon-ru/lara-asp-core": "self.version",
+ "lastdragon-ru/lara-asp-documentator": "self.version",
"nikic/php-parser": "^4.18|^5.0",
"nette/neon": "^3.4",
"phpstan/phpstan": "^1.10",
- "symfony/console": "^6.3.0|^7.0.0",
"symfony/var-dumper": "^6.3.0|^7.0.0"
},
"require-dev": {
@@ -52,6 +51,8 @@
"sort-packages": true,
"optimize-autoloader": true
},
+ "minimum-stability": "dev",
+ "prefer-stable": true,
"repositories": {
"core": {
"type": "path",
diff --git a/packages/dev/src/App/Example.php b/packages/dev/src/App/Example.php
index 8a442ff32..48beec065 100644
--- a/packages/dev/src/App/Example.php
+++ b/packages/dev/src/App/Example.php
@@ -2,12 +2,14 @@
namespace LastDragon_ru\LaraASP\Dev\App;
-use Illuminate\Console\Command;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Foundation\Application;
-use LastDragon_ru\LaraASP\Core\Utils\Cast;
+use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver;
use LastDragon_ru\LaraASP\Core\Utils\ConfigMerger;
+use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Contracts\Runner;
+use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use LogicException;
+use Override;
use PhpParser\ErrorHandler\Collecting;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
@@ -17,36 +19,29 @@
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use Stringable;
-use Symfony\Component\Console\Attribute\AsCommand;
use function array_map;
use function array_slice;
use function debug_backtrace;
use function end;
-use function file;
use function implode;
+use function preg_split;
use function sprintf;
+use function str_contains;
use function trim;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
-#[AsCommand(
- name : Example::Name,
- description: 'Executes example file within Application context.',
-)]
-final class Example extends Command {
- private const Name = 'dev:example';
-
+final class Example implements Runner {
private static ?Application $app = null;
+ private static ?File $file = null;
private static ?Dumper $dumper = null;
- /**
- * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint
- * @var string
- */
- protected $signature = self::Name.<<<'SIGNATURE'
- {file : example file}
- SIGNATURE;
+ public function __construct(
+ private readonly ApplicationResolver $appResolver,
+ ) {
+ // empty
+ }
protected static function getDumper(): Dumper {
if (!self::$dumper) {
@@ -61,28 +56,39 @@ protected static function getDumper(): Dumper {
return self::$dumper;
}
- public function __invoke(Dumper $dumper): void {
- self::$dumper = $dumper;
- self::$app = $this->laravel;
- $file = Cast::toString($this->argument('file'));
+ #[Override]
+ public function __invoke(File $file): ?string {
+ // Runnable?
+ if ($file->getExtension() !== 'php' || !str_contains($file->getContent(), 'Example::')) {
+ return null;
+ }
+
+ // Run
+ $result = null;
+ self::$app = $this->appResolver->getInstance();
+ self::$file = $file;
+ self::$dumper = self::$app->make(Dumper::class);
try {
- // Run
+ // Execute
(static function () use ($file): void {
- include $file;
+ include $file->getPath();
})();
// Output
- $dumps = $dumper->getDumps();
+ $dumps = self::$dumper->getDumps();
$output = implode("\n\n", array_map(trim(...), $dumps));
if ($output) {
- $this->output->writeln("{$output}");
+ $result = "{$output}";
}
} finally {
self::$app = null;
+ self::$file = null;
self::$dumper = null;
}
+
+ return $result;
}
public static function dump(mixed $value, string $expression = null): void {
@@ -130,7 +136,8 @@ private static function getExpression(string $method): ?string {
}
// Extract first arg
- $code = implode("\n", array_slice((array) file($context['file']), $context['line'] - 1));
+ $lines = preg_split('/\R/u', self::$file?->getContent() ?? '') ?: [];
+ $code = implode("\n", array_slice($lines, $context['line'] - 1));
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$stmts = (array) $parser->parse("findFirst($stmts, static function (Node $node) use ($method): bool {
diff --git a/packages/dev/src/App/Provider.php b/packages/dev/src/App/Provider.php
index a3e68d62a..f699645f1 100644
--- a/packages/dev/src/App/Provider.php
+++ b/packages/dev/src/App/Provider.php
@@ -3,11 +3,14 @@
namespace LastDragon_ru\LaraASP\Dev\App;
use Illuminate\Support\ServiceProvider;
+use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Contracts\Runner;
+use Override;
class Provider extends ServiceProvider {
- public function boot(): void {
- $this->commands(
- Example::class,
- );
+ #[Override]
+ public function register(): void {
+ parent::register();
+
+ $this->app->bind(Runner::class, Example::class);
}
}
diff --git a/packages/documentator/UPGRADE.md b/packages/documentator/UPGRADE.md
index ab034f82c..62e044d27 100644
--- a/packages/documentator/UPGRADE.md
+++ b/packages/documentator/UPGRADE.md
@@ -41,6 +41,8 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases)
* `\LastDragon_ru\LaraASP\Documentator\Preprocessor\Contracts\Instruction`
* `\LastDragon_ru\LaraASP\Documentator\Preprocessor\Contracts\Resolver`.
+* [ ] Instruction `include:example` not check/run `.run` file anymore. The `\LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Contracts\Runner` should be used/provided instead.
+
# Upgrade from v5
[include:file]: ../../docs/Shared/Upgrade/FromV5.md
diff --git a/packages/documentator/docs/Commands/preprocess.md b/packages/documentator/docs/Commands/preprocess.md
index 4f54d2ce9..14874337d 100644
--- a/packages/documentator/docs/Commands/preprocess.md
+++ b/packages/documentator/docs/Commands/preprocess.md
@@ -65,10 +65,10 @@ after the Header will be used as a summary.
* `` - File path.
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.
+` ```code block``` `. If {@see Runner} bound, it will be called to execute
+the example. Its return value will be added right after the code block.
-By default, output of `.run` will be included as ` ```plain text``` `
+By default, the `Runner` return value will be included as ` ```plain text``` `
block. You can wrap the output into `text` tags to
insert it as is.
diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Contracts/Runner.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Contracts/Runner.php
new file mode 100644
index 000000000..14a95854c
--- /dev/null
+++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Contracts/Runner.php
@@ -0,0 +1,9 @@
+example->getRelativePath($context->root),
+ $context->file->getRelativePath($context->root),
+ ),
+ $previous,
+ );
+ }
+
+ public function getExample(): File {
+ return $this->example;
+ }
+}
diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Instruction.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Instruction.php
index 4e727d576..c83a9f01c 100644
--- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Instruction.php
+++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/Instruction.php
@@ -2,31 +2,28 @@
namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample;
-use Exception;
-use Illuminate\Process\Factory;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Contracts\Instruction as InstructionContract;
-use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetExecFailed;
+use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Contracts\Runner;
+use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Exceptions\ExampleFailed;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Resolvers\FileResolver;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use Override;
+use Throwable;
-use function dirname;
-use function pathinfo;
use function preg_match;
use function preg_match_all;
use function preg_replace_callback;
use function trim;
-use const PATHINFO_FILENAME;
use const PREG_UNMATCHED_AS_NULL;
/**
* 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.
+ * ` ```code block``` `. If {@see Runner} bound, it will be called to execute
+ * the example. Its return value will be added right after the code block.
*
- * By default, output of `.run` will be included as ` ```plain text``` `
+ * By default, the `Runner` return value will be included as ` ```plain text``` `
* block. You can wrap the output into `text` tags to
* insert it as is.
*
@@ -37,7 +34,7 @@ class Instruction implements InstructionContract {
protected const MarkdownRegexp = '/^\<(?Pmarkdown)\>(?P.*?)\<\/(?P=tag)\>$/msu';
public function __construct(
- protected readonly Factory $factory,
+ protected readonly ?Runner $runner = null,
) {
// empty
}
@@ -68,17 +65,13 @@ public function __invoke(Context $context, mixed $target, mixed $parameters): st
```
CODE;
- // Command?
- $command = $this->getCommand($context, $target, $parameters);
-
- if ($command) {
- // Call
+ // Runner?
+ if ($this->runner) {
+ // Run
try {
- $dir = dirname($target->getPath());
- $output = $this->factory->newPendingProcess()->path($dir)->run($command)->throw()->output();
- $output = trim($output);
- } catch (Exception $exception) {
- throw new TargetExecFailed($context, $exception);
+ $output = trim((string) ($this->runner)($target));
+ } catch (Throwable $exception) {
+ throw new ExampleFailed($context, $target, $exception);
}
// Markdown?
@@ -120,7 +113,7 @@ public function __invoke(Context $context, mixed $target, mixed $parameters): st
CODE;
- } else {
+ } elseif ($output) {
$output = <<getExtension();
}
-
- protected function getCommand(Context $context, File $target, mixed $parameters): ?string {
- $command = pathinfo($target->getName(), PATHINFO_FILENAME).'.run';
- $command = $context->root->getDirectory($target)?->getFile($command)?->getPath();
-
- return $command;
- }
}
diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~example.md b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest.md
similarity index 100%
rename from packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~example.md
rename to packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest.md
diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest.php
index d56133b16..0aeb21240 100644
--- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest.php
+++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest.php
@@ -2,14 +2,16 @@
namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample;
-use Illuminate\Process\Factory;
-use Illuminate\Process\PendingProcess;
+use LastDragon_ru\LaraASP\Core\Utils\Path;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
+use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Contracts\Runner;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use LastDragon_ru\LaraASP\Documentator\Testing\Package\ProcessorHelper;
use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase;
+use Mockery\MockInterface;
use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
use function dirname;
use function implode;
@@ -21,141 +23,39 @@
*/
#[CoversClass(Instruction::class)]
final class InstructionTest extends TestCase {
- public function testInvokeNoRun(): void {
- $path = self::getTestData()->path('~example.md');
- $root = new Directory(dirname($path), false);
- $file = new File($path, false);
- $params = null;
- $context = new Context($root, $file, $file->getName(), $params);
- $content = self::getTestData()->content('~example.md');
- $expected = trim($content);
- $factory = $this->override(Factory::class, function (): Factory {
- return $this->app()->make(Factory::class)
- ->preventStrayProcesses()
- ->fake();
+ //
+ // =========================================================================
+ #[DataProvider('dataProviderInvoke')]
+ public function testInvoke(string $expected, string $output): void {
+ $path = Path::normalize(self::getTestData()->path('.md'));
+ $root = new Directory(dirname($path), false);
+ $file = new File($path, false);
+ $params = null;
+ $context = new Context($root, $file, $file->getName(), $params);
+
+ $this->override(Runner::class, static function (MockInterface $mock) use ($file, $output): void {
+ $mock
+ ->shouldReceive('__invoke')
+ ->with($file)
+ ->once()
+ ->andReturn($output);
});
- $instance = $this->app()->make(Instruction::class);
- $actual = ProcessorHelper::runInstruction($instance, $context, $file, $params);
- self::assertEquals(
- <<assertNothingRan();
- }
-
- public function testInvoke(): void {
- $path = self::getTestData()->path('~runnable.md');
- $root = new Directory(dirname($path), false);
- $file = new File($path, false);
- $params = null;
- $context = new Context($root, $file, $file->getName(), $params);
- $content = self::getTestData()->content('~runnable.md');
- $command = self::getTestData()->path('~runnable.run');
- $expected = trim($content);
- $output = 'command output';
- $factory = $this->override(Factory::class, function () use ($command, $output): Factory {
- $factory = $this->app()->make(Factory::class);
- $factory->preventStrayProcesses();
- $factory->fake([
- $command => $factory->result($output),
- ]);
-
- return $factory;
- });
$instance = $this->app()->make(Instruction::class);
$actual = ProcessorHelper::runInstruction($instance, $context, $file, $params);
- self::assertEquals(
- <<assertRan(static function (PendingProcess $process) use ($path, $command): bool {
- return $process->path === dirname($path)
- && $process->command === $command;
- });
+ self::assertEquals($expected, $actual);
}
- public function testInvokeLongOutput(): void {
- $path = self::getTestData()->path('~runnable.md');
- $root = new Directory(dirname($path), false);
- $file = new File($path, false);
- $params = null;
- $context = new Context($root, $file, $file->getPath(), $params);
- $content = self::getTestData()->content('~runnable.md');
- $command = self::getTestData()->path('~runnable.run');
- $expected = trim($content);
- $output = implode("\n", range(0, Instruction::Limit + 1));
- $factory = $this->override(Factory::class, function () use ($command, $output): Factory {
- $factory = $this->app()->make(Factory::class);
- $factory->preventStrayProcesses();
- $factory->fake([
- $command => $factory->result($output),
- ]);
-
- return $factory;
- });
- $instance = $this->app()->make(Instruction::class);
- $actual = ProcessorHelper::runInstruction($instance, $context, $file, $params);
-
- self::assertEquals(
- <<Example output
-
- ```plain
- {$output}
- ```
-
-
- EXPECTED,
- $actual,
- );
-
- $factory->assertRan(static function (PendingProcess $process) use ($path, $command): bool {
- return $process->path === dirname($path)
- && $process->command === $command;
- });
- }
+ public function testInvokeNoRun(): void {
+ self::assertFalse($this->app()->bound(Runner::class));
- public function testInvokeMarkdown(): void {
- $path = self::getTestData()->path('~runnable.md');
+ $path = Path::normalize(self::getTestData()->path('.md'));
$root = new Directory(dirname($path), false);
$file = new File($path, false);
$params = null;
$context = new Context($root, $file, $file->getName(), $params);
- $content = self::getTestData()->content('~runnable.md');
- $command = self::getTestData()->path('~runnable.run');
- $expected = trim($content);
- $output = 'command output';
- $factory = $this->override(Factory::class, function () use ($command, $output): Factory {
- $factory = $this->app()->make(Factory::class);
- $factory->preventStrayProcesses();
- $factory->fake([
- $command => $factory->result("{$output}"),
- ]);
-
- return $factory;
- });
+ $expected = trim($file->getContent());
$instance = $this->app()->make(Instruction::class);
$actual = ProcessorHelper::runInstruction($instance, $context, $file, $params);
@@ -164,58 +64,89 @@ public function testInvokeMarkdown(): void {
```md
{$expected}
```
-
- {$output}
EXPECTED,
$actual,
);
-
- $factory->assertRan(static function (PendingProcess $process) use ($path, $command): bool {
- return $process->path === dirname($path)
- && $process->command === $command;
- });
}
-
- public function testInvokeMarkdownLongOutput(): void {
- $path = self::getTestData()->path('~runnable.md');
- $root = new Directory(dirname($path), false);
- $file = new File($path, false);
- $params = null;
- $context = new Context($root, $file, $file->getPath(), $params);
- $content = self::getTestData()->content('~runnable.md');
- $command = self::getTestData()->path('~runnable.run');
- $expected = trim($content);
- $output = implode("\n", range(0, Instruction::Limit + 1));
- $factory = $this->override(Factory::class, function () use ($command, $output): Factory {
- $factory = $this->app()->make(Factory::class);
- $factory->preventStrayProcesses();
- $factory->fake([
- $command => $factory->result("{$output}"),
- ]);
-
- return $factory;
- });
- $instance = $this->app()->make(Instruction::class);
- $actual = ProcessorHelper::runInstruction($instance, $context, $file, $params);
-
- self::assertEquals(
- <<Example output
-
- {$output}
-
-
- EXPECTED,
- $actual,
- );
-
- $factory->assertRan(static function (PendingProcess $process) use ($path, $command): bool {
- return $process->path === dirname($path)
- && $process->command === $command;
- });
+ //
+
+ //
+ // =========================================================================
+ /**
+ * @return array
+ */
+ public static function dataProviderInvoke(): array {
+ $long = implode("\n", range(0, Instruction::Limit + 1));
+ $content = <<<'FILE'
+ # File
+
+ content of the file
+ FILE;
+
+ return [
+ 'empty output' => [
+ << [
+ << [
+ <<Example output
+
+ ```plain
+ {$long}
+ ```
+
+
+ EXPECTED,
+ $long,
+ ],
+ 'markdown output' => [
+ <<example
',
+ ],
+ 'markdown long output' => [
+ <<Example output
+
+ {$long}
+
+
+ EXPECTED,
+ "{$long}",
+ ],
+ ];
}
+ //
}
diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~runnable.md b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~runnable.md
deleted file mode 100644
index a6886c0f7..000000000
--- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~runnable.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# File
-
-content of the file
diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~runnable.run b/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~runnable.run
deleted file mode 100644
index 8eca9947a..000000000
--- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample/InstructionTest~runnable.run
+++ /dev/null
@@ -1 +0,0 @@
-/command
diff --git a/packages/documentator/src/Preprocessor/Exceptions/TargetExecFailed.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExec/Exceptions/TargetExecFailed.php
similarity index 75%
rename from packages/documentator/src/Preprocessor/Exceptions/TargetExecFailed.php
rename to packages/documentator/src/Preprocessor/Instructions/IncludeExec/Exceptions/TargetExecFailed.php
index d05f2bafd..321cb22ee 100644
--- a/packages/documentator/src/Preprocessor/Exceptions/TargetExecFailed.php
+++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExec/Exceptions/TargetExecFailed.php
@@ -1,8 +1,9 @@