Skip to content

Commit 5e3d99e

Browse files
authored
feat(console): add backed enum support to ask (#808)
1 parent 2739f4f commit 5e3d99e

File tree

7 files changed

+87
-25
lines changed

7 files changed

+87
-25
lines changed

src/Tempest/Console/src/Components/Interactive/SearchComponent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function __construct(
4545
) {
4646
$this->bufferEnabled = ! $this->multiple;
4747
$this->buffer = new TextBuffer();
48-
$this->renderer = new ChoiceRenderer(default: $default, multiple: $multiple);
48+
$this->renderer = new ChoiceRenderer(default: (string) $default, multiple: $multiple);
4949
$this->options = new OptionCollection([]);
5050

5151
if ($this->multiple) {

src/Tempest/Console/src/Components/Interactive/SingleChoiceComponent.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ final class SingleChoiceComponent implements InteractiveConsoleComponent, HasCur
3535
public function __construct(
3636
public string $label,
3737
iterable $options,
38-
public ?string $default = null,
38+
public null|int|string $default = null,
3939
) {
4040
$this->bufferEnabled = false;
4141
$this->options = new OptionCollection($options);
4242
$this->buffer = new TextBuffer();
43-
$this->renderer = new ChoiceRenderer(default: $default, multiple: false);
43+
$this->renderer = new ChoiceRenderer(default: (string) $default, multiple: false);
4444
$this->updateQuery();
4545
}
4646

src/Tempest/Console/src/Components/Interactive/TextInputComponent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ final class TextInputComponent implements InteractiveConsoleComponent, HasCursor
3030

3131
public function __construct(
3232
public string $label,
33-
public ?string $default = null,
33+
public null|int|string $default = null,
3434
public ?string $placeholder = null,
3535
public ?string $hint = null,
3636
bool $multiline = false,

src/Tempest/Console/src/Components/Static/StaticSingleChoiceComponent.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99

1010
final readonly class StaticSingleChoiceComponent implements StaticConsoleComponent
1111
{
12+
private array $optionValues;
13+
1214
public function __construct(
1315
public string $label,
1416
public array $options,
1517
public mixed $default = null,
1618
) {
19+
$this->optionValues = array_values($options);
1720
}
1821

19-
public function render(Console $console): ?string
22+
public function render(Console $console): null|string|int
2023
{
2124
if (! $console->supportsPrompting()) {
2225
return $this->default;
@@ -26,7 +29,7 @@ public function render(Console $console): ?string
2629

2730
$parsedOptions = [];
2831

29-
foreach ($this->options as $key => $option) {
32+
foreach ($this->optionValues as $key => $option) {
3033
$key = $key === $this->default
3134
? "<em><strong>{$key}</strong></em>"
3235
: $key;
@@ -39,15 +42,19 @@ public function render(Console $console): ?string
3942
$answer = trim($console->readln());
4043

4144
if (! $answer && $this->default) {
42-
return $this->options[$this->default] ?? $this->default;
45+
return $this->default;
4346
}
4447

45-
if (array_is_list($this->options) && in_array($answer, $this->options)) {
46-
return $answer;
48+
if (array_key_exists($answer, $this->optionValues)) {
49+
return array_is_list($this->options)
50+
? $this->optionValues[$answer]
51+
: array_search($this->optionValues[$answer], $this->options, strict: false);
4752
}
4853

49-
if (array_key_exists($answer, $this->options)) {
50-
return $this->options[$answer];
54+
if (in_array($answer, $this->optionValues, strict: false)) {
55+
return array_is_list($this->options)
56+
? $answer
57+
: array_search($answer, $this->options, strict: false);
5158
}
5259

5360
return $this->render($console);

src/Tempest/Console/src/Console.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
namespace Tempest\Console;
66

7+
use BackedEnum;
78
use Closure;
89
use Tempest\Highlight\Language;
10+
use Tempest\Support\ArrayHelper;
911

1012
interface Console
1113
{
@@ -27,19 +29,20 @@ public function writeWithLanguage(string $contents, Language $language): self;
2729
public function component(InteractiveConsoleComponent $component, array $validation = []): mixed;
2830

2931
/**
32+
* @param null|array|ArrayHelper|class-string<BackedEnum> $options
3033
* @param mixed|null $default
3134
* @param \Tempest\Validation\Rule[] $validation
3235
*/
3336
public function ask(
3437
string $question,
35-
?array $options = null,
38+
null|array|ArrayHelper|string $options = null,
3639
mixed $default = null,
3740
bool $multiple = false,
3841
bool $multiline = false,
3942
?string $placeholder = null,
4043
?string $hint = null,
4144
array $validation = [],
42-
): null|string|array;
45+
): null|int|string|array;
4346

4447
public function confirm(string $question, bool $default = false, ?string $yes = null, ?string $no = null): bool;
4548

src/Tempest/Console/src/GenericConsole.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Tempest\Console;
66

7+
use BackedEnum;
78
use Closure;
89
use Tempest\Console\Actions\ExecuteConsoleCommand;
910
use Tempest\Console\Components\Interactive\ConfirmComponent;
@@ -20,6 +21,7 @@
2021
use Tempest\Container\Tag;
2122
use Tempest\Highlight\Highlighter;
2223
use Tempest\Highlight\Language;
24+
use function Tempest\Support\arr;
2325
use Tempest\Support\ArrayHelper;
2426
use Tempest\Support\Conditions\HasConditions;
2527

@@ -186,18 +188,32 @@ public function component(InteractiveConsoleComponent $component, array $validat
186188

187189
public function ask(
188190
string $question,
189-
?array $options = null,
191+
null|array|ArrayHelper|string $options = null,
190192
mixed $default = null,
191193
bool $multiple = false,
192194
bool $multiline = false,
193195
?string $placeholder = null,
194196
?string $hint = null,
195197
array $validation = [],
196-
): null|string|array {
198+
): null|int|string|array {
197199
if ($this->isForced && $default) {
198200
return $default;
199201
}
200202

203+
if ($options instanceof ArrayHelper) {
204+
$options = $options->toArray();
205+
}
206+
207+
if (is_a($options, BackedEnum::class, allow_string: true)) {
208+
$options = arr($options::cases())->mapWithKeys(
209+
fn (BackedEnum $enum) => yield $enum->value => $enum->name,
210+
)->toArray();
211+
}
212+
213+
if ($default instanceof BackedEnum) {
214+
$default = $default->value;
215+
}
216+
201217
$component = match (true) {
202218
$options === null || $options === [] => new TextInputComponent($question, $default, $placeholder, $hint, $multiline),
203219
$multiple => new MultipleChoiceComponent(

tests/Integration/Console/Components/Static/StaticSingleChoiceComponentTest.php

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Tempest\Console\Console;
88
use Tempest\Console\Key;
9+
use Tests\Tempest\Integration\Console\Fixtures\TestStringEnum;
910
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
1011

1112
/**
@@ -39,40 +40,75 @@ public function test_with_default_option(): void
3940
->assertContains('picked b');
4041
}
4142

42-
public function test_as_list(): void
43+
public function test_with_default_option_without_prompting(): void
44+
{
45+
$this->console
46+
->withoutPrompting()
47+
->call(function (Console $console): void {
48+
$answer = $console->ask('test', ['a', 'b'], default: 'b');
49+
50+
$console->writeln("picked {$answer}");
51+
})
52+
->assertContains('picked b');
53+
}
54+
55+
public function test_assoc_submit_key(): void
4356
{
4457
$this->console
4558
->call(function (Console $console): void {
46-
$answer = $console->ask('test', ['a', 'b'], multiple: true);
59+
$answer = $console->ask('test', ['a' => 'A', 'b' => 'B']);
4760

4861
$console->writeln("picked {$answer}");
4962
})
5063
->submit(1)
5164
->assertContains('picked b');
5265
}
5366

54-
public function test_as_list_with_default(): void
67+
public function test_assoc_submit_value(): void
5568
{
5669
$this->console
5770
->call(function (Console $console): void {
58-
$answer = json_encode($console->ask('test', ['a', 'b'], default: 'a', multiple: true));
71+
$answer = $console->ask('test', ['a' => 'A', 'b' => 'B']);
5972

6073
$console->writeln("picked {$answer}");
6174
})
62-
->input(Key::ENTER)
63-
->input('yes')
64-
->assertContains('picked ["a"]');
75+
->submit('B')
76+
->assertContains('picked b');
6577
}
6678

67-
public function test_with_default_option_without_prompting(): void
79+
public function test_enum_submit_name(): void
6880
{
6981
$this->console
70-
->withoutPrompting()
7182
->call(function (Console $console): void {
72-
$answer = $console->ask('test', ['a', 'b'], default: 'b');
83+
$answer = $console->ask('test', options: TestStringEnum::class);
84+
85+
$console->writeln("picked {$answer}");
86+
})
87+
->submit('B')
88+
->assertContains('picked b');
89+
}
90+
91+
public function test_enum_submit_index(): void
92+
{
93+
$this->console
94+
->call(function (Console $console): void {
95+
$answer = $console->ask('test', options: TestStringEnum::class);
96+
97+
$console->writeln("picked {$answer}");
98+
})
99+
->submit(1)
100+
->assertContains('picked b');
101+
}
102+
103+
public function test_enum_default_value(): void
104+
{
105+
$this->console
106+
->call(function (Console $console): void {
107+
$answer = $console->ask('test', options: TestStringEnum::class, default: TestStringEnum::B);
73108

74109
$console->writeln("picked {$answer}");
75110
})
111+
->submit()
76112
->assertContains('picked b');
77113
}
78114
}

0 commit comments

Comments
 (0)