Skip to content

Commit 687e333

Browse files
authored
feat(console): support "no prompt" mode (#661)
1 parent 8f52e4a commit 687e333

21 files changed

+351
-21
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ final class MultipleChoiceComponent implements InteractiveConsoleComponent, HasS
2020
public function __construct(
2121
public string $question,
2222
public array $options,
23+
public array $default = [],
2324
) {
25+
foreach ($this->default as $key => $value) {
26+
$this->selectedOptions[array_is_list($options) ? $key : $value] = true;
27+
}
28+
2429
$this->activeOption = array_key_first($this->options);
2530
}
2631

@@ -105,8 +110,9 @@ public function down(): void
105110
public function getStaticComponent(): StaticConsoleComponent
106111
{
107112
return new StaticMultipleChoiceComponent(
108-
$this->question,
109-
$this->options,
113+
question: $this->question,
114+
options: $this->options,
115+
default: $this->default,
110116
);
111117
}
112118
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ final class SearchComponent implements InteractiveConsoleComponent, HasCursor, H
2828
public function __construct(
2929
public string $label,
3030
public Closure $search,
31+
public ?string $default = null
3132
) {
3233
$this->cursorPosition = new Point(2, 1);
3334
}
@@ -68,6 +69,10 @@ public function enter(): ?string
6869
{
6970
$selected = $this->options[$this->selectedOption] ?? null;
7071

72+
if (! $selected && $this->default) {
73+
return $this->default;
74+
}
75+
7176
if (! $selected) {
7277
return null;
7378
}
@@ -168,6 +173,7 @@ public function getStaticComponent(): StaticConsoleComponent
168173
return new StaticSearchComponent(
169174
label: $this->label,
170175
search: $this->search,
176+
default: $this->default,
171177
);
172178
}
173179
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public function getCursorPosition(): Point
118118

119119
public function getStaticComponent(): StaticConsoleComponent
120120
{
121-
return new StaticTextBoxComponent($this->label);
121+
return new StaticTextBoxComponent(
122+
label: $this->label,
123+
default: $this->default
124+
);
122125
}
123126
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public function __construct(
1717

1818
public function render(Console $console): bool
1919
{
20+
if (! $console->supportsPrompting()) {
21+
return $this->default;
22+
}
23+
2024
$answer = $console->ask(
2125
question: $this->question,
2226
options: ['yes', 'no'],

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,22 @@
1212
public function __construct(
1313
public string $question,
1414
public array $options,
15+
public array $default = [],
1516
) {
1617
}
1718

1819
public function render(Console $console): array
1920
{
21+
if (! $console->supportsPrompting()) {
22+
return array_is_list($this->options)
23+
? array_filter($this->default, fn (mixed $value) => in_array($value, $this->options))
24+
: array_filter($this->default, fn (mixed $value) => array_key_exists($value, $this->options));
25+
}
26+
2027
do {
2128
$answer = $this->askQuestion($console);
2229

23-
$answerAsString = implode(', ', $answer);
30+
$answerAsString = implode(', ', $answer) ?: 'no option';
2431

2532
$confirm = $console->confirm(
2633
question: "You picked {$answerAsString}; continue?",

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,42 @@
1010

1111
final readonly class StaticSearchComponent implements StaticConsoleComponent
1212
{
13-
public const string SEARCH_AGAIN = 'Search again';
13+
private const string SEARCH_AGAIN = 'Search again';
14+
15+
private const string CANCEL = 'Cancel';
1416

1517
public function __construct(
1618
public string $label,
1719
public Closure $search,
20+
public ?string $default = null,
1821
) {
1922
}
2023

21-
public function render(Console $console): string
24+
public function render(Console $console): ?string
2225
{
26+
if (! $console->supportsPrompting()) {
27+
return $this->default;
28+
}
29+
2330
$answer = null;
2431

2532
while ($answer === null) {
2633
$query = $console->ask($this->label);
2734

2835
$options = ($this->search)($query);
2936

30-
$options = [self::SEARCH_AGAIN, ...$options];
37+
$options = [self::SEARCH_AGAIN, ...(count($options) === 0 ? [self::CANCEL] : []), ...$options];
3138

3239
$answer = $console->ask(
3340
question: 'Please select a result',
3441
options: $options,
3542
asList: true,
3643
);
3744

45+
if ($answer === self::CANCEL) {
46+
return $this->default;
47+
}
48+
3849
if ($answer === self::SEARCH_AGAIN) {
3950
$answer = null;
4051
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ public function __construct(
1717
) {
1818
}
1919

20-
public function render(Console $console): string
20+
public function render(Console $console): ?string
2121
{
22+
if (! $console->supportsPrompting()) {
23+
return $this->default;
24+
}
25+
2226
$console->write("<h2>{$this->question}</h2> ");
2327

2428
$parsedOptions = [];

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111
{
1212
public function __construct(
1313
public string $label,
14+
public ?string $default = null,
1415
) {
1516
}
1617

17-
public function render(Console $console): string
18+
public function render(Console $console): ?string
1819
{
20+
if (! $console->supportsPrompting()) {
21+
return $this->default;
22+
}
23+
1924
$console->write("<question>{$this->label}</question> ");
2025

21-
return trim($console->readln());
26+
return trim($console->readln()) ?: $this->default;
2227
}
2328
}

src/Tempest/Console/src/Console.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function ask(
3434
bool $multiple = false,
3535
bool $asList = false,
3636
array $validation = [],
37-
): string|array;
37+
): null|string|array;
3838

3939
public function confirm(string $question, bool $default = false): bool;
4040

@@ -45,7 +45,7 @@ public function progressBar(iterable $data, Closure $handler): array;
4545
/**
4646
* @param Closure(string $search): array $search
4747
*/
48-
public function search(string $label, Closure $search): mixed;
48+
public function search(string $label, Closure $search, ?string $default = null): mixed;
4949

5050
public function info(string $line): self;
5151

@@ -56,4 +56,12 @@ public function success(string $line): self;
5656
public function when(mixed $expression, callable $callback): self;
5757

5858
public function withLabel(string $label): self;
59+
60+
public function supportsTty(): bool;
61+
62+
public function supportsPrompting(): bool;
63+
64+
public function disableTty(): self;
65+
66+
public function disablePrompting(): self;
5967
}

src/Tempest/Console/src/GenericConsole.php

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Tempest\Console\Components\InteractiveComponentRenderer;
1717
use Tempest\Console\Exceptions\UnsupportedComponent;
1818
use Tempest\Console\Highlight\TempestConsoleLanguage\TempestConsoleLanguage;
19+
use Tempest\Console\Input\ConsoleArgumentBag;
1920
use Tempest\Container\Tag;
2021
use Tempest\Highlight\Highlighter;
2122

@@ -25,6 +26,10 @@ final class GenericConsole implements Console
2526

2627
private bool $isForced = false;
2728

29+
private bool $supportsTty = true;
30+
31+
private bool $supportsPrompting = true;
32+
2833
private ?InteractiveComponentRenderer $componentRenderer = null;
2934

3035
public function __construct(
@@ -33,6 +38,7 @@ public function __construct(
3338
#[Tag('console')]
3439
private readonly Highlighter $highlighter,
3540
private readonly ExecuteConsoleCommand $executeConsoleCommand,
41+
private readonly ConsoleArgumentBag $argumentBag
3642
) {
3743
}
3844

@@ -55,13 +61,35 @@ public function setForced(): self
5561
return $this;
5662
}
5763

64+
public function disableTty(): self
65+
{
66+
$this->supportsTty = false;
67+
68+
return $this;
69+
}
70+
71+
public function disablePrompting(): self
72+
{
73+
$this->supportsPrompting = false;
74+
75+
return $this;
76+
}
77+
5878
public function read(int $bytes): string
5979
{
80+
if (! $this->supportsPrompting()) {
81+
return '';
82+
}
83+
6084
return $this->input->read($bytes);
6185
}
6286

6387
public function readln(): string
6488
{
89+
if (! $this->supportsPrompting()) {
90+
return '';
91+
}
92+
6593
return $this->input->readln();
6694
}
6795

@@ -124,7 +152,7 @@ public function when(mixed $expression, callable $callback): self
124152

125153
public function component(InteractiveConsoleComponent $component, array $validation = []): mixed
126154
{
127-
if ($this->interactiveSupported()) {
155+
if ($this->supportsTty()) {
128156
return $this->componentRenderer->render($this, $component, $validation);
129157
}
130158

@@ -142,13 +170,14 @@ public function ask(
142170
bool $multiple = false,
143171
bool $asList = false,
144172
array $validation = [],
145-
): string|array {
173+
): null|string|array {
146174
if ($options === null || $options === []) {
147175
$component = new TextBoxComponent($question, $default);
148176
} elseif ($multiple) {
149177
$component = new MultipleChoiceComponent(
150178
question: $question,
151179
options: $options,
180+
default: is_array($default) ? $default : [$default],
152181
);
153182
} else {
154183
$component = new SingleChoiceComponent(
@@ -197,17 +226,38 @@ public function progressBar(iterable $data, Closure $handler): array
197226
return $this->component(new ProgressBarComponent($data, $handler));
198227
}
199228

200-
public function search(string $label, Closure $search): mixed
229+
public function search(string $label, Closure $search, ?string $default = null): mixed
201230
{
202-
return $this->component(new SearchComponent($label, $search));
231+
return $this->component(new SearchComponent($label, $search, $default));
203232
}
204233

205-
private function interactiveSupported(): bool
234+
public function supportsTty(): bool
206235
{
236+
if ($this->supportsTty === false) {
237+
return false;
238+
}
239+
207240
if ($this->componentRenderer === null) {
208241
return false;
209242
}
210243

244+
if (! $this->supportsPrompting()) {
245+
return false;
246+
}
247+
211248
return (bool) shell_exec('which tput && which stty');
212249
}
250+
251+
public function supportsPrompting(): bool
252+
{
253+
if ($this->supportsPrompting === false) {
254+
return false;
255+
}
256+
257+
if ($this->argumentBag->get('interaction')?->value === false) {
258+
return false;
259+
}
260+
261+
return true;
262+
}
213263
}

0 commit comments

Comments
 (0)