Passed
Push — master ( bd2f87...823242 )
by Melech
03:56
created

Output::determineQuestionType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Cli\Interaction\Output;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Valkyrja\Cli\Interaction\Enum\ExitCode;
18
use Valkyrja\Cli\Interaction\Formatter\HighlightedTextFormatter;
19
use Valkyrja\Cli\Interaction\Message\Contract\Answer;
20
use Valkyrja\Cli\Interaction\Message\Contract\Message;
21
use Valkyrja\Cli\Interaction\Message\Contract\Question;
22
use Valkyrja\Cli\Interaction\Message\Message as MessageMessage;
23
use Valkyrja\Cli\Interaction\Message\NewLine;
24
use Valkyrja\Cli\Interaction\Output\Contract\Output as Contract;
25
26
/**
27
 * Class Output.
28
 *
29
 * @author Melech Mizrachi
30
 */
31
class Output implements Contract
32
{
33
    /**
34
     * The unwritten messages.
35
     *
36
     * @var Message[]
37
     */
38
    protected array $unwrittenMessages = [];
39
40
    /**
41
     * The written messages.
42
     *
43
     * @var Message[]
44
     */
45
    protected array $writtenMessages = [];
46
47
    public function __construct(
48
        protected bool $isInteractive = true,
49
        protected bool $isQuiet = false,
50
        protected bool $isSilent = false,
51
        protected ExitCode|int $exitCode = ExitCode::SUCCESS,
52
        Message ...$messages,
53
    ) {
54
        $this->unwrittenMessages = $messages;
55
    }
56
57
    /**
58
     * @inheritDoc
59
     *
60
     * @return Message[]
61
     */
62
    #[Override]
63
    public function getMessages(): array
64
    {
65
        return [
66
            ...$this->writtenMessages,
67
            ...$this->unwrittenMessages,
68
        ];
69
    }
70
71
    /**
72
     * @inheritDoc
73
     *
74
     * @return Message[]
75
     */
76
    #[Override]
77
    public function getWrittenMessages(): array
78
    {
79
        return $this->writtenMessages;
80
    }
81
82
    /**
83
     * @inheritDoc
84
     */
85
    #[Override]
86
    public function hasWrittenMessage(): bool
87
    {
88
        return $this->writtenMessages !== [];
89
    }
90
91
    /**
92
     * @inheritDoc
93
     *
94
     * @return Message[]
95
     */
96
    #[Override]
97
    public function getUnwrittenMessages(): array
98
    {
99
        return $this->unwrittenMessages;
100
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105
    #[Override]
106
    public function hasUnwrittenMessage(): bool
107
    {
108
        return $this->unwrittenMessages !== [];
109
    }
110
111
    /**
112
     * @inheritDoc
113
     */
114
    #[Override]
115
    public function withMessages(Message ...$messages): static
116
    {
117
        $new = clone $this;
118
119
        $new->unwrittenMessages = $messages;
120
121
        return $new;
122
    }
123
124
    /**
125
     * @inheritDoc
126
     */
127
    #[Override]
128
    public function withAddedMessages(Message ...$messages): static
129
    {
130
        $new = clone $this;
131
132
        $new->unwrittenMessages = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array($new->unwrittenMessages, $messages) of type array<integer,array|arra...sage\Contract\Message>> is incompatible with the declared type Valkyrja\Cli\Interaction...sage\Contract\Message[] of property $unwrittenMessages.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
133
            ...$new->unwrittenMessages,
134
            ...$messages,
135
        ];
136
137
        return $new;
138
    }
139
140
    /**
141
     * @inheritDoc
142
     */
143
    #[Override]
144
    public function withAddedMessage(Message $message): static
145
    {
146
        $new = clone $this;
147
148
        $new->unwrittenMessages[] = $message;
149
150
        return $new;
151
    }
152
153
    /**
154
     * @inheritDoc
155
     */
156
    #[Override]
157
    public function writeMessages(): static
158
    {
159
        $new = clone $this;
160
161
        // Avoid writing messages twice or more if writeMessages is called in a callback within the foreach loop
162
        $unwrittenMessages = $this->unwrittenMessages;
163
        // Ensure all unwritten messages are truly removed
164
        $new->unwrittenMessages = [];
165
166
        foreach ($unwrittenMessages as $message) {
167
            $new->determineQuestionType($message);
168
        }
169
170
        return $new;
171
    }
172
173
    /**
174
     * @inheritDoc
175
     */
176
    #[Override]
177
    public function isInteractive(): bool
178
    {
179
        return $this->isInteractive;
180
    }
181
182
    /**
183
     * @inheritDoc
184
     */
185
    #[Override]
186
    public function withIsInteractive(bool $isInteractive): static
187
    {
188
        $new = clone $this;
189
190
        $new->isInteractive = $isInteractive;
191
192
        return $new;
193
    }
194
195
    /**
196
     * @inheritDoc
197
     */
198
    #[Override]
199
    public function isQuiet(): bool
200
    {
201
        return $this->isQuiet;
202
    }
203
204
    /**
205
     * @inheritDoc
206
     */
207
    #[Override]
208
    public function withIsQuiet(bool $isQuiet): static
209
    {
210
        $new = clone $this;
211
212
        $new->isQuiet = $isQuiet;
213
214
        return $new;
215
    }
216
217
    /**
218
     * @inheritDoc
219
     */
220
    #[Override]
221
    public function isSilent(): bool
222
    {
223
        return $this->isSilent;
224
    }
225
226
    /**
227
     * @inheritDoc
228
     */
229
    #[Override]
230
    public function withIsSilent(bool $isSilent): static
231
    {
232
        $new = clone $this;
233
234
        $new->isSilent = $isSilent;
235
236
        return $new;
237
    }
238
239
    /**
240
     * @inheritDoc
241
     */
242
    #[Override]
243
    public function getExitCode(): ExitCode|int
244
    {
245
        return $this->exitCode;
246
    }
247
248
    /**
249
     * @inheritDoc
250
     */
251
    #[Override]
252
    public function withExitCode(ExitCode|int $exitCode): static
253
    {
254
        $new = clone $this;
255
256
        $new->exitCode = $exitCode;
257
258
        return $new;
259
    }
260
261
    /**
262
     * Determine the type of message and write it.
263
     *
264
     * @param Message $message The message
265
     *
266
     * @return void
267
     */
268
    protected function determineQuestionType(Message $message): void
269
    {
270
        match (true) {
271
            $message instanceof Question => $this->askQuestion($message),
272
            default                      => $this->writeMessage($message),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->writeMessage($message) targeting Valkyrja\Cli\Interaction...\Output::writeMessage() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
273
        };
274
    }
275
276
    /**
277
     * Write a message.
278
     *
279
     * @param Message $message The message
280
     *
281
     * @return void
282
     */
283
    protected function writeMessage(Message $message): void
284
    {
285
        $this->setMessageAsWritten($message);
286
287
        if ($this->isSilent || ($this->isQuiet && $this->exitCode === ExitCode::SUCCESS)) {
288
            return;
289
        }
290
291
        $this->outputMessage($message);
292
    }
293
294
    /**
295
     * Set a message as written.
296
     *
297
     * @param Message $message The message
298
     *
299
     * @return void
300
     */
301
    protected function setMessageAsWritten(Message $message): void
302
    {
303
        $this->writtenMessages[] = $message;
304
    }
305
306
    /**
307
     * Output a message.
308
     *
309
     * @param Message $message The message
310
     *
311
     * @return void
312
     */
313
    protected function outputMessage(Message $message): void
314
    {
315
        echo $message->getFormattedText();
316
    }
317
318
    /**
319
     * Ask a question.
320
     *
321
     * @param Question $question The question
322
     *
323
     * @return static
324
     */
325
    protected function askQuestion(Question $question): static
326
    {
327
        $this->writeQuestion($question);
328
329
        $answer = $question->getAnswer();
330
331
        if ($this->isInteractive && ! $this->isQuiet && ! $this->isSilent) {
332
            $answer = $question->ask();
333
334
            if (! $answer->isValidResponse()) {
335
                // For posterity add the answer with the invalid user response to the written messages list
336
                $this->writeAnswerAfterResponse($answer);
337
338
                // Re-ask the question
339
                return $this->askQuestion($question);
340
            }
341
        }
342
343
        $this->writeAnswerAfterResponse($answer);
344
345
        $callable = $question->getCallable();
346
347
        /** @var static $output */
348
        $output = $callable($this, $answer);
349
350
        return $output;
351
    }
352
353
    /**
354
     * Write a question.
355
     *
356
     * @param Question $question The question
357
     *
358
     * @return void
359
     */
360
    protected function writeQuestion(Question $question): void
361
    {
362
        // Write the question text
363
        $this->writeMessage($question);
364
365
        $answer = $question->getAnswer();
366
367
        $validResponses = $answer->getAllowedResponses();
368
369
        if ($validResponses !== []) {
370
            // (`valid` or `also valid` or `another valid value`)
371
            $this->writeMessage(new MessageMessage(' ('));
372
            $this->writeMessage(new MessageMessage(implode(' or ', array_map(static fn (string $value) => "`$value`", $validResponses))));
373
            $this->writeMessage(new MessageMessage(')'));
374
        }
375
376
        // [default: "defaultResponse"]
377
        $this->writeMessage(new MessageMessage(' [default: "'));
378
        $this->writeMessage(new MessageMessage($answer->getDefaultResponse(), new HighlightedTextFormatter()));
379
        $this->writeMessage(new MessageMessage('"]'));
380
381
        // :
382
        // > response will be typed here
383
        $this->writeMessage(new MessageMessage(':'));
384
        $this->writeMessage(new NewLine());
385
        $this->writeMessage(new MessageMessage('> '));
386
    }
387
388
    /**
389
     * Write an answer after it has been answered.
390
     *
391
     * @param Answer $answer The answer
392
     *
393
     * @return void
394
     */
395
    protected function writeAnswerAfterResponse(Answer $answer): void
396
    {
397
        $this->setMessageAsWritten($answer);
398
        $this->writeMessage(new NewLine());
399
    }
400
}
401