Passed
Push — main ( 6ad81f...1b5a5a )
by Dimitri
12:10 queued 08:06
created

Command::initProps()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 1
eloc 6
c 3
b 1
f 0
nc 1
nop 0
dl 0
loc 8
ccs 6
cts 6
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Cli\Console;
13
14
use Ahc\Cli\Helper\Terminal;
15
use Ahc\Cli\Input\Reader;
16
use Ahc\Cli\IO\Interactor;
17
use Ahc\Cli\Output\Color;
18
use Ahc\Cli\Output\Cursor;
19
use Ahc\Cli\Output\ProgressBar;
20
use Ahc\Cli\Output\Writer;
21
use BlitzPHP\Exceptions\CLIException;
22
use BlitzPHP\Utilities\String\Text;
23
use Psr\Log\LoggerInterface;
24
25
/**
26
 * Classe de base utilisée pour créer des commandes pour la console
27
 *
28
 * @property string          $alias
29
 * @property array           $arguments
30
 * @property string          $description
31
 * @property string          $group
32
 * @property LoggerInterface $logger
33
 * @property string          $name
34
 * @property array           $options
35
 * @property array           $required
36
 * @property string          $service
37
 * @property bool            $suppress
38
 * @property string          $usage
39
 * @property string          $version
40
 */
41
abstract class Command
42
{
43
    /**
44
     * Le groupe sous lequel la commande est regroupée
45
     * lors de la liste des commandes.
46
     *
47
     * @var string
48
     */
49
    protected $group = '';
50
51
    /**
52
     * Le nom de la commande
53
     *
54
     * @var string
55
     */
56
    protected $name;
57
58
    /**
59
     * La description de l'usage de la commande
60
     *
61
     * @var string
62
     */
63
    protected $usage = '';
64
65
    /**
66
     * La description courte de la commande
67
     *
68
     * @var string
69
     */
70
    protected $description = '';
71
72
    /**
73
     * la description des options de la commande
74
     *
75
     * @var array<string, mixed>
76
     *
77
     * @example
78
     * `[
79
     *      'option' => [string $description, mixed|null $default_value, callable|null $filter]
80
     * ]`
81
     */
82
    protected $options = [];
83
84
    /**
85
     * La description des arguments de la commande
86
     *
87
     * @var array<string, mixed>
88
     *
89
     * @example
90
     * `[
91
     *      'argument' => [string $description, mixed|null $default_value]
92
     * ]`
93
     */
94
    protected $arguments = [];
95
96
    /**
97
     * L'alias de la commande
98
     *
99
     * @var string
100
     */
101
    protected $alias = '';
102
103
    /**
104
     * La version de la commande
105
     *
106
     * @var string
107
     */
108
    protected $version = '';
109
110
    /**
111
     * Le nom du service de la commande
112
     *
113
     * @var string
114
     */
115
    protected $service = '';
116
117
    /**
118
     * Liste des packets requis pour le fonctionnement d'une commande
119
     * Par exemple, toutes le commande du groupe Database ont besoin de blitz/database
120
     *
121
     * @var array
122
     *
123
     * @example
124
     * `[
125
     *      'vendor/package', 'vendor/package:version'
126
     * ]`
127
     */
128
    protected $required = [];
129
130
    /**
131
     * Defini si on doit supprimer les information du header (nom/version du framework) ou pas
132
     */
133
    protected bool $suppress = false;
134
135
    /**
136
     * @var Interactor
137
     */
138
    protected $io;
139
140
    /**
141
     * @var Writer
142
     */
143
    protected $writer;
144
145
    /**
146
     * @var Reader
147
     */
148
    protected $reader;
149
150
    /**
151
     * @var Color
152
     */
153
    protected $color;
154
155
    /**
156
     * @var Cursor
157
     */
158
    protected $cursor;
159
160
    /**
161
     * @var Terminal
162
     */
163
    protected $terminal;
164
165
    /**
166
     * Arguments recus apres executions
167
     */
168
    private array $_arguments = [];
169
170
    /**
171
     * Options recus apres executions
172
     */
173
    protected array $_options = [];
174
175
    /**
176
     * @param Console         $app    Application Console
177
     * @param LoggerInterface $logger Le Logger à utiliser
178
     */
179
    public function __construct(protected Console $app, protected LoggerInterface $logger)
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Cli\Console\Console 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...
180
    {
181 20
        $this->initProps();
182
    }
183
184
    /**
185
     * Instance de l'application console
186
     */
187
    final public function app(): Console
188
    {
189
        return $this->app;
190
    }
191
192
    /**
193
     * Exécution réelle de la commande.
194
     *
195
     * @param array<int|string, string|null> $params
196
     */
197
    abstract public function execute(array $params);
198
199
    /**
200
     * Definit les options recues par la commande a l'execution
201
     *
202
     * @internal Utiliser seulement par le framework pour fournir les options a la commande
203
     */
204
    final public function setOptions(array $options = []): self
205
    {
206 20
        $this->_options = $options;
207
208 20
        return $this;
209
    }
210
211
    /**
212
     * Definit les arguments recus par la commande a l'execution
213
     *
214
     * @internal Utiliser seulement par le framework pour fournir les arguments a la commande
215
     */
216
    final public function setArguments(array $arguments = []): self
217
    {
218 20
        $this->_arguments = $arguments;
219
220 20
        return $this;
221
    }
222
223
    /**
224
     * Recupere la valeur d'un argument lors de l'execution de la commande
225
     */
226
    final public function argument(string $name, mixed $default = null): mixed
227
    {
228
        if (isset($this->_arguments[$name])) {
229 6
            return $this->_arguments[$name];
230
        }
231
232 6
        return $this->_arguments[Text::camel($name)] ?? $default;
233
    }
234
235
    /**
236
     * @deprecated 1.1 Utilisez argument() a la place
237
     */
238
    final public function getArg(string $name, mixed $default = null)
239
    {
240
        return $this->argument($name, $default);
241
    }
242
243
    /**
244
     * Recupere la valeur d'une option lors de l'execution de la commande
245
     */
246
    final public function option(string $name, mixed $default = null): mixed
247
    {
248
        if (isset($this->_options[$name])) {
249 10
            return $this->_options[$name];
250
        }
251
252 12
        return $this->_options[Text::camel($name)] ?? $default;
253
    }
254
255
    /**
256
     * @deprecated 1.1 Utilisez option() a la place
257
     */
258
    final public function getOption(string $name, mixed $default = null)
259
    {
260
        return $this->option($name, $default);
261
    }
262
263
    /**
264
     * Recupere la valeur d'un parametre (option ou argument) lors de l'execution de la commande.
265
     */
266
    final public function param(string $name, mixed $default = null): mixed
267
    {
268
        $params = array_merge($this->_arguments, $this->_options);
269
270
        if (isset($params[$name])) {
271
            return $params[$name];
272
        }
273
274
        return $params[Text::camel($name)] ?? $default;
275
    }
276
277
    /**
278
     * @deprecated 1.1 Utilisez param() a la place
279
     */
280
    final public function getParam(string $name, mixed $default = null)
281
    {
282
        return $this->param($name, $default);
283
    }
284
285
    /**
286
     * Ecrit un message dans une couleur spécifique
287
     */
288
    final public function colorize(string $message, string $color): self
289
    {
290 8
        $this->writer->colors('<' . $color . '>' . $message . '</end><eol>');
291
292 8
        return $this;
293
    }
294
295
    /**
296
     * Ecrit un message de reussite
297
     */
298
    final public function ok(string $message, bool $eol = false): self
299
    {
300 6
        $this->writer->ok($message, $eol);
301
302 6
        return $this;
303
    }
304
305
    /**
306
     * Ecrit un message d'echec
307
     */
308
    final public function fail(string $message, bool $eol = false): self
309
    {
310 6
        $this->writer->error($message, $eol);
311
312 6
        return $this;
313
    }
314
315
    /**
316
     * Ecrit un message de succes
317
     */
318
    final public function success(string $message, bool $badge = true, string $label = 'SUCCESS'): self
319
    {
320
        if (! $badge) {
321 6
            $this->writer->okBold($label);
322
        } else {
323 6
            $this->writer->boldWhiteBgGreen(" {$label} ");
324
        }
325
326 6
        return $this->write(' ' . $message, true);
327
    }
328
329
    /**
330
     * Ecrit un message d'avertissement
331
     */
332
    final public function warning(string $message, bool $badge = true, string $label = 'WARNING'): self
333
    {
334
        if (! $badge) {
335
            $this->writer->warnBold($label);
336
        } else {
337
            $this->writer->boldWhiteBgYellow(" {$label} ");
338
        }
339
340
        return $this->write(' ' . $message, true);
341
    }
342
343
    /**
344
     * Ecrit un message d'information
345
     */
346
    final public function info(string $message, bool $badge = true, string $label = 'INFO'): self
347
    {
348
        if (! $badge) {
349
            $this->writer->infoBold($label);
350
        } else {
351
            $this->writer->boldWhiteBgCyan(" {$label} ");
352
        }
353
354
        return $this->write(' ' . $message, true);
355
    }
356
357
    /**
358
     * Ecrit un message d'erreur
359
     */
360
    final public function error(string $message, bool $badge = true, string $label = 'ERROR'): self
361
    {
362
        if (! $badge) {
363 2
            $this->writer->errorBold($label);
364
        } else {
365 2
            $this->writer->boldWhiteBgRed(" {$label} ");
366
        }
367
368 2
        return $this->write(' ' . $message, true);
369
    }
370
371
    /**
372
     * Ecrit la tâche actuellement en cours d'execution
373
     */
374
    final public function task(string $task, ?int $sleep = null): self
375
    {
376 6
        $this->write('>> ' . $task, true);
377
378
        if ($sleep !== null) {
379 6
            sleep($sleep);
380
        }
381
382 6
        return $this;
383
    }
384
385
    /**
386
     * Écrit EOL n fois.
387
     */
388
    final public function eol(int $n = 1): self
389
    {
390 12
        $this->writer->eol($n);
391
392 12
        return $this;
393
    }
394
395
    /**
396
     * Écrit une nouvelle ligne vide (saut de ligne).
397
     */
398
    final public function newLine(): self
399
    {
400
        return $this->eol(1);
401
    }
402
403
    /**
404
     * Générer une table pour la console. Les clés de la première ligne sont prises comme en-tête.
405
     *
406
     * @param list<array> $rows   Tableau de tableaux associés.
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Cli\Console\list 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...
407
     * @param array       $styles Par exemple : ['head' => 'bold', 'odd' => 'comment', 'even' => 'green']
408
     */
409
    final public function table(array $rows, array $styles = []): self
410
    {
411 4
        $this->writer->table($rows, $styles);
412
413 4
        return $this;
414
    }
415
416
    /**
417
     * Écrit le texte formaté dans stdout ou stderr.
418
     */
419
    final public function write(string $texte, bool $eol = false): self
420
    {
421 12
        $this->writer->write($texte, $eol);
422
423 12
        return $this;
424
    }
425
426
    /**
427
     * Écrit le texte de maniere commentée.
428
     */
429
    final public function comment(string $text, bool $eol = false): self
430
    {
431 2
        $this->writer->comment($text, $eol);
432
433 2
        return $this;
434
    }
435
436
    /**
437
     * Efface la console
438
     */
439
    final public function clear(): self
440
    {
441
        $this->cursor->clear();
442
443
        return $this;
444
    }
445
446
    /**
447
     * Affiche une bordure en pointillés
448
     */
449
    final public function border(?int $length = null, string $char = '-'): self
450
    {
451 2
        $length = $length ?: ($this->terminal->width() ?: 100);
452 2
        $str    = str_repeat($char, $length);
453 2
        $str    = substr($str, 0, $length);
454
455 2
        return $this->comment($str, true);
456
    }
457
458
    /**
459
     * Affiche les donnees formatees en json
460
     *
461
     * @param mixed $data
462
     */
463
    final public function json($data): self
464
    {
465
        $this->write(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), true);
466
467
        return $this;
468
    }
469
470
    /**
471
     * Effectue des tabulations
472
     */
473
    final public function tab(int $repeat = 1): self
474
    {
475
        $this->write(str_repeat("\t", $repeat));
476
477
        return $this;
478
    }
479
480
    /**
481
     * Laissez l'utilisateur faire un choix parmi les choix disponibles.
482
     *
483
     * @param string $text    Texte d'invite.
484
     * @param array  $choices Choix possibles pour l'utilisateur.
485
     * @param mixed  $default Valeur par défaut - si non choisie ou invalide.
486
     * @param bool   $case    Si l'entrée utilisateur doit être sensible à la casse.
487
     *
488
     * @return mixed Entrée utilisateur ou valeur par défaut.
489
     */
490
    final public function choice(string $text, array $choices, $default = null, bool $case = false): mixed
491
    {
492
        return $this->io->choice($text, $choices, $default, $case);
493
    }
494
495
    /**
496
     * Laissez l'utilisateur faire plusieurs choix parmi les choix disponibles.
497
     *
498
     * @param string $text    Texte d'invite.
499
     * @param array  $choices Choix possibles pour l'utilisateur.
500
     * @param mixed  $default Valeur par défaut - si non choisie ou invalide.
501
     * @param bool   $case    Si l'entrée utilisateur doit être sensible à la casse.
502
     *
503
     * @return mixed Entrée utilisateur ou valeur par défaut.
504
     */
505
    final public function choices(string $text, array $choices, $default = null, bool $case = false): mixed
506
    {
507
        return $this->io->choices($text, $choices, $default, $case);
508
    }
509
510
    /**
511
     * Confirme si l'utilisateur accepte une question posée par le texte donné.
512
     *
513
     * @param string $default `y|n`
514
     */
515
    final public function confirm(string $text, string $default = 'y'): bool
516
    {
517
        return $this->io->confirm($text, $default);
518
    }
519
520
    /**
521
     * Demander à l'utilisateur d'entrer une donnée
522
     *
523
     * @param callable|null $fn      L'assainisseur/validateur pour l'entrée utilisateur
524
     *                               Tout message d'exception est imprimé et démandé à nouveau.
525
     * @param int           $retry   Combien de fois encore pour réessayer en cas d'échec.
526
     * @param mixed|null    $default
527
     */
528
    final public function prompt(string $text, $default = null, ?callable $fn = null, int $retry = 3): mixed
529
    {
530
        return $this->io->prompt($text, $default, $fn, $retry);
531
    }
532
533
    /**
534
     * Demander à l'utilisateur une entrée secrète comme un mot de passe. Actuellement pour unix uniquement.
535
     *
536
     * @param callable|null $fn    L'assainisseur/validateur pour l'entrée utilisateur
537
     *                             Tout message d'exception est imprimé en tant qu'erreur.
538
     * @param int           $retry Combien de fois encore pour réessayer en cas d'échec.
539
     */
540
    final public function promptHidden(string $text, ?callable $fn = null, int $retry = 3): mixed
541
    {
542
        return $this->io->promptHidden($text, $fn, $retry);
543
    }
544
545
    /**
546
     * Peut etre utiliser par la commande pour executer d'autres commandes.
547
     *
548
     * @return mixed
549
     *
550
     * @throws CLIException
551
     */
552
    final public function call(string $command, array $arguments = [], array $options = [])
553
    {
554
        return $this->app->call($command, $arguments, $options);
555
    }
556
557
    /**
558
     * Peut etre utiliser par la commande pour verifier si une commande existe dans la liste des commandes enregistrees
559
     */
560
    final public function commandExists(string $commandName): bool
561
    {
562
        return $this->app->commandExists($commandName);
563
    }
564
565
    /**
566
     * Initialise une bar de progression
567
     */
568
    final public function progress(?int $total = null): ProgressBar
569
    {
570
        return new ProgressBar($total, $this->writer);
571
    }
572
573
    /**
574
     * Ecrit deux textes de maniere justifiee dans la console (l'un a droite, l'autre a gauche)
575
     */
576
    final public function justify(string $first, ?string $second = '', array $options = []): self
577
    {
578 2
        $second = trim($second);
0 ignored issues
show
Bug introduced by
It seems like $second can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

578
        $second = trim(/** @scrutinizer ignore-type */ $second);
Loading history...
579 2
        $first  = trim($first);
580
581 2
        $start = $first;
582 2
        $end   = $second;
583
584
        $options = [
585
            'first'  => $options['first'] ?? [],
586
            'second' => ($options['second'] ?? []) + ['bold' => 1],
587
            'sep'    => (string) ($options['sep'] ?? '.'),
588 2
        ];
589
590
        if (preg_match('/(\\x1b(?:.+)m)/U', $first, $matches)) {
591 2
            $first = str_replace($matches[1], '', $first);
592
            $first = preg_replace('/\\x1b\[0m/', '', $first);
593
        }
594
        if (preg_match('/(\\x1b(?:.+)m)/U', $second, $matches)) {
595 2
            $second = str_replace($matches[1], '', $second);
596
            $second = preg_replace('/\\x1b\[0m/', '', $second);
597
        }
598
599 2
        $firstLength  = strlen($first);
600 2
        $secondLength = strlen($second);
601
602 2
        $dashWidth = ($this->terminal->width() ?: 100) - ($firstLength + $secondLength);
603 2
        $dashWidth -= $second === '' ? 1 : 2;
604 2
        $dashWidth = $dashWidth < 0 ? 0 : $dashWidth;
605
606 2
        $first = $this->color->line($start, $options['first']);
607
        if ($second !== '') {
608 2
            $second = $this->color->line($end, $options['second']);
609
        }
610
611 2
        return $this->write($first . ' ' . str_repeat($options['sep'], $dashWidth) . ' ' . $second)->eol();
612
    }
613
614
    /**
615
     * Ecrit un texte au centre de la console
616
     */
617
    final public function center(string $text, array $options = []): self
618
    {
619
        $sep = $options['sep'] ?? ' ';
620
        unset($options['sep']);
621
622
        $dashWidth = ($this->terminal->width() ?: 100) - strlen($text);
623
        $dashWidth -= 2;
624
        $dashWidth = (int) ($dashWidth / 2);
625
626
        $text     = $this->color->line($text, $options);
627
        $repeater = str_repeat($sep, $dashWidth);
628
629
        return $this->write($repeater . ' ' . $text . ' ' . $repeater)->eol();
630
    }
631
632
    /**
633
     * Facilite l'accès à nos propriétés protégées.
634
     *
635
     * @return mixed
636
     */
637
    public function __get(string $key)
638
    {
639 20
        return $this->{$key} ?? null;
640
    }
641
642
    /**
643
     * Facilite la vérification de nos propriétés protégées.
644
     */
645
    public function __isset(string $key): bool
646
    {
647
        return isset($this->{$key});
648
    }
649
650
    /**
651
     * Initalisation des proprieté necessaires
652
     *
653
     * @return void
654
     */
655
    private function initProps()
656
    {
657 20
        $this->io       = $this->app->io();
658 20
        $this->writer   = $this->io->writer();
659 20
        $this->reader   = $this->io->reader();
660 20
        $this->color    = $this->writer->colorizer();
661 20
        $this->cursor   = $this->writer->cursor();
662 20
        $this->terminal = $this->writer->terminal();
663
    }
664
}
665