-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuestion.php
More file actions
185 lines (148 loc) · 5.45 KB
/
Copy pathQuestion.php
File metadata and controls
185 lines (148 loc) · 5.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<?php
declare(strict_types=1);
namespace MagicPush\CliToolkit\Question;
use LogicException;
use RuntimeException;
class Question {
protected const string QUESTION_POSTFIX = ': ';
protected string $answer;
/** @var string[] * */
protected array $possibleAnswers = [];
protected string $questionPostfix;
protected string $defaultAnswer = '';
protected bool $isAnswerCaseSensitive = false;
protected ?string $answerValidatorPattern = null;
protected string $validationErrorMessage;
protected readonly QuestionFormatter $formatter;
/**
* Just a handy alternative for {@see __construct()}.
*/
public static function create(string $question): static {
return new static($question);
}
public function __construct(protected readonly string $question) {
$this->questionPostfix = static::QUESTION_POSTFIX;
$this->formatter = QuestionFormatter::createForStdOut();
}
/**
* Ask user to enter the answer from the keyboard and get the answer.
*/
public function ask(): string {
$this->showQuestion();
$answer = $this->getAnswerOrDefault();
try {
$this->validateAnswer($answer);
$this->answer = $answer;
} catch (RuntimeException $e) {
echo $e->getMessage() . PHP_EOL . PHP_EOL;
$this->ask();
}
return $this->answer;
}
/**
* Ask a question with possible yes/no answers.
*/
public static function confirm(string $question): bool {
$answer = (new static($question))
->possibleAnswers(['Y', 'N'])
->defaultAnswer('N')
->ask();
return 'Y' === mb_strtoupper($answer);
}
/**
* Ask a question with possible yes/no answers, exit if got "no" answer.
*/
public static function confirmOrDie(string $question): void {
if (!static::confirm($question)) {
exit(1);
}
}
/**
* What to add after the question string: `My question%SUBSTRING% `. Like a new line character, colon, etc.
*
* By default the substring is {@see QUESTION_POSTFIX}.
*/
public function substringAfterQuestion(string $substring): static {
$this->questionPostfix = $substring;
return $this;
}
public function defaultAnswer(string $defaultAnswer): static {
$this->defaultAnswer = $defaultAnswer;
return $this;
}
/**
* @param string[] $possibleAnswers
*/
public function possibleAnswers(
array $possibleAnswers,
bool $isCaseSensitive = false,
string $errorMessage = 'Invalid answer',
): static {
if ($this->answerValidatorPattern) {
throw new LogicException('Can`t set possible answers when answer validator pattern is set');
}
$this->possibleAnswers = $possibleAnswers;
$this->isAnswerCaseSensitive = $isCaseSensitive;
$this->validationErrorMessage = $errorMessage;
return $this;
}
public function answerValidatorPattern(
string $answerValidatorPattern,
string $errorMessage = 'Invalid answer',
): static {
if ($this->possibleAnswers) {
throw new LogicException('Can`t set answer validator pattern when possible answers are set');
}
$this->answerValidatorPattern = $answerValidatorPattern;
$this->validationErrorMessage = $errorMessage;
return $this;
}
/**
* Asking the question.
*/
protected function showQuestion(): void {
echo $this->formatter->question($this->question)
. ($this->possibleAnswers ? ' ' . $this->formatter->value(implode(' / ', $this->possibleAnswers)) : '')
. ($this->defaultAnswer ? $this->formatter->defaultValue(" ({$this->defaultAnswer})") : '')
. $this->questionPostfix;
}
/**
* Return a user answer or default value.
*/
protected function getAnswerOrDefault(): string {
$input = trim($this->getInput());
return '' === $input ? $this->defaultAnswer : $input;
}
/**
* Get a user input.
*/
protected function getInput(): string {
return fgets(STDIN);
}
/**
* Validate an answer by a pattern or a list of possible values (if anything of that is set).
*/
protected function validateAnswer(string $answer): void {
if ($this->answerValidatorPattern && !preg_match($this->answerValidatorPattern, $answer)) {
throw new RuntimeException(QuestionFormatter::createForStdErr()->error($this->validationErrorMessage));
}
if ($this->possibleAnswers) {
if ($this->isAnswerCaseSensitive) {
$answerToCompare = $answer;
$possibleAnswers = $this->possibleAnswers;
} else {
$answerToCompare = mb_strtolower($answer);
$possibleAnswers = array_map(function ($possibleAnswer) {
return mb_strtolower($possibleAnswer);
}, $this->possibleAnswers);
}
if (in_array($answerToCompare, $possibleAnswers)) {
return;
}
$formatter = QuestionFormatter::createForStdErr();
$message = $formatter->error($this->validationErrorMessage . '. Possible answers: ')
. $formatter->value(implode(', ', $this->possibleAnswers));
throw new RuntimeException($message);
}
}
}