Skip to content

Commit

Permalink
QA improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
brendt committed Apr 9, 2024
1 parent 9360ef1 commit 0db9af1
Show file tree
Hide file tree
Showing 25 changed files with 249 additions and 113 deletions.
14 changes: 14 additions & 0 deletions app/Console/ComplexCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Console;

use Tempest\Console\ConsoleCommand;

final readonly class ComplexCommand
{
#[ConsoleCommand('complex')]
public function __invoke(string $a, string $b, string $c)
{

}
}
3 changes: 2 additions & 1 deletion app/Console/Hello.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public function world(string $input)
}

#[ConsoleCommand(
aliases: ['t']
description: 'description',
aliases: ['t'],
)]
public function test(
#[ConsoleArgument(
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"autoload-dev": {
"psr-4": {
"App\\": "app/",
"Tests\\Tempest\\": "tests/"
"Tests\\Tempest\\Console\\": "tests/"
}
},
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
displayDetailsOnTestsThatTriggerWarnings="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
<testsuite name="Tests">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage />
Expand Down
21 changes: 20 additions & 1 deletion src/ConsoleArgumentBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function __construct(array $arguments)

foreach (array_values($arguments) as $position => $argument) {
$this->add(
ConsoleInputArgument::fromString($argument, $position)
ConsoleInputArgument::fromString($argument, $position),
);
}
}
Expand All @@ -39,6 +39,25 @@ public function all(): array
return $this->arguments;
}

public function findFor(ConsoleArgumentDefinition $argumentDefinition): ?ConsoleInputArgument
{
foreach ($this->arguments as $argument) {
if ($argumentDefinition->matchesArgument($argument)) {
return $argument;
}
}

if ($argumentDefinition->hasDefault) {
return new ConsoleInputArgument(
name: $argumentDefinition->name,
value: $argumentDefinition->default,
position: $argumentDefinition->position,
);
}

return null;
}

private function add(ConsoleInputArgument $argument): self
{
$this->arguments[] = $argument;
Expand Down
2 changes: 1 addition & 1 deletion src/ConsoleArgumentDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(
public readonly int $position,
public readonly ?string $description = null,
public readonly array $aliases = [],
public readonly string $help = '',
public readonly ?string $help = null,
) {
}

Expand Down
3 changes: 1 addition & 2 deletions src/ConsoleCommandDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@
public function __construct(
/** @var ConsoleArgumentDefinition[] */
public array $argumentDefinitions,
) {
}
) {}
}
67 changes: 15 additions & 52 deletions src/ConsoleInputBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ final class ConsoleInputBuilder
public function __construct(
protected ConsoleCommandDefinition $commandDefinition,
protected ConsoleArgumentBag $argumentBag,
) {

}
) {}

/**
* @return array<ConsoleInputArgument>
Expand All @@ -22,64 +20,29 @@ public function __construct(
public function build(): array
{
$validArguments = [];
$invalidDefinitions = [];

$passedArguments = $this->argumentBag->all();
$argumentDefinitionList = $this->commandDefinition->argumentDefinitions;

if (! $argumentDefinitionList && $passedArguments) {
throw UnresolvedArgumentsException::fromArguments($passedArguments);
}
$argumentDefinitions = $this->commandDefinition->argumentDefinitions;

foreach ($argumentDefinitionList as $definitionKey => $definitionArgument) {
$validArguments[] = $this->resolveArgument($definitionArgument, $passedArguments);
unset($argumentDefinitionList[$definitionKey]);
}
foreach ($argumentDefinitions as $argumentDefinition) {
$value = $this->argumentBag->findFor($argumentDefinition);

if (count($passedArguments) > 0 || count($argumentDefinitionList) > 0) {
throw UnresolvedArgumentsException::fromArguments([
...$passedArguments,
...$argumentDefinitionList,
]);
}
if ($value === null) {
$invalidDefinitions[] = $argumentDefinition;

return $this->toValues($validArguments);
}

/**
* @param ConsoleInputArgument[] $validArguments
*
* @return array
*/
private function toValues(array $validArguments): array
{
return array_map(
callback: fn (ConsoleInputArgument $argument) => $argument->value,
array: $validArguments
);
}

private function resolveArgument(ConsoleArgumentDefinition $argumentDefinition, array &$passedArguments): ?ConsoleInputArgument
{
foreach ($passedArguments as $key => $argument) {
if ($argumentDefinition->matchesArgument($argument)) {
unset($passedArguments[$key]);

return $argument;
continue;
}

$validArguments[] = $value;
}

/**
* In case there was no passed argument that matches this definition argument,
* we'll check if the definition argument has a default value.
*/
if (! $argumentDefinition->hasDefault) {
return null;
if (count($invalidDefinitions)) {
throw new UnresolvedArgumentsException($invalidDefinitions);
}

return new ConsoleInputArgument(
name: $argumentDefinition->name,
value: $argumentDefinition->default,
position: $argumentDefinition->position,
return array_map(
callback: fn (ConsoleInputArgument $argument) => $argument->value,
array: $validArguments,
);
}
}
3 changes: 2 additions & 1 deletion src/Exceptions/CommandNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tempest\Console\Exceptions;

use Tempest\Console\ConsoleOutput;
use Tempest\Console\ConsoleStyle;

final class CommandNotFoundException extends ConsoleException
{
Expand All @@ -16,7 +17,7 @@ public function __construct(
public function render(ConsoleOutput $output): void
{
$output->writeln(
sprintf('Command %s not found', $this->commandName),
sprintf('Command %s not found', ConsoleStyle::FG_DARK_RED(ConsoleStyle::UNDERLINE($this->commandName))),
);
}
}
2 changes: 1 addition & 1 deletion src/Exceptions/ConsoleExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function handle(Throwable $throwable): void
->error($throwable::class)
->when(
expression: $throwable->getMessage(),
callback: fn (ConsoleOutput $output) => $output->writeln($throwable->getMessage()),
callback: fn (ConsoleOutput $output) => $output->error($throwable->getMessage()),
)
->writeln();

Expand Down
48 changes: 8 additions & 40 deletions src/Exceptions/UnresolvedArgumentsException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,24 @@

namespace Tempest\Console\Exceptions;

use Tempest\Console\ConsoleInputArgument;
use Tempest\Console\ConsoleOutput;

final class UnresolvedArgumentsException extends ConsoleException
{
/** @var ConsoleInputArgument[] */
public array $arguments;

/**
* @param ConsoleInputArgument[] $arguments
*
* @return self
*/
public static function fromArguments(array $arguments): self
{
$exception = new self('Unresolved arguments found');
$exception->setArguments(array_values($arguments));

return $exception;
}

/**
* @param ConsoleInputArgument[] $arguments
*
* @return $this
*/
public function setArguments(array $arguments): self
{
$this->arguments = $arguments;

return $this;
}

/**
* @return ConsoleInputArgument[]
*/
public function getArguments(): array
{
return $this->arguments;
}
public function __construct(
/** @var \Tempest\Console\ConsoleArgumentDefinition[] $invalidDefinitions */
private array $invalidDefinitions,
) {}

public function render(ConsoleOutput $output): void
{
$output->error('Unresolved arguments found');
$output->error('Invalid command');

foreach ($this->arguments as $argument) {
foreach ($this->invalidDefinitions as $definition) {
$output->writeln(
sprintf(
'Argument %s is invalid',
$argument->name ?? "#$argument->position",
'Argument %s is missing',
$definition->name,
),
);
}
Expand Down
9 changes: 5 additions & 4 deletions src/Testing/Console/ConsoleCommandTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
{
public function __construct(private Container $container)
{
$this->container->singleton(
ConsoleOutput::class,
fn () => $this->container->get(TestConsoleOutput::class)
);
}

public function call(string $command): TestConsoleHelper
{
$appConfig = $this->container->get(AppConfig::class);

$this->container->singleton(
ConsoleOutput::class,
fn () => $this->container->get(TestConsoleOutput::class)
);

$appConfig->exceptionHandlers[] = $this->container->get(ConsoleExceptionHandler::class);

$application = new ConsoleApplication(
Expand Down
14 changes: 14 additions & 0 deletions src/Testing/Console/TestConsoleHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ public function __construct(
) {
}

public function print(): self
{
echo $this->output->getTextWithoutFormatting();

return $this;
}

public function printFormatted(): self
{
echo $this->output->getTextWithFormatting();

return $this;
}

public function assertContains(string $text): self
{
Assert::assertStringContainsString(
Expand Down
20 changes: 20 additions & 0 deletions tests/Actions/RenderConsoleCommandOverviewTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Tests\Tempest\Console\Actions;

use Tests\Tempest\Console\TestCase;

class RenderConsoleCommandOverviewTest extends TestCase
{
public function test_overview(): void
{
$this->console
->call('')
->assertContains('Tempest')
->assertContains('General')
->assertContains('Hello')
->assertContains('hello:world <input>')
->assertContains('hello:test [optionalValue=null] [--flag=false] - description')
->assertContains('test:test');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Tests\Tempest\Unit\Console;
namespace Tests\Tempest\Console;

use PHPUnit\Framework\TestCase;
use Tempest\Console\ConsoleArgumentBag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Tests\Tempest\Unit\Console;
namespace Tests\Tempest\Console;

use PHPUnit\Framework\TestCase;
use Tempest\Console\ConsoleArgumentBag;
Expand Down
18 changes: 18 additions & 0 deletions tests/Exceptions/CommandNotFoundExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Tests\Tempest\Console\Exceptions;

use Tempest\Console\ConsoleStyle;
use Tests\Tempest\Console\TestCase;

class CommandNotFoundExceptionTest extends TestCase
{
public function test_console_exception_handler(): void
{
$this->console
->call('foo:bar')
->assertContains('Command foo:bar not found')
->assertContainsFormattedText(ConsoleStyle::FG_DARK_RED(ConsoleStyle::UNDERLINE('foo:bar')))
;
}
}
Loading

0 comments on commit 0db9af1

Please sign in to comment.