Skip to content

Commit

Permalink
QA
Browse files Browse the repository at this point in the history
  • Loading branch information
brendt committed May 30, 2024
1 parent 2553899 commit 2f472ab
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 36 deletions.
30 changes: 20 additions & 10 deletions src/Actions/CompleteConsoleCommandArguments.php
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Actions;

use Tempest\Console\CompletesConsoleCommand;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\HasConsole;
use Tempest\Console\Input\ConsoleArgumentBag;

final readonly class CompleteConsoleCommandArguments
final readonly class CompleteConsoleCommandArguments implements CompletesConsoleCommand
{
use HasConsole;

public function __invoke(
public function complete(
ConsoleCommand $command,
ConsoleArgumentBag $argumentBag,
int $current,
): void {
): array {
$definitions = $command->getArgumentDefinitions();

$last = $argumentBag->last();

if ($last && $last->value === null) {
return [];
}

$completions = [];

foreach ($definitions as $definition) {
if ($definition->type !== 'array' && $argumentBag->has($definition->name)) {
continue;
}

$this->write("--{$definition->name}");
$argument = "--{$definition->name}";

if ($definition->type !== 'bool') {
$this->write('=');
$argument .= '=';
}

$this->writeln();
$completions[] = $argument;
}

return $completions;
}
}
}
23 changes: 11 additions & 12 deletions src/Actions/CompleteConsoleCommandNames.php
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Actions;

use Tempest\Console\Console;
use Tempest\Console\ConsoleConfig;
use Tempest\Console\HasConsole;
use Tempest\Console\Input\ConsoleArgumentBag;

final readonly class CompleteConsoleCommandNames
{
use HasConsole;

public function __construct(
private Console $console,
private ConsoleConfig $consoleConfig,
) {}
) {
}

public function __invoke(
ConsoleArgumentBag $argumentBag,
int $current,
): void
public function complete(ConsoleArgumentBag $argumentBag, int $current): array
{
$currentCommandName = $argumentBag->getCommandName();

$completions = [];

foreach ($this->consoleConfig->commands as $name => $definition) {
if (! str_starts_with($name, $currentCommandName)) {
continue;
}

$this->writeln($name);
$completions[] = $name;
}

return $completions;
}
}
}
16 changes: 8 additions & 8 deletions src/Commands/CompleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Tempest\Console\Commands;

use ReflectionMethod;
use Tempest\Console\Actions\CompleteConsoleCommandArguments;
use Tempest\Console\Actions\CompleteConsoleCommandNames;
use Tempest\Console\Console;
Expand Down Expand Up @@ -37,25 +36,26 @@ public function __invoke(
$commandName = $input[1] ?? null;

$command = $this->consoleConfig->commands[$commandName] ?? null;

$argumentBag = new ConsoleArgumentBag($input);

if (! $command) {
$this->container->get(CompleteConsoleCommandNames::class)($argumentBag, $current);
$completions = $this->container->get(CompleteConsoleCommandNames::class)->complete($argumentBag, $current);

$this->writeln(implode(PHP_EOL, $completions));

return;
}

$complete = match(true) {
is_array($command->complete) => new ReflectionMethod(...$command->complete),
is_string($command->complete) && class_exists($command->complete) => new ReflectionMethod($command->complete, '__invoke'),
default => new ReflectionMethod(CompleteConsoleCommandArguments::class, '__invoke'),
is_string($command->complete) && class_exists($command->complete) => $this->container->get($command->complete),
default => $this->container->get(CompleteConsoleCommandArguments::class),
};

$complete->invoke(
$this->container->get($complete->getDeclaringClass()->getName()),
$this->writeln(implode(PHP_EOL, $complete->complete(
$command,
$argumentBag,
$current,
);
)));
}
}
16 changes: 16 additions & 0 deletions src/CompletesConsoleCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Tempest\Console;

use Tempest\Console\Input\ConsoleArgumentBag;

interface CompletesConsoleCommand
{
public function complete(
ConsoleCommand $command,
ConsoleArgumentBag $argumentBag,
int $current,
): array;
}
5 changes: 3 additions & 2 deletions src/ConsoleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Tempest\Console;

use Attribute;
use Closure;
use ReflectionMethod;
use Tempest\Console\Input\ConsoleArgumentDefinition;

Expand All @@ -25,7 +24,9 @@ public function __construct(
/** @var array<array-key, class-string<\Tempest\Console\ConsoleMiddleware>> */
public readonly array $middleware = [],
public readonly bool $hidden = false,
public string|array|Closure|null $complete = null,

/** @var class-string<\Tempest\Console\CompletesConsoleCommand>|null */
public readonly string|null $complete = null,
) {
}

Expand Down
5 changes: 5 additions & 0 deletions src/Input/ConsoleArgumentBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public function all(): array
return $this->arguments;
}

public function last(): ?ConsoleInputArgument
{
return $this->arguments[array_key_last($this->arguments)] ?? null;
}

public function has(string ...$names): bool
{
foreach ($this->arguments as $argument) {
Expand Down
13 changes: 9 additions & 4 deletions src/Input/ConsoleInputArgument.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,23 @@ public function matches(string $name): bool
*/
private static function parseNamedArgument(string $argument): array
{
$parts = explode('=', str_replace('--', '', $argument));
preg_match('/--(?<name>[\w]+)((?<hasValue>=)\"?(?<value>(.*?))(\"|$))?/', $argument, $matches);

$key = $parts[0];
$name = $matches['name'] ?? null;
$hasValue = $matches['hasValue'] ?? null;
$value = $matches['value'] ?? null;

$value = $parts[1] ?? true;
if (! $hasValue) {
return [$name, true];
}

$value = match ($value) {
'true' => true,
'false' => false,
'' => null,
default => $value,
};

return [$key, $value];
return [$name, $value];
}
}
21 changes: 21 additions & 0 deletions src/Testing/ConsoleTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ public function call(string|Closure|array $command): self
return $clone;
}

public function complete(?string $command = null): self
{
if ($command) {
$input = explode(' ', $command);

$inputString = implode(' ', array_map(
fn (string $item) => "--input=\"{$item}\"",
$input
));
} else {
$inputString = '';
}

return $this->call("_complete --current=0 --input=\"./tempest\" {$inputString}");
}

public function input(int|string|Key $input): self
{
$this->output->clear();
Expand Down Expand Up @@ -152,6 +168,11 @@ public function assertSee(string $text): self
return $this->assertContains($text);
}

public function assertNotSee(string $text): self
{
return $this->assertDoesNotContain($text);
}

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

declare(strict_types=1);

namespace Tests\Tempest\Console\Actions;

use Tests\Tempest\Console\TestCase;

/**
* @internal
* @small
*/
class CompleteConsoleCommandArgumentsTest extends TestCase
{
public function test_arguments_are_printed(): void
{
$this->console
->complete('completion:test')
->assertSee("--value=" . PHP_EOL)
->assertSee("--flag" . PHP_EOL)
->assertSee("--items=" . PHP_EOL);
}

public function test_existing_arguments_are_skipped(): void
{
$this->console
->complete('completion:test --flag')
->assertNotSee('--flag');

$this->console
->complete('completion:test --flag=false')
->assertNotSee('--flag');

$this->console
->complete('completion:test --value=bar')
->assertNotSee('--value');
}

public function test_multiple_array_values_are_allowed(): void
{
$this->console
->complete('completion:test --items=a')
->assertSee('--items=');
}

public function test_open_flag_must_first_be_completed(): void
{
$this->console
->complete('completion:test --items=')
->assertNotSee("--value=" . PHP_EOL)
->assertNotSee("--flag" . PHP_EOL)
->assertNotSee("--items=" . PHP_EOL);
}
}
31 changes: 31 additions & 0 deletions tests/Commands/CompleteCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Console\Commands;

use Tests\Tempest\Console\TestCase;

/**
* @internal
* @small
*/
class CompleteCommandTest extends TestCase
{
public function test_complete_commands(): void
{
$this->console
->complete()
->assertSee('tail:server' . PHP_EOL)
->assertSee('schedule:run' . PHP_EOL);
}

public function test_complete_arguments(): void
{
$this->console
->complete('tail:')
->assertSee('tail:server' . PHP_EOL)
->assertSee('tail:project' . PHP_EOL)
->assertSee('tail:debug' . PHP_EOL);
}
}
16 changes: 16 additions & 0 deletions tests/Fixtures/CompletionTestCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Console\Fixtures;

use Tempest\Console\ConsoleCommand;

final readonly class CompletionTestCommand
{
#[ConsoleCommand('completion:test')]
public function __invoke(string $value, bool $flag = false, array $items = [])
{
// TODO: Implement __invoke() method.
}
}
36 changes: 36 additions & 0 deletions tests/Input/ConsoleInputArgumentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Console\Input;

use PHPUnit\Framework\TestCase;
use Tempest\Console\Input\ConsoleInputArgument;

/**
* @internal
* @small
*/
class ConsoleInputArgumentTest extends TestCase
{
public function test_parse_named_arguments(): void
{
$input = ConsoleInputArgument::fromString('--flag=abc');
$this->assertSame('abc', $input->value);

$input = ConsoleInputArgument::fromString('--flag');
$this->assertTrue($input->value);

$input = ConsoleInputArgument::fromString('--flag=true');
$this->assertTrue($input->value);

$input = ConsoleInputArgument::fromString('--flag=false');
$this->assertFalse($input->value);

$input = ConsoleInputArgument::fromString('--flag=');
$this->assertNull($input->value);

$input = ConsoleInputArgument::fromString('--flag="abc"');
$this->assertSame('abc', $input->value);
}
}

0 comments on commit 2f472ab

Please sign in to comment.