Skip to content

Commit

Permalink
feat(documentator): include:artisan instruction (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
LastDragon-ru authored May 27, 2024
2 parents 32e0d3b + f56a32d commit 59f4acb
Show file tree
Hide file tree
Showing 23 changed files with 397 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .markdownlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ MD053:
"//",
"include:document-list",
"include:example",
"include:exec",
"include:artisan",
"include:file",
"include:package-list",
"include:template",
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

The set provides best practices to make development more fun and classes/services that I found very useful while working on big extensible applications.

[include:exec]: <./dev/artisan lara-asp-documentator:requirements>
[//]: # (start: cf085248b2284f354e6966f146d7c1c32821f3031a0316e73df3a80e038e704f)
[include:artisan]: <lara-asp-documentator:requirements "{$directory}">
[//]: # (start: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)
[//]: # (warning: Generated automatically. Do not edit.)

# Requirements
Expand All @@ -25,7 +25,7 @@ The set provides best practices to make development more fun and classes/service
| | `^8.22.1` | `3.0.0 ⋯ 0.2.0` |
| | `^8.0` | `0.1.0` |

[//]: # (end: cf085248b2284f354e6966f146d7c1c32821f3031a0316e73df3a80e038e704f)
[//]: # (end: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)

# Installation

Expand Down
6 changes: 3 additions & 3 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

This package contains useful utilities and classes.

[include:exec]: <../../dev/artisan lara-asp-documentator:requirements>
[//]: # (start: 0c754acbee0a8071717d81a4c18765bb2d605f138e08492b868c0e3f27e481ed)
[include:artisan]: <lara-asp-documentator:requirements "{$directory}">
[//]: # (start: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)
[//]: # (warning: Generated automatically. Do not edit.)

# Requirements
Expand All @@ -24,7 +24,7 @@ This package contains useful utilities and classes.
| | `^9.0.0` | `5.0.0-beta.0 ⋯ 0.12.0` |
| | `^8.22.1` | `3.0.0 ⋯ 0.4.0` |

[//]: # (end: 0c754acbee0a8071717d81a4c18765bb2d605f138e08492b868c0e3f27e481ed)
[//]: # (end: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)

[include:template]: ../../docs/Shared/Installation.md ({"data": {"package": "core"}})
[//]: # (start: 701034cd4b15ff1d0ee9742e3d839f3a0c70ba66d75ff6c56f712d2f69e254ae)
Expand Down
6 changes: 3 additions & 3 deletions packages/dev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Various internal tools and helpers to develop the package itself.

[include:exec]: <../../dev/artisan lara-asp-documentator:requirements>
[//]: # (start: 0c754acbee0a8071717d81a4c18765bb2d605f138e08492b868c0e3f27e481ed)
[include:artisan]: <lara-asp-documentator:requirements "{$directory}">
[//]: # (start: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)
[//]: # (warning: Generated automatically. Do not edit.)

# Requirements
Expand All @@ -15,7 +15,7 @@ Various internal tools and helpers to develop the package itself.
| | `^8.1` | `HEAD ⋯ 6.2.0` |
| PHPStan | `^1.10` | `HEAD ⋯ 6.2.0` |

[//]: # (end: 0c754acbee0a8071717d81a4c18765bb2d605f138e08492b868c0e3f27e481ed)
[//]: # (end: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)

[include:template]: ../../docs/Shared/InstallationDev.md ({"data": {"package": "dev"}})
[//]: # (start: e7fd13bb6d43bcc48d9fe51411b607bee0e47ccbcb1b788889f59b733865e0f1)
Expand Down
6 changes: 3 additions & 3 deletions packages/documentator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

This package provides various utilities for documentation generation such as Markdown Preprocessor, Requirements Dumper and more.

[include:exec]: <../../dev/artisan lara-asp-documentator:requirements>
[//]: # (start: 0c754acbee0a8071717d81a4c18765bb2d605f138e08492b868c0e3f27e481ed)
[include:artisan]: <lara-asp-documentator:requirements "{$directory}">
[//]: # (start: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)
[//]: # (warning: Generated automatically. Do not edit.)

# Requirements
Expand All @@ -18,7 +18,7 @@ This package provides various utilities for documentation generation such as Mar
| | `^10.0.0` | `6.1.0 ⋯ 5.0.0-beta.1` |
| | `^9.21.0` | `5.6.0 ⋯ 5.0.0-beta.1` |

[//]: # (end: 0c754acbee0a8071717d81a4c18765bb2d605f138e08492b868c0e3f27e481ed)
[//]: # (end: 3556073e7992c5bd81cdd63a92c38d136657c7e720caec135fff44e925557f7b)

[include:template]: ../../docs/Shared/Installation.md ({"data": {"package": "documentator"}})
[//]: # (start: d830b5dad8950e88a29e14aa443ca509cfa19889b5c3792b00691760fb8618bb)
Expand Down
1 change: 1 addition & 0 deletions packages/documentator/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"php": "^8.1|^8.2|^8.3",
"ext-mbstring": "*",
"composer/semver": "^3.2",
"illuminate/contracts": "^10.34.0|^11.0.0",
"illuminate/console": "^10.34.0|^11.0.0",
"illuminate/process": "^10.34.0|^11.0.0",
"illuminate/support": "^10.34.0|^11.0.0",
Expand Down
18 changes: 18 additions & 0 deletions packages/documentator/docs/Commands/preprocess.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ Where:

## Instructions

### `[include:artisan]: <target>`

* `<target>` - Artisan command. The following special variables supported:

* `"{$directory}"` - path of the directory where the file is located.
* `"{$file}"` - path of the file.

Executes the `<target>` as Artisan command and returns result.

Please note that the working directory will not be changed to the file
directory (like `include:exec` do). This behavior is close to how Artisan
normally works (I'm also not sure that it is possible to change the current
working directory in any robust way when you call Artisan command from code).
You can use one of the special variables inside command args instead.

### `[include:docblock]: <target> <parameters>`

* `<target>` - File path.
Expand Down Expand Up @@ -78,6 +93,9 @@ insert it as is.

Executes the `<target>` and returns result.

The working directory is equal to the file directory. If you want to run
Artisan command, please check `include:artisan` instruction.

### `[include:file]: <target>`

* `<target>` - File path.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

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

use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\InstructionFailed;
use Throwable;

use function sprintf;

class ArtisanCommandError extends InstructionFailed {
public function __construct(
Context $context,
Throwable $previous = null,
) {
parent::__construct(
$context,
sprintf(
'Artisan command `%s` failed (in `%s`).',
$context->target,
$context->file->getRelativePath($context->root),
),
$previous,
);
}
}
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\IncludeArtisan\Exceptions;

use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\InstructionFailed;
use Throwable;

use function sprintf;

class ArtisanCommandFailed extends InstructionFailed {
public function __construct(
Context $context,
private readonly int $result,
Throwable $previous = null,
) {
parent::__construct(
$context,
sprintf(
'Artisan command `%s` exited with status code `%s` (in `%s`).',
$context->target,
$this->result,
$context->file->getRelativePath($context->root),
),
$previous,
);
}

public function getResult(): int {
return $this->result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeArtisan;

use Exception;
use Illuminate\Contracts\Console\Kernel;
use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Contracts\Instruction as InstructionContract;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\InstructionFailed;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeArtisan\Exceptions\ArtisanCommandError;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeArtisan\Exceptions\ArtisanCommandFailed;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;

use function trim;

/**
* Executes the `<target>` as Artisan command and returns result.
*
* Please note that the working directory will not be changed to the file
* directory (like `include:exec` do). This behavior is close to how Artisan
* normally works (I'm also not sure that it is possible to change the current
* working directory in any robust way when you call Artisan command from code).
* You can use one of the special variables inside command args instead.
*
* @implements InstructionContract<string, null>
*/
class Instruction implements InstructionContract {
public function __construct(
protected readonly ApplicationResolver $application,
) {
// empty
}

#[Override]
public static function getName(): string {
return 'include:artisan';
}

#[Override]
public static function getResolver(): string {
return Resolver::class;
}

#[Override]
public static function getParameters(): ?string {
return null;
}

#[Override]
public function __invoke(Context $context, mixed $target, mixed $parameters): string {
try {
$app = $this->application->getInstance();
$kernel = $app->make(Kernel::class);
$input = new StringInput($target);
$output = new BufferedOutput();
$result = $kernel->handle($input, $output);

if ($result !== Command::SUCCESS) {
throw new ArtisanCommandFailed($context, $result);
}

return trim($output->fetch());
} catch (InstructionFailed $exception) {
throw $exception;
} catch (Exception $exception) {
throw new ArtisanCommandError($context, $exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeArtisan;

use Composer\InstalledVersions;
use Composer\Semver\VersionParser;
use Illuminate\Contracts\Console\Kernel;
use LastDragon_ru\LaraASP\Core\Utils\Path;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Context;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeArtisan\Exceptions\ArtisanCommandFailed;
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 Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

/**
* @internal
*/
#[CoversClass(Instruction::class)]
final class InstructionTest extends TestCase {
public function testInvoke(): void {
$root = new Directory(Path::normalize(__DIR__), false);
$file = new File(Path::normalize(__FILE__), false);
$params = null;
$expected = 'result';
$command = 'command to execute';
$context = new Context($root, $file, $command, $params);
$instance = $this->app()->make(Instruction::class);

$this->override(Kernel::class, static function (MockInterface $mock) use ($command, $expected): void {
if (InstalledVersions::satisfies(new VersionParser(), 'illuminate/contracts', '^11.0.0')) {
// todo(documentator): Remove after https://github.com/LastDragon-ru/lara-asp/issues/143
$mock
->shouldReceive('addCommands')
->atLeast()
->once()
->andReturns();
$mock
->shouldReceive('addCommandPaths')
->atLeast()
->once()
->andReturns();
$mock
->shouldReceive('addCommandRoutePaths')
->atLeast()
->once()
->andReturns();
}

$mock
->shouldReceive('handle')
->withArgs(
static function (InputInterface $input) use ($command): bool {
return (string) $input === $command;
},
)
->once()
->andReturnUsing(
static function (InputInterface $input, OutputInterface $output) use ($expected): int {
$output->writeln($expected);

return Command::SUCCESS;
},
);
});

self::assertEquals($expected, ProcessorHelper::runInstruction($instance, $context, $command, $params));
}

public function testInvokeFailed(): void {
$root = new Directory(Path::normalize(__DIR__), false);
$file = new File(Path::normalize(__FILE__), false);
$params = null;
$command = 'command to execute';
$context = new Context($root, $file, $command, $params);
$instance = $this->app()->make(Instruction::class);

$this->override(Kernel::class, static function (MockInterface $mock) use ($command): void {
// todo(documentator): Remove after https://github.com/LastDragon-ru/lara-asp/issues/143
if (InstalledVersions::satisfies(new VersionParser(), 'illuminate/contracts', '^11.0.0')) {
$mock
->shouldReceive('addCommands')
->atLeast()
->once()
->andReturns();
$mock
->shouldReceive('addCommandPaths')
->atLeast()
->once()
->andReturns();
$mock
->shouldReceive('addCommandRoutePaths')
->atLeast()
->once()
->andReturns();
}

$mock
->shouldReceive('handle')
->withArgs(
static function (InputInterface $input) use ($command): bool {
return (string) $input === $command;
},
)
->once()
->andReturnUsing(
static function (): int {
return Command::FAILURE;
},
);
});

self::expectException(ArtisanCommandFailed::class);
self::expectExceptionMessage(
sprintf(
'Artisan command `%s` exited with status code `%s` (in `%s`).',
$command,
Command::FAILURE,
$context->file->getRelativePath($context->root),
),
);

ProcessorHelper::runInstruction($instance, $context, $command, $params);
}
}
Loading

0 comments on commit 59f4acb

Please sign in to comment.