diff --git a/src/Command.php b/src/Command.php index cf39eb3..06b0783 100644 --- a/src/Command.php +++ b/src/Command.php @@ -341,6 +341,36 @@ public function validate(array $arguments, array $options) : array ); } + /** + * Apply definition defaults to the console. + * + * Any defined argument or option that was not passed on the command line + * and declares a "default" key gets its default written back into the + * Console, so getArgument() and getOption() return the resolved value. + * Values are stored as strings (or booleans for flags) because the + * Console accessors are typed that way. + * + * @param Console $console The console the command is running on + */ + public function applyDefaults(Console $console) : void + { + $arguments = $console->getArguments(); + foreach ($this->argumentDefinitions as $key => $definition) { + $default = $definition['default'] ?? null; + if (!\array_key_exists($key, $arguments) && \is_scalar($default)) { + $console->setArgument((int) $key, (string) $default); + } + } + $options = $console->getOptions(); + foreach ($this->optionDefinitions as $key => $definition) { + $default = $definition['default'] ?? null; + if (\array_key_exists($key, $options) || !\is_scalar($default)) { + continue; + } + $console->setOption((string) $key, \is_bool($default) ? $default : (string) $default); + } + } + /** * Validate a set of values against their definitions. * diff --git a/src/Console.php b/src/Console.php index a5c2b24..8323604 100644 --- a/src/Console.php +++ b/src/Console.php @@ -140,6 +140,38 @@ public function getArgument(int $position) : ?string return $this->arguments[$position] ?? null; } + /** + * Set an argument value. + * + * Used by Command::applyDefaults() to fill in definition defaults. + * + * @param int $position Argument position, starting from zero + * @param string $value The argument value + * + * @return static + */ + public function setArgument(int $position, string $value) : static + { + $this->arguments[$position] = $value; + return $this; + } + + /** + * Set an option value. + * + * Used by Command::applyDefaults() to fill in definition defaults. + * + * @param string $name The option name + * @param bool|string $value The option value + * + * @return static + */ + public function setOption(string $name, bool | string $value) : static + { + $this->options[$name] = $value; + return $this; + } + /** * Set the Language instance. * @@ -324,6 +356,7 @@ protected function dispatch() : void $this->validationFailed($errors); return; } + $command->applyDefaults($this); $command->run(); } diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index c31fb5f..b3acbc4 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -173,4 +173,59 @@ public function testGettersReturnTheDefinitions() : void self::assertSame([0 => ['type' => 'int']], $command->getArgumentDefinitions()); self::assertSame(['count' => ['type' => 'int']], $command->getOptionDefinitions()); } + + public function testDefaultArgumentIsAppliedWhenMissing() : void + { + $command = new ValidatedCommandMock($this->console); + $command->setArgumentDefinitions([ + 0 => ['type' => 'int', 'default' => '7'], + ]); + $this->console->addCommand($command); + $this->console->exec('validated'); + self::assertSame('7', $this->console->getArgument(0)); + self::assertStringContainsString('ran', Stdout::getContents()); + } + + public function testDefaultOptionIsAppliedWhenMissing() : void + { + $command = new ValidatedCommandMock($this->console); + $command->setOptionDefinitions([ + 'count' => ['type' => 'int', 'default' => '3'], + 'verbose' => ['type' => 'flag', 'default' => true], + ]); + $this->console->addCommand($command); + $this->console->exec('validated'); + self::assertSame('3', $this->console->getOption('count')); + self::assertTrue($this->console->getOption('verbose')); + self::assertStringContainsString('ran', Stdout::getContents()); + } + + public function testPassedValuesAreNotOverriddenByDefaults() : void + { + $command = new ValidatedCommandMock($this->console); + $command->setArgumentDefinitions([ + 0 => ['type' => 'int', 'default' => '7'], + ]); + $command->setOptionDefinitions([ + 'count' => ['type' => 'int', 'default' => '3'], + ]); + $this->console->addCommand($command); + $this->console->exec('validated 42 --count=5'); + self::assertSame('42', $this->console->getArgument(0)); + self::assertSame('5', $this->console->getOption('count')); + } + + public function testApplyDefaultsDirectlyFillsTheConsole() : void + { + $command = new ValidatedCommandMock($this->console); + $command->setArgumentDefinitions([ + 0 => ['default' => 'fallback'], + ]); + $command->setOptionDefinitions([ + 'name' => ['default' => 'cli'], + ]); + $command->applyDefaults($this->console); + self::assertSame('fallback', $this->console->getArgument(0)); + self::assertSame('cli', $this->console->getOption('name')); + } }