Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Tempest/Console/src/Actions/RenderConsoleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public function __invoke(ConsoleCommand $consoleCommand): void

private function renderArgument(ConsoleArgumentDefinition $argument): string
{
if ($argument->isBackedEnum()) {
return $this->renderEnumArgument($argument);
}

$name = str($argument->name)
->prepend('<em>')
->append('</em>');
Expand All @@ -53,4 +57,24 @@ private function renderArgument(ConsoleArgumentDefinition $argument): string
default => "[{$asString}={$argument->default}]"
};
}

private function renderEnumArgument(ConsoleArgumentDefinition $argument): string
{
$enum = $argument->type;
$values = $enum::cases();
$parts = [];

foreach ($values as $value) {
$parts[] = $value->value;
}

$partsAsString = ' {<em>' . implode('|', $parts) . '</em>}';
$line = "<em>{$argument->name}</em>";

if ($argument->hasDefault) {
return "[{$line}={$argument->default->value}{$partsAsString}]";
}

return "<{$line}{$partsAsString}>";
}
}
40 changes: 40 additions & 0 deletions src/Tempest/Console/src/Exceptions/InvalidEnumArgument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Exceptions;

use function array_map;
use BackedEnum;
use function gettype;
use function implode;
use function is_string;
use Tempest\Console\Console;

final class InvalidEnumArgument extends ConsoleException
{
/**
* @param class-string<BackedEnum> $argumentType
*/
public function __construct(
private string $argumentName,
private string $argumentType,
private mixed $value,
) {
}

public function render(Console $console): void
{
if (is_string($this->value) || is_numeric($this->value)) {
$value = "`{$this->value}`";
} else {
$value = 'of type `' . gettype($this->value) . '`';
}

$cases = array_map(
callback: fn (BackedEnum $case) => $case->value,
array: $this->argumentType::cases(),
);
$console->error("Invalid argument {$value} for `{$this->argumentName}` argument, valid values are: " . implode(', ', $cases));
}
}
25 changes: 24 additions & 1 deletion src/Tempest/Console/src/Input/ConsoleArgumentBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Tempest\Console\Input;

use Tempest\Console\Exceptions\InvalidEnumArgument;

final class ConsoleArgumentBag
{
/** @var ConsoleInputArgument[] */
Expand Down Expand Up @@ -93,7 +95,7 @@ public function findFor(ConsoleArgumentDefinition $argumentDefinition): ?Console
{
foreach ($this->arguments as $argument) {
if ($argumentDefinition->matchesArgument($argument)) {
return $argument;
return $this->resolveArgumentValue($argumentDefinition, $argument);
}
}

Expand All @@ -108,6 +110,27 @@ public function findFor(ConsoleArgumentDefinition $argumentDefinition): ?Console
return null;
}

private function resolveArgumentValue(
ConsoleArgumentDefinition $argumentDefinition,
ConsoleInputArgument $argument,
): ConsoleInputArgument {
if ($argumentDefinition->isBackedEnum()) {
$resolved = $argumentDefinition->type::tryFrom($argument->value);

if ($resolved === null) {
throw new InvalidEnumArgument(
$argumentDefinition->name,
$argumentDefinition->type,
$argument->value,
);
}

$argument->value = $resolved;
}

return $argument;
}

public function findArrayFor(ConsoleArgumentDefinition $argumentDefinition): ?ConsoleInputArgument
{
$values = [];
Expand Down
6 changes: 6 additions & 0 deletions src/Tempest/Console/src/Input/ConsoleArgumentDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tempest\Console\Input;

use BackedEnum;
use Tempest\Console\ConsoleArgument;
use Tempest\Reflection\ParameterReflector;
use function Tempest\Support\str;
Expand Down Expand Up @@ -70,4 +71,9 @@ private static function normalizeName(string $name, bool $boolean): string

return $normalizedName->toString();
}

public function isBackedEnum(): bool
{
return is_subclass_of($this->type, BackedEnum::class);
}
}
45 changes: 45 additions & 0 deletions tests/Integration/Console/ConsoleArgumentBagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Tests\Tempest\Integration\Console;

use PHPUnit\Framework\Attributes\TestWith;
use Tempest\Console\Exceptions\InvalidEnumArgument;
use Tempest\Console\Input\ConsoleArgumentBag;
use Tempest\Console\Input\ConsoleArgumentDefinition;
use Tests\Tempest\Integration\Console\Fixtures\TestStringEnum;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

/**
Expand Down Expand Up @@ -138,6 +140,49 @@ public function test_negative_input(string $name, bool $expected): void
$this->assertSame($expected, $bag->findFor($definition)->value);
}

public function test_backed_enum_input(): void
{
$argv = [
'tempest',
'test',
'--type=a',
];

$bag = new ConsoleArgumentBag($argv);

$definition = new ConsoleArgumentDefinition(
name: 'type',
type: TestStringEnum::class,
default: null,
hasDefault: false,
position: 0,
);

$this->assertSame(TestStringEnum::A, $bag->findFor($definition)->value);
}

public function test_invalid_backed_enum_input(): void
{
$argv = [
'tempest',
'test',
'--type=invalid',
];

$bag = new ConsoleArgumentBag($argv);

$definition = new ConsoleArgumentDefinition(
name: 'type',
type: TestStringEnum::class,
default: null,
hasDefault: false,
position: 0,
);

$this->expectException(InvalidEnumArgument::class);
$bag->findFor($definition);
}

public function test_name_mapping(): void
{
$this->console
Expand Down
2 changes: 2 additions & 0 deletions tests/Integration/Console/Fixtures/MyConsole.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ final class MyConsole
)]
public function handle(
string $path,
TestStringEnum $type,
TestStringEnum $fallback = TestStringEnum::A,
int $times = 1,
bool $force = false,
): void {
Expand Down
12 changes: 12 additions & 0 deletions tests/Integration/Console/Fixtures/TestStringEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Console\Fixtures;

enum TestStringEnum: string
{
case A = 'a';
case B = 'b';
case C = 'c';
}
2 changes: 1 addition & 1 deletion tests/Integration/Console/RenderConsoleCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function test_render(): void
(new RenderConsoleCommand($console))($consoleCommand);

$this->assertSame(
'test <path> [times=1] [--force=false] - description',
'test <path> <type {a|b|c}> [fallback=a {a|b|c}] [times=1] [--force=false] - description',
trim($output->getBufferWithoutFormatting()[0]),
);
}
Expand Down