Compare commits

..

26 Commits
2.x ... master

Author SHA1 Message Date
Julien Rosset 1d4075240e CommandArgumentList::addOption allowed without value (= true) 2 months ago
Julien Rosset b3d82fff86 Create CommandArgumentList 2 months ago
Julien Rosset 8152f9a673 Improve DateTimeValidator: add time part if not mandatory 2 months ago
Julien Rosset fefb3bf3c1 Fix command with validation when using -n/--no-interaction option 6 months ago
Julien Rosset 6c31a16e80 OutputWithLogger : fix method for “error” output 6 months ago
Julien Rosset 8055eb9132 OutputWithLogger : add method for “error” output 8 months ago
Julien Rosset 8bb34e1b6e Revert "CommandCall : allow command class name as command name"
This reverts commit 69418f455f.
9 months ago
Julien Rosset 69418f455f CommandCall : allow command class name as command name 9 months ago
Julien Rosset ab5cab163e BaseCommand : fix default name when using CliCommand 9 months ago
Julien Rosset 438e256ca3 Correction CliCommand : valeurs par défaut 9 months ago
Julien Rosset c1c36475de CommandCall: fix command name when AutoPrix or AutoDiscovery 9 months ago
Julien Rosset c40a14aba0 Add CliCommand attribute 9 months ago
Julien Rosset 26815fcac3 Add command requirements support 9 months ago
Julien Rosset 7d21bb492c Add method for normalizing command name in TAutoDiscoveryApplication 11 months ago
Julien Rosset 9d43547250 Suppression alerte exécution dans AutoDiscoveryDirectory 2 years ago
Julien Rosset a55725f19d Correction respect des sauts de lignes dans les loggers 2 years ago
Julien Rosset f641fb26eb Corrections documentation 2 years ago
Julien Rosset ddb2a7d4b5 CommandCall: add default value for argument list 2 years ago
Julien Rosset 6643ce813c Add CommandCall 2 years ago
Julien Rosset 6258c680df Replace jrosset/collections with voku/arrayy 2 years ago
Julien Rosset 98319dafb4 Fix arguments/options validation when "IS_ARRAY" is true 2 years ago
Julien Rosset 75a40b9c86 Add validator for directory and file 2 years ago
Julien Rosset 73c12fc82f Generalize application for any LoggerInterface 2 years ago
Julien Rosset 2981e01945 Add EmailValidator 2 years ago
Julien Rosset ede43fb01c Validator : do not check Null or False (default value / not provided) 2 years ago
Julien Rosset f69c7c8fa4 Validator : null also possible for valid default value 2 years ago

@ -9,18 +9,18 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.x-dev" "dev-master": "3.x-dev"
} }
}, },
"minimum-stability": "stable", "minimum-stability": "stable",
"require": { "require": {
"php": "^7.4 || ^8.0.0", "php": "^8.1",
"jrosset/betterphptoken": "^1.0", "jrosset/betterphptoken": "^1.0",
"jrosset/extendedmonolog": "^1.1", "jrosset/extendedmonolog": "^2.0",
"psr/log": "^1.1", "psr/log": "^2.0",
"symfony/console": "^5.4", "symfony/console": "^6.1",
"symfony/event-dispatcher": "^5.4", "symfony/event-dispatcher": "^6.1",
"voku/arrayy": "^7.9" "voku/arrayy": "^7.9"
}, },
"autoload": { "autoload": {

@ -127,7 +127,7 @@ class AutoDiscoveryDirectory implements IAutoDiscoverySpot {
$classes[$fileInfo->getRealPath()] = $this->applyAutoPrefixOnCommand($class->newInstance()); $classes[$fileInfo->getRealPath()] = $this->applyAutoPrefixOnCommand($class->newInstance());
} }
catch (ReflectionException $exception) { catch (ReflectionException) {
continue; continue;
} }
} }

@ -30,7 +30,7 @@ trait TAutoDiscoveryApplication {
* *
* @return $this * @return $this
*/ */
public function addAutoDiscoveredCommands (): self { public function addAutoDiscoveredCommands (): static {
/** @var IAutoDiscoverySpot $autoDiscoverySpot */ /** @var IAutoDiscoverySpot $autoDiscoverySpot */
foreach ($this->getAutoDiscoverySpots() as $autoDiscoverySpot) { foreach ($this->getAutoDiscoverySpots() as $autoDiscoverySpot) {
foreach ($autoDiscoverySpot->getCommands() as $command) { foreach ($autoDiscoverySpot->getCommands() as $command) {

@ -18,6 +18,13 @@ class BaseCommand extends Command {
*/ */
public static function getDefaultName (): ?string { public static function getDefaultName (): ?string {
$reflectionClass = new ReflectionClass(static::class); $reflectionClass = new ReflectionClass(static::class);
if ($attribute = $reflectionClass->getAttributes(CliCommand::class)) {
/** @var CliCommand $cliCommandAttribute */
$cliCommandAttribute = $attribute[0]->newInstance();
if ($cliCommandAttribute->name !== null) {
return $cliCommandAttribute->name;
}
}
if (($name = parent::getDefaultName()) !== null) { if (($name = parent::getDefaultName()) !== null) {
return $name; return $name;
} }
@ -29,18 +36,34 @@ class BaseCommand extends Command {
* @return string[] The command default aliases * @return string[] The command default aliases
*/ */
public static function getDefaultAliases (): array { public static function getDefaultAliases (): array {
$reflectionClass = new ReflectionClass(static::class);
if ($attribute = $reflectionClass->getAttributes(CliCommand::class)) {
/** @var CliCommand $cliCommandAttribute */
$cliCommandAttribute = $attribute[0]->newInstance();
return $cliCommandAttribute->aliases;
}
return []; return [];
} }
/** /**
* @return string|null The command default description * @return string|null The command default description
*/ */
public static function getDefaultDescription (): ?string { public static function getDefaultDescription (): ?string {
if ($attribute = (new ReflectionClass(static::class))->getAttributes(CliCommand::class)) {
/** @var CliCommand $cliCommandAttribute */
$cliCommandAttribute = $attribute[0]->newInstance();
return $cliCommandAttribute->description;
}
return parent::getDefaultDescription(); return parent::getDefaultDescription();
} }
/** /**
* @return bool Is the command hidden from command list by default ? * @return bool Is the command hidden from command list by default ?
*/ */
public static function getDefaultHidden (): bool { public static function getDefaultHidden (): bool {
if ($attribute = (new ReflectionClass(static::class))->getAttributes(CliCommand::class)) {
/** @var CliCommand $cliCommandAttribute */
$cliCommandAttribute = $attribute[0]->newInstance();
return $cliCommandAttribute->hidden;
}
return false; return false;
} }
@ -52,7 +75,7 @@ class BaseCommand extends Command {
* @param string|null $description The command description ; Null if the default one (cf. {@see static::getDefaultDescription()}) * @param string|null $description The command description ; Null if the default one (cf. {@see static::getDefaultDescription()})
* @param bool|null $hidden Is the command hidden from command list ? Null if the default one (cf. {@see static::getDefaultHidden()}) * @param bool|null $hidden Is the command hidden from command list ? Null if the default one (cf. {@see static::getDefaultHidden()})
*/ */
public function __construct (?string $name = null, $aliases = null, ?string $description = null, ?bool $hidden = null) { public function __construct (?string $name = null, array|string|null $aliases = null, ?string $description = null, ?bool $hidden = null) {
parent::__construct($name); parent::__construct($name);
$this->setAliases(is_string($aliases) ? [$aliases] : $aliases ?? static::getDefaultAliases()); $this->setAliases(is_string($aliases) ? [$aliases] : $aliases ?? static::getDefaultAliases());
$this->setDescription($description ?? $this->getDescription()); $this->setDescription($description ?? $this->getDescription());
@ -71,7 +94,7 @@ class BaseCommand extends Command {
* *
* @throws Throwable If an error occurs * @throws Throwable If an error occurs
*/ */
public function runSubCommands ($subcommandList, OutputInterface $output): int { public function runSubCommands (CommandCallsList|CommandCall $subcommandList, OutputInterface $output): int {
if (!$subcommandList instanceof CommandCallsList) { if (!$subcommandList instanceof CommandCallsList) {
$subcommandList = new CommandCallsList([$subcommandList]); $subcommandList = new CommandCallsList([$subcommandList]);
} }

@ -0,0 +1,47 @@
<?php
namespace jrosset\CliProgram;
use Attribute;
use jrosset\CliProgram\AutoDiscovery\IAutoDiscoverySpot;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* Attribute to tag command
*
* Identical to {@see AsCommand} but allow empty command name (i.e., for {@see IAutoDiscoverySpot})
*/
#[Attribute(Attribute::TARGET_CLASS)]
class CliCommand {
/**
* @var string|null The commande name
*/
public readonly ?string $name;
/**
* @var string|null The command description
*/
public readonly ?string $description;
/**
* @var string[] The command aliases
*/
public readonly array $aliases;
/**
* @var bool Is the command hidden from command list ?
*/
public readonly bool $hidden;
/**
* Initialization
*
* @param string|null $name The commande name
* @param string|null $description The command description
* @param string[] $aliases The command aliases
* @param bool $hidden Is the command hidden from command list ?
*/
public function __construct (?string $name = null, ?string $description = null, array $aliases = [], bool $hidden = false) {
$this->name = $name;
$this->description = $description;
$this->aliases = $aliases;
$this->hidden = $hidden;
}
}

@ -33,7 +33,7 @@ abstract class CliHelper {
*/ */
public static final function getCommandNameFromClass (Application $application, string $commandClass): ?string { public static final function getCommandNameFromClass (Application $application, string $commandClass): ?string {
foreach ($application->all() as $possibleCommand) { foreach ($application->all() as $possibleCommand) {
if (get_class($possibleCommand) == $commandClass) { if ($possibleCommand::class == $commandClass) {
return $possibleCommand->getName(); return $possibleCommand->getName();
} }
} }

@ -0,0 +1,67 @@
<?php
namespace jrosset\CliProgram\CommandCall;
use Traversable;
/**
* A list of arguments for a command call
*/
class CommandArgumentList {
/**
* @var array<string, mixed> The arguments
*/
protected array $arguments;
/**
* Initialization
*
* @param Traversable|null $arguments The arguments
*/
public function __construct (?Traversable $arguments = null) {
$this->arguments = [];
if ($arguments !== null) {
foreach ($arguments as $name => $value) {
$this->arguments[$name] = $value;
}
}
}
/**
* The arguments
*
* @return array<string, mixed> The arguments
*/
public function getArguments (): array {
return $this->arguments;
}
/**
* Add an argument to the list
*
* Replace it if existing.
*
* @param string $name The argument name
* @param mixed $value The argument value
*
* @return $this
*/
public function addArgument (string $name, mixed $value): static {
$this->arguments[$name] = $value;
return $this;
}
/**
* Add an option to the list
*
* Replace it if existing.
*
* @param string $name The option name (with or without the leading dashes)
* @param mixed $value The option value
*
* @return $this
*/
public function addOption (string $name, mixed $value = true): static {
$this->arguments[(str_starts_with($name, '-') ? '' : '--') . $name] = $value;
return $this;
}
}

@ -20,17 +20,17 @@ class CommandCall {
*/ */
private string $commandName; private string $commandName;
/** /**
* @var Arrayy<string, mixed> The command arguments * @var CommandArgumentList The command arguments
*/ */
private Arrayy $commandArguments; private CommandArgumentList $commandArguments;
/** /**
* @param string|Command $commandName The new command name * @param string|Command $commandName The new command name
* @param null|Arrayy<string, mixed> $commandArguments The command new arguments * @param CommandArgumentList|Arrayy<string, mixed>|null $commandArguments The command new arguments
*/ */
public function __construct ($commandName, ?Arrayy $commandArguments = null) { public function __construct (string|Command $commandName, CommandArgumentList|Arrayy|null $commandArguments = null) {
$this->setCommandName($commandName); $this->setCommandName($commandName);
$this->setCommandArguments($commandArguments ?? new Arrayy()); $this->setCommandArguments($commandArguments ?? new CommandArgumentList());
} }
/** /**
@ -40,9 +40,12 @@ class CommandCall {
*/ */
public function generateInput (): InputInterface { public function generateInput (): InputInterface {
return new ArrayInput( return new ArrayInput(
(clone $this->getCommandArguments()) array_merge(
->prepend($this->getCommandName(), 'command') [
->toArray() 'command' => $this->getCommandName(),
],
$this->getCommandArguments()->getArguments()
)
); );
} }
/** /**
@ -74,9 +77,9 @@ class CommandCall {
* *
* @return $this * @return $this
*/ */
public function setCommandName ($commandName): self { public function setCommandName (string|Command $commandName): self {
$this->commandName = $commandName instanceof Command $this->commandName = $commandName instanceof Command
? CliHelper::getCommandNameFromClass($commandName->getApplication(), get_class($commandName)) ? CliHelper::getCommandNameFromClass($commandName->getApplication(), $commandName::class)
: $commandName; : $commandName;
return $this; return $this;
} }
@ -86,18 +89,18 @@ class CommandCall {
* *
* @return Arrayy<string, mixed> The command arguments * @return Arrayy<string, mixed> The command arguments
*/ */
public function getCommandArguments (): Arrayy { public function getCommandArguments (): CommandArgumentList {
return $this->commandArguments; return $this->commandArguments;
} }
/** /**
* Set the command arguments * Set the command arguments
* *
* @param Arrayy<string, mixed> $commandArguments The command new arguments * @param CommandArgumentList|Arrayy $commandArguments The command new arguments
* *
* @return $this * @return $this
*/ */
public function setCommandArguments (Arrayy $commandArguments): self { public function setCommandArguments (CommandArgumentList|Arrayy $commandArguments): self {
$this->commandArguments = $commandArguments; $this->commandArguments = $commandArguments instanceof CommandArgumentList ? $commandArguments : new CommandArgumentList($commandArguments);
return $this; return $this;
} }
} }

@ -3,6 +3,7 @@
namespace jrosset\CliProgram\Monolog; namespace jrosset\CliProgram\Monolog;
use jrosset\CliProgram\Output\OutputWithLogger; use jrosset\CliProgram\Output\OutputWithLogger;
use Monolog\Level;
use Monolog\Logger; use Monolog\Logger;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -16,24 +17,24 @@ class ConsoleOutputWithMonolog extends OutputWithLogger {
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected function getLoggerLevelFromVerbosity (int $verbosity): int { protected function getLoggerLevelFromVerbosity (int $verbosity): Level {
if ($verbosity <= OutputInterface::VERBOSITY_QUIET) { if ($verbosity <= OutputInterface::VERBOSITY_QUIET) {
return Logger::ERROR; return Level::Error;
} }
elseif ($verbosity <= OutputInterface::VERBOSITY_NORMAL) { elseif ($verbosity <= OutputInterface::VERBOSITY_NORMAL) {
return Logger::NOTICE; return Level::Notice;
} }
elseif ($verbosity <= OutputInterface::VERBOSITY_VERBOSE) { elseif ($verbosity <= OutputInterface::VERBOSITY_VERBOSE) {
return Logger::INFO; return Level::Info;
} }
elseif ($verbosity <= OutputInterface::VERBOSITY_VERY_VERBOSE) { elseif ($verbosity <= OutputInterface::VERBOSITY_VERY_VERBOSE) {
return Logger::INFO; return Level::Info;
} }
elseif ($verbosity <= OutputInterface::VERBOSITY_DEBUG) { elseif ($verbosity <= OutputInterface::VERBOSITY_DEBUG) {
return Logger::DEBUG; return Level::Debug;
} }
else { else {
return Logger::NOTICE; return Level::Notice;
} }
} }
} }

@ -50,7 +50,7 @@ trait TMonologApplication {
* *
* @return $this * @return $this
*/ */
public function setLogMainDirectory (string $logMainDirectory): self { public function setLogMainDirectory (string $logMainDirectory): static {
$this->logMainDirectory = $logMainDirectory; $this->logMainDirectory = $logMainDirectory;
return $this; return $this;
} }

@ -1,4 +1,5 @@
<?php <?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace jrosset\CliProgram\Output; namespace jrosset\CliProgram\Output;
@ -32,9 +33,9 @@ class OutputWithLogger extends OutputWrapper {
* @param OutputInterface $output The real output interface * @param OutputInterface $output The real output interface
* @param TLogger|null $logger The logger * @param TLogger|null $logger The logger
* *
* @noinspection PhpMissingParamTypeInspection * @noinspection PhpDocSignatureInspection
*/ */
public function __construct (OutputInterface $output, $logger = null) { public function __construct (OutputInterface $output, LoggerInterface $logger = null) {
parent::__construct($output); parent::__construct($output);
$this->setLogger($logger); $this->setLogger($logger);
} }
@ -42,8 +43,8 @@ class OutputWithLogger extends OutputWrapper {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getErrorOutput (): self { public function getErrorOutput (): static {
return new static(CliHelper::getErrorOutput($this->getOutput()), $this->getLogger()); return new static(CliHelper::getErrorOutput($this->getOutput()), $this->logger);
} }
/** /**
@ -63,13 +64,9 @@ class OutputWithLogger extends OutputWrapper {
* *
* @throws InvalidArgumentException If the logger is not null or an instance for {@see LoggerInterface} * @throws InvalidArgumentException If the logger is not null or an instance for {@see LoggerInterface}
* *
* @noinspection PhpMissingParamTypeInspection * @noinspection PhpDocSignatureInspection
*/ */
public function setLogger ($logger): self { public function setLogger (LoggerInterface $logger): self {
if ($logger !== null && !$logger instanceof LoggerInterface) {
throw new InvalidArgumentException('The logger must be null or a ' . LoggerInterface::class . ' instance');
}
$this->logger = $logger; $this->logger = $logger;
return $this; return $this;
} }
@ -83,7 +80,7 @@ class OutputWithLogger extends OutputWrapper {
* *
* @return void * @return void
*/ */
protected function writeToLogger ($messages, bool $newline = false, int $options = 0): void { protected function writeToLogger (iterable|string $messages, bool $newline = false, int $options = 0): void {
if ($this->logger === null || ($options & static::OPTION_SKIP_LOGGER) === static::OPTION_SKIP_LOGGER) { if ($this->logger === null || ($options & static::OPTION_SKIP_LOGGER) === static::OPTION_SKIP_LOGGER) {
return; return;
} }
@ -112,24 +109,23 @@ class OutputWithLogger extends OutputWrapper {
* @param int $verbosity The verbosity * @param int $verbosity The verbosity
* *
* @return mixed The logger level * @return mixed The logger level
* @noinspection PhpReturnDocTypeMismatchInspection
*/ */
protected function getLoggerLevelFromVerbosity (int $verbosity) { protected function getLoggerLevelFromVerbosity (int $verbosity): mixed {
return $verbosity; return $verbosity;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function write ($messages, bool $newline = false, int $options = 0) { public function write (iterable|string $messages, bool $newline = false, int $options = 0) {
$this->writeToLogger($messages, $newline, $options); $this->writeToLogger($messages, $newline, $options);
return parent::write($messages, $newline, $options); return parent::write($messages, $newline, $options);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function writeLn ($messages, int $options = 0) { public function writeln (iterable|string $messages, int $options = 0) {
$this->writeToLogger($messages, true, $options); $this->writeToLogger($messages, true, $options);
return parent::writeLn($messages, $options); return parent::writeln($messages, $options);
} }
} }

@ -37,20 +37,20 @@ abstract class OutputWrapper implements OutputInterface {
* *
* @return static The “error” output * @return static The “error” output
*/ */
public function getErrorOutput (): self { public function getErrorOutput (): static {
return new static(CliHelper::getErrorOutput($this->getOutput())); return new static(CliHelper::getErrorOutput($this->getOutput()));
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function write ($messages, bool $newline = false, int $options = 0) { public function write (iterable|string $messages, bool $newline = false, int $options = 0) {
return $this->getOutput()->write($messages, $newline, $options); return $this->getOutput()->write($messages, $newline, $options);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function writeln ($messages, int $options = 0) { public function writeln (iterable|string $messages, int $options = 0) {
return $this->getOutput()->writeln($messages, $options); return $this->getOutput()->writeln($messages, $options);
} }

@ -3,6 +3,7 @@
namespace jrosset\CliProgram\Requirements; namespace jrosset\CliProgram\Requirements;
use jrosset\CliProgram\CliHelper; use jrosset\CliProgram\CliHelper;
use ReflectionAttribute;
use ReflectionClass; use ReflectionClass;
use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleCommandEvent;
@ -25,7 +26,7 @@ trait TRequirementsApplication {
*/ */
protected final function registerCommandRequirementsListener (?EventDispatcherInterface $dispatcher = null): void { protected final function registerCommandRequirementsListener (?EventDispatcherInterface $dispatcher = null): void {
$dispatcher ??= new EventDispatcher(); $dispatcher ??= new EventDispatcher();
$dispatcher->addListener(ConsoleEvents::COMMAND, [$this, 'checkCommandRequirements']); $dispatcher->addListener(ConsoleEvents::COMMAND, $this->checkCommandRequirements(...));
$this->setDispatcher($dispatcher); $this->setDispatcher($dispatcher);
} }
@ -36,7 +37,7 @@ trait TRequirementsApplication {
* *
* @return void * @return void
*/ */
public function checkCommandRequirements (ConsoleCommandEvent $event): void { private function checkCommandRequirements (ConsoleCommandEvent $event): void {
$commandInput = $event->getInput(); $commandInput = $event->getInput();
$commandOutput = $event->getOutput(); $commandOutput = $event->getOutput();
try { try {
@ -47,6 +48,13 @@ trait TRequirementsApplication {
$commandReflection = new ReflectionClass($command); $commandReflection = new ReflectionClass($command);
//endregion //endregion
//region Check attribute requirements
foreach ($commandReflection->getAttributes(IRequirements::class, ReflectionAttribute::IS_INSTANCEOF) as $commandRequirementsAttribute) {
/** @var IRequirements $commandRequirementsAttributeInstance */
$commandRequirementsAttributeInstance = $commandRequirementsAttribute->newInstance();
$commandRequirementsAttributeInstance->checkRequirements($commandInput, $commandOutput);
}
//endregion
//region Contrôle pré-requis (implémentation directe) //region Contrôle pré-requis (implémentation directe)
if ($command instanceof IRequirements) { if ($command instanceof IRequirements) {
$command->checkRequirements($commandInput, $commandOutput); $command->checkRequirements($commandInput, $commandOutput);

@ -7,12 +7,12 @@ use jrosset\CliProgram\Validation\Validators\InvalidValueException;
use jrosset\CliProgram\Validation\Validators\IValidator; use jrosset\CliProgram\Validation\Validators\IValidator;
use ReflectionFunction; use ReflectionFunction;
use Stringable; use Stringable;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/** /**
* Provide a value validation process ({@see IValidator}) to {@see self::addArgument()} and {@see self::addOption()} * Provide a value validation process ({@see IValidator}) to {@see self::addArgument()} and {@see self::addOption()}
@ -34,8 +34,6 @@ trait TCommandWithValidation {
/** /**
* @inheritDoc * @inheritDoc
*
* @throws Throwable If an argument or option is not valid
*/ */
protected function initialize (InputInterface $input, OutputInterface $output): void { protected function initialize (InputInterface $input, OutputInterface $output): void {
parent::setCode( parent::setCode(
@ -51,10 +49,8 @@ trait TCommandWithValidation {
} }
/** /**
* @inheritDoc * @inheritDoc
*
* @noinspection PhpMissingReturnTypeInspection
*/ */
public function setCode (callable $code) { public function setCode (callable $code): static {
if ($code instanceof Closure) { if ($code instanceof Closure) {
/** @noinspection PhpUnhandledExceptionInspection */ /** @noinspection PhpUnhandledExceptionInspection */
$codeReflection = new ReflectionFunction($code); $codeReflection = new ReflectionFunction($code);
@ -73,6 +69,9 @@ trait TCommandWithValidation {
} }
} }
} }
else {
$code = $code(...);
}
// {@see Command::$code} is used to perform input validation before execution, so set {@see self::$realCode} instead // {@see Command::$code} is used to perform input validation before execution, so set {@see self::$realCode} instead
$this->realCode = $code; $this->realCode = $code;
@ -194,6 +193,7 @@ trait TCommandWithValidation {
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
* @param string $description The argument description * @param string $description The argument description
* @param IValidator|null $validator The validator or Null if none * @param IValidator|null $validator The validator or Null if none
* @param Closure|array|null $suggestedValues The function or list of suggested values when completing ; Null if none
* *
* @return $this * @return $this
* *
@ -203,15 +203,21 @@ trait TCommandWithValidation {
string $name, string $name,
int $mode = null, int $mode = null,
string $description = '', string $description = '',
$default = null, mixed $default = null,
?IValidator $validator = null ?IValidator $validator = null,
): self { Closure|array|null $suggestedValues = null
): static {
$default = static::treatStringableDefaultValue($default); $default = static::treatStringableDefaultValue($default);
if ($validator !== null) { if ($validator !== null) {
$default = $validator->getValidDefault($default); $default = $validator->getValidDefault($default);
} }
if ($suggestedValues !== null) {
parent::addArgument($name, $mode, $description, $default, $suggestedValues);
}
else {
parent::addArgument($name, $mode, $description, $default); parent::addArgument($name, $mode, $description, $default);
}
return $this->setArgumentValidator($name, $validator); return $this->setArgumentValidator($name, $validator);
} }
/** /**
@ -224,7 +230,7 @@ trait TCommandWithValidation {
* *
* @throws InvalidArgumentException If the {@see InputArgument} doesn't exist * @throws InvalidArgumentException If the {@see InputArgument} doesn't exist
*/ */
public function setArgumentValidator (string $name, ?IValidator $validator): self { public function setArgumentValidator (string $name, ?IValidator $validator): static {
if (!$this->getDefinition()->hasArgument($name)) { if (!$this->getDefinition()->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist', $name)); throw new InvalidArgumentException(sprintf('The "%s" argument does not exist', $name));
} }
@ -260,6 +266,7 @@ trait TCommandWithValidation {
* @param string $description The argument description * @param string $description The argument description
* @param mixed $default The default value (must be null for InputOption::VALUE_NONE) * @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
* @param IValidator|null $validator The validator or Null if none ; ignored if **$mode** = {@see InputOption::VALUE_NONE} * @param IValidator|null $validator The validator or Null if none ; ignored if **$mode** = {@see InputOption::VALUE_NONE}
* @param Closure|array|null $suggestedValues The function or list of suggested values when completing ; Null if none
* *
* @return $this * @return $this
* *
@ -267,18 +274,24 @@ trait TCommandWithValidation {
*/ */
public function addOption ( public function addOption (
string $name, string $name,
$shortcut = null, string|array|null $shortcut = null,
int $mode = null, int $mode = null,
string $description = '', string $description = '',
$default = null, mixed $default = null,
?IValidator $validator = null ?IValidator $validator = null,
): self { Closure|array|null $suggestedValues = null
): static {
$default = static::treatStringableDefaultValue($default); $default = static::treatStringableDefaultValue($default);
if ($validator !== null) { if ($validator !== null) {
$default = $validator->getValidDefault($default); $default = $validator->getValidDefault($default);
} }
if ($suggestedValues !== null) {
parent::addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
}
else {
parent::addOption($name, $shortcut, $mode, $description, $default); parent::addOption($name, $shortcut, $mode, $description, $default);
}
return $this->setOptionValidator($name, $validator); return $this->setOptionValidator($name, $validator);
} }
/** /**
@ -291,7 +304,7 @@ trait TCommandWithValidation {
* *
* @throws InvalidArgumentException If the {@see InputOption} doesn't exist * @throws InvalidArgumentException If the {@see InputOption} doesn't exist
*/ */
public function setOptionValidator (string $name, ?IValidator $validator): self { public function setOptionValidator (string $name, ?IValidator $validator): static {
if (!$this->getDefinition()->hasOption($name)) { if (!$this->getDefinition()->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist', $name)); throw new InvalidArgumentException(sprintf('The "--%s" option does not exist', $name));
} }
@ -325,7 +338,7 @@ trait TCommandWithValidation {
* *
* @return mixed The default value * @return mixed The default value
*/ */
protected static function treatStringableDefaultValue ($default) { protected static function treatStringableDefaultValue (mixed $default): mixed {
if ($default instanceof Stringable) { if ($default instanceof Stringable) {
return $default->__toString(); return $default->__toString();
} }

@ -20,7 +20,7 @@ abstract class AbstractDateTimeValidator extends BasedValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
$this->getInternalValidator()->setPattern($this->getPattern()); $this->getInternalValidator()->setPattern($this->getPattern());
return parent::validate($value); return parent::validate($value);
} }

@ -11,16 +11,14 @@ abstract class BasedValidator implements IValidator {
/** /**
* @var TValidator The internal validator * @var TValidator The internal validator
*/ */
private $internalValidator; private IValidator $internalValidator;
/** /**
* Create a validator * Create a validator
* *
* @param TValidator $internalValidator The internal validator * @param TValidator $internalValidator The internal validator
*
* @noinspection PhpMissingParamTypeInspection
*/ */
public function __construct ($internalValidator) { public function __construct (IValidator $internalValidator) {
$this->internalValidator = $internalValidator; $this->internalValidator = $internalValidator;
} }
/** /**
@ -28,14 +26,14 @@ abstract class BasedValidator implements IValidator {
* *
* @return TValidator The internal validator * @return TValidator The internal validator
*/ */
protected function getInternalValidator () { protected function getInternalValidator (): IValidator {
return $this->internalValidator; return $this->internalValidator;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
return $this->internalValidator->validate($value); return $this->internalValidator->validate($value);
} }
} }

@ -15,21 +15,27 @@ class DateTimeValidator extends AbstractDateTimeValidator {
* @var bool Is the time part mandatory ? * @var bool Is the time part mandatory ?
*/ */
private bool $timeMandatory; private bool $timeMandatory;
/**
* @var DateTimeInterface The time part (if not mandatory)
*/
private DateTimeInterface $timePart;
/** /**
* Create a validator * Create a validator
* *
* @param bool $mandatoryDate Is the time part mandatory ? * @param bool $timeMandatory Is the time part mandatory ?
* @param DateTimeInterface|null $timePart The time part (if not mandatory)
*/ */
public function __construct (bool $mandatoryDate = false) { public function __construct (bool $timeMandatory = false, ?DateTimeInterface $timePart = null) {
$this->setTimeMandatory($mandatoryDate); $this->setTimeMandatory($timeMandatory);
$this->setTimePart($timePart ?? (new DateTimeImmutable())->setTimestamp(0));
parent::__construct(); parent::__construct();
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getValidDefault ($default) { public function getValidDefault (mixed $default): string|bool|int|float|array|null {
if ($default instanceof DateTimeInterface) { if ($default instanceof DateTimeInterface) {
return $default->format('Y-m-d H:i:s'); return $default->format('Y-m-d H:i:s');
} }
@ -56,9 +62,9 @@ class DateTimeValidator extends AbstractDateTimeValidator {
(int)$this->getInternalValidator()->getMatch('day'), (int)$this->getInternalValidator()->getMatch('day'),
); );
return $value->setTime( return $value->setTime(
(int)($this->getInternalValidator()->getMatch('hour') ?? 0), (int)($this->getInternalValidator()->getMatch('hour') ?? (int)$this->getTimePart()->format('%H')),
(int)($this->getInternalValidator()->getMatch('minute') ?? 0), (int)($this->getInternalValidator()->getMatch('minute') ?? (int)$this->getTimePart()->format('%i')),
(int)($this->getInternalValidator()->getMatch('second') ?? 0), (int)($this->getInternalValidator()->getMatch('second') ?? (int)$this->getTimePart()->format('%s')),
); );
} }
@ -81,4 +87,24 @@ class DateTimeValidator extends AbstractDateTimeValidator {
$this->timeMandatory = $timeMandatory; $this->timeMandatory = $timeMandatory;
return $this; return $this;
} }
/**
* The time part (if not mandatory)
*
* @return DateTimeInterface The time part (if not mandatory)
*/
public function getTimePart (): DateTimeInterface {
return $this->timePart;
}
/**
* Set the time part (if not mandatory)
*
* @param DateTimeInterface $timePart The time part
*
* @return $this
*/
public function setTimePart (DateTimeInterface $timePart): self {
$this->timePart = $timePart;
return $this;
}
} }

@ -29,7 +29,7 @@ class DateValidator extends AbstractDateTimeValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getValidDefault ($default) { public function getValidDefault (mixed $default): string|bool|int|float|array|null {
if ($default instanceof DateTimeInterface) { if ($default instanceof DateTimeInterface) {
return $default->format('Y-m-d'); return $default->format('Y-m-d');
} }

@ -56,7 +56,7 @@ class DecimalValidator extends BasedValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
if (!parent::validate($value)) { if (!parent::validate($value)) {
return false; return false;
} }

@ -21,7 +21,7 @@ class DirectoryValidator implements IValidator {
* *
* @param FilesystemValidationOptionsList|FilesystemValidationOption[]|FilesystemValidationOption|null $options The options * @param FilesystemValidationOptionsList|FilesystemValidationOption[]|FilesystemValidationOption|null $options The options
*/ */
public function __construct ($options = null) { public function __construct (FilesystemValidationOptionsList|array|FilesystemValidationOption|null $options = null) {
$this->setOptions($options); $this->setOptions($options);
} }
@ -35,7 +35,7 @@ class DirectoryValidator implements IValidator {
* @throws InvalidArgumentException If the default value is not a string or null * @throws InvalidArgumentException If the default value is not a string or null
* @throws InvalidArgumentException If the default directory doesn't match the options * @throws InvalidArgumentException If the default directory doesn't match the options
*/ */
public function getValidDefault ($default) { public function getValidDefault (mixed $default): string|bool|int|float|array|null {
if ($default === null) { if ($default === null) {
return null; return null;
} }
@ -43,13 +43,13 @@ class DirectoryValidator implements IValidator {
throw new InvalidArgumentException('The default value must be a string or null'); throw new InvalidArgumentException('The default value must be a string or null');
} }
if ($this->getOptions()->contains(FilesystemValidationOption::IS_READABLE) && !is_readable($default)) { if ($this->getOptions()->contains(FilesystemValidationOption::IsReadable) && !is_readable($default)) {
throw new InvalidArgumentException('The default value is not readable'); throw new InvalidArgumentException('The default value is not readable');
} }
if ($this->getOptions()->contains(FilesystemValidationOption::IS_WRITABLE) && !is_writable($default)) { if ($this->getOptions()->contains(FilesystemValidationOption::IsWritable) && !is_writable($default)) {
throw new InvalidArgumentException('The default value is not writable'); throw new InvalidArgumentException('The default value is not writable');
} }
if ($this->getOptions()->contains(FilesystemValidationOption::MUST_EXISTS) && !file_exists($default)) { if ($this->getOptions()->contains(FilesystemValidationOption::MustExists) && !file_exists($default)) {
throw new InvalidArgumentException('The default value doesn\'t exist'); throw new InvalidArgumentException('The default value doesn\'t exist');
} }
return $default; return $default;
@ -58,18 +58,18 @@ class DirectoryValidator implements IValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
if (!is_string($value) && !$value instanceof Stringable) { if (!is_string($value) && !$value instanceof Stringable) {
return false; return false;
} }
if ($this->getOptions()->contains(FilesystemValidationOption::IS_READABLE) && !is_readable($value)) { if ($this->getOptions()->contains(FilesystemValidationOption::IsReadable->name) && !is_readable($value)) {
return false; return false;
} }
if ($this->getOptions()->contains(FilesystemValidationOption::IS_WRITABLE) && !is_writable($value)) { if ($this->getOptions()->contains(FilesystemValidationOption::IsWritable->name) && !is_writable($value)) {
return false; return false;
} }
if ($this->getOptions()->contains(FilesystemValidationOption::MUST_EXISTS) && !file_exists($value)) { if ($this->getOptions()->contains(FilesystemValidationOption::MustExists->name) && !file_exists($value)) {
return false; return false;
} }
@ -92,22 +92,13 @@ class DirectoryValidator implements IValidator {
* *
* @return $this * @return $this
*/ */
public function setOptions ($options = null): self { public function setOptions (FilesystemValidationOptionsList|array|FilesystemValidationOption|null $options = null): static {
if ($options instanceof FilesystemValidationOptionsList) { $this->options = match (true) {
$this->options = $options; $options instanceof FilesystemValidationOptionsList => $options,
} $options instanceof FilesystemValidationOption => new FilesystemValidationOptionsList([$options]),
elseif (is_int($options)) { is_array($options) => new FilesystemValidationOptionsList($options),
$this->options = new FilesystemValidationOptionsList([$options]); $options === null => new FilesystemValidationOptionsList(),
} };
elseif (is_array($options)) {
$this->options = new FilesystemValidationOptionsList($options);
}
elseif ($options === null) {
$this->options = new FilesystemValidationOptionsList();
}
else {
throw new InvalidArgumentException();
}
return $this; return $this;
} }
} }

@ -12,7 +12,7 @@ class EmailValidator implements IValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
return false; return false;
} }

@ -0,0 +1,60 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use Arrayy\Type\StringCollection;
use ReflectionEnum;
use ReflectionException;
use UnitEnum;
/**
* An argument/option value validator based on an enumeration
*
* @template TEnum of UnitEnum
* @template-implements IValidator<TEnum>
* @template-implements TInternalValueValidator<TEnum>
*/
class EnumValidator extends BasedValidator {
/**
* @var ReflectionEnum The enumeration
*/
private ReflectionEnum $enum;
/**
* Create a validator
*
* @param class-string<UnitEnum> $enumClass The enumeration class
*
* @throws ReflectionException If the enumeration class doesn't exist
*/
public function __construct (string $enumClass) {
$this->enum = new ReflectionEnum($enumClass);
$enumCases = new StringCollection();
foreach ($this->enum->getCases() as $enumCase) {
$enumCases->add($enumCase->getName());
}
parent::__construct(new ListValidator($enumCases));
}
/**
* @inheritDoc
*/
public function getValidDefault (mixed $default): string|bool|int|float|array|null {
if ($default instanceof UnitEnum) {
$default = $default->name;
}
return $default;
}
/**
* @inheritDoc
*
* @throws ReflectionException
*/
public function getValue (): UnitEnum {
$enumCase = $this->getInternalValidator()->getValue();
return $this->enum->getCase($enumCase)->getValue();
}
}

@ -19,7 +19,7 @@ class FileValidator extends DirectoryValidator {
* @param string|null $extensionsPattern The allowed extensions (regex pattern) * @param string|null $extensionsPattern The allowed extensions (regex pattern)
* @param FilesystemValidationOptionsList|FilesystemValidationOption[]|FilesystemValidationOption|null $options The options * @param FilesystemValidationOptionsList|FilesystemValidationOption[]|FilesystemValidationOption|null $options The options
*/ */
public function __construct (?string $extensionsPattern = null, $options = null) { public function __construct (?string $extensionsPattern = null, FilesystemValidationOptionsList|FilesystemValidationOption|array|null $options = null) {
parent::__construct($options); parent::__construct($options);
$this->setExtensionsPattern($extensionsPattern); $this->setExtensionsPattern($extensionsPattern);
} }
@ -27,7 +27,7 @@ class FileValidator extends DirectoryValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getValidDefault ($default) { public function getValidDefault (mixed $default): string|bool|int|float|array|null {
if (($default = parent::getValidDefault($default)) === null) { if (($default = parent::getValidDefault($default)) === null) {
return null; return null;
} }
@ -39,7 +39,7 @@ class FileValidator extends DirectoryValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
if (!parent::validate($value)) { if (!parent::validate($value)) {
return false; return false;
} }
@ -64,7 +64,7 @@ class FileValidator extends DirectoryValidator {
* *
* @return $this * @return $this
*/ */
public function setExtensionsPattern (?string $extensionsPattern = null): self { public function setExtensionsPattern (?string $extensionsPattern = null): static {
$this->extensionsPattern = $extensionsPattern; $this->extensionsPattern = $extensionsPattern;
return $this; return $this;
} }

@ -5,8 +5,8 @@ namespace jrosset\CliProgram\Validation\Validators;
/** /**
* Options of a filesystem based validator * Options of a filesystem based validator
*/ */
abstract class FilesystemValidationOption { enum FilesystemValidationOption {
public const MUST_EXISTS = 1; case MustExists;
public const IS_READABLE = 2; case IsReadable;
public const IS_WRITABLE = 3; case IsWritable;
} }

@ -14,6 +14,6 @@ class FilesystemValidationOptionsList extends AbstractCollection {
* @inheritDoc * @inheritDoc
*/ */
public function getType (): string { public function getType (): string {
return 'int'; return FilesystemValidationOption::class;
} }
} }

@ -13,9 +13,9 @@ interface IValidator {
* *
* @param mixed $default The initial/given default value * @param mixed $default The initial/given default value
* *
* @return string|bool|int|float|array The valid default value * @return string|bool|int|float|array|null The valid default value
*/ */
public function getValidDefault ($default); public function getValidDefault (mixed $default): string|bool|int|float|array|null;
/** /**
* Validate a value * Validate a value
@ -24,7 +24,7 @@ interface IValidator {
* *
* @return bool True if the value is valid, else False * @return bool True if the value is valid, else False
*/ */
public function validate ($value): bool; public function validate (mixed $value): bool;
/** /**
* Get the value, after it's validation * Get the value, after it's validation
* *

@ -50,7 +50,7 @@ class IntegerValidator extends BasedValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
if (!parent::validate($value)) { if (!parent::validate($value)) {
return false; return false;
} }

@ -32,7 +32,7 @@ class ListValidator implements IValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
if ($this->getAllowedValues()->contains($value)) { if ($this->getAllowedValues()->contains($value)) {
$this->setValue($value); $this->setValue($value);
return true; return true;

@ -32,7 +32,7 @@ class RegexValidator implements IValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function validate ($value): bool { public function validate (mixed $value): bool {
return preg_match($this->pattern, $value, $this->matches) === 1; return preg_match($this->pattern, $value, $this->matches) === 1;
} }
@ -71,7 +71,7 @@ class RegexValidator implements IValidator {
* *
* @return string|null The capturing group or Null if not set * @return string|null The capturing group or Null if not set
*/ */
public function getMatch ($group): ?string { public function getMatch (int|string $group): ?string {
return $this->getMatches()[$group] ?? null; return $this->getMatches()[$group] ?? null;
} }
/** /**
@ -81,7 +81,7 @@ class RegexValidator implements IValidator {
* *
* @return bool True if the capturing group exists and is not null * @return bool True if the capturing group exists and is not null
*/ */
public function hasMatch ($group): bool { public function hasMatch (int|string $group): bool {
return $this->getMatches()[$group] !== null; return $this->getMatches()[$group] !== null;
} }

@ -11,7 +11,7 @@ trait TIdenticalValidDefaultValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getValidDefault ($default) { public function getValidDefault (mixed $default): string|bool|int|float|array|null {
return $default; return $default;
} }
} }

@ -13,7 +13,7 @@ namespace jrosset\CliProgram\Validation\Validators;
*/ */
trait TInternalValueValidator { trait TInternalValueValidator {
/** /**
* @var TValue|null The current value, after * @var TValue|null The current value, after it's validation
*/ */
private $value = null; private $value = null;
@ -32,7 +32,7 @@ trait TInternalValueValidator {
* *
* @return $this * @return $this
*/ */
protected function setValue ($value): self { protected function setValue ($value): static {
$this->value = $value; $this->value = $value;
return $this; return $this;
} }

@ -29,7 +29,7 @@ class TimeValidator extends AbstractDateTimeValidator {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getValidDefault ($default) { public function getValidDefault (mixed $default): string|bool|int|float|array|null {
if ($default instanceof DateTimeInterface) { if ($default instanceof DateTimeInterface) {
return $default->format('H:i:s'); return $default->format('H:i:s');
} }

@ -2,10 +2,9 @@
namespace jrosset\Tests\Commands; namespace jrosset\Tests\Commands;
use LogicException; use jrosset\Tests\FailedRequirement;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[FailedRequirement]
class FailedHello extends Hello { class FailedHello extends Hello {
/** /**
* @inheritDoc * @inheritDoc
@ -13,11 +12,4 @@ class FailedHello extends Hello {
public function __construct () { public function __construct () {
parent::__construct('failedHello'); parent::__construct('failedHello');
} }
/**
* @inheritDoc
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void {
throw new LogicException('The "foo" requirement failed');
}
} }

@ -4,34 +4,28 @@ namespace jrosset\Tests\Commands;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use jrosset\CliProgram\CliCommand;
use jrosset\CliProgram\Output\OutputWithLogger; use jrosset\CliProgram\Output\OutputWithLogger;
use jrosset\CliProgram\Requirements\IRequirements;
use jrosset\CliProgram\Validation\CommandWithValidation; use jrosset\CliProgram\Validation\CommandWithValidation;
use jrosset\CliProgram\Validation\Validators\DateValidator; use jrosset\CliProgram\Validation\Validators\DateValidator;
use jrosset\CliProgram\Validation\Validators\EnumValidator;
use jrosset\CliProgram\Validation\Validators\IntegerValidator; use jrosset\CliProgram\Validation\Validators\IntegerValidator;
use jrosset\Tests\Lang;
use jrosset\Tests\SuccessRequirement;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
class Hello extends CommandWithValidation implements IRequirements { #[CliCommand(null, 'Say hello')]
/** #[SuccessRequirement]
* @inheritdoc class Hello extends CommandWithValidation {
*/
protected static $defaultDescription = 'Say hello';
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function __construct (?string $name = null) { public function __construct (?string $name = null) {
parent::__construct($name ?? 'hello', 'bonjour'); parent::__construct($name ?? 'hello', ['bonjour']);
}
/**
* @inheritDoc
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void {
} }
/** /**
@ -48,6 +42,14 @@ class Hello extends CommandWithValidation implements IRequirements {
new DateValidator() new DateValidator()
); );
$this->addOption(
'lang',
'l',
InputOption::VALUE_REQUIRED,
'The lang',
null,
new EnumValidator(Lang::class)
);
$this->addOption( $this->addOption(
'repeat', 'repeat',
'r', 'r',
@ -64,7 +66,7 @@ class Hello extends CommandWithValidation implements IRequirements {
protected function execute (InputInterface $input, OutputInterface $output): int { protected function execute (InputInterface $input, OutputInterface $output): int {
$output->writeln('<info>Command : ' . __CLASS__ . '</info>', OutputInterface::VERBOSITY_DEBUG); $output->writeln('<info>Command : ' . __CLASS__ . '</info>', OutputInterface::VERBOSITY_DEBUG);
$text = 'Hello'; $text = ($input->getOption('lang') ?? Lang::English)->value;
$repeat = $input->getOption('repeat'); $repeat = $input->getOption('repeat');
/** @var DateTimeInterface $day */ /** @var DateTimeInterface $day */

@ -29,7 +29,7 @@ class ReadFile extends CommandWithValidation {
null, null,
new FileValidator( new FileValidator(
/** @lang PhpRegExp */ '#\.txt$#i', /** @lang PhpRegExp */ '#\.txt$#i',
FilesystemValidationOption::IS_READABLE FilesystemValidationOption::IsReadable
) )
); );
} }

@ -0,0 +1,19 @@
<?php
namespace jrosset\Tests;
use Attribute;
use jrosset\CliProgram\Requirements\IRequirements;
use LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[Attribute(Attribute::TARGET_CLASS)]
class FailedRequirement implements IRequirements {
/**
* @inheritDoc
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void {
throw new LogicException('The "foo" requirement failed');
}
}

@ -0,0 +1,9 @@
<?php
namespace jrosset\Tests;
enum Lang: string {
case French = 'Bonjour';
case English = 'Good morning';
case American = 'Hello';
}

@ -0,0 +1,17 @@
<?php
namespace jrosset\Tests;
use Attribute;
use jrosset\CliProgram\Requirements\IRequirements;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[Attribute(Attribute::TARGET_CLASS)]
class SuccessRequirement implements IRequirements {
/**
* @inheritDoc
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void {
}
}
Loading…
Cancel
Save