Passed
Push — develop ( 2e9417...4ff4aa )
by nguereza
02:12
created

Interactor::setWriter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine Console
5
 *
6
 * Platine Console is a powerful library with support of custom
7
 * style to build command line interface applications
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Console
12
 * Copyright (c) 2017-2020 Jitendra Adhikari
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy
15
 * of this software and associated documentation files (the "Software"), to deal
16
 * in the Software without restriction, including without limitation the rights
17
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
 * copies of the Software, and to permit persons to whom the Software is
19
 * furnished to do so, subject to the following conditions:
20
 *
21
 * The above copyright notice and this permission notice shall be included in all
22
 * copies or substantial portions of the Software.
23
 *
24
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
 * SOFTWARE.
31
 */
32
33
/**
34
 *  @file Interactor.php
35
 *
36
 *  The Input/Output interaction class
37
 *
38
 *  @package    Platine\Console\IO
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   https://www.platine-php.com
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Console\IO;
50
51
use Platine\Console\Input\Reader;
52
use Platine\Console\Output\Writer;
53
use Platine\Console\Util\Helper;
54
use Throwable;
55
56
/**
57
 * Class Interactor
58
 * @package Platine\Console\IO
59
 */
60
class Interactor
61
{
62
    /**
63
     * Stream reader instance
64
     * @var Reader
65
     */
66
    protected Reader $reader;
67
68
    /**
69
     * Stream writer instance
70
     * @var Writer
71
     */
72
    protected Writer $writer;
73
74
    /**
75
     * Create new instance
76
     * @param string|null $input
77
     * @param string|null $output
78
     */
79
    public function __construct(?string $input = null, ?string $output = null)
80
    {
81
        $this->reader = new Reader($input);
82
        $this->writer = new Writer($output);
83
    }
84
85
    /**
86
     * Return the reader instance
87
     * @return Reader
88
     */
89
    public function reader(): Reader
90
    {
91
        return $this->reader;
92
    }
93
94
    /**
95
     * Return the writer instance
96
     * @return Writer
97
     */
98
    public function writer(): Writer
99
    {
100
        return $this->writer;
101
    }
102
103
    /**
104
     * Set the reader
105
     * @param Reader $reader
106
     * @return $this
107
     */
108
    public function setReader(Reader $reader): self
109
    {
110
        $this->reader = $reader;
111
        return $this;
112
    }
113
114
    /**
115
     * Set the writer
116
     * @param Writer $writer
117
     * @return $this
118
     */
119
    public function setWriter(Writer $writer): self
120
    {
121
        $this->writer = $writer;
122
        return $this;
123
    }
124
125
126
    /**
127
     * Confirms if user agrees to prompt as indicated by given text.
128
     * @param string $text
129
     * @param string $default
130
     * @return bool
131
     */
132
    public function confirm(string $text, string $default = 'y'): bool
133
    {
134
        $choice = $this->choice($text, ['y', 'n'], $default, false);
135
136
        return strtolower(isset($choice[0]) ? $choice[0] : $default) === 'y';
137
    }
138
139
    /**
140
     * Let user make a choice out of available choices.
141
     * @param string $text
142
     * @param array<int|string, string> $choices
143
     * @param mixed $default
144
     * @param bool $case
145
     *
146
     * @return mixed
147
     */
148
    public function choice(string $text, array $choices, $default = null, bool $case = false)
149
    {
150
        $this->writer->yellow($text);
151
152
        $this->listOptions($choices, $default, false);
153
154
        $choice = $this->reader->read($default);
155
156
        return $this->isValidChoice($choice, $choices, $case)
157
                ? $choice
158
                : $default;
159
    }
160
161
    /**
162
     * Let user make multiple choice out of available choices.
163
     * @param string $text
164
     * @param array<int|string, string> $choices
165
     * @param mixed $default
166
     * @param bool $case
167
     *
168
     * @return mixed
169
     */
170
    public function choices(string $text, array $choices, $default = null, bool $case = false)
171
    {
172
        $this->writer->yellow($text);
173
174
        $this->listOptions($choices, $default, true);
175
176
        $choice = $this->reader->read($default);
177
        $values = [];
178
        if (is_string($choice)) {
179
            $values = explode(',', str_replace(' ', '', $choice));
180
        }
181
182
        $valid = [];
183
184
        foreach ($values as $option) {
185
            if ($this->isValidChoice($option, $choices, $case)) {
186
                $valid[] = $option;
187
            }
188
        }
189
190
        return !empty($valid) ? $valid : (array) $default;
191
    }
192
193
    /**
194
     * Prompt user for free input.
195
     * @param string $text
196
     * @param mixed $default
197
     * @param callable|null $callback
198
     * @param bool $required
199
     * @param bool $hidden
200
     * @return mixed
201
     */
202
    public function prompt(
203
        string $text,
204
        $default = null,
205
        ?callable $callback = null,
206
        bool $required = true,
207
        bool $hidden = false
208
    ) {
209
        $error = 'Invalid value, please try again';
210
        $readFunct = ['read', 'readHidden'][(int) $hidden];
211
212
        $this->writer->yellow($text);
213
214
        $this->writer->dim(
215
            $default !== null
216
                            ? ' [' . $default . ']: '
217
                            : ': '
218
        );
219
220
        try {
221
            $input = $this->reader->{$readFunct}($default, $callback);
222
        } catch (Throwable $ex) {
223
            $input = '';
224
            $error = $ex->getMessage();
225
        }
226
227
        while ($required && $input === '') {
228
            $this->writer->bgRed($error, true);
229
230
            $input = $this->prompt($text, $default, $callback, $required, $hidden);
231
        }
232
233
        return $input ? $input : $default;
234
    }
235
236
    /**
237
     * Prompt user for secret input like password.
238
     * Currently for Unix only.
239
     * @param string $text
240
     * @param callable|null $callback
241
     * @param bool $required
242
     * @return mixed
243
     */
244
    public function promptHidden(
245
        string $text,
246
        ?callable $callback = null,
247
        bool $required = true
248
    ) {
249
        return $this->prompt($text, null, $callback, $required, true);
250
    }
251
252
    /**
253
     * Show choices list.
254
     * @param array<int|string, string> $choices
255
     * @param mixed $default
256
     * @param bool $isMutliple
257
     * @return $this
258
     */
259
    protected function listOptions(
260
        array $choices,
261
        $default = null,
262
        bool $isMutliple = false
263
    ): self {
264
        if (!Helper::isAssocArray($choices)) {
265
            return $this->promptOptions($choices, $default);
266
        }
267
268
        $maxLength = max(array_map('strlen', array_keys($choices)));
269
270
        foreach ($choices as $choice => $desc) {
271
            $this->writer->eol()
272
                         ->cyan(
273
                             str_pad(' [' . $choice . ']', $maxLength + 6)
274
                         );
275
276
            $this->writer->dim($desc);
277
        }
278
279
        $label = $isMutliple ? 'Choices (comma separated)' : 'Choice';
280
281
        $this->writer->eol()->yellow($label);
282
283
        /** @var array<string> $keys */
284
        $keys = array_keys($choices);
285
286
        return $this->promptOptions($keys, $default);
287
    }
288
289
    /**
290
     * Show prompt with possible options.
291
     * @param array<int|string, string> $choices
292
     * @param mixed $default
293
     * @return $this
294
     */
295
    protected function promptOptions(array $choices, $default): self
296
    {
297
        $options = '';
298
299
        foreach ($choices as $choice) {
300
            $style = in_array($choice, (array) $default) ? 'info' : 'ok';
301
            $options .= '/<' . $style . '>' . $choice . '</end>';
302
        }
303
304
        $finalOptions = ltrim($options, '/');
305
306
        $this->writer()->colors(' (' . $finalOptions . '): ');
307
308
        return $this;
309
    }
310
311
    /**
312
     * Check if user choice is one of possible choices.
313
     * @param string $choice
314
     * @param array<int|string, string> $choices
315
     * @param bool $case
316
     * @return bool
317
     */
318
    protected function isValidChoice(string $choice, array $choices, bool $case): bool
319
    {
320
        if (Helper::isAssocArray($choices)) {
321
            /** @var array<string> $choices */
322
            $choices = array_keys($choices);
323
        }
324
325
        $func = ['strcasecmp', 'strcmp'][(int) $case];
326
        foreach ($choices as $option) {
327
            //Don't use === here
328
            if ($func($choice, (string) $option) == 0) {
329
                return true;
330
            }
331
        }
332
333
        return false;
334
    }
335
}
336