Passed
Pull Request — main (#39)
by Dimitri
04:36
created

Command::app()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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