• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 14049246919

24 Mar 2025 09:42PM UTC coverage: 79.353% (-0.04%) from 79.391%
14049246919

push

github

web-flow
feat(support): support array parameters in string manipulations (#1073)

48 of 48 new or added lines in 2 files covered. (100.0%)

735 existing lines in 126 files now uncovered.

10492 of 13222 relevant lines covered (79.35%)

90.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.0
/src/Tempest/Console/src/GenericConsole.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Console;
6

7
use BackedEnum;
8
use Closure;
9
use InvalidArgumentException;
10
use Stringable;
11
use Symfony\Component\Process\Process;
12
use Tempest\Console\Actions\ExecuteConsoleCommand;
13
use Tempest\Console\Components\Interactive\ConfirmComponent;
14
use Tempest\Console\Components\Interactive\MultipleChoiceComponent;
15
use Tempest\Console\Components\Interactive\PasswordComponent;
16
use Tempest\Console\Components\Interactive\ProgressBarComponent;
17
use Tempest\Console\Components\Interactive\SearchComponent;
18
use Tempest\Console\Components\Interactive\SingleChoiceComponent;
19
use Tempest\Console\Components\Interactive\TaskComponent;
20
use Tempest\Console\Components\Interactive\TextInputComponent;
21
use Tempest\Console\Components\InteractiveComponentRenderer;
22
use Tempest\Console\Components\Renderers\InstructionsRenderer;
23
use Tempest\Console\Components\Renderers\KeyValueRenderer;
24
use Tempest\Console\Components\Renderers\MessageRenderer;
25
use Tempest\Console\Exceptions\UnsupportedComponent;
26
use Tempest\Console\Highlight\TempestConsoleLanguage\TempestConsoleLanguage;
27
use Tempest\Console\Input\ConsoleArgumentBag;
28
use Tempest\Container\Tag;
29
use Tempest\Highlight\Highlighter;
30
use Tempest\Highlight\Language;
31
use Tempest\Support\Conditions\HasConditions;
32
use UnitEnum;
33

34
use function Tempest\Support\Arr\wrap;
35

36
final class GenericConsole implements Console
37
{
38
    use HasConditions;
39

40
    private ?string $label = null;
41

42
    private bool $isForced = false;
43

44
    private bool $supportsPrompting = true;
45

46
    private ?InteractiveComponentRenderer $componentRenderer = null;
47

48
    public function __construct(
247✔
49
        private readonly OutputBuffer $output,
50
        private readonly InputBuffer $input,
51
        #[Tag('console')]
52
        private readonly Highlighter $highlighter,
53
        private readonly ExecuteConsoleCommand $executeConsoleCommand,
54
        private readonly ConsoleArgumentBag $argumentBag,
55
    ) {}
247✔
56

57
    public function call(string|array $command, string|array $arguments = []): ExitCode|int
2✔
58
    {
59
        return ($this->executeConsoleCommand)($command, $arguments);
2✔
60
    }
61

62
    public function setComponentRenderer(InteractiveComponentRenderer $componentRenderer): self
81✔
63
    {
64
        $this->componentRenderer = $componentRenderer;
81✔
65

66
        return $this;
81✔
67
    }
68

69
    public function setForced(): self
7✔
70
    {
71
        $this->isForced = true;
7✔
72

73
        return $this;
7✔
74
    }
75

76
    public function disablePrompting(): self
39✔
77
    {
78
        $this->supportsPrompting = false;
39✔
79

80
        return $this;
39✔
81
    }
82

UNCOV
83
    public function read(int $bytes): string
×
84
    {
UNCOV
85
        if (! $this->supportsPrompting()) {
×
86
            return '';
×
87
        }
88

UNCOV
89
        return $this->input->read($bytes);
×
90
    }
91

92
    public function readln(): string
76✔
93
    {
94
        if (! $this->supportsPrompting()) {
76✔
UNCOV
95
            return '';
×
96
        }
97

98
        return $this->input->readln();
76✔
99
    }
100

101
    public function write(string $contents): static
186✔
102
    {
103
        $this->writeWithLanguage($contents, new TempestConsoleLanguage());
186✔
104

105
        return $this;
186✔
106
    }
107

108
    public function header(string $header, ?string $subheader = null): static
10✔
109
    {
110
        $this->writeln();
10✔
111
        $this->writeln("<style='dim fg-blue'>//</style> <style='bold fg-blue'>" . mb_strtoupper($header) . '</style>');
10✔
112

113
        if ($subheader) {
10✔
114
            $this->writeln($subheader);
4✔
115
        }
116

117
        return $this;
10✔
118
    }
119

UNCOV
120
    public function instructions(array|string $lines): static
×
121
    {
UNCOV
122
        $this->writeln(new InstructionsRenderer()->render(wrap($lines)));
×
123

UNCOV
124
        return $this;
×
125
    }
126

127
    public function writeln(string $line = ''): static
177✔
128
    {
129
        $this->write($line . PHP_EOL);
177✔
130

131
        return $this;
177✔
132
    }
133

134
    public function writeWithLanguage(string $contents, Language $language): self
187✔
135
    {
136
        if ($this->label) {
187✔
137
            $contents = "<style='dim fg-blue'>//</style> <style='bold fg-blue'>" . $this->label . "\n{$contents}";
4✔
138
        }
139

140
        $this->output->write($this->highlighter->parse($contents, $language));
187✔
141

142
        return $this;
187✔
143
    }
144

145
    public function writeRaw(string $contents): self
1✔
146
    {
147
        $this->output->write($contents);
1✔
148

149
        return $this;
1✔
150
    }
151

152
    public function info(string $contents, ?string $title = null): self
4✔
153
    {
154
        $this->writeln(new MessageRenderer('𝓲', 'blue')->render($contents));
4✔
155

156
        return $this;
4✔
157
    }
158

159
    public function error(string $contents, ?string $title = null): self
7✔
160
    {
161
        $this->writeln(new MessageRenderer('×', 'red')->render($contents, $title));
7✔
162

163
        return $this;
7✔
164
    }
165

UNCOV
166
    public function warning(string $contents, ?string $title = null): self
×
167
    {
UNCOV
168
        $this->writeln(new MessageRenderer('⚠', 'yellow')->render($contents, $title));
×
169

UNCOV
170
        return $this;
×
171
    }
172

173
    public function success(string $contents, ?string $title = null): self
54✔
174
    {
175
        $this->writeln(new MessageRenderer('✓', 'green')->render($contents, $title));
54✔
176

177
        return $this;
54✔
178
    }
179

180
    public function withLabel(string $label): self
4✔
181
    {
182
        $clone = clone $this;
4✔
183

184
        $clone->label = $label;
4✔
185

186
        return $clone;
4✔
187
    }
188

189
    public function keyValue(string $key, ?string $value = null): self
8✔
190
    {
191
        $this->writeln(new KeyValueRenderer()->render($key, $value));
8✔
192

193
        return $this;
8✔
194
    }
195

196
    public function component(InteractiveConsoleComponent $component, array $validation = []): mixed
82✔
197
    {
198
        if ($this->componentRenderer !== null && $this->supportsPrompting() && $this->componentRenderer->isComponentSupported($this, $component)) {
82✔
UNCOV
199
            return $this->componentRenderer->render($this, $component, $validation);
×
200
        }
201

202
        if ($component instanceof HasStaticComponent) {
82✔
203
            return $component->staticComponent->render($this);
82✔
204
        }
205

UNCOV
206
        throw new UnsupportedComponent($component);
×
207
    }
208

209
    public function ask(
78✔
210
        string $question,
211
        null|iterable|string $options = null,
212
        mixed $default = null,
213
        bool $multiple = false,
214
        bool $multiline = false,
215
        ?string $placeholder = null,
216
        ?string $hint = null,
217
        array $validation = [],
218
    ): null|int|string|Stringable|UnitEnum|array {
219
        if ($this->isForced && $default) {
78✔
UNCOV
220
            return $default;
×
221
        }
222

223
        if (is_iterable($options)) {
78✔
224
            $options = wrap($options);
29✔
225
        }
226

227
        if (is_string($options) && is_a($options, BackedEnum::class, allow_string: true)) {
78✔
UNCOV
228
            $options = $options::cases();
×
229
        }
230

231
        if (! $multiple && is_array($default)) {
78✔
UNCOV
232
            throw new InvalidArgumentException('The default value cannot be an array when `multiple` is `false`.');
×
233
        }
234

235
        if ($options === null || $options === []) {
78✔
236
            return $this->component(new TextInputComponent(
52✔
237
                label: $question,
52✔
238
                default: $default,
52✔
239
                placeholder: $placeholder,
52✔
240
                hint: $hint,
52✔
241
                multiline: $multiline,
52✔
242
            ));
52✔
243
        }
244

245
        if ($multiple) {
29✔
246
            return $this->component(new MultipleChoiceComponent(
7✔
247
                label: $question,
7✔
248
                options: $options,
7✔
249
                default: wrap($default),
7✔
250
            ));
7✔
251
        }
252

253
        $component = new SingleChoiceComponent(
29✔
254
            label: $question,
29✔
255
            options: $options,
29✔
256
            default: $default,
29✔
257
        );
29✔
258

259
        return $this->component($component, $validation);
29✔
260
    }
261

262
    public function confirm(string $question, bool $default = false, ?string $yes = null, ?string $no = null): bool
22✔
263
    {
264
        if ($this->isForced) {
22✔
265
            return true;
6✔
266
        }
267

268
        return $this->component(new ConfirmComponent($question, $default, $yes, $no));
16✔
269
    }
270

UNCOV
271
    public function password(string $label = 'Password', bool $confirm = false, array $validation = []): ?string
×
272
    {
UNCOV
273
        if (! $confirm) {
×
274
            return $this->component(new PasswordComponent($label), $validation);
×
275
        }
276

UNCOV
277
        $password = null;
×
278
        $passwordConfirm = null;
×
279

UNCOV
280
        while ($password === null || $password !== $passwordConfirm) { // @mago-expect security/no-insecure-comparison
×
281
            if ($password !== null) {
×
282
                $this->error("Passwords don't match");
×
283
            }
284

UNCOV
285
            $password = $this->component(new PasswordComponent($label));
×
286
            $passwordConfirm = $this->component(new PasswordComponent('Please confirm'));
×
287
        }
288

UNCOV
289
        return $password;
×
290
    }
291

292
    public function progressBar(iterable $data, Closure $handler): array
1✔
293
    {
294
        return $this->component(new ProgressBarComponent($data, $handler));
1✔
295
    }
296

UNCOV
297
    public function task(string $label, null|Process|Closure $handler = null): bool
×
298
    {
UNCOV
299
        return $this->component(new TaskComponent($label, $handler));
×
300
    }
301

302
    public function search(string $label, Closure $search, bool $multiple = false, null|string|array $default = null): mixed
5✔
303
    {
304
        return $this->component(new SearchComponent($label, $search, $multiple, $default));
5✔
305
    }
306

307
    public function supportsPrompting(): bool
81✔
308
    {
309
        if ($this->supportsPrompting === false) {
81✔
310
            return false;
5✔
311
        }
312

313
        if ($this->argumentBag->get('interaction')?->value === false) {
76✔
UNCOV
314
            return false;
×
315
        }
316

317
        return true;
76✔
318
    }
319
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc