Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
Command
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
10 / 10
16
100.00% covered (success)
100.00%
1 / 1
 handle
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 safeHandle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handleException
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 fileLink
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 infoComment
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 gray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 inlineGray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 href
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 indentedLine
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 askForString
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Console\Concerns;
6
7use Exception;
8use Hyde\Hyde;
9use Hyde\Facades\Config;
10use LaravelZero\Framework\Commands\Command as BaseCommand;
11
12use function is_string;
13use function array_keys;
14use function array_values;
15use function realpath;
16use function sprintf;
17use function str_repeat;
18use function str_replace;
19
20/**
21 * A base class for HydeCLI command that adds some extra functionality and output
22 * helpers to reduce repeated code and to provide a consistent user interface.
23 */
24abstract class Command extends BaseCommand
25{
26    final public const USER_EXIT = 130;
27
28    /**
29     * The base handle method that can be overridden by child classes.
30     *
31     * Alternatively, implement the safeHandle method in your child class
32     * to utilize the automatic exception handling provided by this method.
33     *
34     * @return int The exit code.
35     */
36    public function handle(): int
37    {
38        try {
39            return $this->safeHandle();
40        } catch (Exception $exception) {
41            return $this->handleException($exception);
42        }
43    }
44
45    /**
46     * This method can be overridden by child classes to provide automatic exception handling.
47     *
48     * Existing code can be converted simply by renaming the handle() method to safeHandle().
49     *
50     * @return int The exit code.
51     */
52    protected function safeHandle(): int
53    {
54        return Command::SUCCESS;
55    }
56
57    /**
58     * Handle an exception that occurred during command execution.
59     *
60     * @return int The exit code
61     */
62    public function handleException(Exception $exception): int
63    {
64        // When testing it might be more useful to see the full stack trace, so we have an option to actually throw the exception.
65        if (Config::getBool('app.throw_on_console_exception', false)) {
66            throw $exception;
67        }
68
69        // If the exception was thrown from the same file as a command, then we don't need to show which file it was thrown from.
70        $location = str_ends_with($exception->getFile(), 'Command.php') ? '' : sprintf(' at %s:%s',
71            $exception->getFile(), $exception->getLine()
72        );
73        $this->error("Error: {$exception->getMessage()}".$location);
74
75        return Command::FAILURE;
76    }
77
78    /**
79     * Create a filepath that can be opened in the browser from a terminal.
80     *
81     * @param  string|null  $label  If provided, the link will be wrapped in a Symfony Console `href` tag.
82     *                              Note that not all terminals support this, and it may lead to only
83     *                              the label being shown, and the path being lost to the void.
84     */
85    public static function fileLink(string $path, string $label = null): string
86    {
87        $link = 'file://'.str_replace('\\', '/', realpath($path) ?: Hyde::path($path));
88
89        return $label ? "<href=$link>$label</>" : $link;
90    }
91
92    /**
93     * Write a nicely formatted and consistent message to the console. Using InfoComment for a lack of a better term.
94     *
95     * Text in [brackets] will automatically be wrapped in <comment> tags.
96     */
97    public function infoComment(string $string): void
98    {
99        $replacements = [
100            '[' => '</info>[<comment>',
101            ']' => '</comment>]<info>',
102        ];
103
104        $string = str_replace(array_keys($replacements), array_values($replacements), $string);
105
106        $this->line("<info>$string</info>");
107    }
108
109    /** Write a grey-coloured line */
110    public function gray(string $string): void
111    {
112        $this->line($this->inlineGray($string));
113    }
114
115    /** @internal This method may be confused with the ->gray method and may be removed */
116    public function inlineGray(string $string): string
117    {
118        return "<fg=gray>$string</>";
119    }
120
121    /** @internal This method may be removed and should not be relied upon */
122    public function href(string $link, string $label): string
123    {
124        return "<href=$link>$label</>";
125    }
126
127    /** Write a line with the specified indentation level */
128    public function indentedLine(int $spaces, string $string): void
129    {
130        $this->line(str_repeat(' ', $spaces).$string);
131    }
132
133    public function askForString(string $question, ?string $default = null): ?string
134    {
135        /** @var string|null $answer */
136        $answer = $this->output->ask($question, $default);
137
138        return is_string($answer) ? $answer : $default;
139    }
140}