Add utility methods for asking arguments/options in interactive mode

master 3.14.0
Julien Rosset 1 month ago
parent e5798e83ac
commit 70a2fb7de4

@ -15,13 +15,14 @@
"minimum-stability": "stable",
"require": {
"php": "^8.1",
"jrosset/betterphptoken": "^1.0",
"jrosset/extendedmonolog": "^2.0",
"psr/log": "^2.0",
"symfony/console": "^6.1",
"symfony/event-dispatcher": "^6.1",
"voku/arrayy": "^7.9"
"php": "^8.1",
"jrosset/betterphptoken": "^1.0",
"jrosset/extendedmonolog": "^2.0",
"jrosset/mbstring-extended": "^1.3",
"psr/log": "^2.0",
"symfony/console": "^6.1",
"symfony/event-dispatcher": "^6.1",
"voku/arrayy": "^7.9"
},
"autoload": {
"psr-4": {

@ -4,9 +4,15 @@ namespace jrosset\CliProgram;
use jrosset\CliProgram\CommandCall\CommandCall;
use jrosset\CliProgram\CommandCall\CommandCallsList;
use jrosset\CliProgram\Validation\Validators\IValidator;
use jrosset\MbstringExtended;
use ReflectionClass;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Throwable;
/**
@ -82,6 +88,198 @@ class BaseCommand extends Command {
$this->setHidden($hidden ?? static::getDefaultHidden());
}
/**
* Interacting: asking for a parameter value
*
* - Do nothing if the parameter already has a value
* - Ask multiple times if the parameter is an array, an empty value stops the loop
*
* @param InputInterface $input The command input
* @param OutputInterface $output The command output
* @param string $argumentName The argument name
* @param string $questionText The question text
* @param string|null $errorMessage The error message to display if the value is invalid (only if a {@see IValidator validator} is set for the argument)
* @param bool $askEvenIfOptional If true, ask even if the parameter is optional and without a value
*
* @return void
*/
protected final function interact_askParameter (
InputInterface $input,
OutputInterface $output,
string $argumentName,
string $questionText,
?string $errorMessage = null,
bool $askEvenIfOptional = false
): void {
$argumentObject = $this->getDefinition()->getArgument($argumentName);
//region Check if the argument is optional and must not be asked
if (!$askEvenIfOptional && !$argumentObject->isRequired()) {
return;
}
//endregion
//region Check if the argument already has a value
if ($input->hasArgument($argumentName) && MbstringExtended::trim($input->getArgument($argumentName) ?? '') !== '') {
return;
}
//endregion
//region Create the question
$argumentValidator = null;
if (method_exists($this, 'getArgumentValidator')) {
$argumentValidator = $this->getArgumentValidator($argumentName);
}
$question = (new Question($questionText, $argumentObject->getDefault()))
->setValidator(
function (?string $argumentValue) use ($argumentValidator, $errorMessage): ?string {
if (
MbstringExtended::trim($argumentValue ?? '') === ''
|| preg_match('/^\s*(?:\033\s*)*$/', $argumentValue) // Value contains only [Esc] characters
) {
return null;
}
if ($argumentValidator !== null && !$argumentValidator->validate($argumentValue)) {
throw new RuntimeException($errorMessage);
}
return $argumentValidator->getValue();
}
);
//endregion
//region Ask the question (multiple times if multiple values are allowed)
$questionHelper = $this->getHelper('question');
$argumentValues = [];
do {
$argumentValue = $questionHelper
->ask($input, $output, $question);
$argumentValues[] = $argumentValue;
} while ($argumentObject->isArray() && MbstringExtended::trim($argumentValue) !== '');
//endregion
//region Set the argument value(s), if at least one given
if ($argumentObject->isArray()) {
array_pop($argumentValues); // Always an empty value at the end
if (count($argumentValues) > 0 || !$argumentObject->isRequired()) {
$input->setArgument($argumentName, $argumentValues);
}
}
else {
$argumentValue = array_pop($argumentValues);
if (MbstringExtended::trim($argumentValue ?? '') !== '') {
$input->setArgument($argumentName, $argumentValue);
}
}
//endregion
}
/**
* Interacting: asking for an option
*
* - Work even if the parameter is optional
* - Do nothing if the option is already set or has a value
* - Ask multiple times if the option is an array, an empty value stops the loop
*
* @param InputInterface $input The command input
* @param OutputInterface $output The command output
* @param string $optionName The option name
* @param string $questionText The question text
* @param string|null $errorMessage The error message to display if the value is invalid (only if a {@see IValidator validator} is set for the option)
* @param bool $askEvenIfOptional If true, ask even if the parameter is optional and without a value
*
* @return void
*/
protected final function interact_askOption (
InputInterface $input,
OutputInterface $output,
string $optionName,
string $questionText,
?string $errorMessage = null,
bool $askEvenIfOptional = false
): void {
//region Initialisation
$optionObject = $this->getDefinition()->getOption($optionName);
$questionHelper = $this->getHelper('question');
//endregion
//region Case #1: Option with values
if ($optionObject->acceptValue()) {
//region Check if the option's value is optional and must not be asked
if (!$askEvenIfOptional && $optionObject->isValueOptional()) {
return;
}
//endregion
//region Check if the option already has a value
if ($input->hasOption($optionName) && MbstringExtended::trim($input->getOption($optionName) ?? '') !== '') {
return;
}
//endregion
//region Create the question
$optionValidator = null;
if (method_exists($this, 'getOptionValidator')) {
$optionValidator = $this->getOptionValidator($optionName);
}
$question = (new Question($questionText))
->setValidator(
function (?string $optionValue) use ($optionValidator, $errorMessage): ?string {
if (
MbstringExtended::trim($optionValue ?? '') === ''
|| preg_match('/^\s*(?:\033\s*)*$/', $optionValue) // Value contains only [Esc] characters
) {
return null;
}
if ($optionValidator !== null && !$optionValidator->validate($optionValue)) {
throw new RuntimeException($errorMessage);
}
return $optionValidator->getValue();
}
);
//endregion
//region Ask the question (multiple times if multiple values are allowed)
$optionValues = [];
do {
$optionValue = $questionHelper
->ask($input, $output, $question);
$optionValues[] = $optionValue;
} while ($optionObject->isArray() && MbstringExtended::trim($optionValue) !== '');
//endregion
//region Set the option value(s), if at least one given
if ($optionObject->isArray()) {
array_pop($optionValues); // Always an empty value at the end
if (count($optionValues) > 0 || $optionObject->isValueOptional()) {
$input->setOption($optionName, $optionValues);
}
}
else {
$optionValue = array_pop($optionValues);
if (MbstringExtended::trim($optionValue ?? '') !== '') {
$input->setOption($optionName, $optionValue);
}
}
//endregion
}
//endregion
//region Case #2: Option without any value (even optional)
else {
//region Check if the option (or the negative) is set
if ($input->hasOption($optionName)) {
return;
}
//endregion
//region Ask the question
$optionValue = $questionHelper->ask(
$input, $output,
new ConfirmationQuestion($questionText, $optionObject->getDefault(), /** @lang PhpRegExp */ '/^\s*[yo1]/i')
);
//endregion
//region Set the option value, if given
if ($optionValue !== null) {
$input->setOption($optionName, $optionValue);
}
//endregion
}
//endregion
}
/**
* Run a list of subcommands
*

Loading…
Cancel
Save