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