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..a5c2b24 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,6 +348,19 @@ 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; } @@ -494,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/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..9957c61 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 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 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);