Skip to content

Commit e966ecb

Browse files
authored
feat(framework): overhaul console interactions (#754)
1 parent dd01ef1 commit e966ecb

File tree

105 files changed

+3626
-1218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+3626
-1218
lines changed

src/Tempest/Cache/src/CacheClearCommand.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function __construct(
1818
) {
1919
}
2020

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

3838
$cache->clear();
3939

40-
$this->writeln("<em>{$cacheClass}</em> cleared successfully");
40+
$this->info("<em>{$cacheClass}</em> cleared successfully.");
4141
}
42-
43-
$this->success('Done');
4442
}
4543
}

src/Tempest/Cache/src/CacheStatusCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function __construct(
2020
) {
2121
}
2222

23-
#[ConsoleCommand(name: 'cache:status', aliases: ['cs'])]
23+
#[ConsoleCommand(name: 'cache:status', description: 'Shows which caches are enabled', aliases: ['cs'])]
2424
public function __invoke(): void
2525
{
2626
$caches = $this->cacheConfig->caches;
@@ -38,7 +38,7 @@ public function __invoke(): void
3838
$this->writeln(sprintf(
3939
'<em>%s</em> %s%s',
4040
$cacheClass,
41-
$cache->isEnabled() ? '<success>enabled</success>' : '<error>disabled</error>',
41+
$cache->isEnabled() ? '<style="fg-green">enabled</style>' : '<style="fg-red">disabled</style>',
4242
$reason
4343
));
4444
}

src/Tempest/CommandBus/src/MonitorAsyncCommands.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ public function __invoke(): void
3737

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

4545
if ($output = trim($process->getOutput())) {

src/Tempest/Console/src/Actions/RenderConsoleCommand.php

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,39 @@
1212

1313
final readonly class RenderConsoleCommand
1414
{
15-
public function __construct(private Console $console)
16-
{
15+
public function __construct(
16+
private Console $console,
17+
private ?int $longestCommandName = null,
18+
private bool $renderArguments = false,
19+
private bool $renderDescription = true,
20+
) {
1721
}
1822

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

23-
foreach ($consoleCommand->getArgumentDefinitions() as $argument) {
24-
$parts[] = $this->renderArgument($argument);
27+
if ($this->renderArguments) {
28+
foreach ($consoleCommand->getArgumentDefinitions() as $argument) {
29+
$parts[] = '<style="fg-gray">' . $this->renderArgument($argument) . '</style>';
30+
}
2531
}
2632

27-
if ($consoleCommand->description !== null && $consoleCommand->description !== '') {
28-
$parts[] = "- {$consoleCommand->description}";
33+
if ($this->renderDescription) {
34+
if ($consoleCommand->description !== null && $consoleCommand->description !== '') {
35+
$parts[] = $consoleCommand->description;
36+
}
2937
}
3038

31-
$this->console->writeln(' ' . implode(' ', $parts));
39+
$this->console->writeln(implode(' ', $parts));
40+
}
41+
42+
private function renderName(ConsoleCommand $consoleCommand): string
43+
{
44+
return str($consoleCommand->getName())
45+
->alignRight($this->longestCommandName, padding: $this->longestCommandName ? 2 : 0)
46+
->wrap(before: '<style="fg-cyan">', after: '</style>')
47+
->toString();
3248
}
3349

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

40-
$name = str($argument->name)
41-
->prepend('<em>')
42-
->append('</em>');
43-
44-
$asString = match($argument->type) {
45-
'bool' => "<em>--</em>{$name}",
46-
default => $name,
56+
$formattedArgumentName = match($argument->type) {
57+
'bool' => "--{$argument->name}",
58+
default => $argument->name,
4759
};
4860

61+
$formattedArgumentName = str($formattedArgumentName)->wrap('<style="fg-cyan">', '</style>');
62+
4963
if (! $argument->hasDefault) {
50-
return "<{$asString}>";
64+
return $formattedArgumentName->wrap('<style="fg-gray dim"><</style>', '<style="fg-gray dim">></style>')->toString();
5165
}
5266

53-
return match (true) {
54-
$argument->default === true => "[{$asString}=true]",
55-
$argument->default === false => "[{$asString}=false]",
56-
is_null($argument->default) => "[{$asString}=null]",
57-
is_array($argument->default) => "[{$asString}=array]",
58-
default => "[{$asString}={$argument->default}]"
67+
$defaultValue = match (true) {
68+
$argument->default === true => "true",
69+
$argument->default === false => "false",
70+
is_null($argument->default) => "null",
71+
is_array($argument->default) => "array",
72+
default => "$argument->default",
5973
};
74+
75+
return str()
76+
->append(str('[')->wrap('<style="fg-gray dim">', '</style>'))
77+
->append($formattedArgumentName)
78+
->append(str('=')->wrap('<style="fg-gray dim">', '</style>'))
79+
->append(str($defaultValue)->wrap('<style="fg-gray">', '</style>'))
80+
->append(str(']')->wrap('<style="fg-gray dim">', '</style>'))
81+
->toString();
6082
}
6183

6284
private function renderEnumArgument(ConsoleArgumentDefinition $argument): string
@@ -66,8 +88,8 @@ private function renderEnumArgument(ConsoleArgumentDefinition $argument): string
6688
array: $argument->type::cases()
6789
);
6890

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

7294
if ($argument->hasDefault) {
7395
return "[{$line}={$argument->default->value}{$partsAsString}]";
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console;
6+
7+
interface CanOpenInEditor
8+
{
9+
}

src/Tempest/Console/src/Commands/ScheduleRunCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function __construct(
2121
) {
2222
}
2323

24-
#[ConsoleCommand('schedule:run')]
24+
#[ConsoleCommand('schedule:run', description: 'Executes due tasks')]
2525
public function __invoke(): void
2626
{
2727
$this->scheduler->run();

src/Tempest/Console/src/Commands/ScheduleTaskCommand.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public function __construct(
2222

2323
#[ConsoleCommand(
2424
name: self::NAME,
25+
description: 'Executes a scheduled task immediately'
2526
)]
2627
public function __invoke(string $task): void
2728
{
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console\Components;
6+
7+
enum ComponentState
8+
{
9+
/**
10+
* Component is available for input.
11+
*/
12+
case ACTIVE;
13+
14+
/**
15+
* There are validation errors.
16+
*/
17+
case ERROR;
18+
19+
/**
20+
* Input was cancelled.
21+
*/
22+
case CANCELLED;
23+
24+
/**
25+
* Input was submitted.
26+
*/
27+
case SUBMITTED;
28+
29+
/**
30+
* Input is blocked.
31+
*/
32+
case BLOCKED;
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console\Components\Concerns;
6+
7+
use Tempest\Console\Components\ComponentState;
8+
use Tempest\Console\InteractiveConsoleComponent;
9+
10+
/**
11+
* @mixin InteractiveConsoleComponent
12+
* @phpstan-require-implements InteractiveConsoleComponent
13+
*/
14+
trait HasErrors
15+
{
16+
/** @var string[] */
17+
private array $errors = [];
18+
19+
public function setErrors(array $errors): self
20+
{
21+
$this->errors = $errors;
22+
23+
// Set the state to ERROR if we have errors and we're not already cancelled.
24+
if ($this->errors !== [] && $this->getState() === ComponentState::ACTIVE) {
25+
$this->setState(ComponentState::ERROR);
26+
}
27+
28+
return $this;
29+
}
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Console\Components\Concerns;
6+
7+
use Tempest\Console\Components\ComponentState;
8+
use Tempest\Console\HandlesKey;
9+
use Tempest\Console\InteractiveConsoleComponent;
10+
use Tempest\Console\Key;
11+
12+
/**
13+
* @mixin InteractiveConsoleComponent
14+
* @phpstan-require-implements InteractiveConsoleComponent
15+
*/
16+
trait HasState
17+
{
18+
private ComponentState $state = ComponentState::ACTIVE;
19+
20+
public function getState(): ComponentState
21+
{
22+
return $this->state;
23+
}
24+
25+
public function setState(ComponentState $state): void
26+
{
27+
$this->state = $state;
28+
}
29+
30+
#[HandlesKey(Key::ENTER)]
31+
public function setSubmitted(): void
32+
{
33+
$this->state = ComponentState::SUBMITTED;
34+
}
35+
}

0 commit comments

Comments
 (0)