Skip to content

Commit

Permalink
feat(documentator)!: New include:example runner (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
LastDragon-ru authored May 27, 2024
2 parents f95ac52 + 13b3c52 commit 3b74c52
Show file tree
Hide file tree
Showing 22 changed files with 215 additions and 269 deletions.
5 changes: 3 additions & 2 deletions packages/dev/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -52,6 +51,8 @@
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability": "dev",
"prefer-stable": true,
"repositories": {
"core": {
"type": "path",
Expand Down
61 changes: 34 additions & 27 deletions packages/dev/src/App/Example.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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("<markdown>{$output}</markdown>");
$result = "<markdown>{$output}</markdown>";
}
} finally {
self::$app = null;
self::$file = null;
self::$dumper = null;
}

return $result;
}

public static function dump(mixed $value, string $expression = null): void {
Expand Down Expand Up @@ -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("<?php\n{$code}", new Collecting());
$call = (new NodeFinder())->findFirst($stmts, static function (Node $node) use ($method): bool {
Expand Down
11 changes: 7 additions & 4 deletions packages/dev/src/App/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 2 additions & 0 deletions packages/documentator/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<example>.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
Expand Down
6 changes: 3 additions & 3 deletions packages/documentator/docs/Commands/preprocess.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ after the Header will be used as a summary.
* `<target>` - File path.

Includes contents of the `<target>` file as an example wrapped into
` ```code block``` `. It also searches for `<target>.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 `<target>.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 `<markdown>text</markdown>` tags to
insert it as is.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Contracts;

use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;

interface Runner {
public function __invoke(File $file): ?string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample\Exceptions;

use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\InstructionFailed;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use Throwable;

use function sprintf;

class ExampleFailed extends InstructionFailed {
public function __construct(
Context $context,
private readonly File $example,
Throwable $previous = null,
) {
parent::__construct(
$context,
sprintf(
'Example `%s` failed (in `%s`).',
$this->example->getRelativePath($context->root),
$context->file->getRelativePath($context->root),
),
$previous,
);
}

public function getExample(): File {
return $this->example;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<target>` file as an example wrapped into
* ` ```code block``` `. It also searches for `<target>.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 `<target>.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 `<markdown>text</markdown>` tags to
* insert it as is.
*
Expand All @@ -37,7 +34,7 @@ class Instruction implements InstructionContract {
protected const MarkdownRegexp = '/^\<(?P<tag>markdown)\>(?P<markdown>.*?)\<\/(?P=tag)\>$/msu';

public function __construct(
protected readonly Factory $factory,
protected readonly ?Runner $runner = null,
) {
// empty
}
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -120,31 +113,26 @@ public function __invoke(Context $context, mixed $target, mixed $parameters): st
</details>
CODE;
} else {
} elseif ($output) {
$output = <<<CODE
Example output:
```plain
$output
```
CODE;
} else {
// empty
}

$content .= "\n\n{$output}";
}

// Return
return $content;
return trim($content);
}

protected function getLanguage(Context $context, File $target, mixed $parameters): string {
return $target->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;
}
}
Loading

0 comments on commit 3b74c52

Please sign in to comment.