246 lines
7.4 KiB
PHP
246 lines
7.4 KiB
PHP
<?php
|
|
namespace nulib\app\cli;
|
|
|
|
use nulib\cl;
|
|
|
|
class ArgDefs {
|
|
function __construct(array $defs) {
|
|
[$defs, $params] = cl::split_assoc($defs);
|
|
|
|
$argDefs = [];
|
|
foreach ($defs as $def) {
|
|
$argDefs[] = new ArgDef($def);
|
|
}
|
|
foreach ($argDefs as $argDef) {
|
|
$argDef->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);
|
|
}
|
|
}
|