processExtends(); } $index = []; foreach ($argDefs as $argDef) { $options = $argDef->getOptions(); foreach ($options as $option) { if (array_key_exists($option, $index)) { $index[$option]->removeOption($option); } $index[$option] = $argDef; } } foreach ($argDefs as $argDef) { $argDef->processArgs(); $argDef->processAction(); } $this->argDefs = $argDefs; $this->index = $index; } protected array $argDefs; protected array $index; function getArgDef(string $option): ?ArgDef { return $this->index[$option] ?? null; } /** * consommer les arguments de $src en avançant l'index $srci et provisionner * $dest à partir de $desti. si $desti est plus grand que 0, celà veut dire * que $dest a déjà commencé à être provisionné, et qu'il faut continuer. * * $destmin est le nombre minimum d'arguments à consommer. $destmax est le * nombre maximum d'arguments à consommer. * * $srci est la position de l'élément courant à consommer le cas échéant * retourner le nombre d'arguments qui manquent (ou 0 si tous les arguments * ont été consommés) * * pour les arguments optionnels, ils sont consommés tant qu'il y en a de * disponible, ou jusqu'à la présence de '--'. Si $keepsep, l'argument '--' * est gardé dans la liste des arguments optionnels. */ private static function consume_args($src, &$srci, &$dest, $desti, $destmin, $destmax, bool $keepsep): int { $srcmax = count($src); # arguments obligatoires while ($desti < $destmin) { if ($srci < $srcmax) { $dest[] = $src[$srci]; } else { # pas assez d'arguments return $destmin - $desti; } $srci++; $desti++; } # arguments facultatifs while ($desti < $destmax && $srci < $srcmax) { $opt = $src[$srci]; if ($opt === "--") { # fin des options facultatives if ($keepsep) $dest[] = $opt; $srci++; break; } $dest[] = $opt; $srci++; $desti++; } return 0; } private static function check_missing(?string $option, int $count) { if ($count > 0) { throw new ArgException("$option: nombre d'arguments insuffisant (manque $count)"); } } function normalize(array $args): array { $i = 0; $max = count($args); $options = []; $remains = []; $parseOpts = true; while ($i < $max) { $arg = $args[$i++]; if (!$parseOpts) { # le reste n'est que des arguments $remains[] = $arg; continue; } if ($arg === "--") { # fin des options $parseOpts = false; continue; } if (substr($arg, 0, 2) === "--") { ####################################################################### # option longue $pos = strpos($arg, "="); if ($pos !== false) { # option avec valeur $option = substr($arg, 0, $pos); $value = substr($arg, $pos + 1); } else { # option sans valeur $option = $arg; $value = null; } /** @var ArgDef $argDef */ $argDef = $this->index[$option] ?? null; if ($argDef === null) { # chercher une correspondance $len = strlen($option); $candidates = []; foreach (array_keys($this->index) as $candidate) { if (substr($candidate, 0, $len) === $option) { $candidates[] = $candidate; } } switch (count($candidates)) { case 0: throw new ArgException("$option: option invalide"); case 1: $option = $candidates[0]; break; default: $candidates = implode(", ", $candidates); throw new ArgException("$option: option ambigue (les options possibles sont $candidates)"); } $argDef = $this->index[$option]; } if ($argDef->haveArgs) { $minArgs = $argDef->minArgs; $maxArgs = $argDef->maxArgs; $values = []; if ($value !== null) { $values[] = $value; $offset = 1; } elseif ($minArgs == 0) { # cas particulier: la première valeur doit être collée à l'option # si $maxArgs == 1 $offset = $maxArgs == 1 ? 1 : 0; } else { $offset = 0; } $this->check_missing($option, self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true)); if ($minArgs == 0 && $maxArgs == 1) { # cas particulier: la première valeur doit être collée à l'option if (count($values) > 0) { $options[] = "$option=$values[0]"; $values = array_slice($values, 1); } else { $options[] = $option; } } else { $options[] = $option; } $options = array_merge($options, $values); } elseif ($value !== null) { throw new ArgException("$option: cette option ne prend pas d'arguments"); } else { $options[] = $option; } } elseif (substr($arg, 0, 1) === "-") { ####################################################################### # option courte $pos = 1; $len = strlen($arg); while ($pos < $len) { $option = "-".substr($arg, $pos, 1); /** @var ArgDef $argDef */ $argDef = $this->index[$option] ?? null; if ($argDef === null) { throw new ArgException("$option: option invalide"); } if ($argDef->haveArgs) { $minArgs = $argDef->minArgs; $maxArgs = $argDef->maxArgs; $values = []; if ($len > $pos + 1) { $values[] = substr($arg, $pos + 1); $offset = 1; $pos = $len; } elseif ($minArgs == 0) { # cas particulier: la première valeur doit être collée à l'option # si $maxArgs == 1 $offset = $maxArgs == 1 ? 1 : 0; } else { $offset = 0; } $this->check_missing($option, self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true)); if ($minArgs == 0 && $maxArgs == 1) { # cas particulier: la première valeur doit être collée à l'option if (count($values) > 0) { $options[] = "$option$values[0]"; $values = array_slice($values, 1); } else { $options[] = $option; } } else { $options[] = $option; } $options = array_merge($options, $values); } else { $options[] = $option; } $pos++; } } else { #XXX implémenter les commandes ####################################################################### # argument $remains[] = $arg; } } return array_merge($options, ["--"], $remains); } }