diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml
index d039669..e42443d 100644
--- a/.idea/php-docker-settings.xml
+++ b/.idea/php-docker-settings.xml
@@ -18,6 +18,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/.idea/php.xml b/.idea/php.xml
index 8f998bb..d4c313d 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -1,5 +1,10 @@
+
+
+
+
+
@@ -17,60 +22,54 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/remote-mappings.xml b/.idea/remote-mappings.xml
new file mode 100644
index 0000000..965ed3a
--- /dev/null
+++ b/.idea/remote-mappings.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/cli/AbstractArgsParser.php b/src/app/cli/AbstractArgsParser.php
index 36e007b..13aadba 100644
--- a/src/app/cli/AbstractArgsParser.php
+++ b/src/app/cli/AbstractArgsParser.php
@@ -4,6 +4,29 @@ namespace nulib\app\cli;
use stdClass;
abstract class AbstractArgsParser {
+ protected function notEnoughArgs(int $needed, ?string $arg=null): ArgsException {
+ if ($arg !== null) $arg .= ": ";
+ return new ArgsException("${arg}nécessite $needed argument(s) supplémentaires");
+ }
+
+ protected function checkEnoughArgs(?string $option, int $count): void {
+ if ($count > 0) throw $this->notEnoughArgs($count, $option);
+ }
+
+ protected function tooManyArgs(int $count, int $expected, ?string $arg=null): ArgsException {
+ if ($arg !== null) $arg .= ": ";
+ return new ArgsException("${arg}trop d'arguments (attendu $expected, reçu $count)");
+ }
+
+ protected function invalidArg(string $arg): ArgsException {
+ return new ArgsException("$arg: argument invalide");
+ }
+
+ protected function ambiguousArg(string $arg, array $candidates): ArgsException {
+ $candidates = implode(", ", $candidates);
+ return new ArgsException("$arg: argument ambigû (les valeurs possibles sont $candidates)");
+ }
+
/**
* 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
@@ -34,27 +57,26 @@ abstract class AbstractArgsParser {
$desti++;
}
# arguments facultatifs
+ $eoo = false; // l'option a-t-elle été terminée?
while ($desti < $destmax && $srci < $srcmax) {
$opt = $src[$srci];
+ $srci++;
+ $desti++;
if ($opt === "--") {
- # fin des options facultatives
+ # fin des arguments facultatifs en entrée
+ $eoo = true;
if ($keepsep) $dest[] = $opt;
- $srci++;
break;
}
$dest[] = $opt;
- $srci++;
- $desti++;
+ }
+ if (!$eoo && $desti < $destmax) {
+ # pas assez d'arguments en entrée, terminer avec "--"
+ $dest[] = "--";
}
return 0;
}
- protected static function check_missing(?string $option, int $count) {
- if ($count > 0) {
- throw new ArgException("$option: nombre d'arguments insuffisant (manque $count)");
- }
- }
-
abstract function normalize(array $args): array;
/** @var object|array objet destination */
@@ -81,4 +103,6 @@ abstract class AbstractArgsParser {
$this->process($args);
$this->unsetDest();
}
+
+ abstract function actionPrintHelp(string $arg): void;
}
diff --git a/src/app/cli/Aodef.php b/src/app/cli/Aodef.php
new file mode 100644
index 0000000..2ad7fa5
--- /dev/null
+++ b/src/app/cli/Aodef.php
@@ -0,0 +1,620 @@
+origDef = $def;
+ $this->mergeParse($def);
+ //$this->debugTrace("construct");
+ }
+
+ protected array $origDef;
+
+ public bool $show = true;
+ public ?bool $disabled = null;
+ public ?bool $isRemains = null;
+ public ?string $extends = null;
+
+ protected ?array $_removes = null;
+ protected ?array $_adds = null;
+
+ protected ?array $_args = null;
+ public ?string $argsdesc = null;
+
+ public ?bool $ensureArray = null;
+ public $action = null;
+ public ?func $func = null;
+ public ?bool $inverse = null;
+ public $value = null;
+ public ?string $name = null;
+ public ?string $property = null;
+ public ?string $key = null;
+
+ public ?string $help = null;
+
+ protected ?array $_options = [];
+
+ public bool $haveShortOptions = false;
+ public bool $haveLongOptions = false;
+ public bool $isCommand = false;
+ public bool $isHelp = false;
+
+ public bool $haveArgs = false;
+ public ?int $minArgs = null;
+ public ?int $maxArgs = null;
+
+ protected function mergeParse(array $def): void {
+ $merges = $defs["merges"] ?? null;
+ $merge = $defs["merge"] ?? null;
+ if ($merge !== null) $merges[] = $merge;
+ if ($merges !== null) {
+ foreach ($merges as $merge) {
+ if ($merge !== null) $this->mergeParse($merge);
+ }
+ }
+
+ $this->parse($def);
+
+ $merge = $defs["merge_after"] ?? null;
+ if ($merge !== null) $this->mergeParse($merge);
+ }
+
+ protected function parse(array $def): void {
+ [$options, $params] = cl::split_assoc($def);
+
+ $this->show ??= $params["show"] ?? true;
+ $this->extends ??= $params["extends"] ?? null;
+
+ $this->disabled = vbool::withn($params["disabled"] ?? null);
+ $removes = varray::withn($params["remove"] ?? null);
+ A::merge($this->_removes, $removes);
+ $adds = varray::withn($params["add"] ?? null);
+ A::merge($this->_adds, $adds);
+ A::merge($this->_adds, $options);
+
+ $args = $params["args"] ?? null;
+ $args ??= $params["arg"] ?? null;
+ if ($args === true) $args = 1;
+ elseif ($args === "*") $args = [null];
+ elseif ($args === "+") $args = ["value", null];
+ if (is_int($args)) $args = array_fill(0, $args, "value");
+ $this->_args ??= cl::withn($args);
+
+ $this->argsdesc ??= $params["argsdesc"] ?? null;
+
+ $this->ensureArray ??= $params["ensure_array"] ?? null;
+ $this->action = $params["action"] ?? null;
+ $this->inverse ??= $params["inverse"] ?? null;
+ $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;
+ }
+
+ function isExtends(): bool {
+ return $this->extends !== null;
+ }
+
+ function setup1(bool $extends=false, ?Aolist $aolist=null): void {
+ if (!$extends && !$this->isExtends()) {
+ $this->processOptions();
+ } elseif ($extends && $this->isExtends()) {
+ $this->processExtends($aolist);
+ }
+ $this->initRemains();
+ //$this->debugTrace("setup1");
+ }
+
+ protected function processExtends(Aolist $argdefs): void {
+ $option = $this->extends;
+ if ($option === null) {
+ throw ArgsException::missing("extends", "destination arg");
+ }
+ $dest = $argdefs->get($option);
+ if ($dest === null) {
+ throw ArgsException::invalid($option, "destination arg");
+ }
+
+ if ($this->ensureArray !== null) $dest->ensureArray = $this->ensureArray;
+ if ($this->action !== null) $dest->action = $this->action;
+ if ($this->inverse !== null) $dest->inverse = $this->inverse;
+ if ($this->value !== null) $dest->value = $this->value;
+ if ($this->name !== null) $dest->name = $this->name;
+ if ($this->property !== null) $dest->property = $this->property;
+ if ($this->key !== null) $dest->key = $this->key;
+
+ A::merge($dest->_removes, $this->_removes);
+ A::merge($dest->_adds, $this->_adds);
+ $dest->processOptions();
+ }
+
+ function buildOptions(?array $options): array {
+ $result = [];
+ if ($options !== null) {
+ 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 ArgsException::invalid($option, "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 ArgsException::invalid($option, "short option");
+ }
+ } else {
+ $type = self::TYPE_COMMAND;
+ if (preg_match('/^([^:-][^:]*)$/', $option, $ms)) {
+ $name = $ms[1];
+ $args = null;
+ $option = "$name";
+ } else {
+ throw ArgsException::invalid($option, "command");
+ }
+ }
+ if ($args === ":") {
+ $argsType = self::ARGS_MANDATORY;
+ } elseif ($args === "::") {
+ $argsType = self::ARGS_OPTIONAL;
+ } else {
+ $argsType = self::ARGS_NONE;
+ }
+ $result[$option] = [
+ "name" => $name,
+ "option" => $option,
+ "type" => $type,
+ "args_type" => $argsType,
+ ];
+ }
+ }
+ return $result;
+ }
+
+ protected function initRemains(): void {
+ if ($this->isRemains === null) {
+ $options = array_fill_keys(array_keys($this->_options), true);
+ foreach (array_keys($this->buildOptions($this->_removes)) as $option) {
+ unset($options[$option]);
+ }
+ foreach (array_keys($this->buildOptions($this->_adds)) as $option) {
+ unset($options[$option]);
+ }
+ if (!$options) $this->isRemains = true;
+ }
+ }
+
+ /** traiter le paramètre parent */
+ protected function processOptions(): void {
+ $this->removeOptions($this->_removes);
+ $this->_removes = null;
+ $this->addOptions($this->_adds);
+ $this->_adds = null;
+ }
+
+ function addOptions(?array $options): void {
+ A::merge($this->_options, $this->buildOptions($options));
+ $this->updateType();
+ }
+
+ function removeOptions(?array $options): void {
+ foreach ($this->buildOptions($options) as $option) {
+ unset($this->_options[$option["option"]]);
+ }
+ $this->updateType();
+ }
+
+ function removeOption(string $option): void {
+ unset($this->_options[$option]);
+ }
+
+ /** mettre à jour le type d'option */
+ protected function updateType(): void {
+ $haveShortOptions = false;
+ $haveLongOptions = false;
+ $isCommand = false;
+ $isHelp = 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:
+ $isCommand = true;
+ break;
+ }
+ switch ($option["option"]) {
+ case "--help":
+ case "--help++":
+ $isHelp = true;
+ break;
+ }
+ }
+ $this->haveShortOptions = $haveShortOptions;
+ $this->haveLongOptions = $haveLongOptions;
+ $this->isCommand = $isCommand;
+ $this->isHelp = $isHelp;
+ }
+
+ function setup2(): void {
+ $this->processArgs();
+ $this->processAction();
+ $this->afterSetup();
+ //$this->debugTrace("setup2");
+ }
+
+ /**
+ * traiter les informations concernant les arguments puis calculer les nombres
+ * minimum et maximum d'arguments que prend l'option
+ */
+ protected function processArgs(): void {
+ $args = $this->_args;
+ $haveArgs = boolval($args);
+ if ($this->isRemains) {
+ $haveArgs = true;
+ $args = [null];
+ } elseif ($args === null) {
+ $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 = [];
+ $reqs = [];
+ $haveNull = false;
+ $optArgs = null;
+ foreach ($args as $arg) {
+ if (is_string($arg)) {
+ $reqs[] = $arg;
+ $argsdesc[] = strtoupper($arg);
+ } elseif (is_array($arg)) {
+ $optArgs = $arg;
+ break;
+ } elseif ($arg === null) {
+ $haveNull = true;
+ break;
+ } else {
+ throw ArgsException::invalid("$desc: $arg", "option arg");
+ }
+ }
+
+ $opts = [];
+ $optArgsdesc = null;
+ $lastarg = "VALUE";
+ if ($optArgs !== null) {
+ $haveOpt = false;
+ foreach ($optArgs as $arg) {
+ if (is_string($arg)) {
+ $haveOpt = true;
+ $opts[] = $arg;
+ $lastarg = strtoupper($arg);
+ $optArgsdesc[] = $lastarg;
+ } elseif ($arg === null) {
+ $haveNull = true;
+ break;
+ } else {
+ throw ArgsException::invalid("$desc: $arg", "option arg");
+ }
+ }
+ 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);
+ }
+
+ 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;
+ }
+
+ protected function processAction(): void {
+ $this->ensureArray ??= $this->isRemains || $this->maxArgs > 1;
+
+ $action = $this->action;
+ $func = $this->func;
+ if ($action === null) {
+ if ($this->isCommand) $action = "--set-command";
+ elseif ($this->isRemains) $action = "--set-args";
+ elseif ($this->isHelp) $action = "--show-help";
+ elseif ($this->haveArgs) $action = "--set";
+ elseif ($this->value !== null) $action = "--set";
+ else $action = "--inc";
+ }
+ if (is_string($action) && substr($action, 0, 2) === "--") {
+ # fonction interne
+ } else {
+ $func = func::with($action);
+ $action = "--func";
+ }
+ $this->action = $action;
+ $this->func = $func;
+
+ $name = $this->name;
+ $property = $this->property;
+ $key = $this->key;
+ if ($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;
+ }
+
+ protected function afterSetup(): void {
+ $this->disabled ??= false;
+ $this->ensureArray ??= false;
+ $this->inverse ??= false;
+ if (str::del_prefix($this->help, "++")) {
+ $this->show = false;
+ }
+ }
+
+ function getOptions(): array {
+ if ($this->disabled) return [];
+ else return array_keys($this->_options);
+ }
+
+ function isEmpty(): bool {
+ return $this->disabled || !$this->_options;
+ }
+
+ function printHelp(?array $what=null): void {
+ $showDef = $what["show"] ?? $this->show;
+ if (!$showDef) return;
+
+ $prefix = $what["prefix"] ?? null;
+ if ($prefix !== null) echo $prefix;
+
+ $showOptions = $what["options"] ?? true;
+ if ($showOptions) {
+ echo " ";
+ echo implode(", ", array_keys($this->_options));
+ if ($this->haveArgs) {
+ echo " ";
+ echo $this->argsdesc;
+ }
+ echo "\n";
+ }
+
+ $showHelp = $what["help"] ?? true;
+ if ($this->help && $showHelp) {
+ echo str::indent($this->help, " ");
+ echo "\n";
+ }
+ }
+
+ function action(&$dest, $value, ?string $arg, AbstractArgsParser $parser): void {
+ if ($this->ensureArray) {
+ varray::ensure($value);
+ } elseif (is_array($value)) {
+ $count = count($value);
+ if ($count == 0) $value = null;
+ elseif ($count == 1) $value = $value[0];
+ }
+
+ switch ($this->action) {
+ case "--set": $this->actionSet($dest, $value); break;
+ case "--inc": $this->actionInc($dest); break;
+ case "--dec": $this->actionDec($dest); break;
+ case "--add": $this->actionAdd($dest, $value); break;
+ case "--adds": $this->actionAdds($dest, $value); break;
+ case "--merge": $this->actionMerge($dest, $value); break;
+ case "--merges": $this->actionMerges($dest, $value); break;
+ case "--func": $this->func->bind($dest)->invoke([$value, $arg, $this]); break;
+ case "--set-args": $this->actionSetArgs($dest, $value); break;
+ case "--set-command": $this->actionSetCommand($dest, $value); break;
+ case "--show-help": $parser->actionPrintHelp($arg); break;
+ default: throw ArgsException::invalid($this->action, "arg action");
+ }
+ }
+
+ function actionSet(&$dest, $value): void {
+ if ($this->property !== null) {
+ oprop::set($dest, $this->property, $value);
+ } elseif ($this->key !== null) {
+ akey::set($dest, $this->key, $value);
+ } elseif ($this->name !== null) {
+ valx::set($dest, $this->name, $value);
+ }
+ }
+
+ function actionInc(&$dest): void {
+ if ($this->property !== null) {
+ if ($this->inverse) oprop::dec($dest, $this->property);
+ else oprop::inc($dest, $this->property);
+ } elseif ($this->key !== null) {
+ if ($this->inverse) akey::dec($dest, $this->key);
+ else akey::inc($dest, $this->key);
+ } elseif ($this->name !== null) {
+ if ($this->inverse) valx::dec($dest, $this->name);
+ else valx::inc($dest, $this->name);
+ }
+ }
+
+ function actionDec(&$dest): void {
+ if ($this->property !== null) {
+ if ($this->inverse) oprop::inc($dest, $this->property);
+ else oprop::dec($dest, $this->property);
+ } elseif ($this->key !== null) {
+ if ($this->inverse) akey::inc($dest, $this->key);
+ else akey::dec($dest, $this->key);
+ } elseif ($this->name !== null) {
+ if ($this->inverse) valx::inc($dest, $this->name);
+ else valx::dec($dest, $this->name);
+ }
+ }
+
+ function actionAdd(&$dest, $value): void {
+ if ($this->property !== null) {
+ oprop::append($dest, $this->property, $value);
+ } elseif ($this->key !== null) {
+ akey::append($dest, $this->key, $value);
+ } elseif ($this->name !== null) {
+ valx::append($dest, $this->name, $value);
+ }
+ }
+
+ function actionAdds(&$dest, $value): void {
+ if ($this->property !== null) {
+ foreach (cl::with($value) as $value) {
+ oprop::append($dest, $this->property, $value);
+ }
+ } elseif ($this->key !== null) {
+ foreach (cl::with($value) as $value) {
+ akey::append($dest, $this->key, $value);
+ }
+ } elseif ($this->name !== null) {
+ foreach (cl::with($value) as $value) {
+ valx::append($dest, $this->name, $value);
+ }
+ }
+ }
+
+ function actionMerge(&$dest, $value): void {
+ if ($this->property !== null) {
+ oprop::merge($dest, $this->property, $value);
+ } elseif ($this->key !== null) {
+ akey::merge($dest, $this->key, $value);
+ } elseif ($this->name !== null) {
+ valx::merge($dest, $this->name, $value);
+ }
+ }
+
+ function actionMerges(&$dest, $value): void {
+ if ($this->property !== null) {
+ foreach (cl::with($value) as $value) {
+ oprop::merge($dest, $this->property, $value);
+ }
+ } elseif ($this->key !== null) {
+ foreach (cl::with($value) as $value) {
+ akey::merge($dest, $this->key, $value);
+ }
+ } elseif ($this->name !== null) {
+ foreach (cl::with($value) as $value) {
+ valx::merge($dest, $this->name, $value);
+ }
+ }
+ }
+
+ function actionSetArgs(&$dest, $value): void {
+ if ($this->property !== null) {
+ oprop::set($dest, $this->property, $value);
+ } elseif ($this->key !== null) {
+ akey::set($dest, $this->key, $value);
+ } elseif ($this->name !== null) {
+ valx::set($dest, $this->name, $value);
+ }
+ }
+
+ function actionSetCommand(&$dest, $value): void {
+ if ($this->property !== null) {
+ oprop::set($dest, $this->property, $value);
+ } elseif ($this->key !== null) {
+ akey::set($dest, $this->key, $value);
+ } elseif ($this->name !== null) {
+ valx::set($dest, $this->name, $value);
+ }
+ }
+
+ function __toString(): string {
+ $options = implode(",", $this->getOptions());
+ $args = $this->haveArgs? " ({$this->minArgs}-{$this->maxArgs})": false;
+ return "$options$args";
+ }
+ private function debugTrace(string $message): void {
+ $options = implode(",", cl::split_assoc($this->origDef)[0] ?? []);
+ echo "$options $message\n";
+ }
+}
diff --git a/src/app/cli/Aogroup.php b/src/app/cli/Aogroup.php
new file mode 100644
index 0000000..6553c5a
--- /dev/null
+++ b/src/app/cli/Aogroup.php
@@ -0,0 +1,36 @@
+all() as $aodef) {
+ $firstAodef ??= $aodef;
+ $aodef->printHelp(["help" => false]);
+ }
+ if ($firstAodef !== null) {
+ $firstAodef->printHelp(["options" => false]);
+ }
+ }
+}
diff --git a/src/app/cli/Aolist.php b/src/app/cli/Aolist.php
new file mode 100644
index 0000000..820c69f
--- /dev/null
+++ b/src/app/cli/Aolist.php
@@ -0,0 +1,268 @@
+origDefs = $defs;
+ $this->initDefs($defs, $setup);
+ }
+
+ protected array $origDefs;
+
+ protected ?array $aomain;
+ protected ?array $aosections;
+ protected ?array $aospecials;
+
+ public ?Aodef $remainsArgdef = null;
+
+ function initDefs(array $defs, bool $setup=true): void {
+ $this->mergeParse($defs, $aobjects);
+ $this->aomain = $aobjects["main"] ?? null;
+ $this->aosections = $aobjects["sections"] ?? null;
+ $this->aospecials = $aobjects["specials"] ?? null;
+ if ($setup) $this->setup();
+ }
+
+ protected function mergeParse(array $defs, ?array &$aobjects, bool $parse=true): void {
+ $aobjects ??= [];
+
+ $merges = $defs["merges"] ?? null;
+ $merge = $defs["merge"] ?? null;
+ if ($merge !== null) $merges[] = $merge;
+ if ($merges !== null) {
+ foreach ($merges as $merge) {
+ $this->mergeParse($merge, $aobjects, false);
+ $this->parse($merge, $aobjects);
+ }
+ }
+
+ if ($parse) $this->parse($defs, $aobjects);
+
+ $merge = $defs["merge_after"] ?? null;
+ if ($merge !== null) {
+ $this->mergeParse($merge, $aobjects, false);
+ $this->parse($merge, $aobjects);
+ }
+ }
+
+ protected function parse(array $defs, array &$aobjects): void {
+ [$defs, $params] = cl::split_assoc($defs);
+ if ($defs !== null) {
+ $aomain =& $aobjects["main"];
+ foreach ($defs as $def) {
+ $first = $def[0] ?? null;
+ if ($first === "group") {
+ $aobject = new Aogroup($def);
+ } else {
+ $aobject = new Aodef($def);
+ }
+ $aomain[] = $aobject;
+ }
+ }
+ $sections = $params["sections"] ?? null;
+ if ($sections !== null) {
+ $aosections =& $aobjects["sections"];
+ $index = 0;
+ foreach ($sections as $key => $section) {
+ if ($key === $index) {
+ $index++;
+ $aosections[] = new Aosection($section);
+ } else {
+ /** @var Aosection $aosection */
+ $aosection = $aosections[$key] ?? null;
+ if ($aosection === null) {
+ $aosections[$key] = new Aosection($section);
+ } else {
+ #XXX il faut implémenter la fusion en cas de section existante
+ # pour le moment, la liste existante est écrasée
+ $aosection->initDefs($section);
+ }
+ }
+ }
+ }
+ $this->parseParams($params);
+ }
+
+ protected function parseParams(?array $params): void {
+ }
+
+ function all(?array $what=null): iterable {
+ $returnsAodef = $what["aodef"] ?? true;
+ $returnsAolist = $what["aolist"] ?? false;
+ $returnExtends = $what["extends"] ?? false;
+ $withSpecials = $what["aospecials"] ?? true;
+ # lister les sections avant, pour que les options de la section principale
+ # soient prioritaires
+ $aosections = $this->aosections;
+ if ($aosections !== null) {
+ /** @var Aosection $aobject */
+ foreach ($aosections as $aosection) {
+ if ($returnsAolist) {
+ yield $aosection;
+ } elseif ($returnsAodef) {
+ yield from $aosection->all($what);
+ }
+ }
+ }
+
+ $aomain = $this->aomain;
+ if ($aomain !== null) {
+ /** @var Aodef $aobject */
+ foreach ($aomain as $aobject) {
+ if ($aobject instanceof Aodef) {
+ if ($returnsAodef) {
+ if ($returnExtends) {
+ if ($aobject->isExtends()) yield $aobject;
+ } else {
+ if (!$aobject->isExtends()) yield $aobject;
+ }
+ }
+ } elseif ($aobject instanceof Aolist) {
+ if ($returnsAolist) {
+ yield $aobject;
+ } elseif ($returnsAodef) {
+ yield from $aobject->all($what);
+ }
+ }
+ }
+ }
+
+ $aospecials = $this->aospecials;
+ if ($withSpecials && $aospecials !== null) {
+ /** @var Aodef $aobject */
+ foreach ($aospecials as $aobject) {
+ yield $aobject;
+ }
+ }
+ }
+
+ protected function filter(callable $callback): void {
+ $aomain = $this->aomain;
+ if ($aomain !== null) {
+ $filtered = [];
+ /** @var Aodef $aobject */
+ foreach ($aomain as $aobject) {
+ if ($aobject instanceof Aolist) {
+ $aobject->filter($callback);
+ }
+ if (call_user_func($callback, $aobject)) {
+ $filtered[] = $aobject;
+ }
+ }
+ $this->aomain = $filtered;
+ }
+ $aosections = $this->aosections;
+ if ($aosections !== null) {
+ $filtered = [];
+ /** @var Aosection $aosection */
+ foreach ($aosections as $aosection) {
+ $aosection->filter($callback);
+ if (call_user_func($callback, $aosection)) {
+ $filtered[] = $aosection;
+ }
+ }
+ $this->aosections = $filtered;
+ }
+ }
+
+ protected function setup(): void {
+ # calculer les options
+ foreach ($this->all() as $aodef) {
+ $aodef->setup1();
+ }
+ /** @var Aodef $aodef */
+ foreach ($this->all(["extends" => true]) as $aodef) {
+ $aodef->setup1(true, $this);
+ }
+ # ne garder que les objets non vides
+ $this->filter(function($aobject): bool {
+ if ($aobject instanceof Aodef) {
+ return !$aobject->isEmpty();
+ } elseif ($aobject instanceof Aolist) {
+ return !$aobject->isEmpty();
+ } else {
+ return false;
+ }
+ });
+ # puis calculer nombre d'arguments et actions
+ foreach ($this->all() as $aodef) {
+ $aodef->setup2();
+ }
+ }
+
+ function isEmpty(): bool {
+ foreach ($this->all() as $aobject) {
+ return false;
+ }
+ return true;
+ }
+
+ function get(string $option): ?Aodef {
+ return null;
+ }
+
+ function actionPrintHelp(string $arg): void {
+ $this->printHelp([
+ "show_all" => $arg === "--help++",
+ ]);
+ }
+
+ function printHelp(?array $what=null): void {
+ $show = $what["show_all"] ?? false;
+ if (!$show) $show = null;
+
+ $aosections = $this->aosections;
+ if ($aosections !== null) {
+ /** @var Aosection $aosection */
+ foreach ($aosections as $aosection) {
+ $aosection->printHelp(cl::merge($what, [
+ "show" => $show,
+ "prefix" => "\n",
+ ]));
+ }
+ }
+
+ $aomain = $this->aomain;
+ if ($aomain !== null) {
+ echo "\nOPTIONS\n";
+ foreach ($aomain as $aobject) {
+ $aobject->printHelp(cl::merge($what, [
+ "show" => $show,
+ ]));
+ }
+ }
+ }
+
+ function __toString(): string {
+ $items = [];
+ $what = [
+ "aodef" => true,
+ "aolist" => true,
+ ];
+ foreach ($this->all($what) as $aobject) {
+ if ($aobject instanceof Aodef) {
+ $items[] = strval($aobject);
+ } elseif ($aobject instanceof Aogroup) {
+ $items[] = implode("\n", [
+ "group",
+ str::indent(strval($aobject)),
+ ]);
+ } elseif ($aobject instanceof Aosection) {
+ $items[] = implode("\n", [
+ "section",
+ str::indent(strval($aobject)),
+ ]);
+ } else {
+ $items[] = false;
+ }
+ }
+ return implode("\n", $items);
+ }
+}
diff --git a/src/app/cli/Aosection.php b/src/app/cli/Aosection.php
new file mode 100644
index 0000000..8987f5d
--- /dev/null
+++ b/src/app/cli/Aosection.php
@@ -0,0 +1,46 @@
+show = vbool::with($params["show"] ?? true);
+ $this->prefix ??= $params["prefix"] ?? null;
+ $this->title ??= $params["title"] ?? null;
+ $this->description ??= $params["description"] ?? null;
+ $this->suffix ??= $params["suffix"] ?? null;
+ }
+
+ function printHelp(?array $what=null): void {
+ $showSection = $what["show"] ?? $this->show;
+ if (!$showSection) return;
+
+ $prefix = $what["prefix"] ?? null;
+ if ($prefix !== null) echo $prefix;
+
+ if ($this->prefix) echo "{$this->prefix}\n";
+ if ($this->title) echo "{$this->title}\n";
+ if ($this->description) echo "\n{$this->description}\n";
+ /** @var Aodef|Aolist $aobject */
+ foreach ($this->all(["aolist" => true]) as $aobject) {
+ $aobject->printHelp();
+ }
+ if ($this->suffix) echo "{$this->suffix}\n";
+ }
+}
diff --git a/src/app/cli/Application.php b/src/app/cli/Application.php
index c395887..c28129f 100644
--- a/src/app/cli/Application.php
+++ b/src/app/cli/Application.php
@@ -11,8 +11,6 @@ use nulib\output\log;
use nulib\output\msg;
use nulib\output\std\StdMessenger;
use nulib\ValueException;
-use nur\cli\ArgsException;
-use nur\cli\ArgsParser;
use nur\config;
/**
@@ -246,7 +244,7 @@ EOT);
"title" => "PROFILS D'EXECUTION",
["group",
["-p", "--profile", "--app-profile",
- "args" => 1, "argsdesc" => "PROFILE",
+ "args" => "profile",
"action" => [app::class, "set_profile"],
"help" => "spécifier le profil d'exécution",
],
@@ -261,7 +259,7 @@ EOT);
"show" => false,
["group",
["--verbosity",
- "args" => 1, "argsdesc" => "silent|quiet|verbose|debug",
+ "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug",
"action" => [null, "set_application_verbosity"],
"help" => "spécifier le niveau d'informations affiché",
],
@@ -270,7 +268,7 @@ EOT);
["-D", "--debug", "action" => [null, "set_application_verbosity", "debug"]],
],
["-L", "--logfile",
- "args" => "file", "argsdesc" => "OUTPUT",
+ "args" => "output",
"action" => [null, "set_application_log_output"],
"help" => "Logger les messages de l'application dans le fichier spécifié",
],
@@ -342,10 +340,13 @@ EOT);
],
];
+ protected function getArgsParser(): AbstractArgsParser {
+ return new SimpleArgsParser(static::ARGS);
+ }
+
/** @throws ArgsException */
function parseArgs(array $args=null): void {
- $parser = new ArgsParser(static::ARGS);
- $parser->parse($this, $args);
+ $this->getArgsParser()->parse($this, $args);
}
const PROFILE_COLORS = [
diff --git a/src/app/cli/ArgDef.php b/src/app/cli/ArgDef.php
deleted file mode 100644
index fa508bc..0000000
--- a/src/app/cli/ArgDef.php
+++ /dev/null
@@ -1,452 +0,0 @@
-def = $def;
- $this->mergeParse($def);
- }
-
- protected array $def;
-
- protected function mergeParse(array $def): void {
- $defaults = $defs["defaults"] ?? null;
- if ($defaults !== null) $this->mergeParse($defaults);
-
- $this->parse($def);
-
- $merges = $defs["merges"] ?? null;
- $merge = $defs["merge"] ?? null;
- if ($merge !== null) $merges[] = $merge;
- if ($merges !== null) {
- foreach ($merges as $merge) {
- if ($merge !== null) $this->mergeParse($merge);
- }
- }
- }
-
- protected ?string $extends = null;
-
- function isExtends(): bool {
- return $this->extends !== null;
- }
-
- protected ?bool $disabled = null;
- protected ?array $removes = null;
- protected ?array $adds = null;
-
- protected ?array $args = null;
- public ?string $argsdesc = null;
-
- protected ?bool $ensureArray = null;
- protected ?int $action = null;
- protected ?func $func = null;
- protected ?bool $inverse = null;
- protected $value = null;
- protected ?string $name = null;
- protected ?string $property = null;
- protected ?string $key = null;
-
- public ?string $help = null;
-
- protected function parse(array $def): void {
- [$options, $params] = cl::split_assoc($def);
-
- $this->extends ??= $params["extends"] ?? null;
-
- $this->disabled = vbool::withn($params["disabled"] ?? null);
- $removes = varray::withn($params["remove"] ?? null);
- A::merge($this->removes, $removes);
- $adds = varray::withn($params["add"] ?? null);
- A::merge($this->adds, $adds);
- A::merge($this->adds, $options);
-
- $args = $params["args"] ?? null;
- $args ??= $params["arg"] ?? null;
- if ($args === true) $args = 1;
- elseif ($args === "*") $args = [null];
- elseif ($args === "+") $args = ["value", null];
- if (is_int($args)) $args = array_fill(0, $args, "value");
- $this->args ??= cl::withn($args);
-
- $this->argsdesc ??= $params["argsdesc"] ?? null;
-
- $this->ensureArray ??= $params["ensure_array"] ?? null;
- $action = $params["action"] ?? null;
- if ($action !== null) {
- $func = 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"] ?? null;
- $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 {
- if ($this->disabled) return [];
- else return array_keys($this->options);
- }
-
- function isEmpty(): bool {
- return $this->disabled || !boolval($this->options);
- }
-
- /** traiter le paramètre parent */
- function processOptions(): void {
- $this->removeOptions($this->removes);
- $this->removes = null;
- $this->addOptions($this->adds);
- $this->adds = 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]);
- }
-
- public bool $haveShortOptions = false;
- public bool $haveLongOptions = false;
- public bool $isCommand = false;
- public bool $isHelp = false;
- public bool $isRemains = false;
-
- public bool $haveArgs = false;
- public ?int $minArgs = null;
- public ?int $maxArgs = null;
-
- /** mettre à jour le type d'option */
- protected function updateType(): void {
- $haveShortOptions = false;
- $haveLongOptions = false;
- $isCommand = false;
- $isHelp = 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:
- $isCommand = true;
- break;
- }
- if ($option["option"] === "--help") $isHelp = true;
- }
- $this->haveShortOptions = $haveShortOptions;
- $this->haveLongOptions = $haveLongOptions;
- $this->isCommand = $isCommand;
- $this->isHelp = $isHelp;
- $this->isRemains = $isRemains;
- }
-
- function processExtends(ArgDefs $argDefs): void {
- $option = $this->extends;
- if ($option === null) {
- throw new ArgException("extends: missing destination arg");
- }
- $dest = $argDefs->get($option);
- if ($dest === null) {
- throw new ArgException("$option: invalid destination arg");
- }
-
- if ($this->ensureArray !== null) $dest->ensureArray = $this->ensureArray;
- if ($this->action !== null) $dest->action = $this->action;
- if ($this->func !== null) $dest->func = $this->func;
- if ($this->inverse !== null) $dest->inverse = $this->inverse;
- if ($this->value !== null) $dest->value = $this->value;
- if ($this->name !== null) $dest->name = $this->name;
- if ($this->property !== null) $dest->property = $this->property;
- if ($this->key !== null) $dest->key = $this->key;
-
- A::merge($dest->removes, $this->removes);
- A::merge($dest->adds, $this->adds);
- $dest->processOptions();
- }
-
- /**
- * 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;
- $haveArgs = boolval($args);
- if ($args === null) {
- $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);
- }
-
- 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;
- }
-
- function debugInfos(): array {
- return [
- ...$this->getOptions(),
- "empty" => $this->isEmpty(),
- //"help" => $this->help,
- //"have_short_options" => $this->haveShortOptions,
- //"have_long_options" => $this->haveLongOptions,
- //"is_command" => $this->isCommand,
- //"is_help" => $this->isHelp,
- //"is_remains" => $this->isRemains,
- //"ĥave_args" => $this->haveArgs,
- //"min_args" => $this->minArgs,
- //"max_args" => $this->maxArgs,
- //"argsdesc" => $this->argsdesc,
- ];
- }
-}
diff --git a/src/app/cli/ArgDefs.php b/src/app/cli/ArgDefs.php
deleted file mode 100644
index e41f69f..0000000
--- a/src/app/cli/ArgDefs.php
+++ /dev/null
@@ -1,75 +0,0 @@
-defs = $defs;
- $this->mergeParse($defs, $argos);
- $this->setArgos($argos);
- }
-
- protected array $defs;
-
- protected function mergeParse(array $defs, ?array &$argos, bool $parse=true): void {
- $defaults = $defs["defaults"] ?? null;
- if ($defaults !== null) {
- $this->mergeParse($defaults, $argos, false);
- $this->parse($defaults, $argos);
- }
-
- if ($parse) $this->parse($defs, $argos);
-
- $merges = $defs["merges"] ?? null;
- $merge = $defs["merge"] ?? null;
- if ($merge !== null) $merges[] = $merge;
- if ($merges !== null) {
- foreach ($merges as $merge) {
- $this->mergeParse($merge, $argos, false);
- $this->parse($merge, $argos);
- }
- }
- }
-
- protected function parse(array $defs, ?array &$argos): void {
- [$defs, $params] = cl::split_assoc($defs);
- foreach ($defs as $def) {
- $argos[] = new ArgDef($def);
- }
- $this->parseParams($params);
- }
-
- protected function parseParams(?array $params): void {
- }
-
- protected array $argos;
-
- function getArgos(): array {
- return $this->argos;
- }
-
- protected function setArgos(?array $argos): void {
- $argos ??= [];
- $this->argos = array_filter($argos, function (IArgo $argo): bool {
- return !$argo->isEmpty();
- });
- }
-
- function isEmpty(): bool {
- return !$this->getArgos();
- }
-
- function get(string $option): ?ArgDef {
- return null;
- }
-
- function debugInfos(): array {
- return array_map(function (IArgo $argo) {
- return $argo->debugInfos();
- }, $this->argos);
- }
-}
diff --git a/src/app/cli/ArgException.php b/src/app/cli/ArgException.php
deleted file mode 100644
index 2a96218..0000000
--- a/src/app/cli/ArgException.php
+++ /dev/null
@@ -1,7 +0,0 @@
-show = vbool::with($section["show"] ?? true);
- $this->prefix ??= $params["prefix"] ?? null;
- $this->title ??= $params["name"] ?? null;
- $this->description ??= $params["description"] ?? null;
- $this->suffix ??= $params["suffix"] ?? null;
- }
-}
diff --git a/src/app/cli/ArgsException.php b/src/app/cli/ArgsException.php
new file mode 100644
index 0000000..bfb818a
--- /dev/null
+++ b/src/app/cli/ArgsException.php
@@ -0,0 +1,20 @@
+prefix ??= $params["prefix"] ?? null;
+ $this->name ??= $params["name"] ?? null;
+ $this->purpose ??= $params["purpose"] ?? null;
+ $this->usage ??= $params["usage"] ?? null;
+ $this->description ??= $params["description"] ?? null;
+ $this->suffix ??= $params["suffix"] ?? null;
+
+ $this->commandname ??= $params["commandname"] ?? null;
+ $this->commandproperty ??= $params["commandproperty"] ?? null;
+ $this->commandkey ??= $params["commandkey"] ?? null;
+
+ $this->argsname ??= $params["argsname"] ?? null;
+ $this->argsproperty ??= $params["argsproperty"] ?? null;
+ $this->argskey ??= $params["argskey"] ?? null;
+
+ $this->autohelp ??= vbool::withn($params["autohelp"] ?? null);
+ $this->autoremains ??= vbool::withn($params["autoremains"] ?? null);
+ }
+
+ /** @return string[] */
+ function getOptions(): array {
+ return array_keys($this->index);
+ }
+
+ protected function indexAodefs(): void {
+ $this->index = [];
+ foreach ($this->all() as $aodef) {
+ $options = $aodef->getOptions();
+ foreach ($options as $option) {
+ /** @var Aodef $prevAodef */
+ $prevAodef = $this->index[$option] ?? null;
+ if ($prevAodef !== null) $prevAodef->removeOption($option);
+ $this->index[$option] = $aodef;
+ }
+ }
+ }
+
+ protected function setup(): void {
+ # calculer les options pour les objets déjà fusionnés
+ /** @var Aodef $aodef */
+ foreach ($this->all() as $aodef) {
+ $aodef->setup1();
+ }
+
+ # puis traiter les extensions d'objets et calculer les options pour ces
+ # objets sur la base de l'index que l'on crée une première fois
+ $this->indexAodefs();
+ /** @var Aodef $aodef */
+ foreach ($this->all(["extends" => true]) as $aodef) {
+ $aodef->setup1(true, $this);
+ }
+
+ # ne garder que les objets non vides
+ $this->filter(function($aobject) {
+ if ($aobject instanceof Aodef) {
+ return !$aobject->isEmpty();
+ } elseif ($aobject instanceof Aolist) {
+ return !$aobject->isEmpty();
+ } else {
+ return false;
+ }
+ });
+
+ # rajouter remains et help si nécessaire
+ $this->aospecials = [];
+ $helpArgdef = null;
+ $remainsArgdef = null;
+ /** @var Aodef $aodef */
+ foreach ($this->all() as $aodef) {
+ if ($aodef->isHelp) $helpArgdef = $aodef;
+ if ($aodef->isRemains) $remainsArgdef = $aodef;
+ }
+
+ $this->autohelp ??= true;
+ if ($helpArgdef === null && $this->autohelp) {
+ $helpArgdef = new Aodef([
+ "--help", "--help++",
+ "action" => "--show-help",
+ "help" => "Afficher l'aide",
+ ]);
+ $helpArgdef->setup1();
+ }
+ if ($helpArgdef !== null) $this->aospecials[] = $helpArgdef;
+
+ $this->autoremains ??= true;
+ if ($remainsArgdef === null && $this->autoremains) {
+ $remainsArgdef = new Aodef([
+ "args" => [null],
+ "action" => "--set-args",
+ "name" => $this->argsname ?? "args",
+ "property" => $this->argsproperty,
+ "key" => $this->argskey,
+ ]);
+ $remainsArgdef->setup1();
+ }
+ if ($remainsArgdef !== null) {
+ $this->remainsArgdef = $remainsArgdef;
+ $this->aospecials[] = $remainsArgdef;
+ }
+
+ # puis calculer nombre d'arguments et actions
+ $this->indexAodefs();
+ /** @var Aodef $aodef */
+ foreach ($this->all() as $aodef) {
+ $aodef->setup2();
+ }
+ }
+
+ function get(string $option): ?Aodef {
+ return $this->index[$option] ?? null;
+ }
+
+ function printHelp(?array $what = null): void {
+ $showList = $what["show"] ?? true;
+ if (!$showList) return;
+
+ $prefix = $what["prefix"] ?? null;
+ if ($prefix !== null) echo $prefix;
+
+ if ($this->prefix) echo "{$this->prefix}\n";
+ if ($this->purpose) {
+ echo "{$this->name}: {$this->purpose}\n";
+ } elseif (!$this->prefix) {
+ # s'il y a un préfixe sans purpose, il remplace purpose
+ echo "{$this->name}\n";
+ }
+ if ($this->usage) {
+ echo "\nUSAGE\n";
+ foreach (cl::with($this->usage) as $usage) {
+ echo " {$this->name} $usage\n";
+ }
+ }
+ if ($this->description) echo "\n{$this->description}\n";
+ parent::printHelp($what);
+ if ($this->suffix) echo "{$this->suffix}\n";
+ }
+
+ function __toString(): string {
+ return implode("\n", [
+ "objects:",
+ str::indent(parent::__toString()),
+ "index:",
+ str::indent(implode("\n", array_keys($this->index))),
+ ]);
+ }
+}
diff --git a/src/app/cli/SimpleArgDefs.php b/src/app/cli/SimpleArgDefs.php
deleted file mode 100644
index e384081..0000000
--- a/src/app/cli/SimpleArgDefs.php
+++ /dev/null
@@ -1,135 +0,0 @@
-prefix ??= $params["prefix"] ?? null;
- $this->name ??= $params["name"] ?? null;
- $this->purpose ??= $params["purpose"] ?? null;
- $this->usage ??= $params["usage"] ?? null;
- $this->description ??= $params["description"] ?? null;
- $this->suffix ??= $params["suffix"] ?? null;
-
- $this->commandname ??= $params["commandname"] ?? null;
- $this->commandproperty ??= $params["commandproperty"] ?? null;
- $this->commandkey ??= $params["commandkey"] ?? null;
-
- $this->argsname ??= $params["argsname"] ?? null;
- $this->argsproperty ??= $params["argsproperty"] ?? null;
- $this->argskey ??= $params["argskey"] ?? null;
-
- $this->autohelp ??= vbool::withn($params["autohelp"] ?? null);
- $this->autoremains ??= vbool::withn($params["autoremains"] ?? null);
- }
-
- protected array $index;
-
- /** @return string[] */
- function getOptions(): array {
- return array_keys($this->index);
- }
-
- protected function setArgos(?array $argos): void {
- $argos ??= [];
-
- # calculer les options pour les objets déjà fusionnés puis indexer une
- # première fois
- foreach ($argos as $argo) {
- /** @var ArgDef $argo */
- if (!$argo->isExtends()) {
- $argo->processOptions();
- }
- }
- $this->index = [];
- foreach ($argos as $argo) {
- if ($argo->isExtends()) continue;
- $options = $argo->getOptions();
- foreach ($options as $option) {
- /** @var ArgDef $prevArgo */
- $prevArgo = $this->index[$option] ?? null;
- if ($prevArgo !== null) $prevArgo->removeOption($option);
- $this->index[$option] = $argo;
- }
- }
-
- # puis traiter les extensions d'objets, calculer les options pour ces
- # objets et indexer pour la deuxième fois
- foreach ($argos as $argo) {
- /** @var ArgDef $argo */
- if ($argo->isExtends()) {
- $argo->processExtends($this);
- }
- }
- $this->index = [];
- foreach ($argos as $argo) {
- if ($argo->isExtends()) continue;
- $options = $argo->getOptions();
- foreach ($options as $option) {
- /** @var ArgDef $prevArgo */
- $prevArgo = $this->index[$option] ?? null;
- if ($prevArgo !== null) $prevArgo->removeOption($option);
- $this->index[$option] = $argo;
- }
- }
-
- # ne garder que les objets non vides
- $argos = array_filter($argos, function(IArgo $argo): bool {
- return !$argo->isEmpty();
- });
-
- # puis calculer nombre d'arguments et actions
- foreach ($argos as $argo) {
- if ($argo->isExtends()) continue;
- $argo->processArgs();
- $argo->processAction();
- }
-
- $this->argos = $argos;
- }
-
- function get(string $option): ?ArgDef {
- return $this->index[$option] ?? null;
- }
-
- function debugInfos(): array {
- return [
- "argos" => array_map(function (IArgo $argo) {
- return $argo->debugInfos();
- }, $this->argos),
- "index" => array_map(function (IArgo $argo) {
- return $argo->debugInfos();
- }, $this->index),
- ];
- }
-}
diff --git a/src/app/cli/SimpleArgsParser.php b/src/app/cli/SimpleArgsParser.php
index abcc9cb..2141a16 100644
--- a/src/app/cli/SimpleArgsParser.php
+++ b/src/app/cli/SimpleArgsParser.php
@@ -1,21 +1,25 @@
argDefs = new SimpleArgDefs($defs);
+ global $argv;
+ $defs["name"] ??= basename($argv[0]);
+ $this->aolist = new SimpleAolist($defs);
}
- protected SimpleArgDefs $argDefs;
+ protected SimpleAolist $aolist;
- protected function getArgDef(string $option): ?ArgDef {
- return $this->argDefs->get($option);
+ protected function getArgdef(string $option): ?Aodef {
+ return $this->aolist->get($option);
}
protected function getOptions(): array {
- return $this->argDefs->getOptions();
+ return $this->aolist->getOptions();
}
function normalize(array $args): array {
@@ -50,8 +54,8 @@ class SimpleArgsParser extends AbstractArgsParser {
$option = $arg;
$value = null;
}
- $argDef = $this->getArgDef($option);
- if ($argDef === null) {
+ $argdef = $this->getArgdef($option);
+ if ($argdef === null) {
# chercher une correspondance
$len = strlen($option);
$candidates = [];
@@ -61,21 +65,16 @@ class SimpleArgsParser extends AbstractArgsParser {
}
}
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)");
+ case 0: throw $this->invalidArg($option);
+ case 1: $option = $candidates[0]; break;
+ default: throw $this->ambiguousArg($option, $candidates);
}
- $argDef = $this->getArgDef($option);
+ $argdef = $this->getArgdef($option);
}
- if ($argDef->haveArgs) {
- $minArgs = $argDef->minArgs;
- $maxArgs = $argDef->maxArgs;
+ if ($argdef->haveArgs) {
+ $minArgs = $argdef->minArgs;
+ $maxArgs = $argdef->maxArgs;
$values = [];
if ($value !== null) {
$values[] = $value;
@@ -87,7 +86,7 @@ class SimpleArgsParser extends AbstractArgsParser {
} else {
$offset = 0;
}
- $this->check_missing($option,
+ $this->checkEnoughArgs($option,
self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true));
if ($minArgs == 0 && $maxArgs == 1) {
@@ -103,7 +102,7 @@ class SimpleArgsParser extends AbstractArgsParser {
}
$options = array_merge($options, $values);
} elseif ($value !== null) {
- throw new ArgException("$option: cette option ne prend pas d'arguments");
+ throw $this->tooManyArgs(1, 0, $option);
} else {
$options[] = $option;
}
@@ -115,13 +114,11 @@ class SimpleArgsParser extends AbstractArgsParser {
$len = strlen($arg);
while ($pos < $len) {
$option = "-".substr($arg, $pos, 1);
- $argDef = $this->getArgDef($option);
- if ($argDef === null) {
- throw new ArgException("$option: option invalide");
- }
- if ($argDef->haveArgs) {
- $minArgs = $argDef->minArgs;
- $maxArgs = $argDef->maxArgs;
+ $argdef = $this->getArgdef($option);
+ if ($argdef === null) throw $this->invalidArg($option);
+ if ($argdef->haveArgs) {
+ $minArgs = $argdef->minArgs;
+ $maxArgs = $argdef->maxArgs;
$values = [];
if ($len > $pos + 1) {
$values[] = substr($arg, $pos + 1);
@@ -134,7 +131,7 @@ class SimpleArgsParser extends AbstractArgsParser {
} else {
$offset = 0;
}
- $this->check_missing($option,
+ $this->checkEnoughArgs($option,
self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true));
if ($minArgs == 0 && $maxArgs == 1) {
@@ -166,5 +163,85 @@ class SimpleArgsParser extends AbstractArgsParser {
}
function process(array $args) {
+ $i = 0;
+ $max = count($args);
+ # d'abord traiter les options
+ while ($i < $max) {
+ $arg = $args[$i++];
+ if ($arg === "--") {
+ # fin des options
+ break;
+ }
+
+ if (preg_match('/^(--[^=]+)(?:=(.*))?/', $arg, $ms)) {
+ # option longue
+ } elseif (preg_match('/^(-.)(.+)?/', $arg, $ms)) {
+ # option courte
+ } else {
+ # commande
+ throw StateException::unexpected_state("commands are not supported");
+ }
+ $option = $ms[1];
+ $ovalue = $ms[2] ?? null;
+ $argdef = $this->getArgdef($option);
+ if ($argdef === null) throw StateException::unexpected_state();
+ $defvalue = $argdef->value;
+ if ($argdef->haveArgs) {
+ $minArgs = $argdef->minArgs;
+ $maxArgs = $argdef->maxArgs;
+ if ($minArgs == 0 && $maxArgs == 1) {
+ # argument facultatif
+ if ($ovalue !== null) $value = [$ovalue];
+ else $value = cl::with($defvalue);
+ $offset = 1;
+ } else {
+ $value = [];
+ $offset = 0;
+ }
+ self::consume_args($args, $i, $value, $offset, $minArgs, $maxArgs, false);
+ } else {
+ $value = $defvalue;
+ }
+
+ $this->action($value, $arg, $argdef);
+ }
+
+ # construire la liste des arguments qui restent
+ $args = array_slice($args, $i);
+ $i = 0;
+ $max = count($args);
+ $argdef = $this->aolist->remainsArgdef;
+ if ($argdef !== null && $argdef->haveArgs) {
+ $minArgs = $argdef->minArgs;
+ $maxArgs = $argdef->maxArgs;
+ if ($maxArgs == PHP_INT_MAX) {
+ # cas particulier: si le nombre d'arguments restants est non borné,
+ # les prendre tous sans distinction ni traitement de '--'
+ $value = $args;
+ # mais tester tout de même s'il y a le minimum requis d'arguments
+ $this->checkEnoughArgs(null, $minArgs - $max);
+ } else {
+ $value = [];
+ $this->checkEnoughArgs(null,
+ self::consume_args($args, $i, $value, 0, $minArgs, $maxArgs, false));
+ if ($i <= $max - 1) throw $this->tooManyArgs($max, $i);
+ }
+ $this->action($value, null, $argdef);
+ } elseif ($i <= $max - 1) {
+ throw $this->tooManyArgs($max, $i);
+ }
+ }
+
+ function action($value, ?string $arg, Aodef $argdef) {
+ $argdef->action($this->dest, $value, $arg, $this);
+ }
+
+ public function actionPrintHelp(string $arg): void {
+ $this->aolist->actionPrintHelp($arg);
+ throw new ExitError(0);
+ }
+
+ function showDebugInfos() {
+ echo $this->aolist."\n"; #XXX
}
}
diff --git a/src/app/cli/TODO.md b/src/app/cli/TODO.md
index 7f3a2c1..c97a85d 100644
--- a/src/app/cli/TODO.md
+++ b/src/app/cli/TODO.md
@@ -1,24 +1,20 @@
# cli
-* [ ] implémenter les arguments avancés avec le préfixe "++" sur la description
-* [x] pour le nombre d'arguments, supporter l'alias `*` pour `0..N` et `+` pour `1..N`
* [ ] transformer un schéma en définition d'arguments, un tableau en liste d'arguments, et vice-versa
+* [ ] faire une implémentation ArgsParser qui supporte les commandes, et les options dynamiques
+ * commandes:
+ `program [options] command [options]`
+ * multi-commandes:
+ `program [options] command [options] // command [options] // ...`
+ * dynamique: la liste des options et des commandes supportées est calculée dynamiquement
-faire une implémentation SimpleArgsParser qui ne supporte pas les commandes, uniquement les options
+## support des commandes
-puis faire une implémentation ArgsParser qui supporte les commandes, et les options dynamiques
+faire une interface Runnable qui représente un composant pouvant être exécuté.
+Application implémente Runnable, mais l'analyse des arguments peut retourner une
+autre instance de runnable pour faciliter l'implémentation de différents
+sous-outils
-documenter que dans les cas simples, on peut tout simplement refaire la définition, e.g
-~~~php
-[
- ["-x", "help" => "first"],
- ["-x", "help" => "second"],
-]
-~~~
-
-ajouter le support des sections, la section par défaut ayant la clé `0` (c'est
-la première section définie implicitement)
-
-ajouter le support des groupes
+## BUGS
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
\ No newline at end of file
diff --git a/src/schema/TODO.md b/src/schema/TODO.md
index b6d7505..038d6c0 100644
--- a/src/schema/TODO.md
+++ b/src/schema/TODO.md
@@ -1,5 +1,14 @@
# nulib\schema
+* deux modes d'opération pour les schéma
+ * check -- vérifier simplement si les valeurs sont dans le bon type
+ * ça ne fonctionne que pour les types scalaires standard
+ * autoconv pour les cas simples, e.g pour le type int "42" devient 42
+ * verifix -- convertir les valeurs au bon type
+l'idée générale est de gagner du temps si on veut juste vérifier par exemple
+les valeurs provenant d'une base de données, mais il faut voir si ça change
+vraiment quelque chose
+
* rajouter l'attribut "size" pour spécifier la taille maximale des valeurs
* cela pourrait servir pour générer automatiquement des tables SQL
* ou pour modéliser un schéma FSV
diff --git a/tbin/test-application.php b/tbin/test-application.php
new file mode 100755
index 0000000..f85ad10
--- /dev/null
+++ b/tbin/test-application.php
@@ -0,0 +1,62 @@
+#!/usr/bin/php
+ "tester la gestion des arguments",
+ "usage" => "-A|-a|-b",
+
+ "merge" => parent::ARGS,
+ ["group",
+ ["-A:", "--seta", "args" => "int", "name" => "a",
+ "help" => "spécifier a",
+ ],
+ ["--seta10", "name" => "a", "value" => 10],
+ ["--seta20", "name" => "a", "value" => 20],
+ ],
+ ["-a", "--inca", "name" => "a",
+ "help" => "incrémenter a",
+ ],
+ ["-b", "--deca", "name" => "a", "inverse" => true,
+ "help" => "décrémenter a",
+ ],
+ ["-D::", "--override",
+ "help" => "++remplace celui de la section principale",
+ ],
+ "sections" => [
+ [
+ "title" => "Section X",
+ "show" => false,
+ ["group",
+ ["-X:", "--setx", "args" => "int", "name" => "x",
+ "help" => "spécifier x",
+ ],
+ ["--setx10", "name" => "x", "value" => 10],
+ ["--setx20", "name" => "x", "value" => 20],
+ ],
+ ["-x", "--incx", "name" => "x"],
+ ["-y", "--decx", "name" => "x", "inverse" => true],
+ ],
+ ],
+ ];
+
+ private ?int $a = null;
+ private ?int $x = null;
+ private ?string $override = null;
+
+ private ?array $args = null;
+
+ function main() {
+ msg::info([
+ "variables:",
+ "\na=", var_export($this->a, true),
+ "\nx=", var_export($this->x, true),
+ "\noverride=", var_export($this->override, true),
+ "\nargs=", var_export($this->args, true),
+ ]);
+ }
+});
diff --git a/tests/app/cli/AodefTest.php b/tests/app/cli/AodefTest.php
new file mode 100644
index 0000000..43fa16a
--- /dev/null
+++ b/tests/app/cli/AodefTest.php
@@ -0,0 +1,158 @@
+setup1();
+ $aodef->setup2();
+ #var_export($aodef->debugInfos()); #XXX
+ self::assertSame($options, $aodef->getOptions());
+ self::assertSame($haveShortOptions, $aodef->haveShortOptions, "haveShortOptions");
+ self::assertSame($haveLongOptions, $aodef->haveLongOptions, "haveLongOptions");
+ self::assertSame($isCommand, $aodef->isCommand, "isCommand");
+ self::assertSame($haveArgs, $aodef->haveArgs, "haveArgs");
+ self::assertSame($minArgs, $aodef->minArgs, "minArgs");
+ self::assertSame($maxArgs, $aodef->maxArgs, "maxArgs");
+ self::assertSame($argsdesc, $aodef->argsdesc, "argsdesc");
+ }
+
+ function testArgsNone() {
+ $aodef = new Aodef(["-o"]);
+ self::assertArg($aodef,
+ ["-o"],
+ true, false, false,
+ false, 0, 0, "");
+
+ $aodef = new Aodef(["--longo"]);
+ self::assertArg($aodef,
+ ["--longo"],
+ false, true, false,
+ false, 0, 0, "");
+
+ $aodef = new Aodef(["-o", "--longo"]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ false, 0, 0, "");
+ }
+
+ function testArgsMandatory() {
+ $aodef = new Aodef(["-o:", "--longo"]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-a:", "-b:"]);
+ self::assertArg($aodef,
+ ["-a", "-b"],
+ true, false, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-a:", "-b::"]);
+ self::assertArg($aodef,
+ ["-a", "-b"],
+ true, false, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-a::", "-b:"]);
+ self::assertArg($aodef,
+ ["-a", "-b"],
+ true, false, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => true]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => 1]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => "value"]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, 1, "VALUE");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => ["value"]]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, 1, "VALUE");
+ }
+
+ function testArgsOptional() {
+ $aodef = new Aodef(["-o::", "--longo"]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 0, 1, "[VALUE]");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => [["value"]]]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 0, 1, "[VALUE]");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => [[null]]]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 0, PHP_INT_MAX, "[VALUEs...]");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => ["value", null]]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, PHP_INT_MAX, "VALUE [VALUEs...]");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => "*"]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 0, PHP_INT_MAX, "[VALUEs...]");
+
+ $aodef = new Aodef(["-o", "--longo", "args" => "+"]);
+ self::assertArg($aodef,
+ ["-o", "--longo"],
+ true, true, false,
+ true, 1, PHP_INT_MAX, "VALUE [VALUEs...]");
+ }
+
+ function testMerge() {
+ $BASE = ["-o:", "--longo"];
+
+ $aodef = new Aodef([
+ "merge" => $BASE,
+ "add" => ["-a", "--longa"],
+ "remove" => ["-o", "--longo"],
+ ]);
+ self::assertArg($aodef,
+ ["-a", "--longa"],
+ true, true, false,
+ false, 0, 0, "");
+
+ $aodef = new Aodef([
+ "merge" => $BASE,
+ "add" => ["-a", "--longa"],
+ "remove" => ["-o", "--longo"],
+ "-x",
+ ]);
+ self::assertArg($aodef,
+ ["-a", "--longa", "-x"],
+ true, true, false,
+ false, 0, 0, "");
+ }
+}
diff --git a/tests/app/cli/AolistTest.php b/tests/app/cli/AolistTest.php
new file mode 100644
index 0000000..863edc4
--- /dev/null
+++ b/tests/app/cli/AolistTest.php
@@ -0,0 +1,60 @@
+ "value",
+ ["--opt"],
+ ["group",
+ ["--gopt1"],
+ ["--gopt2"],
+ ],
+ "sections" => [
+ [
+ ["--s0opt"],
+ ["group",
+ ["--s0gopt1"],
+ ["--s0gopt2"],
+ ],
+ ],
+ "ns" => [
+ ["--nsopt"],
+ ["group",
+ ["--nsgopt1"],
+ ["--nsgopt2"],
+ ],
+ ],
+ ],
+ ]) extends Aolist {};
+
+ echo "$aolist\n";
+ self::assertTrue(true);
+ }
+}
diff --git a/tests/app/cli/ArgDefTest.php b/tests/app/cli/ArgDefTest.php
deleted file mode 100644
index d84a787..0000000
--- a/tests/app/cli/ArgDefTest.php
+++ /dev/null
@@ -1,211 +0,0 @@
-processOptions();
- $argDef->processArgs();
- $argDef->processAction();
- var_export($argDef->debugInfos()); #XXX
- self::assertSame($options, $argDef->getOptions());
- self::assertSame($haveShortOptions, $argDef->haveShortOptions, "haveShortOptions");
- self::assertSame($haveLongOptions, $argDef->haveLongOptions, "haveLongOptions");
- self::assertSame($isCommand, $argDef->isCommand, "isCommand");
- 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 testArgsNone() {
- $argDef = new ArgDef(["-o"]);
- self::assertArg($argDef,
- ["-o"],
- true, false, false,
- false, 0, 0, "");
-
- $argDef = new ArgDef(["--longo"]);
- self::assertArg($argDef,
- ["--longo"],
- false, true, false,
- false, 0, 0, "");
-
- $argDef = new ArgDef(["-o", "--longo"]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- false, 0, 0, "");
- }
-
- function testArgsMandatory() {
- $argDef = new ArgDef(["-o:", "--longo"]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-a:", "-b:"]);
- self::assertArg($argDef,
- ["-a", "-b"],
- true, false, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-a:", "-b::"]);
- self::assertArg($argDef,
- ["-a", "-b"],
- true, false, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-a::", "-b:"]);
- self::assertArg($argDef,
- ["-a", "-b"],
- true, false, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => true]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => 1]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => "value"]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, 1, "VALUE");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => ["value"]]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, 1, "VALUE");
- }
-
- function testArgsOptional() {
- $argDef = new ArgDef(["-o::", "--longo"]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 0, 1, "[VALUE]");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => [["value"]]]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 0, 1, "[VALUE]");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => [[null]]]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 0, PHP_INT_MAX, "[VALUEs...]");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => ["value", null]]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, PHP_INT_MAX, "VALUE [VALUEs...]");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => "*"]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 0, PHP_INT_MAX, "[VALUEs...]");
-
- $argDef = new ArgDef(["-o", "--longo", "args" => "+"]);
- self::assertArg($argDef,
- ["-o", "--longo"],
- true, true, false,
- true, 1, PHP_INT_MAX, "VALUE [VALUEs...]");
- }
-
- function testMerge() {
- $BASE = ["-o:", "--longo"];
-
- $argDef = new ArgDef([
- "merge" => $BASE,
- "add" => ["-a", "--longa"],
- "remove" => ["-o", "--longo"],
- ]);
- self::assertArg($argDef,
- ["-a", "--longa"],
- true, true, false,
- false, 0, 0, "");
-
- $argDef = new ArgDef([
- "merge" => $BASE,
- "add" => ["-a", "--longa"],
- "remove" => ["-o", "--longo"],
- "-x",
- ]);
- self::assertArg($argDef,
- ["-a", "--longa", "-x"],
- true, true, false,
- false, 0, 0, "");
- }
-
- function testOverride() {
- $argDefs = new SimpleArgDefs([
- ["-o", "--longx"],
- "merge" => [
- ["-o", "--longo"],
- ],
- ]);
- var_export($argDefs->debugInfos()); #XXX
-
- $argDefs = new SimpleArgDefs([
- ["-o", "--longo"],
- ["-o", "--longx"],
- ]);
- var_export($argDefs->debugInfos()); #XXX
-
- $argDefs = new SimpleArgDefs([
- ["-o", "--longo"],
- ["-o"],
- ["--longo"],
- ]);
- var_export($argDefs->debugInfos()); #XXX
-
- self::assertTrue(true);
- }
-
- function testExtends() {
- $ARGS1 = [
- ["-o:", "--longo",
- "name" => "desto",
- "help" => "help longo"
- ],
- ["-a:", "--longa",
- "name" => "desta",
- "help" => "help longa"
- ],
- ];
- $ARGS2 = [
- "merge" => $ARGS1,
- ["extends" => "-a",
- "remove" => ["--longa"],
- "add" => ["--desta"],
- "help" => "help desta"
- ],
- ];
- //$argDefs1 = new SimpleArgDefs($ARGS1);
- //var_export($argDefs1->debugInfos()); #XXX
- $argDefs2 = new SimpleArgDefs($ARGS2);
- var_export($argDefs2->debugInfos()); #XXX
-
- self::assertTrue(true);
- }
-}
diff --git a/tests/app/cli/SimpleAolistTest.php b/tests/app/cli/SimpleAolistTest.php
new file mode 100644
index 0000000..ed67a79
--- /dev/null
+++ b/tests/app/cli/SimpleAolistTest.php
@@ -0,0 +1,58 @@
+ [
+ ["-o", "--longo"],
+ ],
+ ]);
+ echo "$aolist\n"; #XXX
+
+ $aolist = new SimpleAolist([
+ ["-o", "--longo"],
+ ["-o", "--longx"],
+ ]);
+ echo "$aolist\n"; #XXX
+
+ $aolist = new SimpleAolist([
+ ["-o", "--longo"],
+ ["-o"],
+ ["--longo"],
+ ]);
+ echo "$aolist\n"; #XXX
+
+ self::assertTrue(true);
+ }
+
+ function testExtends() {
+ $ARGS0 = [
+ ["-o:", "--longo",
+ "name" => "desto",
+ "help" => "help longo"
+ ],
+ ["-a:", "--longa",
+ "name" => "desta",
+ "help" => "help longa"
+ ],
+ ];
+ $ARGS = [
+ "merge" => $ARGS0,
+ ["extends" => "-a",
+ "remove" => ["--longa"],
+ "add" => ["--desta"],
+ "help" => "help desta"
+ ],
+ ];
+ //$aolist0 = new SimpleArgDefs($ARGS0);
+ //echo "$aolist0\n"; #XXX
+ $aolist = new SimpleAolist($ARGS);
+ echo "$aolist\n"; #XXX
+
+ self::assertTrue(true);
+ }
+}
diff --git a/tests/app/cli/SimpleArgDefsTest.php b/tests/app/cli/SimpleArgDefsTest.php
deleted file mode 100644
index 50d0a41..0000000
--- a/tests/app/cli/SimpleArgDefsTest.php
+++ /dev/null
@@ -1,31 +0,0 @@
-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"]));
- }
-}
diff --git a/tests/app/cli/SimpleArgsParserTest.php b/tests/app/cli/SimpleArgsParserTest.php
new file mode 100644
index 0000000..aa11c56
--- /dev/null
+++ b/tests/app/cli/SimpleArgsParserTest.php
@@ -0,0 +1,174 @@
+ [["value", "value"]]],
+ ["--mo12:", "args" => ["value", ["value"]]],
+ ["--mo22:", "args" => ["value", "value"]],
+ ];
+ const NORMALIZE_TESTS = [
+ [], ["--"],
+ ["--"], ["--"],
+ ["--", "--"], ["--", "--"],
+ ["-aa"], ["-a", "-a", "--"],
+ ["a", "b"], ["--", "a", "b"],
+ ["-a", "--ma", "x", "a", "--ma=y", "b"], ["-a", "--mandatory", "x", "--mandatory", "y", "--", "a", "b"],
+ ["-mx", "-m", "y"], ["-m", "x", "-m", "y", "--"],
+ ["-ox", "-o", "y"], ["-ox", "-o", "--", "y"],
+ ["-a", "--", "-a", "-c"], ["-a", "--", "-a", "-c"],
+
+ # -a et -b doivent être considérés comme arguments, -n comme option
+ ["--mo02"], ["--mo02", "--", "--"],
+ ["--mo02", "-a"], ["--mo02", "-a", "--", "--"],
+ ["--mo02", "--"], ["--mo02", "--", "--"],
+ ["--mo02", "--", "-n"], ["--mo02", "--", "-n", "--"],
+ ["--mo02", "--", "--", "-b"], ["--mo02", "--", "--", "-b"],
+ #
+ ["--mo02", "-a"], ["--mo02", "-a", "--", "--"],
+ ["--mo02", "-a", "-a"], ["--mo02", "-a", "-a", "--"],
+ ["--mo02", "-a", "--"], ["--mo02", "-a", "--", "--"],
+ ["--mo02", "-a", "--", "-n"], ["--mo02", "-a", "--", "-n", "--"],
+ ["--mo02", "-a", "--", "--", "-b"], ["--mo02", "-a", "--", "--", "-b"],
+
+ [
+ "--mo02", "--",
+ "--mo02", "x", "--",
+ "--mo02", "x", "y",
+ "--mo12", "x", "--",
+ "--mo12", "x", "y",
+ "--mo22", "x", "y",
+ "z",
+ ], [
+ "--mo02", "--",
+ "--mo02", "x", "--",
+ "--mo02", "x", "y",
+ "--mo12", "x", "--",
+ "--mo12", "x", "y",
+ "--mo22", "x", "y",
+ "--",
+ "z",
+ ],
+ ];
+
+ function testNormalize() {
+ $parser = new SimpleArgsParser(self::NORMALIZE_ARGS);
+ $count = count(self::NORMALIZE_TESTS);
+ for ($i = 0; $i < $count; $i += 2) {
+ $args = self::NORMALIZE_TESTS[$i];
+ $expected = self::NORMALIZE_TESTS[$i + 1];
+ $normalized = $parser->normalize($args);
+ self::assertSame($expected, $normalized
+ , "for ".var_export($args, true)
+ .", normalized is ".var_export($normalized, true)
+ );
+ }
+ }
+
+ function testArgsNone() {
+ $parser = new SimpleArgsParser([
+ ["-z"],
+ ["-a"],
+ ["-b"],
+ ["-c",],
+ ["-d", "value" => 42],
+ ]);
+
+ $dest = []; $parser->parse($dest, ["-a", "-bb", "-ccc", "-dddd"]);
+ self::assertSame(null, $dest["z"] ?? null);
+ self::assertSame(1, $dest["a"] ?? null);
+ self::assertSame(2, $dest["b"] ?? null);
+ self::assertSame(3, $dest["c"] ?? null);
+ self::assertSame(42, $dest["d"] ?? null);
+
+ self::assertTrue(true);
+ }
+
+ function testArgsMandatory() {
+ $parser = new SimpleArgsParser([
+ ["-z:"],
+ ["-a:"],
+ ["-b:"],
+ ["-c:", "value" => 42],
+ ]);
+
+ $dest = []; $parser->parse($dest, [
+ "-a",
+ "-bb",
+ "-c",
+ "-c15",
+ "-c30",
+ "-c45",
+ ]);
+ self::assertSame(null, $dest["z"] ?? null);
+ self::assertSame("-bb", $dest["a"] ?? null);
+ self::assertSame(null, $dest["b"] ?? null);
+ self::assertSame("45", $dest["c"] ?? null);
+
+ self::assertTrue(true);
+ }
+
+ function testArgsOptional() {
+ $parser = new SimpleArgsParser([
+ ["-z::"],
+ ["-a::"],
+ ["-b::"],
+ ["-c::", "value" => 42],
+ ["-d::", "value" => 42],
+ ]);
+
+ $dest = []; $parser->parse($dest, [
+ "-a",
+ "-bb",
+ "-c",
+ "-d15",
+ "-d30",
+ ]);
+ self::assertSame(null, $dest["z"] ?? null);
+ self::assertSame(null, $dest["a"] ?? null);
+ self::assertSame("b", $dest["b"] ?? null);
+ self::assertSame(42, $dest["c"] ?? null);
+ self::assertSame("30", $dest["d"] ?? null);
+
+ self::assertTrue(true);
+ }
+
+ function testRemains() {
+ $parser = new SimpleArgsParser([]);
+ $dest = []; $parser->parse($dest, ["x", "y"]);
+ self::assertSame(["x", "y"], $dest["args"] ?? null);
+ }
+
+ function test() {
+ $parser = new SimpleArgsParser([
+ ["-n", "--none"],
+ ["-m:", "--mandatory"],
+ ["-o::", "--optional"],
+ ["--mo02:", "args" => [["value", "value"]]],
+ ["--mo12:", "args" => ["value", ["value"]]],
+ ["--mo22:", "args" => ["value", "value"]],
+ ]);
+ $parser->parse($dest, [
+ "--mo02", "--",
+ "--mo02", "x", "--",
+ "--mo02", "x", "y",
+ "--mo12", "x", "--",
+ "--mo12", "x", "y",
+ "--mo22", "x", "y",
+ "z",
+ ]);
+
+ self::assertTrue(true);
+ }
+}