implémenter ArgsParser
This commit is contained in:
parent
2efb0687f1
commit
0a9a39a240
16
.idea/php-docker-settings.xml
generated
16
.idea/php-docker-settings.xml
generated
@ -18,6 +18,22 @@
|
||||
</DockerContainerSettings>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="38915385-b3ff-4f4b-8a9a-d5f3ecae559e">
|
||||
<value>
|
||||
<DockerContainerSettings>
|
||||
<option name="runCliOptions" value="" />
|
||||
<option name="version" value="1" />
|
||||
<option name="volumeBindings">
|
||||
<list>
|
||||
<DockerVolumeBindingImpl>
|
||||
<option name="containerPath" value="/opt" />
|
||||
<option name="hostPath" value="$PROJECT_DIR$/.." />
|
||||
</DockerVolumeBindingImpl>
|
||||
</list>
|
||||
</option>
|
||||
</DockerContainerSettings>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="c4cf2564-ed91-488c-a93d-fe2daeae80db">
|
||||
<value>
|
||||
<DockerContainerSettings>
|
||||
|
97
.idea/php.xml
generated
97
.idea/php.xml
generated
@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetector">
|
||||
<phpmd_settings>
|
||||
<phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" />
|
||||
</phpmd_settings>
|
||||
</component>
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
@ -17,60 +22,54 @@
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/phpss" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
|
||||
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
|
||||
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/markbaker/complex" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
|
||||
<path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
|
||||
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
|
||||
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
|
||||
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/tests" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
|
||||
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
|
||||
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
|
||||
<path value="$PROJECT_DIR$/vendor/markbaker/complex" />
|
||||
<path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
|
||||
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
|
||||
<path value="$PROJECT_DIR$/vendor/myclabs/php-enum" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/php" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/base" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/php" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/phpss" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/tests" />
|
||||
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
|
||||
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
|
||||
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
|
||||
|
10
.idea/remote-mappings.xml
generated
Normal file
10
.idea/remote-mappings.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteMappingsManager">
|
||||
<list>
|
||||
<list>
|
||||
<remote-mappings server-id="php@38915385-b3ff-4f4b-8a9a-d5f3ecae559e" />
|
||||
</list>
|
||||
</list>
|
||||
</component>
|
||||
</project>
|
@ -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;
|
||||
}
|
||||
|
620
src/app/cli/Aodef.php
Normal file
620
src/app/cli/Aodef.php
Normal file
@ -0,0 +1,620 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\php\akey;
|
||||
use nulib\php\func;
|
||||
use nulib\php\oprop;
|
||||
use nulib\php\types\varray;
|
||||
use nulib\php\types\vbool;
|
||||
use nulib\php\valx;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
* Class Aodef: une définition d'un argument
|
||||
*
|
||||
* il y a 3 temps dans l'initialisation de l'objet:
|
||||
* - constructeur: accumuler les informations
|
||||
* - setup1($extends): calculer les options effectives. $extends permet de
|
||||
* cibler les définitions qui étendent une définition existante
|
||||
* - setup2(): calculer les arguments et les actions
|
||||
*/
|
||||
class Aodef {
|
||||
const TYPE_SHORT = 0, TYPE_LONG = 1, TYPE_COMMAND = 2;
|
||||
const ARGS_NONE = 0, ARGS_MANDATORY = 1, ARGS_OPTIONAL = 2;
|
||||
|
||||
function __construct(array $def) {
|
||||
$this->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";
|
||||
}
|
||||
}
|
36
src/app/cli/Aogroup.php
Normal file
36
src/app/cli/Aogroup.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\A;
|
||||
|
||||
/**
|
||||
* Class Aogroup: groupe d'arguments fonctionnant ensemble
|
||||
*/
|
||||
class Aogroup extends Aolist {
|
||||
function __construct(array $defs, bool $setup=false) {
|
||||
$marker = A::pop($defs, 0);
|
||||
if ($marker !== "group") {
|
||||
throw ArgsException::invalid(null, "group");
|
||||
}
|
||||
# réordonner les clés numériques
|
||||
$defs = array_merge($defs);
|
||||
parent::__construct($defs, $setup);
|
||||
}
|
||||
|
||||
function printHelp(?array $what=null): void {
|
||||
$showGroup = $what["show"] ?? true;
|
||||
if (!$showGroup) return;
|
||||
|
||||
$prefix = $what["prefix"] ?? null;
|
||||
if ($prefix !== null) echo $prefix;
|
||||
|
||||
$firstAodef = null;
|
||||
foreach ($this->all() as $aodef) {
|
||||
$firstAodef ??= $aodef;
|
||||
$aodef->printHelp(["help" => false]);
|
||||
}
|
||||
if ($firstAodef !== null) {
|
||||
$firstAodef->printHelp(["options" => false]);
|
||||
}
|
||||
}
|
||||
}
|
268
src/app/cli/Aolist.php
Normal file
268
src/app/cli/Aolist.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use const true;
|
||||
|
||||
/**
|
||||
* Class Aodefs: une liste d'objets Aodef
|
||||
*/
|
||||
abstract class Aolist {
|
||||
function __construct(array $defs, bool $setup=true) {
|
||||
$this->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);
|
||||
}
|
||||
}
|
46
src/app/cli/Aosection.php
Normal file
46
src/app/cli/Aosection.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\php\types\vbool;
|
||||
|
||||
/**
|
||||
* Class Aosection: un regroupement d'arguments pour améliorer la mise en forme
|
||||
* de l'affichage de l'aide
|
||||
*/
|
||||
class Aosection extends Aolist {
|
||||
function __construct(array $defs, bool $setup=false) {
|
||||
parent::__construct($defs, $setup);
|
||||
}
|
||||
|
||||
public bool $show = true;
|
||||
public ?string $prefix = null;
|
||||
public ?string $title = null;
|
||||
public ?string $description = null;
|
||||
public ?string $suffix = null;
|
||||
|
||||
protected function parseParams(?array $params): void {
|
||||
$this->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";
|
||||
}
|
||||
}
|
@ -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 = [
|
||||
|
@ -1,452 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\php\func;
|
||||
use nulib\php\types\varray;
|
||||
use nulib\php\types\vbool;
|
||||
|
||||
class ArgDef implements IArgo {
|
||||
const TYPE_SHORT = 0, TYPE_LONG = 1, TYPE_COMMAND = 2;
|
||||
const ARGS_NONE = 0, ARGS_MANDATORY = 1, ARGS_OPTIONAL = 2;
|
||||
const ACTION_SET = 0, ACTION_INC = 1, ACTION_DEC = 2, ACTION_FUNC = 3;
|
||||
|
||||
function __construct(array $def) {
|
||||
$this->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,
|
||||
];
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class ArgDefs: une liste d'objets ArgDef
|
||||
*/
|
||||
abstract class ArgDefs implements IArgo {
|
||||
function __construct(array $defs) {
|
||||
$this->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);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\ValueException;
|
||||
|
||||
class ArgException extends ValueException {
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
class ArgGroup extends ArgDefs {
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\php\types\vbool;
|
||||
|
||||
class ArgSection extends ArgDefs {
|
||||
public bool $show = true;
|
||||
public ?string $prefix = null;
|
||||
public ?string $title = null;
|
||||
public ?string $description = null;
|
||||
public ?string $suffix = null;
|
||||
|
||||
protected function parseParams(?array $params): void {
|
||||
$this->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;
|
||||
}
|
||||
}
|
20
src/app/cli/ArgsException.php
Normal file
20
src/app/cli/ArgsException.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\ValueException;
|
||||
|
||||
class ArgsException extends ValueException {
|
||||
static function missing(?string $value, string $kind): self {
|
||||
$msg = $value;
|
||||
if ($msg !== null) $msg .= ": ";
|
||||
$msg .= "missing $kind";
|
||||
throw new self($msg);
|
||||
}
|
||||
|
||||
static function invalid(?string $value, string $kind): self {
|
||||
$msg = $value;
|
||||
if ($msg !== null) $msg .= ": ";
|
||||
$msg .= "invalid $kind";
|
||||
throw new self($msg);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
/**
|
||||
* Interface IArgo: une instance de ArgDef, ArgGroup ou ArgSection
|
||||
*/
|
||||
interface IArgo {
|
||||
function isEmpty(): bool;
|
||||
|
||||
function debugInfos(): array;
|
||||
}
|
188
src/app/cli/SimpleAolist.php
Normal file
188
src/app/cli/SimpleAolist.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\app;
|
||||
use nulib\cl;
|
||||
use nulib\php\types\vbool;
|
||||
use nulib\str;
|
||||
use const true;
|
||||
|
||||
/**
|
||||
* Class SimpleArgdefs: une définition simple des arguments et des options
|
||||
* valides d'un programme: les commandes ne sont pas supportées, ni les suites
|
||||
* de commandes
|
||||
*
|
||||
* i.e
|
||||
* -x --long est supporté
|
||||
* cmd -a -b n'est PAS supporté
|
||||
* cmd1 -x // cmd2 -y n'est PAS supporté
|
||||
*/
|
||||
class SimpleAolist extends Aolist {
|
||||
public ?string $prefix = null;
|
||||
public ?string $name = null;
|
||||
public ?string $purpose = null;
|
||||
public $usage = null;
|
||||
public ?string $description = null;
|
||||
public ?string $suffix = null;
|
||||
|
||||
public ?string $commandname = null;
|
||||
public ?string $commandproperty = null;
|
||||
public ?string $commandkey = null;
|
||||
|
||||
public ?string $argsname = null;
|
||||
public ?string $argsproperty = null;
|
||||
public ?string $argskey = null;
|
||||
|
||||
public ?bool $autohelp = null;
|
||||
public ?bool $autoremains = null;
|
||||
|
||||
protected array $index;
|
||||
|
||||
protected function parseParams(?array $params): void {
|
||||
# méta-informations
|
||||
$this->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))),
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nulib\php\types\vbool;
|
||||
|
||||
/**
|
||||
* Class SimpleArgDefs: une définition simple des arguments et des options
|
||||
* valides d'un programme: les commandes ne sont pas supportées, ni les suites
|
||||
* de commandes
|
||||
*
|
||||
* i.e
|
||||
* -x --long est supporté
|
||||
* cmd -a -b n'est PAS supporté
|
||||
* cmd1 -x // cmd2 -y n'est PAS supporté
|
||||
*/
|
||||
class SimpleArgDefs extends ArgDefs {
|
||||
public ?string $prefix = null;
|
||||
public ?string $name = null;
|
||||
public ?string $purpose = null;
|
||||
public ?string $usage = null;
|
||||
public ?string $description = null;
|
||||
public ?string $suffix = null;
|
||||
|
||||
public ?string $commandname = null;
|
||||
public ?string $commandproperty = null;
|
||||
public ?string $commandkey = null;
|
||||
|
||||
public ?string $argsname = null;
|
||||
public ?string $argsproperty = null;
|
||||
public ?string $argskey = null;
|
||||
|
||||
public ?bool $autohelp = null;
|
||||
public ?bool $autoremains = null;
|
||||
|
||||
protected function parseParams(?array $params): void {
|
||||
# méta-informations
|
||||
$this->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),
|
||||
];
|
||||
}
|
||||
}
|
@ -1,21 +1,25 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use stdClass;
|
||||
use nulib\cl;
|
||||
use nulib\ExitError;
|
||||
use nulib\StateException;
|
||||
|
||||
class SimpleArgsParser extends AbstractArgsParser {
|
||||
function __construct(array $defs) {
|
||||
$this->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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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
|
||||
|
62
tbin/test-application.php
Executable file
62
tbin/test-application.php
Executable file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
require __DIR__."/../vendor/autoload.php";
|
||||
|
||||
use nulib\app\cli\Application;
|
||||
use nulib\output\msg;
|
||||
|
||||
Application::run(new class extends Application {
|
||||
const ARGS = [
|
||||
"purpose" => "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),
|
||||
]);
|
||||
}
|
||||
});
|
158
tests/app/cli/AodefTest.php
Normal file
158
tests/app/cli/AodefTest.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class AodefTest extends TestCase {
|
||||
protected static function assertArg(
|
||||
Aodef $aodef,
|
||||
array $options,
|
||||
bool $haveShortOptions, bool $haveLongOptions, bool $isCommand,
|
||||
bool $haveArgs, ?int $minArgs, ?int $maxArgs, ?string $argsdesc
|
||||
) {
|
||||
$aodef->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, "");
|
||||
}
|
||||
}
|
60
tests/app/cli/AolistTest.php
Normal file
60
tests/app/cli/AolistTest.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class AolistTest extends TestCase {
|
||||
function testGroup() {
|
||||
$aogroup = new Aogroup([
|
||||
"group",
|
||||
["--gopt1"],
|
||||
["--gopt2"],
|
||||
], true);
|
||||
|
||||
echo "$aogroup\n";
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testSection() {
|
||||
$aosection = new Aosection([
|
||||
["--sopt"],
|
||||
["group",
|
||||
["--sgopt1"],
|
||||
["--sgopt2"],
|
||||
],
|
||||
], true);
|
||||
|
||||
echo "$aosection\n";
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testList() {
|
||||
$aolist = new class([
|
||||
"param" => "value",
|
||||
["--opt"],
|
||||
["group",
|
||||
["--gopt1"],
|
||||
["--gopt2"],
|
||||
],
|
||||
"sections" => [
|
||||
[
|
||||
["--s0opt"],
|
||||
["group",
|
||||
["--s0gopt1"],
|
||||
["--s0gopt2"],
|
||||
],
|
||||
],
|
||||
"ns" => [
|
||||
["--nsopt"],
|
||||
["group",
|
||||
["--nsgopt1"],
|
||||
["--nsgopt2"],
|
||||
],
|
||||
],
|
||||
],
|
||||
]) extends Aolist {};
|
||||
|
||||
echo "$aolist\n";
|
||||
self::assertTrue(true);
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class ArgDefTest extends TestCase {
|
||||
protected static function assertArg(
|
||||
ArgDef $argDef,
|
||||
array $options,
|
||||
bool $haveShortOptions, bool $haveLongOptions, bool $isCommand,
|
||||
bool $haveArgs, ?int $minArgs, ?int $maxArgs, ?string $argsdesc
|
||||
) {
|
||||
$argDef->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);
|
||||
}
|
||||
}
|
58
tests/app/cli/SimpleAolistTest.php
Normal file
58
tests/app/cli/SimpleAolistTest.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class SimpleAolistTest extends TestCase {
|
||||
function testOverride() {
|
||||
$aolist = new SimpleAolist([
|
||||
["-o", "--longx"],
|
||||
"merge" => [
|
||||
["-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);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class SimpleArgDefsTest extends TestCase {
|
||||
function testNormalize() {
|
||||
$argDefs = new SimpleArgDefs([
|
||||
["-a"],
|
||||
["--longb"],
|
||||
["-c", "--longc"],
|
||||
["-m:", "--mandatory"],
|
||||
["-o::", "--optional"],
|
||||
["-x", "--x1"],
|
||||
["-x", "--x2"],
|
||||
]);
|
||||
|
||||
self::assertSame(["--"]
|
||||
, $argDefs->normalize([]));
|
||||
self::assertSame(["--", "a", "b"]
|
||||
, $argDefs->normalize(["a", "b"]));
|
||||
self::assertSame(["-a", "--mandatory", "x", "--mandatory", "y", "--", "z", "x"]
|
||||
, $argDefs->normalize(["-a", "--m", "x", "z", "--m=y", "x"]));
|
||||
self::assertSame(["-m", "x", "-m", "y", "--"]
|
||||
, $argDefs->normalize(["-mx", "-m", "y"]));
|
||||
self::assertSame(["-ox", "-o", "--", "y"]
|
||||
, $argDefs->normalize(["-ox", "-o", "y"]));
|
||||
self::assertSame(["-a", "--", "-a", "-c"]
|
||||
, $argDefs->normalize(["-a", "--", "-a", "-c"]));
|
||||
}
|
||||
}
|
174
tests/app/cli/SimpleArgsParserTest.php
Normal file
174
tests/app/cli/SimpleArgsParserTest.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use nur\t\TestCase;
|
||||
|
||||
class SimpleArgsParserTest extends TestCase {
|
||||
const NORMALIZE_ARGS = [
|
||||
["-a"],
|
||||
["--longb"],
|
||||
["-c", "--longc"],
|
||||
["-x", "--x1"],
|
||||
["-x", "--x2"],
|
||||
|
||||
["-n", "--none"],
|
||||
["-m:", "--mandatory"],
|
||||
["-o::", "--optional"],
|
||||
["--mo02:", "args" => [["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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user