From 383d22f48708c7526234432134333ad8f0a32874 Mon Sep 17 00:00:00 2001 From: Hafiz Muhammad Moaz Date: Mon, 7 Sep 2026 14:04:40 +0500 Subject: [PATCH 1/2] fix: resolve help through the registry, skip value args and let commands own -h (fixes #36) --- src/Commands/Help.php | 5 ++++ src/Console.php | 38 ++++++++++++++++++++++++++- tests/Commands/Host.php | 28 ++++++++++++++++++++ tests/ConsoleTest.php | 57 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 tests/Commands/Host.php diff --git a/src/Commands/Help.php b/src/Commands/Help.php index 90493ee..9bf8d19 100644 --- a/src/Commands/Help.php +++ b/src/Commands/Help.php @@ -28,6 +28,11 @@ public function run() : void $commandName = $this->console->getArgument(0); if ($commandName === null || $commandName === '') { $commandName = $this->console->getCommandName(); + } elseif (!$this->console->hasCommand($commandName) + && $this->console->getCommandName() !== 'help') { + // The help option was passed to a command that takes arguments, + // so the first argument is a value and not a command name. + $commandName = $this->console->getCommandName(); } if ($commandName === '') { $commandName = 'help'; diff --git a/src/Console.php b/src/Console.php index bb852a5..d08272e 100644 --- a/src/Console.php +++ b/src/Console.php @@ -310,7 +310,8 @@ protected function dispatch() : void $this->command = 'index'; } if ($this->isHelpRequested()) { - (new Help($this))->run(); + $help = $this->getCommand('help') ?? new Help($this); + $help->run(); return; } $command = $this->getCommand($this->command); @@ -347,9 +348,44 @@ protected function validationFailed(array $errors) : void */ protected function isHelpRequested() : bool { + $command = $this->getCommand($this->command); + if ($command !== null) { + $declared = static::declaredOptionNames($command); + if ($this->getOption('help') === true + && !\in_array('help', $declared, true)) { + return true; + } + if ($this->getOption('h') === true + && !\in_array('h', $declared, true)) { + return true; + } + return false; + } return $this->getOption('help') === true || $this->getOption('h') === true; } + /** + * List the short and long option names a command declares for itself. + * + * @param Command $command The command to inspect + * + * @return array Names without their leading dashes + */ + #[Pure] + protected static function declaredOptionNames(Command $command) : array + { + $names = []; + foreach (\array_keys($command->getOptions()) as $key) { + foreach (\explode(',', (string) $key) as $part) { + $names[] = \ltrim(\trim($part), '-'); + } + } + foreach (\array_keys($command->getOptionDefinitions()) as $key) { + $names[] = \ltrim(\trim((string) $key), '-'); + } + return $names; + } + /** * Handle an unknown command by reporting it and suggesting the closest * registered command when there is a close match. diff --git a/tests/Commands/Host.php b/tests/Commands/Host.php new file mode 100644 index 0000000..0d441f3 --- /dev/null +++ b/tests/Commands/Host.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Tests\CLI\Commands; + +use Framework\CLI\CLI; +use Framework\CLI\Command; + +class Host extends Command +{ + protected string $name = 'host'; + protected string $description = 'Host command test'; + protected array $options = [ + '-h, --host' => 'The host to bind.', + ]; + + public function run() : void + { + CLI::write('host: ' . \print_r($this->console->getOption('h'), true)); + CLI::write('host value: ' . (string) $this->console->getArgument(0)); + } +} diff --git a/tests/ConsoleTest.php b/tests/ConsoleTest.php index 032be35..a5e9531 100644 --- a/tests/ConsoleTest.php +++ b/tests/ConsoleTest.php @@ -418,6 +418,63 @@ public function testAutoHelpShortOption() : void self::assertStringContainsString('Usage', Stdout::getContents()); } + public function testAutoHelpWithArgumentsShowsCommandHelp() : void + { + $this->console->addCommand(new \Tests\CLI\Commands\Host($this->console)); + Stderr::reset(); + $this->console->prepare([ + 'file.php', + 'host', + '0.0.0.0', + '--help', + ]); + $this->console->run(); + $output = Stdout::getContents(); + self::assertStringContainsString('host', $output); + self::assertStringNotContainsString( + 'Command not found', + Stderr::getContents() + ); + } + + public function testCommandDeclaringHReceivesShortOption() : void + { + $this->console->addCommand(new \Tests\CLI\Commands\Host($this->console)); + $this->console->prepare([ + 'file.php', + 'host', + '-h', + '0.0.0.0', + ]); + $this->console->run(); + $output = Stdout::getContents(); + self::assertStringContainsString('host: 1', $output); + self::assertStringContainsString('host value: 0.0.0.0', $output); + self::assertStringNotContainsString('Usage', $output); + } + + public function testAutoHelpUsesRegisteredHelpCommand() : void + { + $this->console->addCommand(new class ($this->console) extends Command { + protected string $name = 'help'; + + public function run() : void + { + CLI::write('custom help called'); + } + }); + $this->console->prepare([ + 'file.php', + 'index', + '--help', + ]); + $this->console->run(); + self::assertStringContainsString( + 'custom help called', + Stdout::getContents() + ); + } + public function testQuietOption() : void { CLI::setQuiet(false); From b30dfcee1632c5e8eae58253be5fb23c040e575d Mon Sep 17 00:00:00 2001 From: Hafiz Muhammad Moaz Date: Mon, 7 Sep 2026 14:10:04 +0500 Subject: [PATCH 2/2] style: apply coding standard --- src/Console.php | 44 +++++++++++++++++++++---------------------- tests/ConsoleTest.php | 6 +++--- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Console.php b/src/Console.php index d08272e..a5c2b24 100644 --- a/src/Console.php +++ b/src/Console.php @@ -364,28 +364,6 @@ protected function isHelpRequested() : bool return $this->getOption('help') === true || $this->getOption('h') === true; } - /** - * List the short and long option names a command declares for itself. - * - * @param Command $command The command to inspect - * - * @return array Names without their leading dashes - */ - #[Pure] - protected static function declaredOptionNames(Command $command) : array - { - $names = []; - foreach (\array_keys($command->getOptions()) as $key) { - foreach (\explode(',', (string) $key) as $part) { - $names[] = \ltrim(\trim($part), '-'); - } - } - foreach (\array_keys($command->getOptionDefinitions()) as $key) { - $names[] = \ltrim(\trim((string) $key), '-'); - } - return $names; - } - /** * Handle an unknown command by reporting it and suggesting the closest * registered command when there is a close match. @@ -530,6 +508,28 @@ protected function applyGlobalOptions() : void } } + /** + * List the short and long option names a command declares for itself. + * + * @param Command $command The command to inspect + * + * @return array Names without their leading dashes + */ + #[Pure] + protected static function declaredOptionNames(Command $command) : array + { + $names = []; + foreach (\array_keys($command->getOptions()) as $key) { + foreach (\explode(',', (string) $key) as $part) { + $names[] = \ltrim(\trim($part), '-'); + } + } + foreach (\array_keys($command->getOptionDefinitions()) as $key) { + $names[] = \ltrim(\trim((string) $key), '-'); + } + return $names; + } + /** * @param string $command * diff --git a/tests/ConsoleTest.php b/tests/ConsoleTest.php index a5e9531..9957c61 100644 --- a/tests/ConsoleTest.php +++ b/tests/ConsoleTest.php @@ -420,7 +420,7 @@ public function testAutoHelpShortOption() : void public function testAutoHelpWithArgumentsShowsCommandHelp() : void { - $this->console->addCommand(new \Tests\CLI\Commands\Host($this->console)); + $this->console->addCommand(new Commands\Host($this->console)); Stderr::reset(); $this->console->prepare([ 'file.php', @@ -439,7 +439,7 @@ public function testAutoHelpWithArgumentsShowsCommandHelp() : void public function testCommandDeclaringHReceivesShortOption() : void { - $this->console->addCommand(new \Tests\CLI\Commands\Host($this->console)); + $this->console->addCommand(new Commands\Host($this->console)); $this->console->prepare([ 'file.php', 'host', @@ -455,7 +455,7 @@ public function testCommandDeclaringHReceivesShortOption() : void public function testAutoHelpUsesRegisteredHelpCommand() : void { - $this->console->addCommand(new class ($this->console) extends Command { + $this->console->addCommand(new class($this->console) extends Command { protected string $name = 'help'; public function run() : void