diff --git a/src/app/cli/Arg.php b/src/app/cli/Arg.php deleted file mode 100644 index 4a7dd9c..0000000 --- a/src/app/cli/Arg.php +++ /dev/null @@ -1,194 +0,0 @@ -_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; - } -} diff --git a/src/app/cli/ArgDef.php b/src/app/cli/ArgDef.php new file mode 100644 index 0000000..8674e56 --- /dev/null +++ b/src/app/cli/ArgDef.php @@ -0,0 +1,369 @@ +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; + } +} diff --git a/src/app/cli/ArgDefs.php b/src/app/cli/ArgDefs.php new file mode 100644 index 0000000..0c19bf8 --- /dev/null +++ b/src/app/cli/ArgDefs.php @@ -0,0 +1,245 @@ +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); + } +} diff --git a/tests/app/cli/ArgDefTest.php b/tests/app/cli/ArgDefTest.php new file mode 100644 index 0000000..d420572 --- /dev/null +++ b/tests/app/cli/ArgDefTest.php @@ -0,0 +1,72 @@ +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, ""); + } +} diff --git a/tests/app/cli/ArgDefsTest.php b/tests/app/cli/ArgDefsTest.php new file mode 100644 index 0000000..94cb19b --- /dev/null +++ b/tests/app/cli/ArgDefsTest.php @@ -0,0 +1,31 @@ +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/ArgTest.php b/tests/app/cli/ArgTest.php deleted file mode 100644 index 597440a..0000000 --- a/tests/app/cli/ArgTest.php +++ /dev/null @@ -1,48 +0,0 @@ -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); - } -}