nur-ture/src/app/cli/ArgDefs.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);
}
}