Passed
Push — develop ( 6a6d60...d23fdb )
by nguereza
02:35
created

Command::onExit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
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 Command.php
35
 *
36
 *  The Command class
37
 *
38
 *  @package    Platine\Console\Command
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   http://www.iacademy.cf
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Console\Command;
50
51
use Platine\Console\Application;
52
use Platine\Console\Exception\InvalidParameterException;
53
use Platine\Console\Exception\RuntimeException;
54
use Platine\Console\Input\Argument;
55
use Platine\Console\Input\Option;
56
use Platine\Console\Input\Parser;
57
use Platine\Console\Input\Reader;
58
use Platine\Console\IO\Interactor;
59
use Platine\Console\Output\Writer;
60
use Platine\Console\Util\Helper;
61
use Platine\Console\Util\OutputHelper;
62
63
/**
64
 * Class Command
65
 * @package Platine\Console\Command
66
 */
67
class Command extends Parser
68
{
69
70
    /**
71
     * The command version
72
     * @var string
73
     */
74
    protected string $version = '';
75
76
    /**
77
     * The command name
78
     * @var string
79
     */
80
    protected string $name;
81
82
    /**
83
     * The command description
84
     * @var string
85
     */
86
    protected string $description = '';
87
88
    /**
89
     * The command usage example
90
     * @var string
91
     */
92
    protected string $usage = '';
93
94
    /**
95
     * The command alias
96
     * @var string
97
     */
98
    protected string $alias = '';
99
100
    /**
101
     * The Application instance
102
     * @var Application|null
103
     */
104
    protected ?Application $app = null;
105
106
    /**
107
     * The options events
108
     * @var array<callable>
109
     */
110
    protected array $events = [];
111
112
    /**
113
     * Whether to allow unknown (not registered) options
114
     * @var bool
115
     */
116
    protected bool $allowUnknown = false;
117
118
    /**
119
     * If the last seen argument was variadic
120
     * @var bool
121
     */
122
    protected bool $argVariadic = false;
123
124
    /**
125
     * The verbosity level
126
     * @var int
127
     */
128
    protected int $verbosity = 0;
129
130
    /**
131
     * Create new instance
132
     * @param string $name
133
     * @param string $description
134
     * @param bool $allowUnknown
135
     * @param Application|null $app
136
     */
137
    public function __construct(
138
        string $name,
139
        string $description = '',
140
        bool $allowUnknown = false,
141
        ?Application $app = null
142
    ) {
143
        $this->name = $name;
144
        $this->description = $description;
145
        $this->allowUnknown = $allowUnknown;
146
        $this->app = $app;
147
148
        $this->defaults();
149
    }
150
151
    /**
152
     * Return the version of this command
153
     * @return string
154
     */
155
    public function getVersion(): string
156
    {
157
        return $this->version;
158
    }
159
160
    /**
161
     * Set the command version
162
     * @param string $version
163
     * @return $this
164
     */
165
    public function setVersion(string $version): self
166
    {
167
        $this->version = $version;
168
169
        return $this;
170
    }
171
172
    /**
173
     * Return the command name
174
     * @return string
175
     */
176
    public function getName(): string
177
    {
178
        return $this->name;
179
    }
180
181
    /**
182
     * Set the command name
183
     * @param string $name
184
     * @return $this
185
     */
186
    public function setName(string $name): self
187
    {
188
        $this->name = $name;
189
190
        return $this;
191
    }
192
193
    /**
194
     * Return the command description
195
     * @return string
196
     */
197
    public function getDescription(): string
198
    {
199
        return $this->description;
200
    }
201
202
    /**
203
     * Set the command description
204
     * @param string $description
205
     * @return $this
206
     */
207
    public function setDescription(string $description): self
208
    {
209
        $this->description = $description;
210
211
        return $this;
212
    }
213
214
    /**
215
     * Return the application instance
216
     * @return Application|null
217
     */
218
    public function getApp(): ?Application
219
    {
220
        return $this->app;
221
    }
222
223
    /**
224
     * Bind to given application
225
     * @param Application|null $app
226
     * @return $this
227
     */
228
    public function bind(?Application $app): self
229
    {
230
        $this->app = $app;
231
232
        return $this;
233
    }
234
235
    /**
236
     * Return the command usage
237
     * @return string
238
     */
239
    public function getUsage(): string
240
    {
241
        return $this->usage;
242
    }
243
244
    /**
245
     * Set the command usage
246
     * @param string $usage
247
     * @return $this
248
     */
249
    public function setUsage(string $usage): self
250
    {
251
        $this->usage = $usage;
252
253
        return $this;
254
    }
255
256
    /**
257
     * Return the command alias
258
     * @return string
259
     */
260
    public function getAlias(): string
261
    {
262
        return $this->alias;
263
    }
264
265
    /**
266
     * Set the command alias
267
     * @param string $alias
268
     * @return $this
269
     */
270
    public function setAlias(string $alias): self
271
    {
272
        $this->alias = $alias;
273
274
        return $this;
275
    }
276
277
    /**
278
     * Sets event handler for last (or given) option.
279
     * @param callable $callback
280
     * @param string|null $option
281
     * @return $this
282
     */
283
    public function on(callable $callback, ?string $option = null): self
284
    {
285
        $names = array_keys($this->options());
286
        $this->events[$option ? $option : end($names)] = $callback;
287
288
        return $this;
289
    }
290
291
    /**
292
     * Register exit handler.
293
     * @param callable $callback
294
     * @return $this
295
     */
296
    public function onExit(callable $callback): self
297
    {
298
        $this->events['_exit'] = $callback;
299
300
        return $this;
301
    }
302
303
    /**
304
     * Add command option
305
     * @param string $raw
306
     * @param string $description
307
     * @param mixed $default
308
     * @param bool $required
309
     * @param bool $optional
310
     * @param bool $variadic
311
     * @param callable|null $filter
312
     * @return $this
313
     */
314
    public function addOption(
315
        string $raw,
316
        string $description,
317
        $default = null,
318
        bool $required = false,
319
        bool $optional = false,
320
        bool $variadic = false,
321
        ?callable $filter = null
322
    ): self {
323
        $option = new Option(
324
            $raw,
325
            $description,
326
            $default,
327
            $required,
328
            $optional,
329
            $variadic,
330
            $filter
331
        );
332
        $this->register($option);
333
334
        return $this;
335
    }
336
337
    /**
338
     * Add command argument
339
     * @param string $raw
340
     * @param string $description
341
     * @param mixed $default
342
     * @param bool $required
343
     * @param bool $optional
344
     * @param bool $variadic
345
     * @param callable|null $filter
346
     * @return $this
347
     */
348
    public function addArgument(
349
        string $raw,
350
        string $description = '',
351
        $default = null,
352
        bool $required = false,
353
        bool $optional = false,
354
        bool $variadic = false,
355
        ?callable $filter = null
356
    ): self {
357
        $argument = new Argument(
358
            $raw,
359
            $description,
360
            $default,
361
            $required,
362
            $optional,
363
            $variadic,
364
            $filter
365
        );
366
367
        if ($this->argVariadic) {
368
            throw new InvalidParameterException('Only last argument can be variadic');
369
        }
370
371
        if ($argument->isVariadic()) {
372
            $this->argVariadic = true;
373
        }
374
375
        $this->register($argument);
376
377
        return $this;
378
    }
379
380
    /**
381
     * Return all command options (i.e without defaults).
382
     * @return array<Option>
383
     */
384
    public function commandOptions(): array
385
    {
386
        $options = $this->options;
387
388
        unset($options['help']);
389
        unset($options['version']);
390
        unset($options['verbosity']);
391
392
        return $options;
393
    }
394
395
    /**
396
     * Show this command help and abort
397
     * @return mixed
398
     */
399
    public function showHelp()
400
    {
401
        $io = $this->io();
402
        $helper = new OutputHelper($io->writer());
403
404
        $io->writer()
405
            ->bold(
406
                sprintf(
407
                    'Command %s, version %s',
408
                    $this->name,
409
                    $this->version
410
                ),
411
                true
412
            )->eol();
413
414
        $io->writer()
415
                ->dim($this->description, true)->eol();
416
417
        $io->writer()
418
            ->bold(
419
                'Usage: '
420
            );
421
422
        $io->writer()
423
            ->yellow(
424
                sprintf('%s [OPTIONS...] [ARGUMENTS...]', $this->name),
425
                true
426
            );
427
428
        $helper->showArgumentsHelp($this->arguments())
429
                ->showOptionsHelp(
430
                    $this->options(),
431
                    '',
432
                    'Legend: <required> [optional] variadic...'
433
                );
434
435
        if ($this->usage) {
436
            $helper->showUsage($this->usage, $this->name);
437
        }
438
439
        return $this->emit('_exit', 0);
440
    }
441
442
    /**
443
     * Show this command version and abort
444
     * @return mixed
445
     */
446
    public function showVersion()
447
    {
448
        $this->writer()->bold(
449
            sprintf(
450
                '%s, %s',
451
                $this->name,
452
                $this->version
453
            ),
454
            true
455
        );
456
457
        return $this->emit('_exit', 0);
458
    }
459
460
    /**
461
     * Execute the command
462
     * @return mixed
463
     */
464
    public function execute()
465
    {
466
    }
467
468
    /**
469
     * Performs user interaction if required to set some missing values.
470
     * @param Reader $reader
471
     * @param Writer $writer
472
     * @return void
473
     */
474
    public function interact(Reader $reader, Writer $writer): void
0 ignored issues
show
Unused Code introduced by
The parameter $reader is not used and could be removed. ( Ignorable by Annotation )

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

474
    public function interact(/** @scrutinizer ignore-unused */ Reader $reader, Writer $writer): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $writer is not used and could be removed. ( Ignorable by Annotation )

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

474
    public function interact(Reader $reader, /** @scrutinizer ignore-unused */ Writer $writer): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
475
    {
476
    }
477
478
    /**
479
     * Tap return given object or if that is null then app instance.
480
     * This aids for chaining.
481
     * @param mixed $object
482
     * @return mixed
483
     */
484
    public function tap($object = null)
485
    {
486
        return $object ?? $this->app;
487
    }
488
489
    /**
490
     * {@inheritdoc}
491
     */
492
    public function emit(string $event, $value = null)
493
    {
494
        if (empty($this->events[$event])) {
495
            return null;
496
        }
497
498
        return ($this->events[$event])($value);
499
    }
500
501
    /**
502
     * Return the value of the given option
503
     * @param string $longName
504
     * @return mixed|null
505
     */
506
    public function getOptionValue(string $longName)
507
    {
508
        $values = $this->values();
509
510
        return array_key_exists($longName, $values)
511
                    ? $values[$longName]
512
                    : null;
513
    }
514
515
    /**
516
     * Return the value of the given argument
517
     * @param string $name
518
     * @return mixed|null
519
     */
520
    public function getArgumentValue(string $name)
521
    {
522
        $values = $this->values();
523
524
        return array_key_exists($name, $values)
525
                    ? $values[$name]
526
                    : null;
527
    }
528
529
    /**
530
     * Return the input/output instance
531
     * @return Interactor
532
     */
533
    protected function io(): Interactor
534
    {
535
        if ($this->app !== null) {
536
            return $this->app->io();
537
        }
538
539
        return new Interactor();
540
    }
541
542
    /**
543
     * Return the writer instance
544
     * @return Writer
545
     */
546
    protected function writer(): Writer
547
    {
548
        return $this->io()->writer();
549
    }
550
551
    /**
552
     * {@inheritdoc}
553
     */
554
    protected function handleUnknown(string $arg, $value = null)
555
    {
556
        if ($this->allowUnknown) {
557
            return $this->set(Helper::toCamelCase($arg), $value);
558
        }
559
560
        $values = array_filter($this->values(false));
561
562
        // Has some value, error!
563
        if (empty($values)) {
564
            throw new RuntimeException(sprintf(
565
                'Unknown option [%s]',
566
                $arg
567
            ));
568
        }
569
570
        // Has no value, show help!
571
        return $this->showHelp();
572
    }
573
574
    /**
575
     * Sets default options and exit handler.
576
     * @return $this
577
     */
578
    protected function defaults(): self
579
    {
580
        $this->addOption('-h|--help', 'Show help')
581
              ->on([$this, 'showHelp']);
582
583
        $this->addOption('-v|--version', 'Show version')
584
             ->on([$this, 'showVersion']);
585
586
        $this->addOption('-V|--verbosity', 'Verbosity level', 0)
587
             ->on(function () {
588
                $this->set('verbosity', ++$this->verbosity);
589
590
                return false;
591
             });
592
593
        $this->onExit(function (int $exitCode = 0) {
594
            $this->terminate($exitCode);
595
        });
596
597
        return $this;
598
    }
599
600
    /**
601
     * Terminate the program
602
     * @codeCoverageIgnore
603
     * @param int $exitCode
604
     * @return void
605
     */
606
    protected function terminate(int $exitCode): void
607
    {
608
        exit($exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
609
    }
610
}
611