Skip to content

Commit 64011bf

Browse files
support for partially typed commands (e.g. c:c) and added verbose mode
1 parent d16d233 commit 64011bf

13 files changed

+436
-79
lines changed

bin/console

Lines changed: 0 additions & 25 deletions
This file was deleted.

examples/commands.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
// $container = $kernel->getContainer();
2929
// $app = $container->get(CommandRunner::class);
3030

31-
$app = new CommandRunner([
31+
$runner = new CommandRunner([
3232
new FooCommand(),
3333
]);
34-
$exitCode = $app->run(new CommandParser(), new Output());
34+
$exitCode = $runner->run(new CommandParser(), new Output());
3535
exit($exitCode);
3636

3737

src/Command/HelpCommand.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66

77
use PhpDevCommunity\Console\InputInterface;
8+
use PhpDevCommunity\Console\Option\CommandOption;
89
use PhpDevCommunity\Console\Output;
910
use PhpDevCommunity\Console\OutputInterface;
1011

@@ -44,6 +45,22 @@ public function execute(InputInterface $input, OutputInterface $output): void
4445
$commands[$command->getName()] = $command->getDescription();
4546
}
4647
$io->listKeyValues($commands, true);
48+
49+
$io->writeColor('Options:', 'yellow');
50+
$io->write(PHP_EOL);
51+
$options = [];
52+
foreach ([new CommandOption('help', 'h', 'Display this help message.', true), new CommandOption('verbose', 'v', 'Enable verbose output', true)] as $option) {
53+
$name = sprintf('--%s', $option->getName());
54+
if ($option->getShortcut() !== null) {
55+
$name = sprintf('-%s, --%s', $option->getShortcut(), $option->getName());
56+
}
57+
58+
if (!$option->isFlag()) {
59+
$name = sprintf('%s=VALUE', $name);
60+
}
61+
$options[$name] = $option->getDescription();
62+
}
63+
$io->listKeyValues($options, true);
4764
}
4865

4966
/**

src/CommandRunner.php

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use InvalidArgumentException;
66
use PhpDevCommunity\Console\Command\CommandInterface;
77
use PhpDevCommunity\Console\Command\HelpCommand;
8+
use PhpDevCommunity\Console\Option\CommandOption;
89
use PhpDevCommunity\Console\Output\ConsoleOutput;
910
use Throwable;
1011
use const PHP_EOL;
@@ -13,6 +14,9 @@ final class CommandRunner
1314
{
1415
const CLI_ERROR = 1;
1516
const CLI_SUCCESS = 0;
17+
public const CLI_COMMAND_NOT_FOUND = 10; // Aucune commande trouvée
18+
public const CLI_INVALID_ARGUMENTS = 11; // Arguments invalides
19+
public const CLI_AMBIGUOUS_COMMAND = 12; // Plusieurs correspondances possibles
1620

1721
/**
1822
* @var CommandInterface[]
@@ -58,7 +62,7 @@ public function run(CommandParser $commandParser, OutputInterface $output): int
5862
}
5963

6064
if (empty($commands)) {
61-
throw new InvalidArgumentException(sprintf('Command "%s" is not defined.', $commandParser->getCommandName()));
65+
throw new InvalidArgumentException(sprintf('Command "%s" is not defined.', $commandParser->getCommandName()), self::CLI_COMMAND_NOT_FOUND);
6266
}
6367

6468
if (count($commands) > 1) {
@@ -69,7 +73,7 @@ public function run(CommandParser $commandParser, OutputInterface $output): int
6973
$consoleOutput = new ConsoleOutput($output);
7074
$consoleOutput->error(sprintf('Command "%s" is ambiguous.', $commandParser->getCommandName()));
7175
$consoleOutput->listKeyValues($names, true);
72-
return self::CLI_ERROR;
76+
return self::CLI_AMBIGUOUS_COMMAND;
7377
}
7478

7579
$command = $commands[0];
@@ -84,16 +88,32 @@ public function run(CommandParser $commandParser, OutputInterface $output): int
8488

8589
} catch (Throwable $e) {
8690
(new ConsoleOutput($output))->error($e->getMessage());
87-
return self::CLI_ERROR;
91+
return in_array($e->getCode(), [self::CLI_COMMAND_NOT_FOUND, self::CLI_INVALID_ARGUMENTS]) ? $e->getCode() : self::CLI_ERROR;
8892
}
8993

9094
}
9195

9296
private function execute(CommandInterface $command, CommandParser $commandParser, OutputInterface $output)
9397
{
9498
$argvOptions = [];
95-
9699
$options = $command->getOptions();
100+
$forbidden = ['help', 'h', 'verbose', 'v'];
101+
foreach ($options as $option) {
102+
$name = $option->getName();
103+
$shortcut = $option->getShortcut();
104+
if (in_array($name, $forbidden, true) || ($shortcut !== null && in_array($shortcut, $forbidden, true))) {
105+
$invalid = in_array($name, $forbidden, true) ? $name : $shortcut;
106+
throw new \InvalidArgumentException(
107+
sprintf(
108+
'The option "%s" is reserved and cannot be used with the "%s" command.',
109+
$invalid,
110+
$command->getName()
111+
)
112+
);
113+
}
114+
}
115+
116+
$options[] = new CommandOption('verbose', 'v', 'Enable verbose output', true);
97117
foreach ($options as $option) {
98118
if ($option->isFlag()) {
99119
$argvOptions["--{$option->getName()}"] = false;
@@ -135,7 +155,20 @@ private function execute(CommandInterface $command, CommandParser $commandParser
135155
throw new InvalidArgumentException(sprintf('Too many arguments for command "%s". Expected %d, got %d.', $command->getName(), count($arguments), count($commandParser->getArguments())));
136156
}
137157

138-
$command->execute(new Input($commandParser->getCommandName(), $argvOptions, $argv), $output);
158+
$startTime = microtime(true);
159+
$input = new Input($commandParser->getCommandName(), $argvOptions, $argv);
160+
$command->execute($input, $output);
161+
$endTime = microtime(true);
162+
$peakMemoryBytes = memory_get_peak_usage(true);
163+
$peakMemoryMB = round($peakMemoryBytes / 1024 / 1024, 2);
164+
$duration = round($endTime - $startTime, 2);
165+
if ($input->getOptionValue('verbose')) {
166+
$output->writeln(sprintf(
167+
'Execution time: %.2fs; Peak memory usage: %.2f MB',
168+
$duration,
169+
$peakMemoryMB
170+
));
171+
}
139172
}
140173

141174
private function showCommandHelp(CommandInterface $selectedCommand, OutputInterface $output): void
@@ -171,8 +204,18 @@ private function showCommandHelp(CommandInterface $selectedCommand, OutputInterf
171204
$consoleOutput->listKeyValues($options, true);
172205
}
173206

174-
private static function stringStartsWith(string $haystack, string $needle): bool
207+
private static function stringStartsWith(string $command, string $input): bool
175208
{
176-
return substr($haystack, 0, strlen($needle)) === $needle;
209+
$commandParts = explode(':', $command);
210+
$inputParts = explode(':', $input);
211+
foreach ($inputParts as $i => $inPart) {
212+
$cmdPart = $commandParts[$i] ?? null;
213+
214+
if ($cmdPart === null || strpos($cmdPart, $inPart) !== 0) {
215+
return false;
216+
}
217+
}
218+
219+
return true;
177220
}
178221
}

tests/Command/CacheClearCommand.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Test\PhpDevCommunity\Console\Command;
4+
5+
use PhpDevCommunity\Console\Command\CommandInterface;
6+
use PhpDevCommunity\Console\InputInterface;
7+
use PhpDevCommunity\Console\OutputInterface;
8+
9+
class CacheClearCommand implements CommandInterface
10+
{
11+
public function getName(): string
12+
{
13+
return 'cache:clear';
14+
}
15+
16+
public function getDescription(): string
17+
{
18+
return 'TEST : Clear cache';
19+
}
20+
21+
public function getOptions(): array
22+
{
23+
return [
24+
];
25+
}
26+
27+
public function getArguments(): array
28+
{
29+
return [
30+
];
31+
}
32+
33+
public function execute(InputInterface $input, OutputInterface $output): void
34+
{
35+
$output->write('Test OK : Clear cache');
36+
}
37+
}

tests/Command/FooCommand.php

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,44 @@
1-
<?php
2-
3-
namespace Test\PhpDevCommunity\Console\Command;
4-
5-
use PhpDevCommunity\Console\Argument\CommandArgument;
6-
use PhpDevCommunity\Console\Command\CommandInterface;
7-
use PhpDevCommunity\Console\InputInterface;
8-
use PhpDevCommunity\Console\Option\CommandOption;
9-
use PhpDevCommunity\Console\OutputInterface;
10-
11-
class FooCommand implements CommandInterface
12-
{
13-
public function getName(): string
14-
{
15-
return 'foo';
16-
}
17-
18-
public function getDescription(): string
19-
{
20-
return 'Performs the foo operation with optional parameters.';
21-
}
22-
23-
public function getOptions(): array
24-
{
25-
return [
26-
new CommandOption('verbose', 'v', 'Enable verbose output', true),
27-
new CommandOption('output', 'o', 'Specify output file', false)
28-
];
29-
}
30-
31-
public function getArguments(): array
32-
{
33-
return [
34-
new CommandArgument('input', false, 'none', 'The input file for the foo operation')
35-
];
36-
}
37-
38-
public function execute(InputInterface $input, OutputInterface $output): void
39-
{
40-
41-
$output->writeln('Test OK');
42-
$output->writeln('ARGUMENTS: ' . json_encode($input->getArguments()));
43-
$output->writeln('OPTIONS: ' . json_encode($input->getOptions()));
44-
}
45-
}
1+
<?php
2+
3+
namespace Test\PhpDevCommunity\Console\Command;
4+
5+
use PhpDevCommunity\Console\Argument\CommandArgument;
6+
use PhpDevCommunity\Console\Command\CommandInterface;
7+
use PhpDevCommunity\Console\InputInterface;
8+
use PhpDevCommunity\Console\Option\CommandOption;
9+
use PhpDevCommunity\Console\OutputInterface;
10+
11+
class FooCommand implements CommandInterface
12+
{
13+
public function getName(): string
14+
{
15+
return 'foo';
16+
}
17+
18+
public function getDescription(): string
19+
{
20+
return 'Performs the foo operation with optional parameters.';
21+
}
22+
23+
public function getOptions(): array
24+
{
25+
return [
26+
new CommandOption('verbose', 'v', 'Enable verbose output', true),
27+
new CommandOption('output', 'o', 'Specify output file', false)
28+
];
29+
}
30+
31+
public function getArguments(): array
32+
{
33+
return [
34+
new CommandArgument('input', false, 'none', 'The input file for the foo operation')
35+
];
36+
}
37+
38+
public function execute(InputInterface $input, OutputInterface $output): void
39+
{
40+
$output->writeln('Test OK');
41+
$output->writeln('ARGUMENTS: ' . json_encode($input->getArguments()));
42+
$output->writeln('OPTIONS: ' . json_encode($input->getOptions()));
43+
}
44+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Test\PhpDevCommunity\Console\Command;
4+
5+
use PhpDevCommunity\Console\Argument\CommandArgument;
6+
use PhpDevCommunity\Console\Command\CommandInterface;
7+
use PhpDevCommunity\Console\InputInterface;
8+
use PhpDevCommunity\Console\Option\CommandOption;
9+
use PhpDevCommunity\Console\OutputInterface;
10+
11+
class MakeControllerCommand implements CommandInterface
12+
{
13+
public function getName(): string
14+
{
15+
return 'make:controller';
16+
}
17+
18+
public function getDescription(): string
19+
{
20+
return 'TEST : Make a new controller';
21+
}
22+
23+
public function getOptions(): array
24+
{
25+
return [
26+
];
27+
}
28+
29+
public function getArguments(): array
30+
{
31+
return [
32+
];
33+
}
34+
35+
public function execute(InputInterface $input, OutputInterface $output): void
36+
{
37+
$output->write('Test OK : Make a new controller');
38+
}
39+
}

tests/Command/MakeEntityCommand.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Test\PhpDevCommunity\Console\Command;
4+
5+
use PhpDevCommunity\Console\Command\CommandInterface;
6+
use PhpDevCommunity\Console\InputInterface;
7+
use PhpDevCommunity\Console\OutputInterface;
8+
9+
class MakeEntityCommand implements CommandInterface
10+
{
11+
public function getName(): string
12+
{
13+
return 'make:controller';
14+
}
15+
16+
public function getDescription(): string
17+
{
18+
return 'TEST : Make a new Entity';
19+
}
20+
21+
public function getOptions(): array
22+
{
23+
return [
24+
];
25+
}
26+
27+
public function getArguments(): array
28+
{
29+
return [
30+
];
31+
}
32+
33+
public function execute(InputInterface $input, OutputInterface $output): void
34+
{
35+
$output->write('Test OK : Make a new entity');
36+
}
37+
}

0 commit comments

Comments
 (0)