suite gestion des arguments
This commit is contained in:
parent
ce86cfe354
commit
87aa8fd4ee
@ -1,194 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\php\types\varray;
|
||||
|
||||
class Arg {
|
||||
const TYPE_SHORT = 0, TYPE_LONG = 1, TYPE_COMMAND = 2;
|
||||
const ARGS_NONE = 0, ARGS_MANDATORY = 1, ARGS_OPTIONAL = 2;
|
||||
|
||||
function __construct(array $def) {
|
||||
[$options, $params] = cl::split_assoc($def);
|
||||
|
||||
$args = $params["args"] ?? null;
|
||||
$args ??= $params["arg"] ?? null;
|
||||
if ($args === true) $args = 1;
|
||||
if (is_int($args)) $args = array_fill(0, $args, "value");
|
||||
$this->_args = cl::withn($args);
|
||||
|
||||
$this->argsdesc = $params["argsdesc"] ?? null;
|
||||
|
||||
$extends = $params["extends"] ?? null;
|
||||
if ($extends !== null) {
|
||||
A::merge($extends["add"], $options);
|
||||
$this->extends = $extends;
|
||||
$this->processExtends();
|
||||
#XXX à terme, processExtends() est appelé par ArgsParser après le
|
||||
# chargement de tous les arguments, parce que [arg] peut-être une
|
||||
# référence e.g ["extends" => ["arg" => "-o", "add" => ["--longo"]]]
|
||||
} else {
|
||||
$this->addOptions($options);
|
||||
}
|
||||
}
|
||||
|
||||
protected ?array $options = [];
|
||||
|
||||
function getOptions(): array {
|
||||
return array_keys($this->options);
|
||||
}
|
||||
|
||||
public bool $haveShortOptions = false;
|
||||
public bool $haveLongOptions = false;
|
||||
public bool $haveCommands = false;
|
||||
|
||||
public bool $haveArgs = false;
|
||||
public ?int $minArgs = null;
|
||||
public ?int $maxArgs = null;
|
||||
public ?string $argsdesc = null;
|
||||
|
||||
function addOptions(?array $options): void {
|
||||
if ($options === null) return;
|
||||
foreach ($options as $option) {
|
||||
if (substr($option, 0, 2) === "--") {
|
||||
$type = self::TYPE_LONG;
|
||||
if (preg_match('/^--([^:-]+)(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$args = $ms[2] ?? null;
|
||||
$option = "--$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid long option");
|
||||
}
|
||||
} elseif (substr($option, 0, 1) === "-") {
|
||||
$type = self::TYPE_SHORT;
|
||||
if (preg_match('/^-([^:-])(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$args = $ms[2] ?? null;
|
||||
$option = "-$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid short option");
|
||||
}
|
||||
} else {
|
||||
$type = self::TYPE_COMMAND;
|
||||
if (preg_match('/^([^:-]+)$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$args = null;
|
||||
$option = "$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid command");
|
||||
}
|
||||
}
|
||||
if ($args === ":") {
|
||||
$argsType = self::ARGS_MANDATORY;
|
||||
} elseif ($args === "::") {
|
||||
$argsType = self::ARGS_OPTIONAL;
|
||||
} else {
|
||||
$argsType = self::ARGS_NONE;
|
||||
}
|
||||
$this->options[$option] = [
|
||||
"name" => $name,
|
||||
"option" => $option,
|
||||
"type" => $type,
|
||||
"args_type" => $argsType,
|
||||
];
|
||||
}
|
||||
$this->updateType();
|
||||
}
|
||||
|
||||
function removeOptions(?array $options): void {
|
||||
if ($options === null) return;
|
||||
foreach ($options as $option) {
|
||||
if (substr($option, 0, 2) === "--") {
|
||||
if (preg_match('/^--([^:-]+)(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$option = "--$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid long option");
|
||||
}
|
||||
} elseif (substr($option, 0, 1) === "-") {
|
||||
if (preg_match('/^-([^:-])(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$option = "-$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid short option");
|
||||
}
|
||||
} else {
|
||||
if (preg_match('/^([^:-]+)$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$option = "$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid command");
|
||||
}
|
||||
}
|
||||
unset($this->options[$option]);
|
||||
}
|
||||
$this->updateType();
|
||||
}
|
||||
|
||||
protected ?array $extends;
|
||||
|
||||
function processExtends(): void {
|
||||
$extends = $this->extends;
|
||||
$base = $extends["arg"] ?? null;
|
||||
if ($base === null) return;
|
||||
$base = new self($base);
|
||||
$this->options = $base->options;
|
||||
$this->removeOptions(varray::withn($extends["remove"] ?? null));
|
||||
$this->addOptions(varray::withn($extends["add"] ?? null));
|
||||
}
|
||||
|
||||
protected function updateType(): void {
|
||||
$haveShortOptions = false;
|
||||
$haveLongOptions = false;
|
||||
$haveCommands = false;
|
||||
foreach ($this->options as $option) {
|
||||
switch ($option["type"]) {
|
||||
case self::TYPE_SHORT:
|
||||
$haveShortOptions = true;
|
||||
break;
|
||||
case self::TYPE_LONG:
|
||||
$haveLongOptions = true;
|
||||
break;
|
||||
case self::TYPE_COMMAND:
|
||||
$haveCommands = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->haveShortOptions = $haveShortOptions;
|
||||
$this->haveLongOptions = $haveLongOptions;
|
||||
$this->haveCommands = $haveCommands;
|
||||
}
|
||||
|
||||
protected ?array $_args = null;
|
||||
|
||||
protected function updateArgs(): void {
|
||||
if ($this->_args === null) {
|
||||
$haveArgs = false;
|
||||
$optionalArgs = null;
|
||||
foreach ($this->options as $option) {
|
||||
switch ($option["args_type"]) {
|
||||
case self::ARGS_NONE:
|
||||
break;
|
||||
case self::ARGS_MANDATORY:
|
||||
$haveArgs = true;
|
||||
$optionalArgs = false;
|
||||
break;
|
||||
case self::ARGS_OPTIONAL:
|
||||
$haveArgs = true;
|
||||
$optionalArgs ??= true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$optionalArgs ??= false;
|
||||
if ($haveArgs) {
|
||||
$args = ["value"];
|
||||
if ($optionalArgs) $args = [$args];
|
||||
}
|
||||
}
|
||||
#XXX calculer minArgs, maxArgs, argsdesc
|
||||
$this->haveArgs = $haveArgs;
|
||||
$this->_args = $args;
|
||||
$this->argsdesc = $argsdesc;
|
||||
}
|
||||
}
|
369
src/app/cli/ArgDef.php
Normal file
369
src/app/cli/ArgDef.php
Normal file
@ -0,0 +1,369 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\php\func;
|
||||
use nulib\php\types\varray;
|
||||
|
||||
class ArgDef {
|
||||
const TYPE_SHORT = 0, TYPE_LONG = 1, TYPE_COMMAND = 2;
|
||||
const ARGS_NONE = 0, ARGS_MANDATORY = 1, ARGS_OPTIONAL = 2;
|
||||
const ACTION_SET = 0, ACTION_INC = 1, ACTION_DEC = 2, ACTION_FUNC = 3;
|
||||
|
||||
function __construct(array $def) {
|
||||
[$options, $params] = cl::split_assoc($def);
|
||||
|
||||
$args = $params["args"] ?? null;
|
||||
$args ??= $params["arg"] ?? null;
|
||||
if ($args === true) $args = 1;
|
||||
if (is_int($args)) $args = array_fill(0, $args, "value");
|
||||
$this->args = cl::withn($args);
|
||||
|
||||
$this->argsdesc = $params["argsdesc"] ?? null;
|
||||
|
||||
$extends = $params["extends"] ?? null;
|
||||
if ($extends !== null) {
|
||||
A::merge($extends["add"], $options);
|
||||
$this->extends = $extends;
|
||||
} else {
|
||||
$this->addOptions($options);
|
||||
}
|
||||
|
||||
$this->ensureArray = $params["ensure_array"] ?? null;
|
||||
$action = $params["action"] ?? null;
|
||||
$func = null;
|
||||
if ($action !== null) {
|
||||
switch ($action) {
|
||||
case "--set":
|
||||
$action = self::ACTION_SET;
|
||||
break;
|
||||
case "--inc":
|
||||
$action = self::ACTION_INC;
|
||||
break;
|
||||
case "--dec":
|
||||
$action = self::ACTION_DEC;
|
||||
break;
|
||||
default:
|
||||
$func = func::with($action);
|
||||
$action = self::ACTION_FUNC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->action = $action;
|
||||
$this->func = $func;
|
||||
$this->inverse = $params["inverse"] ?? false;
|
||||
$this->value = $params["value"] ?? null;
|
||||
$this->name = $params["name"] ?? null;
|
||||
$this->property = $params["property"] ?? null;
|
||||
$this->key = $params["key"] ?? null;
|
||||
|
||||
$this->help = $params["help"] ?? null;
|
||||
}
|
||||
|
||||
protected ?array $options = [];
|
||||
|
||||
function getOptions(): array {
|
||||
return array_keys($this->options);
|
||||
}
|
||||
|
||||
public bool $isRemains = false;
|
||||
public bool $haveShortOptions = false;
|
||||
public bool $haveLongOptions = false;
|
||||
public bool $haveCommands = false;
|
||||
|
||||
public bool $haveArgs = false;
|
||||
public ?int $minArgs = null;
|
||||
public ?int $maxArgs = null;
|
||||
public ?string $argsdesc = null;
|
||||
|
||||
function addOptions(?array $options): void {
|
||||
if ($options === null) return;
|
||||
foreach ($options as $option) {
|
||||
if (substr($option, 0, 2) === "--") {
|
||||
$type = self::TYPE_LONG;
|
||||
if (preg_match('/^--([^:-]+)(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$args = $ms[2] ?? null;
|
||||
$option = "--$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid long option");
|
||||
}
|
||||
} elseif (substr($option, 0, 1) === "-") {
|
||||
$type = self::TYPE_SHORT;
|
||||
if (preg_match('/^-([^:-])(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$args = $ms[2] ?? null;
|
||||
$option = "-$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid short option");
|
||||
}
|
||||
} else {
|
||||
$type = self::TYPE_COMMAND;
|
||||
if (preg_match('/^([^:-]+)$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$args = null;
|
||||
$option = "$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid command");
|
||||
}
|
||||
}
|
||||
if ($args === ":") {
|
||||
$argsType = self::ARGS_MANDATORY;
|
||||
} elseif ($args === "::") {
|
||||
$argsType = self::ARGS_OPTIONAL;
|
||||
} else {
|
||||
$argsType = self::ARGS_NONE;
|
||||
}
|
||||
$this->options[$option] = [
|
||||
"name" => $name,
|
||||
"option" => $option,
|
||||
"type" => $type,
|
||||
"args_type" => $argsType,
|
||||
];
|
||||
}
|
||||
$this->updateType();
|
||||
}
|
||||
|
||||
function removeOptions(?array $options): void {
|
||||
if ($options === null) return;
|
||||
foreach ($options as $option) {
|
||||
if (substr($option, 0, 2) === "--") {
|
||||
if (preg_match('/^--([^:-]+)(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$option = "--$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid long option");
|
||||
}
|
||||
} elseif (substr($option, 0, 1) === "-") {
|
||||
if (preg_match('/^-([^:-])(::?)?$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$option = "-$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid short option");
|
||||
}
|
||||
} else {
|
||||
if (preg_match('/^([^:-]+)$/', $option, $ms)) {
|
||||
$name = $ms[1];
|
||||
$option = "$name";
|
||||
} else {
|
||||
throw new ArgException("$option: invalid command");
|
||||
}
|
||||
}
|
||||
unset($this->options[$option]);
|
||||
}
|
||||
$this->updateType();
|
||||
}
|
||||
|
||||
function removeOption(string $option): void {
|
||||
unset($this->options[$option]);
|
||||
}
|
||||
|
||||
protected ?array $extends = null;
|
||||
|
||||
/** traiter le paramètre extends */
|
||||
function processExtends(): void {
|
||||
$extends = $this->extends;
|
||||
if ($extends === null) return;
|
||||
$base = $extends["arg"] ?? null;
|
||||
if ($base === null) return;
|
||||
$base = new self($base);
|
||||
$this->options = $base->options;
|
||||
$this->removeOptions(varray::withn($extends["remove"] ?? null));
|
||||
$this->addOptions(varray::withn($extends["add"] ?? null));
|
||||
}
|
||||
|
||||
/** mettre à jour le type d'option */
|
||||
protected function updateType(): void {
|
||||
$haveShortOptions = false;
|
||||
$haveLongOptions = false;
|
||||
$haveCommands = false;
|
||||
$isRemains = true;
|
||||
foreach ($this->options as $option) {
|
||||
$isRemains = false;
|
||||
switch ($option["type"]) {
|
||||
case self::TYPE_SHORT:
|
||||
$haveShortOptions = true;
|
||||
break;
|
||||
case self::TYPE_LONG:
|
||||
$haveLongOptions = true;
|
||||
break;
|
||||
case self::TYPE_COMMAND:
|
||||
$haveCommands = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->isRemains = $isRemains;
|
||||
$this->haveShortOptions = $haveShortOptions;
|
||||
$this->haveLongOptions = $haveLongOptions;
|
||||
$this->haveCommands = $haveCommands;
|
||||
}
|
||||
|
||||
protected ?array $args = null;
|
||||
|
||||
/**
|
||||
* traiter les informations concernant les arguments puis calculer les nombres
|
||||
* minimum et maximum d'arguments que prend l'option
|
||||
*/
|
||||
function processArgs(): void {
|
||||
$args = $this->args;
|
||||
if ($args === null) {
|
||||
$haveArgs = false;
|
||||
$optionalArgs = null;
|
||||
foreach ($this->options as $option) {
|
||||
switch ($option["args_type"]) {
|
||||
case self::ARGS_NONE:
|
||||
break;
|
||||
case self::ARGS_MANDATORY:
|
||||
$haveArgs = true;
|
||||
$optionalArgs = false;
|
||||
break;
|
||||
case self::ARGS_OPTIONAL:
|
||||
$haveArgs = true;
|
||||
$optionalArgs ??= true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$optionalArgs ??= false;
|
||||
if ($haveArgs) {
|
||||
$args = ["value"];
|
||||
if ($optionalArgs) $args = [$args];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isRemains) $desc = "remaining args";
|
||||
else $desc = cl::first($this->options)["option"];
|
||||
|
||||
$args ??= [];
|
||||
$argsdesc = [];
|
||||
$nbArgs = 0;
|
||||
$reqs = [];
|
||||
$haveNull = false;
|
||||
$optArgs = null;
|
||||
foreach ($args as $arg) {
|
||||
$nbArgs++;
|
||||
if (is_string($arg)) {
|
||||
$reqs[] = $arg;
|
||||
$argsdesc[] = strtoupper($arg);
|
||||
} elseif (is_array($arg)) {
|
||||
$optArgs = $arg;
|
||||
break;
|
||||
} elseif ($arg === null) {
|
||||
$haveNull = true;
|
||||
break;
|
||||
} else {
|
||||
throw new ArgException("$desc: $arg: invalid option arg");
|
||||
}
|
||||
}
|
||||
if ($nbArgs !== count($args)) {
|
||||
throw new ArgException("$desc: invalid args format");
|
||||
}
|
||||
|
||||
$opts = [];
|
||||
$optArgsdesc = null;
|
||||
$lastarg = "VALUE";
|
||||
if ($optArgs !== null) {
|
||||
$haveOpt = false;
|
||||
$nbArgs = 0;
|
||||
foreach ($optArgs as $arg) {
|
||||
$nbArgs++;
|
||||
if (is_string($arg)) {
|
||||
$haveOpt = true;
|
||||
$opts[] = $arg;
|
||||
$lastarg = strtoupper($arg);
|
||||
$optArgsdesc[] = $lastarg;
|
||||
} elseif ($arg === null) {
|
||||
$haveNull = true;
|
||||
break;
|
||||
} else {
|
||||
throw new ArgException("$desc: $arg: invalid option arg");
|
||||
}
|
||||
}
|
||||
if ($nbArgs !== count($args)) {
|
||||
throw new ArgException("$desc: invalid args format");
|
||||
}
|
||||
if (!$haveOpt) $haveNull = true;
|
||||
}
|
||||
if ($haveNull) $optArgsdesc[] = "${lastarg}s...";
|
||||
if ($optArgsdesc !== null) {
|
||||
$argsdesc[] = "[".implode(" ", $optArgsdesc)."]";
|
||||
}
|
||||
|
||||
$minArgs = count($reqs);
|
||||
if ($haveNull) $maxArgs = PHP_INT_MAX;
|
||||
else $maxArgs = $minArgs + count($opts);
|
||||
|
||||
$this->haveArgs = $haveArgs;
|
||||
$this->minArgs = $minArgs;
|
||||
$this->maxArgs = $maxArgs;
|
||||
$this->argsdesc = implode(" ", $argsdesc);
|
||||
}
|
||||
|
||||
protected ?bool $ensureArray = null;
|
||||
protected ?int $action = null;
|
||||
protected ?func $func = null;
|
||||
protected bool $inverse = false;
|
||||
protected $value = null;
|
||||
protected ?string $name = null;
|
||||
protected ?string $property = null;
|
||||
protected ?string $key = null;
|
||||
|
||||
private static function get_longest(array $options, int $type): ?string {
|
||||
$longest = null;
|
||||
$maxlen = 0;
|
||||
foreach ($options as $option) {
|
||||
if ($option["type"] !== $type) continue;
|
||||
$name = $option["name"];
|
||||
$len = strlen($name);
|
||||
if ($len > $maxlen) {
|
||||
$longest = $name;
|
||||
$maxlen = $len;
|
||||
}
|
||||
}
|
||||
return $longest;
|
||||
}
|
||||
|
||||
function processAction(): void {
|
||||
$this->ensureArray ??= $this->isRemains || $this->maxArgs > 1;
|
||||
|
||||
$action = $this->action;
|
||||
if ($action === null) {
|
||||
if ($this->haveArgs) {
|
||||
$action = self::ACTION_SET;
|
||||
} elseif ($this->value !== null) {
|
||||
$action = self::ACTION_SET;
|
||||
} elseif ($this->inverse) {
|
||||
$action = self::ACTION_DEC;
|
||||
} else {
|
||||
$action = self::ACTION_INC;
|
||||
}
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
$name = $this->name;
|
||||
$property = $this->property;
|
||||
$key = $this->key;
|
||||
if ($action !== self::ACTION_FUNC && !$this->isRemains &&
|
||||
$name === null && $property === null && $key === null
|
||||
) {
|
||||
# si on ne précise pas le nom de la propriété, la dériver à partir du
|
||||
# nom de l'option la plus longue
|
||||
$longest = self::get_longest($this->options, self::TYPE_LONG);
|
||||
$longest ??= self::get_longest($this->options, self::TYPE_COMMAND);
|
||||
$longest ??= self::get_longest($this->options, self::TYPE_SHORT);
|
||||
if ($longest !== null) {
|
||||
$longest = preg_replace('/[^A-Za-z0-9]+/', "_", $longest);
|
||||
if (preg_match('/^[0-9]/', $longest)) {
|
||||
# le nom de la propriété ne doit pas commencer par un chiffre
|
||||
$longest = "p$longest";
|
||||
}
|
||||
$name = $longest;
|
||||
}
|
||||
} elseif ($name === null && $property !== null) {
|
||||
$name = $property;
|
||||
} elseif ($name === null && $key !== null) {
|
||||
$name = $key;
|
||||
}
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
245
src/app/cli/ArgDefs.php
Normal file
245
src/app/cli/ArgDefs.php
Normal file
@ -0,0 +1,245 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
72
tests/app/cli/ArgDefTest.php
Normal file
72
tests/app/cli/ArgDefTest.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class ArgDefTest extends TestCase {
|
||||
protected static function assertArg(
|
||||
ArgDef $argDef,
|
||||
array $options,
|
||||
bool $haveShortOptions, bool $haveLongOptions, bool $haveCommands,
|
||||
bool $haveArgs, ?int $minArgs, ?int $maxArgs, ?string $argsdesc
|
||||
) {
|
||||
$argDef->processExtends();
|
||||
$argDef->processArgs();
|
||||
$argDef->processAction();
|
||||
self::assertSame($options, $argDef->getOptions());
|
||||
self::assertSame($haveShortOptions, $argDef->haveShortOptions, "haveShortOptions");
|
||||
self::assertSame($haveLongOptions, $argDef->haveLongOptions, "haveLongOptions");
|
||||
self::assertSame($haveCommands, $argDef->haveCommands, "haveCommands");
|
||||
self::assertSame($haveArgs, $argDef->haveArgs, "haveArgs");
|
||||
self::assertSame($minArgs, $argDef->minArgs, "minArgs");
|
||||
self::assertSame($maxArgs, $argDef->maxArgs, "maxArgs");
|
||||
self::assertSame($argsdesc, $argDef->argsdesc, "argsdesc");
|
||||
}
|
||||
|
||||
function testBase() {
|
||||
$argDef = new ArgDef(["-o", "--longo"]);
|
||||
self::assertArg($argDef,
|
||||
["-o", "--longo"],
|
||||
true, true, false,
|
||||
false, 0, 0, "");
|
||||
|
||||
$argDef = new ArgDef(["-o:", "--longo"]);
|
||||
self::assertArg($argDef,
|
||||
["-o", "--longo"],
|
||||
true, true, false,
|
||||
true, 1, 1, "VALUE");
|
||||
|
||||
$argDef = new ArgDef(["-o::", "--longo"]);
|
||||
self::assertArg($argDef,
|
||||
["-o", "--longo"],
|
||||
true, true, false,
|
||||
true, 0, 1, "[VALUE]");
|
||||
|
||||
$argDef = new ArgDef(["-o:", "--longo:"]);
|
||||
self::assertArg($argDef,
|
||||
["-o", "--longo"],
|
||||
true, true, false,
|
||||
true, 1, 1, "VALUE");
|
||||
|
||||
$argDef = new ArgDef(["-o:", "--longo::"]);
|
||||
self::assertArg($argDef,
|
||||
["-o", "--longo"],
|
||||
true, true, false,
|
||||
true, 1, 1, "VALUE");
|
||||
}
|
||||
|
||||
function testExtends() {
|
||||
$argDef = [
|
||||
"extends" => [
|
||||
"arg" => ["-o:", "--longo"],
|
||||
"add" => ["-a", "--longa"],
|
||||
"remove" => ["-o", "--longo"],
|
||||
],
|
||||
];
|
||||
$arg = new ArgDef($argDef);
|
||||
self::assertArg($arg,
|
||||
["-a", "--longa"],
|
||||
true, true, false,
|
||||
false, 0, 0, "");
|
||||
}
|
||||
}
|
31
tests/app/cli/ArgDefsTest.php
Normal file
31
tests/app/cli/ArgDefsTest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class ArgDefsTest extends TestCase {
|
||||
function testBase() {
|
||||
$argDefs = new ArgDefs([
|
||||
["-a"],
|
||||
["--longb"],
|
||||
["-c", "--longc"],
|
||||
["-m:", "--mandatory"],
|
||||
["-o::", "--optional"],
|
||||
["-x", "--x1"],
|
||||
["-x", "--x2"],
|
||||
]);
|
||||
|
||||
self::assertSame(["--"]
|
||||
, $argDefs->normalize([]));
|
||||
self::assertSame(["--", "a", "b"]
|
||||
, $argDefs->normalize(["a", "b"]));
|
||||
self::assertSame(["-a", "--mandatory", "x", "--mandatory", "y", "--", "z", "x"]
|
||||
, $argDefs->normalize(["-a", "--m", "x", "z", "--m=y", "x"]));
|
||||
self::assertSame(["-m", "x", "-m", "y", "--"]
|
||||
, $argDefs->normalize(["-mx", "-m", "y"]));
|
||||
self::assertSame(["-ox", "-o", "--", "y"]
|
||||
, $argDefs->normalize(["-ox", "-o", "y"]));
|
||||
self::assertSame(["-a", "--", "-a", "-c"]
|
||||
, $argDefs->normalize(["-a", "--", "-a", "-c"]));
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class ArgTest extends TestCase {
|
||||
function testBase() {
|
||||
$arg = new Arg(["-o", "--longo"]);
|
||||
self::assertSame(["-o", "--longo"], $arg->getOptions());
|
||||
self::assertFalse($arg->haveArgs);
|
||||
self::assertFalse($arg->optionalArgs);
|
||||
|
||||
$arg = new Arg(["-o:", "--longo"]);
|
||||
self::assertSame(["-o", "--longo"], $arg->getOptions());
|
||||
self::assertTrue($arg->haveArgs);
|
||||
self::assertFalse($arg->optionalArgs);
|
||||
|
||||
$arg = new Arg(["-o::", "--longo"]);
|
||||
self::assertSame(["-o", "--longo"], $arg->getOptions());
|
||||
self::assertTrue($arg->haveArgs);
|
||||
self::assertTrue($arg->optionalArgs);
|
||||
|
||||
$arg = new Arg(["-o:", "--longo:"]);
|
||||
self::assertSame(["-o", "--longo"], $arg->getOptions());
|
||||
self::assertTrue($arg->haveArgs);
|
||||
self::assertFalse($arg->optionalArgs);
|
||||
|
||||
$arg = new Arg(["-o:", "--longo::"]);
|
||||
self::assertSame(["-o", "--longo"], $arg->getOptions());
|
||||
self::assertTrue($arg->haveArgs);
|
||||
self::assertFalse($arg->optionalArgs);
|
||||
}
|
||||
|
||||
function testExtends() {
|
||||
$basedef = ["-o:", "--longo"];
|
||||
$def = [
|
||||
"extends" => [
|
||||
"arg" => $basedef,
|
||||
"add" => ["-a", "--longa"],
|
||||
"remove" => ["-o", "--longo"],
|
||||
],
|
||||
];
|
||||
$arg = new Arg($def);
|
||||
self::assertSame(["-a", "--longa"], $arg->getOptions());
|
||||
self::assertFalse($arg->haveArgs);
|
||||
self::assertFalse($arg->optionalArgs);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user