Passed
Branch master (e894a3)
by Melech
15:39 queued 01:19
created

CommandsList   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 23
eloc 60
dl 0
loc 168
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Console\Command;
15
16
use Valkyrja\Console\Commander\Commander;
17
use Valkyrja\Console\Constant\ExitCode;
18
use Valkyrja\Console\Contract\Console;
19
use Valkyrja\Console\Input\Argument;
20
use Valkyrja\Console\Input\Contract\Input as InputContract;
21
use Valkyrja\Console\Input\Input;
22
use Valkyrja\Console\Model\Contract\Command;
23
use Valkyrja\Console\Output\Contract\Output as OutputContract;
24
use Valkyrja\Console\Output\Output;
25
use Valkyrja\Console\Support\Provides;
26
27
use function array_merge;
28
use function explode;
29
use function max;
30
use function strlen;
31
use function usort;
32
33
/**
34
 * Class CommandsList.
35
 *
36
 * @author Melech Mizrachi
37
 */
38
class CommandsList extends Commander
39
{
40
    use Provides;
41
42
    /** @var string */
43
    public const string COMMAND = 'commands';
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 43 at column 24
Loading history...
44
    /** @var string */
45
    public const string PATH = '[' . self::COMMAND . '][ {namespace:[a-zA-Z0-9]+}]';
46
    /** @var string */
47
    public const string SHORT_DESCRIPTION = 'List all the commands';
48
    /** @var string */
49
    public const string DESCRIPTION = 'List all the commands';
50
51
    public function __construct(
52
        protected Console $console,
53
        InputContract $input = new Input(),
54
        OutputContract $output = new Output()
55
    ) {
56
        parent::__construct($input, $output);
57
    }
58
59
    /**
60
     * @inheritDoc
61
     *
62
     * @param string|null $namespace [optional] The namespace to show commands for
63
     */
64
    public function run(string|null $namespace = null): int
65
    {
66
        $commands        = $this->console->all();
67
        $longestLength   = 0;
68
        $previousSection = '';
69
70
        $this->filterCommands($commands, $longestLength, $namespace);
71
72
        $this->applicationMessage();
73
        $this->sectionDivider();
74
75
        $this->usageMessage('command [options] [arguments]');
76
77
        $this->optionsSection(...$this->input->getGlobalOptions());
78
        $this->sectionDivider();
79
80
        $this->sectionTitleMessage(
81
            'Commands' . ($namespace !== null && $namespace !== '' ? " for the \"$namespace\" namespace" : '')
82
        );
83
84
        foreach ($commands as $command) {
85
            if ($namespace === null) {
86
                $this->commandSection($command, $previousSection);
87
            }
88
89
            $this->sectionMessage(
90
                ($namespace === null || $namespace === '' ? static::TAB : '') . ($command->getName() ?? ''),
91
                $command->getDescription() ?? '',
92
                $longestLength + 2
93
            );
94
        }
95
96
        return ExitCode::SUCCESS;
97
    }
98
99
    /**
100
     * @inheritDoc
101
     */
102
    protected function getArguments(): array
103
    {
104
        return [
105
            new Argument('namespace', 'The namespace to list commands for'),
106
        ];
107
    }
108
109
    /**
110
     * Filter the commands by type and name.
111
     *
112
     * @param Command[]   $commands      The commands
113
     * @param int         $longestLength The longest length
114
     * @param string|null $namespace     [optional] The namespace to show commands
115
     *                                   for
116
     *
117
     * @return void
118
     */
119
    protected function filterCommands(array &$commands, int &$longestLength, string|null $namespace = null): void
120
    {
121
        $globalCommands = [];
122
123
        /** @var Command $command */
124
        foreach ($commands as $key => $command) {
125
            $name = $command->getName();
126
127
            if ($name === null || $name === '') {
128
                continue;
129
            }
130
131
            $parts            = explode(':', $name);
132
            $commandName      = $parts[1] ?? null;
133
            $commandNamespace = $commandName !== null && $commandName !== ''
134
                ? $parts[0]
135
                : 'global';
136
137
            // If there was a namespace passed to the command (commands
138
            // namespace)  and the namespace for this command doesn't match
139
            // what was passed then get rid of it so only commands in the
140
            // namespace are shown.
141
            if ($commandNamespace !== $namespace && $namespace !== null) {
142
                unset($commands[$key]);
143
144
                continue;
145
            }
146
147
            $longestLength = max(strlen($name), $longestLength);
148
149
            // If this is a global namespaced command
150
            if ($commandNamespace === 'global') {
151
                // Set it in the global commands array so when we show the list
152
                // of commands global commands will be at the top
153
                $globalCommands[] = $command;
154
155
                // Unset from the commands list to avoid duplicates
156
                unset($commands[$key]);
157
            }
158
        }
159
160
        // Sort the global commands by name
161
        $this->sortCommands($globalCommands);
162
        // Sort the rest of the commands by name
163
        $this->sortCommands($commands);
164
165
        // Set the commands as the merged results of the global and other
166
        // commands with the global commands at the top of the list
167
        $commands = array_merge($globalCommands, $commands);
168
    }
169
170
    /**
171
     * The command section.
172
     *
173
     * @param Command $command         The current command
174
     * @param string  $previousSection The previous section
175
     *
176
     * @return void
177
     */
178
    protected function commandSection(Command $command, string &$previousSection): void
179
    {
180
        $parts          = explode(':', $command->getName() ?? '');
181
        $commandName    = $parts[1] ?? null;
182
        $currentSection = $commandName !== null && $commandName !== ''
183
            ? $parts[0]
184
            : 'global';
185
186
        if ($previousSection !== $currentSection) {
187
            $this->output->getFormatter()->cyan();
188
            $this->output->writeMessage(static::TAB);
189
            $this->output->writeMessage($currentSection, true);
190
191
            $previousSection = $currentSection;
192
        }
193
    }
194
195
    /**
196
     * Sort commands by name.
197
     *
198
     * @param Command[] $commands The commands
199
     *
200
     * @return void
201
     */
202
    protected function sortCommands(array &$commands): void
203
    {
204
        usort(
205
            $commands,
206
            static fn (Command $item1, Command $item2) => $item1->getName() <=> $item2->getName()
207
        );
208
    }
209
}
210