Fixes --version and --help

This commit is contained in:
Nuno Maduro
2022-09-17 23:47:47 +01:00
parent 08b62f6633
commit 0e0e2adfbe
26 changed files with 511 additions and 86 deletions

View File

@ -59,16 +59,18 @@ final class BootFiles
}
/**
* Loads the given filename, if possible.
* Loads, if possible, the given file.
*/
private function load(string $filename): void
{
if (! Str::endsWith($filename, '.php')) {
return;
}
if (! file_exists($filename)) {
return;
}
include_once $filename;
}
}

View File

@ -6,6 +6,7 @@ namespace Pest\Bootstrappers;
use Pest\Subscribers;
use PHPUnit\Event;
use PHPUnit\Event\Subscriber;
/**
* @internal
@ -15,7 +16,7 @@ final class BootSubscribers
/**
* The Kernel subscribers.
*
* @var array<int, class-string<\PHPUnit\Event\Subscriber>>
* @var array<int, class-string<Subscriber>>
*/
private const SUBSCRIBERS = [
Subscribers\EnsureConfigurationIsValid::class,

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Support\View;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BootView
{
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* Boots the view renderer.
*/
public function __invoke(): void
{
View::renderUsing($this->output);
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Bootable
{
/**
* Boots the plugin.
*/
public function boot(): void;
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Shutdownable
{
/**
* Shutdowns the plugin.
*/
public function shutdown(): void;
}

View File

@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Pest;
use Pest\Plugins\Actions\CallsAddsOutput;
use Pest\Plugins\Actions\CallsBoot;
use Pest\Plugins\Actions\CallsShutdown;
use Pest\Support\Container;
use PHPUnit\TextUI\Application;
use PHPUnit\TextUI\Exception;
@ -21,6 +25,7 @@ final class Kernel
Bootstrappers\BootExceptionHandler::class,
Bootstrappers\BootSubscribers::class,
Bootstrappers\BootFiles::class,
Bootstrappers\BootView::class,
];
/**
@ -29,7 +34,13 @@ final class Kernel
public function __construct(
private readonly Application $application
) {
// ..
register_shutdown_function(function (): void {
if (error_get_last() !== null) {
return;
}
$this->shutdown();
});
}
/**
@ -38,9 +49,11 @@ final class Kernel
public static function boot(): self
{
foreach (self::BOOTSTRAPPERS as $bootstrapper) {
(new $bootstrapper())->__invoke();
Container::getInstance()->get($bootstrapper)->__invoke();
}
(new CallsBoot())->__invoke();
return new self(new Application());
}
@ -53,13 +66,13 @@ final class Kernel
*/
public function handle(array $argv): int
{
$argv = (new Plugins\Actions\HandleArguments())->__invoke($argv);
$argv = (new Plugins\Actions\CallsHandleArguments())->__invoke($argv);
$this->application->run(
$argv, false,
);
return (new Plugins\Actions\AddsOutput())->__invoke(
return (new CallsAddsOutput())->__invoke(
Result::exitCode(),
);
}
@ -69,6 +82,6 @@ final class Kernel
*/
public function shutdown(): void
{
// ..
(new CallsShutdown())->__invoke();
}
}

View File

@ -10,7 +10,7 @@ use Pest\Plugin\Loader;
/**
* @internal
*/
final class AddsOutput
final class CallsAddsOutput
{
/**
* Executes the Plugin action.

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins\Actions;
use Pest\Contracts\Plugins;
use Pest\Plugin\Loader;
/**
* @internal
*/
final class CallsBoot
{
/**
* Executes the Plugin action.
*
* Provides an opportunity for any plugins to boot..
*/
public function __invoke(): void
{
$plugins = Loader::getPlugins(Plugins\Bootable::class);
/** @var Plugins\Bootable $plugin */
foreach ($plugins as $plugin) {
$plugin->boot();
}
}
}

View File

@ -10,7 +10,7 @@ use Pest\Plugin\Loader;
/**
* @internal
*/
final class HandleArguments
final class CallsHandleArguments
{
/**
* Executes the Plugin action.

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins\Actions;
use Pest\Contracts\Plugins;
use Pest\Plugin\Loader;
/**
* @internal
*/
final class CallsShutdown
{
/**
* Executes the Plugin action.
*
* Provides an opportunity for any plugins to shutdown.
*/
public function __invoke(): void
{
$plugins = Loader::getPlugins(Plugins\Shutdownable::class);
/** @var Plugins\Shutdownable $plugin */
foreach ($plugins as $plugin) {
$plugin->shutdown();
}
}
}

105
src/Plugins/Help.php Normal file
View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Support\View;
use function Pest\version;
use PHPUnit\TextUI\Help as PHPUnitHelp;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class Help implements HandlesArguments
{
use Concerns\HandleArguments;
/**
* Creates a new Plugin instance.
*/
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* {@inheritDoc}
*/
public function handleArguments(array $arguments): array
{
if ($this->hasArgument('--help', $arguments)) {
View::render('version', [
'version' => version(),
]);
View::render('usage');
foreach ($this->getContent() as $title => $options) {
if ($title === 'Usage') {
continue;
}
$this->output->writeln([
'',
sprintf(' <fg=blue;options=bold>%s:</>', mb_strtoupper($title)),
]);
foreach ($options as $option) {
if (! array_key_exists('arg', $option)) {
continue;
}
[
'arg' => $argument,
'desc' => $description,
] = $option;
View::render('components.two-column-detail', [
'left' => $argument,
'right' => $description,
]);
}
}
$this->output->write('', true);
exit(0);
}
return $arguments;
}
/**
* @return array<string, array<int, array{arg?: string, desc: string}>>
*/
private function getContent(): array
{
// Access the PHPUnit help class's private const HELP
$helpReflection = new \ReflectionClass(PHPUnitHelp::class);
/** @var array<string, array<int, array{arg: string, desc: string}>> $content */
$content = $helpReflection->getConstant('HELP_TEXT');
$content['Configuration'] = [[
'arg' => '--init',
'desc' => 'Initialise a standard Pest configuration',
]] + $content['Configuration'];
$content['Code Coverage'] = [
[
'arg' => '--coverage ',
'desc' => 'Generate code coverage report and output to standard output',
],
[
'arg' => '--coverage --min',
'desc' => 'Set the minimum required coverage percentage, and fail if not met',
],
] + $content['Code Coverage'];
return $content;
}
}

View File

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Support\View;
use function Pest\version;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
@ -15,24 +15,17 @@ final class Version implements HandlesArguments
{
use Concerns\HandleArguments;
/**
* Creates a new Plugin instance.
*/
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* {@inheritDoc}
*/
public function handleArguments(array $arguments): array
{
if ($this->hasArgument('--version', $arguments)) {
$this->output->writeln(
sprintf('Pest %s', version()),
);
View::render('version', [
'version' => version(),
]);
exit(0);
}
return $arguments;

View File

@ -35,16 +35,21 @@ final class Container
/**
* Gets a dependency from the container.
*
* @param class-string $id
* @return mixed
* @template TObject of object
*
* @param class-string<TObject> $id
* @return TObject
*/
public function get(string $id)
public function get(string $id): mixed
{
if (! array_key_exists($id, $this->instances)) {
$this->instances[$id] = $this->build($id);
}
return $this->instances[$id];
/** @var TObject $concrete */
$concrete = $this->instances[$id];
return $concrete;
}
/**
@ -58,9 +63,12 @@ final class Container
/**
* Tries to build the given instance.
*
* @param class-string $id
* @template TObject of object
*
* @param class-string<TObject> $id
* @return TObject
*/
private function build(string $id): object
private function build(string $id): mixed
{
$reflectionClass = new ReflectionClass($id);

71
src/Support/View.php Normal file
View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Pest\Support;
use Symfony\Component\Console\Output\OutputInterface;
use function Termwind\render;
use function Termwind\renderUsing;
use Termwind\Termwind;
/**
* @internal
*/
final class View
{
/**
* The implementation of the output.
*/
private static OutputInterface $output;
/**
* Renders views using the given Output instance.
*/
public static function renderUsing(OutputInterface $output): void
{
self::$output = $output;
}
/**
* Renders the given view.
*
* @param array<string, mixed> $data
*/
public static function render(string $path, array $data = []): void
{
$contents = self::compile($path, $data);
$existing = Termwind::getRenderer();
renderUsing(self::$output);
try {
render($contents);
} finally {
renderUsing($existing);
}
}
/**
* Compiles the given view.
*
* @param array<string, mixed> $data
*/
private static function compile(string $path, array $data): string
{
extract($data);
ob_start();
$path = str_replace('.', '/', $path);
include sprintf('%s/../../resources/views/%s.php', __DIR__, $path);
$contents = ob_get_contents();
ob_clean();
return (string) $contents;
}
}