246 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nulib\app\cli;
 | |
| 
 | |
| use nulib\cl;
 | |
| 
 | |
| class ArgDefs {
 | |
|   function __construct(array $defs) {
 | |
|     [$defs, $params] = cl::split_assoc($defs);
 | |
| 
 | |
|     $argDefs = [];
 | |
|     foreach ($defs as $def) {
 | |
|       $argDefs[] = new ArgDef($def);
 | |
|     }
 | |
|     foreach ($argDefs as $argDef) {
 | |
|       $argDef->processExtends();
 | |
|     }
 | |
| 
 | |
|     $index = [];
 | |
|     foreach ($argDefs as $argDef) {
 | |
|       $options = $argDef->getOptions();
 | |
|       foreach ($options as $option) {
 | |
|         if (array_key_exists($option, $index)) {
 | |
|           $index[$option]->removeOption($option);
 | |
|         }
 | |
|         $index[$option] = $argDef;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     foreach ($argDefs as $argDef) {
 | |
|       $argDef->processArgs();
 | |
|       $argDef->processAction();
 | |
|     }
 | |
| 
 | |
|     $this->argDefs = $argDefs;
 | |
|     $this->index = $index;
 | |
|   }
 | |
| 
 | |
|   protected array $argDefs;
 | |
| 
 | |
|   protected array $index;
 | |
| 
 | |
|   function getArgDef(string $option): ?ArgDef {
 | |
|     return $this->index[$option] ?? null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * consommer les arguments de $src en avançant l'index $srci et provisionner
 | |
|    * $dest à partir de $desti. si $desti est plus grand que 0, celà veut dire
 | |
|    * que $dest a déjà commencé à être provisionné, et qu'il faut continuer.
 | |
|    *
 | |
|    * $destmin est le nombre minimum d'arguments à consommer. $destmax est le
 | |
|    * nombre maximum d'arguments à consommer.
 | |
|    *
 | |
|    * $srci est la position de l'élément courant à consommer le cas échéant
 | |
|    * retourner le nombre d'arguments qui manquent (ou 0 si tous les arguments
 | |
|    * ont été consommés)
 | |
|    *
 | |
|    * pour les arguments optionnels, ils sont consommés tant qu'il y en a de
 | |
|    * disponible, ou jusqu'à la présence de '--'. Si $keepsep, l'argument '--'
 | |
|    * est gardé dans la liste des arguments optionnels.
 | |
|    */
 | |
|   private static function consume_args($src, &$srci, &$dest, $desti, $destmin, $destmax, bool $keepsep): int {
 | |
|     $srcmax = count($src);
 | |
|     # arguments obligatoires
 | |
|     while ($desti < $destmin) {
 | |
|       if ($srci < $srcmax) {
 | |
|         $dest[] = $src[$srci];
 | |
|       } else {
 | |
|         # pas assez d'arguments
 | |
|         return $destmin - $desti;
 | |
|       }
 | |
|       $srci++;
 | |
|       $desti++;
 | |
|     }
 | |
|     # arguments facultatifs
 | |
|     while ($desti < $destmax && $srci < $srcmax) {
 | |
|       $opt = $src[$srci];
 | |
|       if ($opt === "--") {
 | |
|         # fin des options facultatives
 | |
|         if ($keepsep) $dest[] = $opt;
 | |
|         $srci++;
 | |
|         break;
 | |
|       }
 | |
|       $dest[] = $opt;
 | |
|       $srci++;
 | |
|       $desti++;
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   private static function check_missing(?string $option, int $count) {
 | |
|     if ($count > 0) {
 | |
|       throw new ArgException("$option: nombre d'arguments insuffisant (manque $count)");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function normalize(array $args): array {
 | |
|     $i = 0;
 | |
|     $max = count($args);
 | |
|     $options = [];
 | |
|     $remains = [];
 | |
|     $parseOpts = true;
 | |
|     while ($i < $max) {
 | |
|       $arg = $args[$i++];
 | |
|       if (!$parseOpts) {
 | |
|         # le reste n'est que des arguments
 | |
|         $remains[] = $arg;
 | |
|         continue;
 | |
|       }
 | |
|       if ($arg === "--") {
 | |
|         # fin des options
 | |
|         $parseOpts = false;
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (substr($arg, 0, 2) === "--") {
 | |
|         #######################################################################
 | |
|         # option longue
 | |
|         $pos = strpos($arg, "=");
 | |
|         if ($pos !== false) {
 | |
|           # option avec valeur
 | |
|           $option = substr($arg, 0, $pos);
 | |
|           $value = substr($arg, $pos + 1);
 | |
|         } else {
 | |
|           # option sans valeur
 | |
|           $option = $arg;
 | |
|           $value = null;
 | |
|         }
 | |
|         /** @var ArgDef $argDef */
 | |
|         $argDef = $this->index[$option] ?? null;
 | |
|         if ($argDef === null) {
 | |
|           # chercher une correspondance
 | |
|           $len = strlen($option);
 | |
|           $candidates = [];
 | |
|           foreach (array_keys($this->index) as $candidate) {
 | |
|             if (substr($candidate, 0, $len) === $option) {
 | |
|               $candidates[] = $candidate;
 | |
|             }
 | |
|           }
 | |
|           switch (count($candidates)) {
 | |
|           case 0:
 | |
|             throw new ArgException("$option: option invalide");
 | |
|           case 1:
 | |
|             $option = $candidates[0];
 | |
|             break;
 | |
|           default:
 | |
|             $candidates = implode(", ", $candidates);
 | |
|             throw new ArgException("$option: option ambigue (les options possibles sont $candidates)");
 | |
|           }
 | |
|           $argDef = $this->index[$option];
 | |
|         }
 | |
| 
 | |
|         if ($argDef->haveArgs) {
 | |
|           $minArgs = $argDef->minArgs;
 | |
|           $maxArgs = $argDef->maxArgs;
 | |
|           $values = [];
 | |
|           if ($value !== null) {
 | |
|             $values[] = $value;
 | |
|             $offset = 1;
 | |
|           } elseif ($minArgs == 0) {
 | |
|             # cas particulier: la première valeur doit être collée à l'option
 | |
|             # si $maxArgs == 1
 | |
|             $offset = $maxArgs == 1 ? 1 : 0;
 | |
|           } else {
 | |
|             $offset = 0;
 | |
|           }
 | |
|           $this->check_missing($option,
 | |
|             self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true));
 | |
| 
 | |
|           if ($minArgs == 0 && $maxArgs == 1) {
 | |
|             # cas particulier: la première valeur doit être collée à l'option
 | |
|             if (count($values) > 0) {
 | |
|               $options[] = "$option=$values[0]";
 | |
|               $values = array_slice($values, 1);
 | |
|             } else {
 | |
|               $options[] = $option;
 | |
|             }
 | |
|           } else {
 | |
|             $options[] = $option;
 | |
|           }
 | |
|           $options = array_merge($options, $values);
 | |
|         } elseif ($value !== null) {
 | |
|           throw new ArgException("$option: cette option ne prend pas d'arguments");
 | |
|         } else {
 | |
|           $options[] = $option;
 | |
|         }
 | |
| 
 | |
|       } elseif (substr($arg, 0, 1) === "-") {
 | |
|         #######################################################################
 | |
|         # option courte
 | |
|         $pos = 1;
 | |
|         $len = strlen($arg);
 | |
|         while ($pos < $len) {
 | |
|           $option = "-".substr($arg, $pos, 1);
 | |
|           /** @var ArgDef $argDef */
 | |
|           $argDef = $this->index[$option] ?? null;
 | |
|           if ($argDef === null) {
 | |
|             throw new ArgException("$option: option invalide");
 | |
|           }
 | |
|           if ($argDef->haveArgs) {
 | |
|             $minArgs = $argDef->minArgs;
 | |
|             $maxArgs = $argDef->maxArgs;
 | |
|             $values = [];
 | |
|             if ($len > $pos + 1) {
 | |
|               $values[] = substr($arg, $pos + 1);
 | |
|               $offset = 1;
 | |
|               $pos = $len;
 | |
|             } elseif ($minArgs == 0) {
 | |
|               # cas particulier: la première valeur doit être collée à l'option
 | |
|               # si $maxArgs == 1
 | |
|               $offset = $maxArgs == 1 ? 1 : 0;
 | |
|             } else {
 | |
|               $offset = 0;
 | |
|             }
 | |
|             $this->check_missing($option,
 | |
|               self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true));
 | |
| 
 | |
|             if ($minArgs == 0 && $maxArgs == 1) {
 | |
|               # cas particulier: la première valeur doit être collée à l'option
 | |
|               if (count($values) > 0) {
 | |
|                 $options[] = "$option$values[0]";
 | |
|                 $values = array_slice($values, 1);
 | |
|               } else {
 | |
|                 $options[] = $option;
 | |
|               }
 | |
|             } else {
 | |
|               $options[] = $option;
 | |
|             }
 | |
|             $options = array_merge($options, $values);
 | |
|           } else {
 | |
|             $options[] = $option;
 | |
|           }
 | |
|           $pos++;
 | |
|         }
 | |
|       } else {
 | |
|         #XXX implémenter les commandes
 | |
| 
 | |
|         #######################################################################
 | |
|         # argument
 | |
|         $remains[] = $arg;
 | |
|       }
 | |
|     }
 | |
|     return array_merge($options, ["--"], $remains);
 | |
|   }
 | |
| }
 |