Skip to content

Commit

Permalink
Improve command not found
Browse files Browse the repository at this point in the history
  • Loading branch information
brendt committed Apr 15, 2024
1 parent d354334 commit 44d5b79
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 95 deletions.
52 changes: 25 additions & 27 deletions app/Console/InteractiveCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,45 @@
namespace App\Console;

use Tempest\Console\Components\MultipleChoiceComponent;
use Tempest\Console\Components\PasswordComponent;
use Tempest\Console\Components\ProgressBarComponent;
use Tempest\Console\Components\TextBoxComponent;
use Tempest\Console\Console;
use Tempest\Console\ConsoleCommand;
use Tests\Tempest\Console\Components\TextBoxComponentTest;

final readonly class InteractiveCommand
{
public function __construct(private Console $console) {}
public function __construct(private Console $console)
{
}

#[ConsoleCommand('interactive')]
public function __invoke(): void
{
// $result = $this->console->component(new MultipleChoiceComponent(
// 'Pick multiple options, which is best?',
// [
// 'interfaces + final',
// 'abstract classes + extend',
// 'I don\'t really care',
// 'interfaces + final',
// 'abstract classes + extend',
// ],
// ));
//
// $result = json_encode($result);
//
// $this->console->writeln("You picked <em>{$result}</em>");
// $result = $this->console->component(new MultipleChoiceComponent(
// 'Pick multiple options, which is best?',
// [
// 'interfaces + final',
// 'abstract classes + extend',
// 'I don\'t really care',
// 'interfaces + final',
// 'abstract classes + extend',
// ],
// ));
//
// $result = json_encode($result);
//
// $this->console->writeln("You picked <em>{$result}</em>");

// $result = $this->console->writeln()->ask('Next question:');
//
// $this->console->writeln("You wrote <em>{$result}</em>");

// $result = $this->console->progressBar(
// data: array_fill(0, 10, 'a'),
// handler: function ($i) {
// usleep(100000);
//
// return $i;
// },
// );
// $result = $this->console->progressBar(
// data: array_fill(0, 10, 'a'),
// handler: function ($i) {
// usleep(100000);
//
// return $i;
// },
// );


$password = $this->console->ask('hello?');
Expand Down
7 changes: 4 additions & 3 deletions src/Components/ConfirmComponent.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Components;

use Tempest\Console\ConsoleComponent;
Expand All @@ -16,8 +18,7 @@ final class ConfirmComponent implements ConsoleComponent, HasCursor
public function __construct(
private string $question,
bool $default = false,
)
{
) {
$this->answer = $default;
}

Expand Down Expand Up @@ -71,4 +72,4 @@ public function placeCursor(Cursor $cursor): void
{
$cursor->moveUp()->moveRight(strlen($this->question) + 12 + strlen($this->textualAnswer));
}
}
}
5 changes: 2 additions & 3 deletions src/ConsoleApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function run(): void
$this->executeCommand($commandName);
} catch (MistypedCommandException $e) {
$this->executeCommand(
$e->intendedCommand->getName(),
$e->intendedCommand,
);
}
}
Expand All @@ -76,7 +76,7 @@ private function executeCommand(string $commandName): void
try {
$this->handleCommand($commandName);
} catch (ConsoleException $consoleException) {
$consoleException->render($this->container->get(ConsoleOutput::class));
$consoleException->render($this->container->get(Console::class));
} catch (Throwable $throwable) {
if (
! $this->appConfig->enableExceptionHandling
Expand All @@ -101,7 +101,6 @@ private function handleCommand(string $commandName): void
throw new CommandNotFoundException(
commandName: $commandName,
consoleConfig: $config,
input: $this->container->get(ConsoleInput::class),
);
}

Expand Down
61 changes: 17 additions & 44 deletions src/Exceptions/CommandNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,56 @@

namespace Tempest\Console\Exceptions;

use Tempest\Console\Console;
use Tempest\Console\ConsoleConfig;
use Tempest\Console\ConsoleInput;
use Tempest\Console\ConsoleOutput;
use Tempest\Console\ConsoleOutputType;

final class CommandNotFoundException extends ConsoleException
{
public function __construct(
private readonly string $commandName,
private ConsoleConfig $consoleConfig,
private ConsoleInput $input,
private readonly ConsoleConfig $consoleConfig,
) {
parent::__construct();
}

public function render(ConsoleOutput $output): void
public function render(Console $console): void
{
$similarCommands = $this->getSimilarCommands();

$output->writeln(
$console->writeln(
sprintf('Command %s not found', $this->commandName),
ConsoleOutputType::ERROR,
)
->when(
expression: count($similarCommands) > 0,
callback: function (ConsoleOutput $output) use ($similarCommands) {
if (
count($similarCommands) === 1
&& $this->input->ask("Did you mean {$similarCommands[0]->getName()}?", options: ['y', 'n'])
) {
throw MistypedCommandException::for($similarCommands[0]);
}
);

$output->writeln('Did you mean one of these?', ConsoleOutputType::INFO)
->writeln();

$this->listCommands($output, $similarCommands);
if (count($similarCommands) === 1) {
if ($console->confirm("Did you mean {$similarCommands[0]}?")) {
throw new MistypedCommandException($similarCommands[0]);
}

$intendedCommandKey = $this->input->ask(
'Select intended command:',
options: array_keys($similarCommands),
);
return;
}

$intendedCommand = $similarCommands[$intendedCommandKey];
$intendedCommand = $console->ask(
'Did you mean to run one of these?',
options: $similarCommands,
);

throw MistypedCommandException::for($intendedCommand);
}
);
throw MistypedCommandException::for($intendedCommand);
}

private function getSimilarCommands(): array
{
$similarCommands = [];


foreach ($this->consoleConfig->commands as $consoleCommand) {
$levenshtein = levenshtein($this->commandName, $consoleCommand->getName());

if ($levenshtein <= 3) {
$similarCommands[] = $consoleCommand;
$similarCommands[] = $consoleCommand->getName();
}
}

return $similarCommands;
}

private function listCommands(ConsoleOutput $output, array $similarCommands): void
{
foreach ($similarCommands as $index => $similarCommand) {
$output->delimiter(' ')
->write("[$index] ", ConsoleOutputType::INFO)
->write($similarCommand->getName())
->writeln();
}

$output->delimiter(PHP_EOL)
->writeln()
->writeln();
}
}
4 changes: 2 additions & 2 deletions src/Exceptions/ConsoleException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace Tempest\Console\Exceptions;

use RuntimeException;
use Tempest\Console\ConsoleOutput;
use Tempest\Console\Console;

abstract class ConsoleException extends RuntimeException
{
abstract public function render(ConsoleOutput $output): void;
abstract public function render(Console $console): void;
}
10 changes: 5 additions & 5 deletions src/Exceptions/InvalidCommandException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace Tempest\Console\Exceptions;

use Tempest\Console\Actions\RenderConsoleCommand;
use Tempest\Console\Console;
use Tempest\Console\ConsoleArgumentDefinition;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\ConsoleOutput;

final class InvalidCommandException extends ConsoleException
{
Expand All @@ -18,19 +18,19 @@ public function __construct(
) {
}

public function render(ConsoleOutput $output): void
public function render(Console $console): void
{
$output->error("Invalid command usage:");
$console->error("Invalid command usage:");

(new RenderConsoleCommand($output))($this->consoleCommand);
(new RenderConsoleCommand($console))($this->consoleCommand);

$missingArguments = implode(', ', array_map(
fn (ConsoleArgumentDefinition $argumentDefinition) => $argumentDefinition->name,
$this->invalidDefinitions,
));

if ($missingArguments) {
$output->writeln("Missing arguments: {$missingArguments}");
$console->writeln("Missing arguments: {$missingArguments}");
}
}
}
12 changes: 4 additions & 8 deletions src/Exceptions/MistypedCommandException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@
namespace Tempest\Console\Exceptions;

use Exception;
use Tempest\Console\ConsoleCommand;

final class MistypedCommandException extends Exception
{
public readonly ConsoleCommand $intendedCommand;

public function __construct(ConsoleCommand $intendedCommand)
{
parent::__construct('Command not found');
$this->intendedCommand = $intendedCommand;
public function __construct(
public readonly string $intendedCommand,
) {
}

public static function for(mixed $intendedCommand): MistypedCommandException
public static function for(string $intendedCommand): MistypedCommandException
{
return new self($intendedCommand);
}
Expand Down
3 changes: 2 additions & 1 deletion src/GenericConsole.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ final class GenericConsole implements Console
public function __construct(
private readonly ConsoleInput $input,
private readonly ConsoleOutput $output,
) {}
) {
}

public function delimiter(string $delimiter): ConsoleOutput
{
Expand Down
4 changes: 2 additions & 2 deletions tests/Fixtures/TestConsoleException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

namespace Tests\Tempest\Console\Fixtures;

use Tempest\Console\ConsoleOutput;
use Tempest\Console\Console;
use Tempest\Console\Exceptions\ConsoleException;

final class TestConsoleException extends ConsoleException
{
public function render(ConsoleOutput $output): void
public function render(Console $console): void
{
}
}

0 comments on commit 44d5b79

Please sign in to comment.