Skip to content

Commit

Permalink
feat(framework): overhaul console interactions (tempestphp#754)
Browse files Browse the repository at this point in the history
  • Loading branch information
innocenzi authored Dec 3, 2024
1 parent dd01ef1 commit e966ecb
Show file tree
Hide file tree
Showing 105 changed files with 3,626 additions and 1,218 deletions.
6 changes: 2 additions & 4 deletions src/Tempest/Cache/src/CacheClearCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(
) {
}

#[ConsoleCommand(name: 'cache:clear', aliases: ['cc'])]
#[ConsoleCommand(name: 'cache:clear', description: 'Clears all or specified caches', aliases: ['cc'])]
public function __invoke(bool $all = false): void
{
$caches = $this->cacheConfig->caches;
Expand All @@ -37,9 +37,7 @@ public function __invoke(bool $all = false): void

$cache->clear();

$this->writeln("<em>{$cacheClass}</em> cleared successfully");
$this->info("<em>{$cacheClass}</em> cleared successfully.");
}

$this->success('Done');
}
}
4 changes: 2 additions & 2 deletions src/Tempest/Cache/src/CacheStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function __construct(
) {
}

#[ConsoleCommand(name: 'cache:status', aliases: ['cs'])]
#[ConsoleCommand(name: 'cache:status', description: 'Shows which caches are enabled', aliases: ['cs'])]
public function __invoke(): void
{
$caches = $this->cacheConfig->caches;
Expand All @@ -38,7 +38,7 @@ public function __invoke(): void
$this->writeln(sprintf(
'<em>%s</em> %s%s',
$cacheClass,
$cache->isEnabled() ? '<success>enabled</success>' : '<error>disabled</error>',
$cache->isEnabled() ? '<style="fg-green">enabled</style>' : '<style="fg-red">disabled</style>',
$reason
));
}
Expand Down
4 changes: 2 additions & 2 deletions src/Tempest/CommandBus/src/MonitorAsyncCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public function __invoke(): void

if ($process->isTerminated()) {
if ($process->isSuccessful()) {
$this->writeln("<success>{$uuid}</success> finished at {$time->format('Y-m-d H:i:s')}");
$this->writeln("<style=\"fg-green\">{$uuid}</style> finished at {$time->format('Y-m-d H:i:s')}");
} else {
$this->writeln("<error>{$uuid}</error> failed at {$time->format('Y-m-d H:i:s')}");
$this->writeln("<style=\"fg-red\">{$uuid}</style> failed at {$time->format('Y-m-d H:i:s')}");
}

if ($output = trim($process->getOutput())) {
Expand Down
70 changes: 46 additions & 24 deletions src/Tempest/Console/src/Actions/RenderConsoleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,39 @@

final readonly class RenderConsoleCommand
{
public function __construct(private Console $console)
{
public function __construct(
private Console $console,
private ?int $longestCommandName = null,
private bool $renderArguments = false,
private bool $renderDescription = true,
) {
}

public function __invoke(ConsoleCommand $consoleCommand): void
{
$parts = ["<em><strong>{$consoleCommand->getName()}</strong></em>"];
$parts = [$this->renderName($consoleCommand)];

foreach ($consoleCommand->getArgumentDefinitions() as $argument) {
$parts[] = $this->renderArgument($argument);
if ($this->renderArguments) {
foreach ($consoleCommand->getArgumentDefinitions() as $argument) {
$parts[] = '<style="fg-gray">' . $this->renderArgument($argument) . '</style>';
}
}

if ($consoleCommand->description !== null && $consoleCommand->description !== '') {
$parts[] = "- {$consoleCommand->description}";
if ($this->renderDescription) {
if ($consoleCommand->description !== null && $consoleCommand->description !== '') {
$parts[] = $consoleCommand->description;
}
}

$this->console->writeln(' ' . implode(' ', $parts));
$this->console->writeln(implode(' ', $parts));
}

private function renderName(ConsoleCommand $consoleCommand): string
{
return str($consoleCommand->getName())
->alignRight($this->longestCommandName, padding: $this->longestCommandName ? 2 : 0)
->wrap(before: '<style="fg-cyan">', after: '</style>')
->toString();
}

private function renderArgument(ConsoleArgumentDefinition $argument): string
Expand All @@ -37,26 +53,32 @@ private function renderArgument(ConsoleArgumentDefinition $argument): string
return $this->renderEnumArgument($argument);
}

$name = str($argument->name)
->prepend('<em>')
->append('</em>');

$asString = match($argument->type) {
'bool' => "<em>--</em>{$name}",
default => $name,
$formattedArgumentName = match($argument->type) {
'bool' => "--{$argument->name}",
default => $argument->name,
};

$formattedArgumentName = str($formattedArgumentName)->wrap('<style="fg-cyan">', '</style>');

if (! $argument->hasDefault) {
return "<{$asString}>";
return $formattedArgumentName->wrap('<style="fg-gray dim"><</style>', '<style="fg-gray dim">></style>')->toString();
}

return match (true) {
$argument->default === true => "[{$asString}=true]",
$argument->default === false => "[{$asString}=false]",
is_null($argument->default) => "[{$asString}=null]",
is_array($argument->default) => "[{$asString}=array]",
default => "[{$asString}={$argument->default}]"
$defaultValue = match (true) {
$argument->default === true => "true",
$argument->default === false => "false",
is_null($argument->default) => "null",
is_array($argument->default) => "array",
default => "$argument->default",
};

return str()
->append(str('[')->wrap('<style="fg-gray dim">', '</style>'))
->append($formattedArgumentName)
->append(str('=')->wrap('<style="fg-gray dim">', '</style>'))
->append(str($defaultValue)->wrap('<style="fg-gray">', '</style>'))
->append(str(']')->wrap('<style="fg-gray dim">', '</style>'))
->toString();
}

private function renderEnumArgument(ConsoleArgumentDefinition $argument): string
Expand All @@ -66,8 +88,8 @@ private function renderEnumArgument(ConsoleArgumentDefinition $argument): string
array: $argument->type::cases()
);

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

if ($argument->hasDefault) {
return "[{$line}={$argument->default->value}{$partsAsString}]";
Expand Down
9 changes: 9 additions & 0 deletions src/Tempest/Console/src/CanOpenInEditor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Tempest\Console;

interface CanOpenInEditor
{
}
2 changes: 1 addition & 1 deletion src/Tempest/Console/src/Commands/ScheduleRunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __construct(
) {
}

#[ConsoleCommand('schedule:run')]
#[ConsoleCommand('schedule:run', description: 'Executes due tasks')]
public function __invoke(): void
{
$this->scheduler->run();
Expand Down
1 change: 1 addition & 0 deletions src/Tempest/Console/src/Commands/ScheduleTaskCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(

#[ConsoleCommand(
name: self::NAME,
description: 'Executes a scheduled task immediately'
)]
public function __invoke(string $task): void
{
Expand Down
33 changes: 33 additions & 0 deletions src/Tempest/Console/src/Components/ComponentState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Components;

enum ComponentState
{
/**
* Component is available for input.
*/
case ACTIVE;

/**
* There are validation errors.
*/
case ERROR;

/**
* Input was cancelled.
*/
case CANCELLED;

/**
* Input was submitted.
*/
case SUBMITTED;

/**
* Input is blocked.
*/
case BLOCKED;
}
30 changes: 30 additions & 0 deletions src/Tempest/Console/src/Components/Concerns/HasErrors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Components\Concerns;

use Tempest\Console\Components\ComponentState;
use Tempest\Console\InteractiveConsoleComponent;

/**
* @mixin InteractiveConsoleComponent
* @phpstan-require-implements InteractiveConsoleComponent
*/
trait HasErrors
{
/** @var string[] */
private array $errors = [];

public function setErrors(array $errors): self
{
$this->errors = $errors;

// Set the state to ERROR if we have errors and we're not already cancelled.
if ($this->errors !== [] && $this->getState() === ComponentState::ACTIVE) {
$this->setState(ComponentState::ERROR);
}

return $this;
}
}
35 changes: 35 additions & 0 deletions src/Tempest/Console/src/Components/Concerns/HasState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Tempest\Console\Components\Concerns;

use Tempest\Console\Components\ComponentState;
use Tempest\Console\HandlesKey;
use Tempest\Console\InteractiveConsoleComponent;
use Tempest\Console\Key;

/**
* @mixin InteractiveConsoleComponent
* @phpstan-require-implements InteractiveConsoleComponent
*/
trait HasState
{
private ComponentState $state = ComponentState::ACTIVE;

public function getState(): ComponentState
{
return $this->state;
}

public function setState(ComponentState $state): void
{
$this->state = $state;
}

#[HandlesKey(Key::ENTER)]
public function setSubmitted(): void
{
$this->state = ComponentState::SUBMITTED;
}
}
Loading

0 comments on commit e966ecb

Please sign in to comment.