setProgramName($programName); $this->setDescription($mainDescription); $this->setCommand($command); $this->setVersion($version); } /** * La commande de lancement du programme * * NULL = {@see CommandLine::$programName Nom du programme} * * @return string|null La commande * @see CommandLine::$command */ public function getCommand (): ?string { return $this->command; } /** * Modifie la commande de lancement du programme * * @param string|null $command La nouvelle commande * * @return $this * @see CommandLine::$command */ public function setCommand (?string $command): self { $this->command = $command; return $this; } /** * Le nom du programme. * * @return string Le nom. * @see CommandLine::$programName */ public function getProgramName (): string { return $this->programName; } /** * Modifie le nom du programme. * * @param string $programName Le nouveau nom * * @return $this * @see CommandLine::$programName */ public function setProgramName (string $programName): self { $this->programName = $programName; return $this; } /** * La description du programme * * @return string La description * @see CommandLine::$description */ public function getDescription (): string { return $this->description; } /** * Modifie la description du programme. * * @param string $description La nouvelle description. * * @return $this * @see CommandLine::$description */ public function setDescription (string $description): self { $this->description = $description; return $this; } /** * La version du programme * * @return string|null La version * @see CommandLine::$version */ public function getVersion (): ?string { return $this->version; } /** * Modifie la version du programme. * * @param string|null $version La nouvelle version. * * @return $this * @see CommandLine::$version */ public function setVersion (?string $version): self { $this->version = $version; return $this; } /** * La liste des arguments "value" * * @return IValueArgument[] La liste des valeurs * @see CommandLine::$argumentsValues */ public function getValues (): array { return $this->argumentsValues; } /** * Modifie la liste des arguments "value" * * *Attention* : cette fonction _remplace_ la liste des valeurs. * Pour seulement en ajouter, utiliser {@see CommandLine::addValues()} * * @param IValueArgument[] $values La nouvelle liste de valeurs * * @return $this * @see CommandLine::$argumentsValues */ public function setValues (array $values): self { self::_checkValueList($values); $this->argumentsValues = $values; return $this; } /** * Ajoute des arguments "value" à celles existantes * * Pour ajouter une seule valeur, il est conseillé d'utiliser {@see CommandLine::addValue()} * * @param IValueArgument[]|IValueArgument $values La liste des valeurs à ajouter * * @return $this * @see CommandLine::$argumentsValues */ public function addValues ($values): self { if (!is_array($values) && $values instanceof IValueArgument) { $values = array($values); } self::_checkValueList($values); $this->argumentsValues = array_merge($this->argumentsValues, $values); return $this; } /** * Ajoute un seul argument "value" à celles existantes * * Pour ajouter plusieurs valeurs à la fois, il est conseillé d'utiliser {@see CommandLine::addValues()} * * @param IValueArgument $value La valeur à ajouter * * @return $this * @see CommandLine::$argumentsValues */ public function addValue (IValueArgument $value): self { return $this->addValues(array($value)); } /** * Est-ce que l'argument "value" existe déjà ? * * @param string $valueName Le nom de la valeur * * @return bool True si la valeur existe déjà, sinon False */ public function existsValue (string $valueName): bool { foreach ($this->argumentsValues as $option) { if ($option->getName() == $valueName) { return true; } } return false; } /** * Est-ce que c'est un argument "value" ? * * @param mixed $argument L'argument à tester * * @return bool True si c'est bien un argument "value", sinon False */ public static function isValue ($argument): bool { return $argument instanceof IValueArgument; } /** * Vérifie que la liste d'arguments "value" est valide * * Doit être un tableau et chaque élément doit implémenter {@see IValueArgument} * * @param IValueArgument[] $values Les valeurs à vérifier */ protected static function _checkValueList (array $values) { if (!is_array($values)) { throw new InvalidArgumentException('La liste des valeurs n\'est pas un tableau'); } foreach ($values as $key => $value) { if (!self::isValue($value)) { throw new InvalidArgumentException('La valeur ' . $key . ' n\'est pas valide (n\'implémente pas IValueArgument)'); } } } /** * La liste des arguments "option" * * @return IOptionArgument[] La liste des options * @see CommandLine::$argumentsOptions */ public function getOptions (): array { return $this->argumentsOptions; } /** * Modifie la liste des arguments "option" * * *Attention* : cette fonction _remplace_ la liste des options. * Pour seulement en ajouter, utiliser {@see CommandLine::addOptions()} * * @param IOptionArgument[] $options La nouvelle liste d'options * * @return $this * @see CommandLine::$argumentsOptions */ public function setOptions (array $options): self { self::_checkOptionList($options); $this->argumentsOptions = $options; return $this; } /** * Ajoute des arguments "option" à ceux existants * * Pour ajouter une seule option, il est conseillé d'utiliser {@see CommandLine::addOption()} * * @param IOptionArgument[]|IOptionArgument $options La liste d'options à ajouter * * @return $this * @see CommandLine::$argumentsOptions */ public function addOptions ($options): self { if (!is_array($options) && $options instanceof IOptionArgument) { $options = array($options); } self::_checkOptionList($options); $this->argumentsOptions = array_merge($this->argumentsOptions, $options); return $this; } /** * Ajoute un seul argument "option" à ceux existants * * Pour ajouter plusieurs options à la fois, il est conseillé d'utiliser {@see CommandLine::addOptions()} * * @param IOptionArgument $option L'options à ajouter * * @return $this * @see CommandLine::$argumentsOptions */ public function addOption (IOptionArgument $option): self { return $this->addOptions(array($option)); } /** * Est-ce que l'argument "option" existe déjà ? * * @param string $optionName Le nom de l'option * * @return bool True si l'option existe déjà, sinon False */ public function existsOption (string $optionName): bool { foreach ($this->argumentsOptions as $option) { if ($option->getName() == $optionName) { return true; } } return false; } /** * Est-ce que c'est un argument "option" ? * * @param mixed $argument L'argument à tester * * @return bool True si c'est bien un argument "option", sinon False */ public static function isOption ($argument): bool { return $argument instanceof IOptionArgument; } /** * Vérifie que la liste d'arguments "option" est valide * * Doit être un tableau et chaque élément doit implémenter {@see IOptionArgument} * * @param IOptionArgument[] $options Les options à vérifier */ protected static function _checkOptionList (array $options) { if (!is_array($options)) { throw new InvalidArgumentException('La liste des options n\'est pas un tableau'); } foreach ($options as $key => $option) { if (!self::isOption($option)) { throw new InvalidArgumentException('L\'option ' . $key . ' n\'est pas valide (n\'implémente pas IOptionArgument)'); } } } /** * La liste de tous les arguments ("value" et "option") * * @return IArgument[] La liste des arguments */ public function getArguments (): array { return array_merge($this->getValues(), $this->getOptions()); } /** * Modifie la liste de tous les arguments ("value" et "option") * * *Attention* : cette fonction _remplace_ la liste des arguments. * Pour seulement en ajouter, utiliser {@see CommandLine::addArguments()} * * @param IValueArgument[] $arguments La nouvelle liste d'arguments * * @return $this */ public function setArguments (array $arguments): self { $this->setOptions(array()); $this->setValues(array()); return $this->addArguments($arguments); } /** * Ajoute des arguments "value" à celles existantes * * Pour ajouter un seul argument, il est conseillé d'utiliser {@see CommandLine::addArgument()} * * @param IArgument[]|IArgument $arguments La liste des arguments à ajouter * * @return $this */ public function addArguments ($arguments): self { if (!is_array($arguments) && $arguments instanceof IValueArgument) { $arguments = array($arguments); } self::_checkValueList($arguments); foreach ($arguments as $argument) { $this->addArgument($argument); } return $this; } /** * Ajoute un seul argument ("value" ou "option") à ceux existants * * Pour ajouter plusieurs arguments à la fois, il est conseillé d'utiliser {@see CommandLine::addArguments()} * * @param IArgument $argument L'argument à ajouter * * @return $this */ public function addArgument (IArgument $argument): self { if (!self::isArgument($argument)) { throw new InvalidArgumentException('L\'argument n\'est pas valide (n\'implémente pas IArgument)'); } if (self::isOption($argument)) { /** @var IOptionArgument $argument */ $this->addOption($argument); } elseif (self::isValue($argument)) { /** @var IValueArgument $argument */ $this->addValue($argument); } else { $type = ''; try { $reflex = new ReflectionClass($argument); foreach ($reflex->getInterfaces() as $interface) { if ($interface->implementsInterface(IArgument::class)) { $type = $interface->getName(); break; } } } /** @noinspection PhpRedundantCatchClauseInspection */ catch (ReflectionException $e) { $type = /** @lang text */ ''; } throw new InvalidArgumentException('L\'argument n\'est pas d\'un type géré : ' . $type); } return $this; } /** * Est-ce que l'argument ("value" ou "option") existe déjà ? * * @param string $argumentName Le nom de l'argument * * @return bool True si l'argument existe déjà, sinon False * * @noinspection PhpUnused */ public function existsArgument (string $argumentName): bool { $arguments = $this->getArguments(); foreach ($arguments as $argument) { if ($argument->getName() == $argumentName) { return true; } } return false; } /** * Est-ce que c'est un argument ("value" ou "option") ? * * @param mixed $argument L'argument à tester * * @return bool True si c'est bien un argument, sinon False */ public static function isArgument ($argument): bool { return $argument instanceof IArgument; } /** * Vérifie que la liste d'arguments ("value" et "option") est valide * * Doit être un tableau et chaque élément doit implémenter {@see IArgument} * * @param IArgument[] $arguments Les arguments à vérifier * * @noinspection PhpUnused */ protected static function _checkArgumentList (array $arguments) { if (!is_array($arguments)) { throw new InvalidArgumentException('La liste des arguments n\'est pas un tableau'); } foreach ($arguments as $key => $argument) { if (!self::isArgument($argument)) { throw new InvalidArgumentException('L\'argument ' . $key . ' n\'est pas valide (n\'implémente pas IArgument)'); } } } /** * La liste des codes de retour avec leur descriptif * * @return string[] La liste des codes de retour avec leur descriptifs */ public function getExitCodes (): array { return $this->exitCodes; } /** * La description de l'un de code de retour * * @param int $code Le code de retour * * @return string|null La description correspondante ou Null si le code n'existe pas ou n'a pas de description * * @throws InvalidArgumentException Si le code de retour n'est pas un entier */ public function getExitCodeDescription (int $code): ?string { if (filter_var($code, FILTER_VALIDATE_INT) === false) { throw new InvalidArgumentException('Le code de retour "' . $code . '" n\'est pas un entier'); } return $this->exitCodes[$code]; } /** * Remplace la liste des codes de retour avec leur descriptif * * @param string[] $exitCodes La nouvelle liste des codes de retour * * @return $this * * @throws InvalidArgumentException Si la clé du tableaux (les codes) ne sont pas des entiers ou * que la valeur (description) n'est pas convertible en chaine de caractères */ public function setExitCodes (array $exitCodes = array()): self { $this->exitCodes = array(); return $this->addExitCodes($exitCodes); } /** * Ajoute une liste de codes de retour avec leur descriptif * * @param string[] $exitCodes La liste des codes de retour à ajouter * * @return $this * * @throws InvalidArgumentException Si la clé du tableaux (les codes) ne sont pas des entiers ou * que la valeur (description) n'est pas convertible en chaine de caractères */ public function addExitCodes (array $exitCodes): self { foreach ($exitCodes as $code => $description) { $this->addExitCode($code, $description); } return $this; } /** * Ajoute un code de retour avec son descriptif * * @param int $code Le code de retour * @param string|null $description La description * * @return $this * * @throws InvalidArgumentException Si le code n'est pas un entier ou * que la description n'est pas convertible en chaine de caractères */ public function addExitCode (int $code, ?string $description = null): self { if (filter_var($code, FILTER_VALIDATE_INT) === false) { throw new InvalidArgumentException('Le code de retour "' . $code . '" n\'est pas un entier'); } if ( is_array($description) || (is_object($description) && !method_exists($description, '__toString')) || (!is_object($description) && settype($description, 'string') === false) ) { throw new InvalidArgumentException('La description "' . $code . '" n\'est pas convertible en chaine de caractères'); } $this->exitCodes[$code] = (string)$description; return $this; } /** * Est-ce que le code de retour demandé existe ? * * @param int $code Le code de retour * * @return bool True si le code de retour existe * * @throws InvalidArgumentException Si le code de retour n'est pas un entier */ public function existsExitCode (int $code): bool { if (filter_var($code, FILTER_VALIDATE_INT) === false) { throw new InvalidArgumentException('Le code de retour "' . $code . '" n\'est pas un entier'); } return array_key_exists($code, $this->exitCodes); } /** * Ajoute les options communes à la plupart des programmes * * Voici la liste des options générées : * - Aide : --help ou -h * - Version : --version ou -v (voir $shortForVersion) * * @param bool $shortForVersion L'option pour la version existe également au format "court" (-v) * * @return $this */ public function addDefaultArguments (bool $shortForVersion = true): self { if (!$this->existsOption(self::ARGUMENT_OPTION_HELP)) { $this->addOption( (new Argument\Option\Flag(self::ARGUMENT_OPTION_HELP, false, 'Affiche cette aide', 'help', 'h')) ->setStoppingParse(true) ); } if (!$this->existsOption(self::ARGUMENT_OPTION_HELP) && !is_null($this->getVersion())) { $this->addOption( (new Argument\Option\Flag(self::ARGUMENT_OPTION_VERSION, false, 'Affiche la version', 'version', $shortForVersion ? 'v' : null)) ->setStoppingParse(true) ); } return $this; } /** * Auto-lance les fonctions correspondantes aux options communes. * * @param stdClass $values Les valeurs du parsage * @param boolean $exitAtEnd Terminer le script à la fin de la fonction correspondante ? */ public function treatDefaultArguments (stdClass $values, bool $exitAtEnd = true) { if (isset($values->{self::ARGUMENT_OPTION_HELP}) && $values->{self::ARGUMENT_OPTION_HELP} === true) { $this->showHelp($exitAtEnd); } if (isset($values->{self::ARGUMENT_OPTION_VERSION}) && $values->{self::ARGUMENT_OPTION_VERSION} === true) { $this->showVersion($exitAtEnd); } } /** * Affiche la version du programme * * @param boolean $exitAtEnd Terminer le script à la fin de la fonction ? * * @see CommandLine::getVersion() */ public function showVersion (bool $exitAtEnd = true) { echo $this->getVersion() . "\n"; if ($exitAtEnd) { exit(0); } } /** * Affiche l'aide du programme. * * @param boolean $exitAtEnd Terminer le script à la fin de la fonction ? * * @see CommandLine::generateHelp() */ public function showHelp (bool $exitAtEnd = true) { echo $this->generateHelp() . "\n"; if ($exitAtEnd) { exit(0); } } /** * Génère l'aide du programme. * * @return string Le texte de l'aide. * * @see CommandLine::showHelp() */ public function generateHelp (): string { $help = array(); $help[] = $this->getProgramName() . (is_null($this->getVersion()) ? '' : ', version ' . $this->getVersion()); $help[] = $this->getDescription(); $help[] = ''; $syntax = array( empty($this->getCommand()) ? $this->getProgramName() : $this->getCommand(), count($this->getOptions()) > 0 ? '[OPTIONS]' : '', ); $syntax = array_merge($syntax, array_map(array(__CLASS__, 'getSyntaxOfValue'), $this->getValues())); $help[] = implode(' ', $syntax); $help[] = 'Arguments :'; $help = array_merge($help, self::getValuesListing($this->getValues())); $help[] = ''; $help[] = 'Options :'; $help = array_merge($help, self::getOptionsListing($this->getOptions())); $help[] = ''; if (count($this->getExitCodes()) > 0) { $help[] = 'Exit codes :'; $help = array_merge($help, self::getExitCodesListing($this->getExitCodes())); $help[] = ''; } return implode("\n", $help); } /** * La syntax d'un argument "value" * * @param IValueArgument $value L'argument * * @return string La syntax de l'argument * @see generateHelp() */ protected static function getSyntaxOfValue (IValueArgument $value): string { $syntax = ''; $min = $value->getOccurMin(); $max = $value->getOccurMax(); if (is_null($max)) { $max = -1; } if ($min == 0) { $syntax .= '['; } $syntax .= $value->getName(); for ($curr = 2; $curr <= $min; $curr++) { $syntax .= ' ' . $value->getName() . $curr; } if ($max === -1 || $max > 1) { if ($min < 2) { $syntax .= ' [' . $value->getName() . '2]'; } $syntax .= ' ... [' . $value->getName() . ($max === -1 ? 'N' : $max) . ']'; } if ($min == 0) { $syntax .= ']'; } return $syntax; } /** * Génère l'aide d'une liste d'arguments "value" * * @param IValueArgument[] $values La liste des valeurs * * @return string[] L'aide de chaque valeur */ protected static function getValuesListing (array $values): array { /* * Calcul des différents padding */ // Initialisation $pads = new stdClass; $pads->name = 0; $pads->occurMin = 0; $pads->occurMax = 0; $pads->default = 0; $pads->valueDescription = 0; // Lecture des arguments foreach ($values as $value) { $pads->name = max($pads->name, strlen($value->getName())); $max = $value->getOccurMax(); $pads->occurMin = max($pads->occurMin, strlen($value->getOccurMin())); $pads->occurMax = max($pads->occurMax, strlen(is_null($max) ? 'N' : $max)); $pads->default = max($pads->default, strlen($value->getDefault())); if ($value instanceof IArgumentValueDescription) { $pads->valueDescription = max($pads->valueDescription, strlen($value->getValueDescription())); } } $spaces = array(); $spaces[] = str_pad('', $pads->name); $spaces[] = str_pad('', $pads->occurMin + 4 + $pads->occurMax); if ($pads->valueDescription > 0) { $spaces[] = str_pad('', $pads->valueDescription); } if ($pads->default > 0) { $spaces[] = str_pad('', $pads->default); } $spaces[] = ''; /* * Génération des descriptifs */ $entries = array(); foreach ($values as $value) { $entry = array(); $entry[] = str_pad($value->getName(), $pads->name, ' ', STR_PAD_RIGHT); $max = $value->getOccurMax(); $occur = str_pad($value->getOccurMin(), $pads->occurMin, ' ', STR_PAD_LEFT); $occur .= ' => '; $occur .= str_pad(is_null($max) ? 'N' : $max, $pads->occurMax, ' ', STR_PAD_RIGHT); $entry[] = $occur; if ($pads->valueDescription > 0) { $entry[] = str_pad( $value instanceof IArgumentValueDescription ? $value->getValueDescription() : '', $pads->valueDescription, ' ', STR_PAD_RIGHT ); } if ($pads->default > 0) { $entry[] = str_pad($value->getDefault(), $pads->default, ' ', STR_PAD_RIGHT); } $entry[] = preg_replace( '@\r?\n@', '$0' . self::HELP_INDENT . implode(self::HELP_INDENT, $spaces), $value->getDescription() ); $entries[] = self::HELP_INDENT . implode(self::HELP_INDENT, $entry); } return $entries; } /** * Génère l'aide d'une liste d'arguments "option" * * @param IOptionArgument[] $options La liste des options * * @return string[] L'aide de chaque option */ protected static function getOptionsListing (array $options): array { /* * Calcul des différents padding */ // Initialisation $pads = new stdClass; $pads->tagShort = 0; $pads->tagLong = 0; $pads->default = 0; $pads->valueDescription = 0; // Lecture des arguments foreach ($options as $option) { $short = $option->getTagShort(); if (!is_null($short)) { $pads->tagShort = max($pads->tagShort, strlen($short)); } $pads->tagLong = max($pads->tagLong, strlen($option->getTagLong())); $pads->default = max($pads->default, strlen($option->getDefault())); if ($option instanceof IArgumentValueDescription) { $pads->valueDescription = max($pads->valueDescription, strlen($option->getValueDescription())); } } // Les tags ont une taille suplémentaire incompressible $pads->tagShort += 1; $pads->tagLong += 2; $spaces = array(); $spaces[] = str_pad('', $pads->tagShort + 1 + $pads->tagLong + 2); if ($pads->valueDescription > 0) { $spaces[] = str_pad('', $pads->valueDescription); } if ($pads->default > 0) { $spaces[] = str_pad('', $pads->default); } $spaces[] = ''; /* * Génération des descriptifs */ $entries = array(); foreach ($options as $option) { $entry = array(); $short = $option->getTagShort(); $label = str_pad(is_null($short) ? '' : '-' . $short, $pads->tagShort, ' ', STR_PAD_RIGHT); $label .= ' '; $label .= str_pad('--' . $option->getTagLong(), $pads->tagLong, ' ', STR_PAD_RIGHT); $label .= $option->allowMultiple() ? ' *' : ($option->isStoppingParse() ? ' X' : ' '); $entry[] = $label; if ($pads->valueDescription) { $entry[] = str_pad( $option instanceof IArgumentValueDescription ? $option->getValueDescription() : '', $pads->valueDescription, ' ', STR_PAD_RIGHT ); } if ($pads->default > 0) { $entry[] = str_pad($option->getDefault(), $pads->default, ' ', STR_PAD_RIGHT); } $entry[] = preg_replace( '@\r?\n@', '$0' . self::HELP_INDENT . implode(self::HELP_INDENT, $spaces), $option->getDescription() ); $entries[] = self::HELP_INDENT . implode(self::HELP_INDENT, $entry); } return $entries; } /** * Génère l'aide d'une liste de codes de retour * * @param string[] $exitCodes La liste des codes de retour et leur description * * @return string[] L'aide de chaque code de retour */ protected static function getExitCodesListing (array $exitCodes): array { /* * Calcul des différents padding */ // Initialisation $pads = new stdClass; $pads->codes = 0; // Lecture des codes de retour foreach ($exitCodes as $code => $_) { $pads->codes = max($pads->codes, strlen($code)); } /* * Génération des descriptifs */ $entries = array(); foreach ($exitCodes as $code => $description) { $entry = array(); $entry[] = str_pad($code, $pads->codes, ' ', STR_PAD_RIGHT); $entry[] = preg_replace( '@\r?\n@', '$0' . self::HELP_INDENT . str_repeat(' ', $pads->codes) . self::HELP_INDENT, $description ); $entries[] = self::HELP_INDENT . implode(self::HELP_INDENT, $entry); } return $entries; } /** * Traite les arguments du script * * Revient à appeler {@see CommandLine::parseExplicit()} avec les arguments du script {@link https://www.php.net/manual/en/reserved.variables.argv.php $_SERVER['argv']} * * @return stdClass Les valeurs extraites * * @throws MissingArgument Quand un argument de la liste est manquant ou en quantité insuffisante * @throws TooMuchValues Quand il reste des valeurs supplémentaires après le traitement de la liste d'arguments */ public function parse (): stdClass { $argv = $_SERVER['argv']; array_shift($argv); // Supprime le 1er paramètre : le nom du script PHP return $this->parseExplicit($argv); } /** * Traite les arguments du script * * Le lève pas d'exceptions : le message d'erreur est redirigé vers $file * * @param int|null $exitCode Null si n'arrête pas l'exécution, sinon le code retour (exit) * @param resource $file Le fichier dans lequel écrire l'erreur * * @return stdClass|null Les valeurs extraites (Null si erreur) * * @see CommandLine::parse() * * @noinspection PhpMissingParamTypeInspection */ public function parseNoExcept (?int $exitCode = null, $file = STDERR): ?stdClass { try { return $this->parse(); } catch (Throwable $e) { fwrite($file, $e->getMessage()); if ($exitCode !== null) { exit($exitCode); } return null; } } /** * Traite une liste d'arguments * * 1/ Vérifie que la liste d'arguments correspond à la syntaxe de la ligne de commande * 2/ Extrait les valeurs voulues de la liste d'arguments * * @param string[] $argv La liste d'arguments * * @return stdClass Les valeurs extraites * @throws MissingArgument Quand un argument de la liste est manquant ou en quantité insuffisante * @throws TooMuchValues Quand il reste des valeurs supplémentaires après le traitement de la liste d'arguments */ public function parseExplicit (array $argv): stdClass { $stop = false; $nb_args = count($argv); $out = new stdClass(); // Valeurs par défaut foreach ($this->getOptions() as $option) { if (!isset($out->{$option->getVarName()})) { $out->{$option->getVarName()} = $option->getDefault(); } } $this->_parseOptions($argv, $out, $this->getOptions(), $stop); if ($stop) { return $out; } if (($arg = AbstractOptionArgument::containsOption($argv)) !== false) { throw new IncorrectParse('Option inconnue : ' . $arg); } $values = array_values($this->getValues()); // Valeurs par défaut foreach ($values as $value) { if (!isset($out->{$value->getVarName()})) { $out->{$value->getVarName()} = $value->getDefault(); } } /** * @var int $ordre * @var IValueArgument $value */ foreach ($values as $ordre => $value) { $min = $value->getOccurMin(); self::_parseXTimes($argv, $out, $value, $min); $max = $value->getOccurMax(); if (is_null($max) || $max > $min) { $nbArgs = $this->_getNbArgumentRestant($ordre); if ($nbArgs >= 0 && count($argv) > $nbArgs) { $nbParse = count($argv) - $nbArgs; if (!is_null($max)) { $nbParse = min($nbParse, $max - $min); } self::_parseXTimes($argv, $out, $value, $nbParse, $min); } } } if (count($argv)) { throw new TooMuchValues('Trop de paramètres : ' . count($argv) . ' / ' . $nb_args); } return $out; } /** * Traite une liste d'arguments * * Le lève pas d'exceptions : le message d'erreur est redirigé vers $file * * @param string[] $argv La liste d'arguments * @param int|null $exitCode Null si n'arrête pas l'exécution, sinon le code retour (exit) * @param resource $file Le fichier dans lequel écrire l'erreur * * @return stdClass|null Les valeurs extraites (Null si erreur) * * @see CommandLine::parseExplicit() * * @noinspection PhpUnused * @noinspection PhpMissingParamTypeInspection */ public function parseExplicitNoExcept (array $argv, ?int $exitCode = null, $file = STDERR): ?stdClass { try { return $this->parseExplicit($argv); } catch (Throwable $e) { fwrite($file, $e->getMessage()); if ($exitCode !== null) { exit($exitCode); } return null; } } /** * Calcule le nombre d'argument "value" restant à honorer. * * @param int $ordre_start L'ordre de la valeur en cours * * @return int Le nombre de valeurs restantes à traiter, ou -1 si un nombre "illimité" */ protected function _getNbArgumentRestant (int $ordre_start): int { $nb = 0; /** * @var int $ordre * @var IValueArgument $argument */ foreach (array_values($this->getValues()) as $ordre => $argument) { if ($ordre <= $ordre_start) { continue; } $max = $argument->getOccurMax(); if (is_null($max)) { return -1; } $nb += $max; } return $nb; } /** * Parse des arguments "option" * * @param string[] $argv La liste des arguments du script * @param stdClass $out Les valeurs de sortie * @param IOptionArgument[] $options La liste des options * @param boolean $stop Arrêt du parsage ? * * @throws IncorrectParse Si le parsage d'une des options échoue */ protected static function _parseOptions (array &$argv, stdClass &$out, array $options, bool &$stop): void { $stop = false; foreach ($options as $option) { do { $argv_option = $argv; $find = false; while (count($argv_option) > 0) { $result = $option->parse($argv_option); if (!is_null($result)) { if ($option->isStoppingParse()) { $out = new stdClass(); } self::_setValue($out, $option, $result, $option->allowMultiple() ? 2 : 1); if ($option->isStoppingParse()) { $stop = true; } array_splice($argv, count($argv) - count($argv_option), $result->getConsume()); $find = true; break; } array_shift($argv_option); } } while (count($argv_option) > 1 && (!$find || $option->allowMultiple())); } } /** * Parse un argument "value" X fois * * @param string[] $argv Liste des arguments du script * @param stdClass $out Les valeurs de sortie * @param IValueArgument $value L'argument "value" à parser * @param int $xTimes Le nombre de fois à parser * @param int $offset L'offset pour le n° d'occurence * * @throws MissingArgument Si l'argument n'est pas en quantité suffisante * @throws IncorrectParse Si l'argument échoue à se parser */ protected static function _parseXTimes (array &$argv, stdClass &$out, IValueArgument $value, int $xTimes, int $offset = 0) { if ($xTimes > count($argv)) { throw new MissingArgument('L\'argument ' . $value->getName() . ' est en quantité insuffisante (' . count($argv) . ' / ' . $xTimes . ')'); } for ($curr = 1; $curr <= $xTimes; $curr++) { $result = $value->parse($argv); if (is_null($result)) { throw new MissingArgument('L\'occurence n° ' . ($offset + $curr) . ' de l\'argument ' . $value->getName() . ' ne correspond pas'); } self::_setValue($out, $value, $result, $value->getOccurMax()); $argv = array_slice($argv, $result->getConsume()); } } /** * Ajoute le résultat d'un argument "value" ou "option") aux valeurs de sortie * * @param stdClass $out Les valeurs de sortie, auxquelles ajouter la résultat * @param IArgument $argument L'argument actuel * @param ParseResult $result Le résultat du parsage de l'argument * @param int|null $max Le nombre maximum de valeur autorisé */ protected static function _setValue (stdClass &$out, IArgument $argument, ParseResult $result, ?int $max) { if (is_null($max) || $max > 1) { if (!isset($out->{$argument->getVarName()})) { $out->{$argument->getVarName()} = array(); } $out->{$argument->getVarName()}[] = $result->getValue(); } else { $out->{$argument->getVarName()} = $result->getValue(); } } }