migration d'outils vers nulib-base
This commit is contained in:
		
							parent
							
								
									fd46d60888
								
							
						
					
					
						commit
						91ce3a4467
					
				
							
								
								
									
										1
									
								
								.idea/nur-ture.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/nur-ture.iml
									
									
									
										generated
									
									
									
								
							| @ -6,6 +6,7 @@ | ||||
|       <sourceFolder url="file://$MODULE_DIR$/nur_tests" isTestSource="true" packagePrefix="nur\" /> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="nulib\" /> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="nulib\" /> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/cli" isTestSource="false" packagePrefix="cli\" /> | ||||
|       <excludeFolder url="file://$MODULE_DIR$/vendor" /> | ||||
|     </content> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|  | ||||
							
								
								
									
										17
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							| @ -70,6 +70,23 @@ | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/yaml" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/theseer/tokenizer" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/psr/cache" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/psr/container" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/nette/utils" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/nette/schema" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/psr/log" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/dflydev/dot-access-data" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/cache" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/expression-language" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/polyfill-php73" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/service-contracts" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/symfony/var-exporter" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/league/config" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/league/commonmark" /> | ||||
|       <path value="$PROJECT_DIR$/vendor/phpmailer/phpmailer" /> | ||||
|     </include_path> | ||||
|   </component> | ||||
|   <component name="PhpProjectSharedConfiguration" php_language_level="7.4" /> | ||||
|  | ||||
| @ -2,6 +2,6 @@ | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\NucacheApp; | ||||
| use cli\CachectlApp; | ||||
| 
 | ||||
| NucacheApp::run(); | ||||
| CachectlApp::run(); | ||||
| @ -2,6 +2,6 @@ | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\Csv2xlsxApp; | ||||
| use cli\Csv2xlsxApp; | ||||
| 
 | ||||
| Csv2xlsxApp::run(); | ||||
|  | ||||
| @ -1,7 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\DumpserApp; | ||||
| 
 | ||||
| DumpserApp::run(); | ||||
| @ -1,7 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\Json2yamlApp; | ||||
| 
 | ||||
| Json2yamlApp::run(); | ||||
| @ -1,7 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\MysqlStorageApp; | ||||
| 
 | ||||
| MysqlStorageApp::run(); | ||||
| @ -1,7 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\PgsqlStorageApp; | ||||
| 
 | ||||
| PgsqlStorageApp::run(); | ||||
| @ -1,7 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\SqliteStorageApp; | ||||
| 
 | ||||
| SqliteStorageApp::run(); | ||||
| @ -1,7 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cli\Yaml2jsonApp; | ||||
| 
 | ||||
| Yaml2jsonApp::run(); | ||||
| @ -1,5 +1,5 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| namespace cli; | ||||
| 
 | ||||
| use Exception; | ||||
| use nulib\app\cli\Application; | ||||
| @ -8,7 +8,7 @@ use nulib\ext\yaml; | ||||
| use nulib\os\path; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class NucacheApp extends Application { | ||||
| class CachectlApp extends Application { | ||||
|   const ACTION_READ = 10, ACTION_INFOS = 20, ACTION_CLEAN = 30; | ||||
|   const ACTION_UPDATE = 40, ACTION_UPDATE_ADD = 41, ACTION_UPDATE_SUB = 42, ACTION_UPDATE_SET = 43; | ||||
| 
 | ||||
| @ -1,5 +1,5 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| namespace cli; | ||||
| 
 | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\ext\tab\SsBuilder; | ||||
| @ -9,8 +9,6 @@ use nulib\os\path; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| class Csv2xlsxApp extends Application { | ||||
|   private $args; | ||||
| 
 | ||||
|   function main() { | ||||
|     $input = ValueException::check_null($this->args[0] ?? null); | ||||
|     $inputname = path::filename($input); | ||||
| @ -65,7 +65,8 @@ | ||||
| 	"autoload": { | ||||
| 		"psr-4": { | ||||
| 			"nulib\\": "src", | ||||
| 			"nur\\": "nur_src" | ||||
| 			"nur\\": "nur_src", | ||||
|       "cli\\": "cli" | ||||
| 		}, | ||||
| 		"files": [ | ||||
| 			"nur_autoload.php" | ||||
| @ -78,19 +79,13 @@ | ||||
| 		} | ||||
| 	}, | ||||
| 	"bin": [ | ||||
| 		"bin/dumpser.php", | ||||
| 		"bin/nucache.php", | ||||
| 		"bin/csv2xlsx.php", | ||||
| 		"bin/json2yml.php", | ||||
| 		"bin/yml2json.php", | ||||
| 		"bin/sqlite.storage.php", | ||||
| 		"bin/mysql.storage.php", | ||||
| 		"bin/pgsql.storage.php", | ||||
|     "bin/cachectl.php", | ||||
|     "bin/csv2xlsx.php", | ||||
| 		"nur_bin/compctl.php", | ||||
| 		"nur_bin/compdep.php", | ||||
| 		"nur_bin/datectl.php", | ||||
| 		"nur_bin/fsvdiff.php", | ||||
| 		"nur_bin/cachectl.php", | ||||
| 		"nur_bin/legacy-cachectl.php", | ||||
| 		"nur_bin/ldap-delete.php", | ||||
| 		"nur_bin/ldap-get-infos.php", | ||||
| 		"nur_bin/ldap-search.php" | ||||
|  | ||||
							
								
								
									
										1453
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1453
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										616
									
								
								src/app/app.php
									
									
									
									
									
								
							
							
						
						
									
										616
									
								
								src/app/app.php
									
									
									
									
									
								
							| @ -1,616 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\app\config\ProfileManager; | ||||
| use nulib\cl; | ||||
| use nulib\ExitError; | ||||
| use nulib\os\path; | ||||
| use nulib\os\sh; | ||||
| use nulib\php\func; | ||||
| use nulib\php\types\varray; | ||||
| use nulib\str; | ||||
| use nulib\ValueException; | ||||
| use nur\cli\Application as nur_Application; | ||||
| 
 | ||||
| class app { | ||||
|   private static function isa_Application($app): bool { | ||||
|     if (!is_string($app)) return false; | ||||
|     return $app === Application::class || is_subclass_of($app, Application::class) | ||||
|       || $app === nur_Application::class || is_subclass_of($app, nur_Application::class); | ||||
|   } | ||||
| 
 | ||||
|   private static function get_params($app): array { | ||||
|     if ($app instanceof self) { | ||||
|       $params = $app->getParams(); | ||||
|     } elseif ($app instanceof Application) { | ||||
|       $class = get_class($app); | ||||
|       $params = [ | ||||
|         "class" => $class, | ||||
|         "projdir" => $app::PROJDIR, | ||||
|         "vendor" => $app::VENDOR, | ||||
|         "appcode" => $app::APPCODE, | ||||
|         "datadir" => $app::DATADIR, | ||||
|         "etcdir" => $app::ETCDIR, | ||||
|         "vardir" => $app::VARDIR, | ||||
|         "logdir" => $app::LOGDIR, | ||||
|         "appgroup" => $app::APPGROUP, | ||||
|         "name" => $app::NAME, | ||||
|         "title" => $app::TITLE, | ||||
|       ]; | ||||
|     } elseif (self::isa_Application($app)) { | ||||
|       $class = $app; | ||||
|       $params = [ | ||||
|         "class" => $class, | ||||
|         "projdir" => constant("$app::PROJDIR"), | ||||
|         "vendor" => constant("$app::VENDOR"), | ||||
|         "appcode" => constant("$app::APPCODE"), | ||||
|         "datadir" => constant("$app::DATADIR"), | ||||
|         "etcdir" => constant("$app::ETCDIR"), | ||||
|         "vardir" => constant("$app::VARDIR"), | ||||
|         "logdir" => constant("$app::LOGDIR"), | ||||
|         "appgroup" => constant("$app::APPGROUP"), | ||||
|         "name" => constant("$app::NAME"), | ||||
|         "title" => constant("$app::TITLE"), | ||||
|       ]; | ||||
|     } elseif (is_array($app)) { | ||||
|       $params = $app; | ||||
|     } else { | ||||
|       throw ValueException::invalid_type($app, Application::class); | ||||
|     } | ||||
|     return $params; | ||||
|   } | ||||
| 
 | ||||
|   protected static ?self $app = null; | ||||
| 
 | ||||
|   /** | ||||
|    * @param Application|string|array $app | ||||
|    * @param Application|string|array|null $proj | ||||
|    */ | ||||
|   static function with($app, $proj=null): self { | ||||
|     $params = self::get_params($app); | ||||
|     $proj ??= self::params_getenv(); | ||||
|     $proj ??= self::$app; | ||||
|     $proj_params = $proj !== null? self::get_params($proj): null; | ||||
|     if ($proj_params !== null) { | ||||
|       A::merge($params, cl::select($proj_params, [ | ||||
|         "projdir", | ||||
|         "vendor", | ||||
|         "appcode", | ||||
|         "cwd", | ||||
|         "datadir", | ||||
|         "etcdir", | ||||
|         "vardir", | ||||
|         "logdir", | ||||
|         "profile", | ||||
|         "facts", | ||||
|         "debug", | ||||
|       ])); | ||||
|     } | ||||
|     return new static($params, $proj_params !== null); | ||||
|   } | ||||
| 
 | ||||
|   static function init($app, $proj=null): void { | ||||
|     self::$app = static::with($app, $proj); | ||||
|   } | ||||
| 
 | ||||
|   static function get(): self { | ||||
|     return self::$app ??= new static(null); | ||||
|   } | ||||
| 
 | ||||
|   static function params_putenv(): void { | ||||
|     $params = serialize(self::get()->getParams()); | ||||
|     putenv("NULIB_APP_app_params=$params"); | ||||
|   } | ||||
| 
 | ||||
|   static function params_getenv(): ?array { | ||||
|     $params = getenv("NULIB_APP_app_params"); | ||||
|     if ($params === false) return null; | ||||
|     return unserialize($params); | ||||
|   } | ||||
| 
 | ||||
|   static function get_profile(?bool &$productionMode=null): string { | ||||
|     return self::get()->getProfile($productionMode); | ||||
|   } | ||||
|    | ||||
|   static function is_prod(): bool { | ||||
|     return self::get_profile() === "prod"; | ||||
|   } | ||||
| 
 | ||||
|   static function is_devel(): bool { | ||||
|     return self::get_profile() === "devel"; | ||||
|   } | ||||
| 
 | ||||
|   static function set_profile(?string $profile=null, ?bool $productionMode=null): void { | ||||
|     self::get()->setProfile($profile, $productionMode); | ||||
|   } | ||||
| 
 | ||||
|   const FACT_WEB_APP = "web-app"; | ||||
|   const FACT_CLI_APP = "cli-app"; | ||||
| 
 | ||||
|   static final function is_fact(string $fact, $value=true): bool { | ||||
|     return self::get()->isFact($fact, $value); | ||||
|   } | ||||
| 
 | ||||
|   static final function set_fact(string $fact, $value=true): void { | ||||
|     self::get()->setFact($fact, $value); | ||||
|   } | ||||
|    | ||||
|   static function is_debug(): bool { | ||||
|     return self::get()->isDebug(); | ||||
|   } | ||||
| 
 | ||||
|   static function set_debug(?bool $debug=true): void { | ||||
|     self::get()->setDebug($debug); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @var array répertoires vendor exprimés relativement à PROJDIR | ||||
|    */ | ||||
|   const DEFAULT_VENDOR = [ | ||||
|     "bindir" => "vendor/bin", | ||||
|     "autoload" => "vendor/autoload.php", | ||||
|   ]; | ||||
| 
 | ||||
|   function __construct(?array $params, bool $useProjParams=false) { | ||||
|     if ($useProjParams) { | ||||
|       [ | ||||
|         "projdir" => $projdir, | ||||
|         "vendor" => $vendor, | ||||
|         "appcode" => $appcode, | ||||
|         "datadir" => $datadir, | ||||
|         "etcdir" => $etcdir, | ||||
|         "vardir" => $vardir, | ||||
|         "logdir" => $logdir, | ||||
|       ] = $params; | ||||
|       $cwd = $params["cwd"] ?? null; | ||||
|       $datadirIsDefined = true; | ||||
|     } else { | ||||
|       # projdir
 | ||||
|       $projdir = $params["projdir"] ?? null; | ||||
|       if ($projdir === null) { | ||||
|         global $_composer_autoload_path, $_composer_bin_dir; | ||||
|         $autoload = $_composer_autoload_path ?? null; | ||||
|         $bindir = $_composer_bin_dir ?? null; | ||||
|         if ($autoload !== null) { | ||||
|           $vendor = preg_replace('/\/[^\/]+\.php$/', "", $autoload); | ||||
|           $bindir ??= "$vendor/bin"; | ||||
|           $projdir = preg_replace('/\/[^\/]+$/', "", $vendor); | ||||
|           $params["vendor"] = [ | ||||
|             "autoload" => $autoload, | ||||
|             "bindir" => $bindir, | ||||
|           ]; | ||||
|         } | ||||
|       } | ||||
|       if ($projdir === null) $projdir = "."; | ||||
|       $projdir = path::abspath($projdir); | ||||
|       # vendor
 | ||||
|       $vendor = $params["vendor"] ?? self::DEFAULT_VENDOR; | ||||
|       $vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]); | ||||
|       $vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]); | ||||
|       # appcode
 | ||||
|       $appcode = $params["appcode"] ?? null; | ||||
|       if ($appcode === null) { | ||||
|         $appcode = str::without_suffix("-app", path::basename($projdir)); | ||||
|       } | ||||
|       $APPCODE = str_replace("-", "_", strtoupper($appcode)); | ||||
|       # cwd
 | ||||
|       $cwd = $params["cwd"] ?? null; | ||||
|       # datadir
 | ||||
|       $datadir = getenv("${APPCODE}_DATADIR"); | ||||
|       $datadirIsDefined = $datadir !== false; | ||||
|       if ($datadir === false) $datadir = $params["datadir"] ?? null; | ||||
|       if ($datadir === null) $datadir = "devel"; | ||||
|       $datadir = path::reljoin($projdir, $datadir); | ||||
|       # etcdir
 | ||||
|       $etcdir = getenv("${APPCODE}_ETCDIR"); | ||||
|       if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; | ||||
|       if ($etcdir === null) $etcdir = "etc"; | ||||
|       $etcdir = path::reljoin($datadir, $etcdir); | ||||
|       # vardir
 | ||||
|       $vardir = getenv("${APPCODE}_VARDIR"); | ||||
|       if ($vardir === false) $vardir = $params["vardir"] ?? null; | ||||
|       if ($vardir === null) $vardir = "var"; | ||||
|       $vardir = path::reljoin($datadir, $vardir); | ||||
|       # logdir
 | ||||
|       $logdir = getenv("${APPCODE}_LOGDIR"); | ||||
|       if ($logdir === false) $logdir = $params["logdir"] ?? null; | ||||
|       if ($logdir === null) $logdir = "log"; | ||||
|       $logdir = path::reljoin($datadir, $logdir); | ||||
|     } | ||||
|     # cwd
 | ||||
|     $cwd ??= getcwd(); | ||||
|     # profile
 | ||||
|     $this->profileManager = new ProfileManager([ | ||||
|       "app" => true, | ||||
|       "name" => $appcode, | ||||
|       "default_profile" => $datadirIsDefined? "prod": "devel", | ||||
|       "profile" => $params["profile"] ?? null, | ||||
|     ]); | ||||
|     # $facts
 | ||||
|     $this->facts = $params["facts"] ?? null; | ||||
|     # debug
 | ||||
|     $this->debug = $params["debug"] ?? null; | ||||
| 
 | ||||
|     $this->projdir = $projdir; | ||||
|     $this->vendor = $vendor; | ||||
|     $this->appcode = $appcode; | ||||
|     $this->cwd = $cwd; | ||||
|     $this->datadir = $datadir; | ||||
|     $this->etcdir = $etcdir; | ||||
|     $this->vardir = $vardir; | ||||
|     $this->logdir = $logdir; | ||||
| 
 | ||||
|     # name, title
 | ||||
|     $appgroup = $params["appgroup"] ?? null; | ||||
|     $name = $params["name"] ?? $params["class"] ?? null; | ||||
|     if ($name === null) { | ||||
|       $name = $appcode; | ||||
|     } else { | ||||
|       # si $name est une classe, enlever le package et normaliser i.e
 | ||||
|       # my\package\MyApplication --> my-application
 | ||||
|       $name = preg_replace('/.*\\\\/', "", $name); | ||||
|       $name = str::camel2us($name, false, "-"); | ||||
|       $name = str::without_suffix("-app", $name); | ||||
|     } | ||||
|     $this->appgroup = $appgroup; | ||||
|     $this->name = $name; | ||||
|     $this->title = $params["title"] ?? null; | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
|   # Paramètres partagés par tous les scripts d'un projet (et les scripts lancés
 | ||||
|   # à partir d'une application de ce projet)
 | ||||
| 
 | ||||
|   protected string $projdir; | ||||
| 
 | ||||
|   function getProjdir(): string { | ||||
|     return $this->projdir; | ||||
|   } | ||||
| 
 | ||||
|   protected array $vendor; | ||||
| 
 | ||||
|   function getVendorBindir(): string { | ||||
|     return $this->vendor["bindir"]; | ||||
|   } | ||||
| 
 | ||||
|   function getVendorAutoload(): string { | ||||
|     return $this->vendor["autoload"]; | ||||
|   } | ||||
| 
 | ||||
|   protected string $appcode; | ||||
| 
 | ||||
|   function getAppcode(): string { | ||||
|     return $this->appcode; | ||||
|   } | ||||
| 
 | ||||
|   protected string $cwd; | ||||
| 
 | ||||
|   function getCwd(): string { | ||||
|     return $this->cwd; | ||||
|   } | ||||
| 
 | ||||
|   protected string $datadir; | ||||
| 
 | ||||
|   function getDatadir(): string { | ||||
|     return $this->datadir; | ||||
|   } | ||||
| 
 | ||||
|   protected string $etcdir; | ||||
| 
 | ||||
|   function getEtcdir(): string { | ||||
|     return $this->etcdir; | ||||
|   } | ||||
| 
 | ||||
|   protected string $vardir; | ||||
| 
 | ||||
|   function getVardir(): string { | ||||
|     return $this->vardir; | ||||
|   } | ||||
| 
 | ||||
|   protected string $logdir; | ||||
| 
 | ||||
|   function getLogdir(): string { | ||||
|     return $this->logdir; | ||||
|   } | ||||
| 
 | ||||
|   protected ProfileManager $profileManager; | ||||
| 
 | ||||
|   function getProfile(?bool &$productionMode=null): string { | ||||
|     return $this->profileManager->getProfile($productionMode); | ||||
|   } | ||||
| 
 | ||||
|   function isProductionMode(): bool { | ||||
|     return $this->profileManager->isProductionMode(); | ||||
|   } | ||||
| 
 | ||||
|   function setProfile(?string $profile, ?bool $productionMode=null): void { | ||||
|     $this->profileManager->setProfile($profile, $productionMode); | ||||
|   } | ||||
| 
 | ||||
|   protected ?array $facts; | ||||
| 
 | ||||
|   function isFact(string $fact, $value=true): bool { | ||||
|     return ($this->facts[$fact] ?? false) === $value; | ||||
|   } | ||||
| 
 | ||||
|   function setFact(string $fact, $value=true): void { | ||||
|     $this->facts[$fact] = $value; | ||||
|   } | ||||
| 
 | ||||
|   protected ?bool $debug; | ||||
| 
 | ||||
|   function isDebug(): bool { | ||||
|     $debug = $this->debug; | ||||
|     if ($debug === null) { | ||||
|       $debug = defined("DEBUG")? DEBUG: null; | ||||
|       $DEBUG = getenv("DEBUG"); | ||||
|       $debug ??= $DEBUG !== false? $DEBUG: null; | ||||
|       $debug ??= config::k("debug"); | ||||
|       $debug ??= false; | ||||
|       $this->debug = $debug; | ||||
|     } | ||||
|     return $debug; | ||||
|   } | ||||
| 
 | ||||
|   function setDebug(bool $debug=true): void { | ||||
|     $this->debug = $debug; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @param ?string|false $profile | ||||
|    * | ||||
|    * false === pas de profil | ||||
|    * null === profil par défaut | ||||
|    */ | ||||
|   function withProfile(string $file, $profile): string { | ||||
|     if ($profile !== false) { | ||||
|       $profile ??= $this->getProfile(); | ||||
|       [$dir, $filename] = path::split($file); | ||||
|       $basename = path::basename($filename); | ||||
|       $ext = path::ext($file); | ||||
|       $file = path::join($dir, "$basename.$profile$ext"); | ||||
|     } | ||||
|     return $file; | ||||
|   } | ||||
| 
 | ||||
|   function findFile(array $dirs, array $names, $profile=null): string { | ||||
|     # d'abord chercher avec le profil
 | ||||
|     if ($profile !== false) { | ||||
|       foreach ($dirs as $dir) { | ||||
|         foreach ($names as $name) { | ||||
|           $file = path::join($dir, $name); | ||||
|           $file = $this->withProfile($file, $profile); | ||||
|           if (file_exists($file)) return $file; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     # puis sans profil
 | ||||
|     foreach ($dirs as $dir) { | ||||
|       foreach ($names as $name) { | ||||
|         $file = path::join($dir, $name); | ||||
|         if (file_exists($file)) return $file; | ||||
|       } | ||||
|     } | ||||
|     # la valeur par défaut est avec profil
 | ||||
|     return $this->withProfile(path::join($dirs[0], $names[0]), $profile); | ||||
|   } | ||||
| 
 | ||||
|   function fencedJoin(string $basedir, ?string ...$paths): string { | ||||
|     $path = path::reljoin($basedir, ...$paths); | ||||
|     if (!path::is_within($path, $basedir)) { | ||||
|       throw ValueException::invalid_value($path, "path"); | ||||
|     } | ||||
|     return $path; | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
|   # Paramètres spécifiques à cette application
 | ||||
| 
 | ||||
|   protected ?string $appgroup; | ||||
| 
 | ||||
|   function getAppgroup(): ?string { | ||||
|     return $this->appgroup; | ||||
|   } | ||||
| 
 | ||||
|   protected string $name; | ||||
| 
 | ||||
|   function getName(): ?string { | ||||
|     return $this->name; | ||||
|   } | ||||
| 
 | ||||
|   protected ?string $title; | ||||
| 
 | ||||
|   function getTitle(): ?string { | ||||
|     return $this->title; | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
|   # Méthodes outils
 | ||||
| 
 | ||||
|   /** recréer le tableau des paramètres */ | ||||
|   function getParams(): array { | ||||
|     return [ | ||||
|       "projdir" => $this->projdir, | ||||
|       "vendor" => $this->vendor, | ||||
|       "appcode" => $this->appcode, | ||||
|       "cwd" => $this->cwd, | ||||
|       "datadir" => $this->datadir, | ||||
|       "etcdir" => $this->etcdir, | ||||
|       "vardir" => $this->vardir, | ||||
|       "logdir" => $this->logdir, | ||||
|       "profile" => $this->getProfile(), | ||||
|       "facts" => $this->facts, | ||||
|       "debug" => $this->debug, | ||||
|       "appgroup" => $this->appgroup, | ||||
|       "name" => $this->name, | ||||
|       "title" => $this->title, | ||||
|     ]; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir le chemin vers le fichier de configuration. par défaut, retourner | ||||
|    * une valeur de la forme "$ETCDIR/$name[.$profile].conf" | ||||
|    */ | ||||
|   function getEtcfile(?string $name=null, $profile=null): string { | ||||
|     if ($name === null) $name = "{$this->name}.conf"; | ||||
|     return $this->findFile([$this->etcdir], [$name], $profile); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir le chemin vers le fichier de travail. par défaut, retourner une | ||||
|    * valeur de la forme "$VARDIR/$appgroup/$name[.$profile].tmp" | ||||
|    */ | ||||
|   function getVarfile(?string $name=null, $profile=null): string { | ||||
|     if ($name === null) $name = "{$this->name}.tmp"; | ||||
|     $file = $this->fencedJoin($this->vardir, $this->appgroup, $name); | ||||
|     $file = $this->withProfile($file, $profile); | ||||
|     sh::mkdirof($file); | ||||
|     return $file; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir le chemin vers le fichier de log. par défaut, retourner une | ||||
|    * valeur de la forme "$LOGDIR/$appgroup/$name.log" (sans le profil, parce | ||||
|    * qu'il s'agit du fichier de log par défaut) | ||||
|    * | ||||
|    * Si $name est spécifié, la valeur retournée sera de la forme | ||||
|    * "$LOGDIR/$appgroup/$basename[.$profile].$ext" | ||||
|    */ | ||||
|   function getLogfile(?string $name=null, $profile=null): string { | ||||
|     if ($name === null) { | ||||
|       $name = "{$this->name}.log"; | ||||
|       $profile ??= false; | ||||
|     } | ||||
|     $file = $this->fencedJoin($this->logdir, $this->appgroup, $name); | ||||
|     $file = $this->withProfile($file, $profile); | ||||
|     sh::mkdirof($file); | ||||
|     return $file; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir le chemin absolu vers un fichier de travail | ||||
|    * - si le chemin est absolu, il est inchangé | ||||
|    * - sinon le chemin est exprimé par rapport à $vardir/$appgroup | ||||
|    * | ||||
|    * is $ensureDir, créer le répertoire du fichier s'il n'existe pas déjà | ||||
|    * | ||||
|    * la différence avec {@link self::getVarfile()} est que le fichier peut | ||||
|    * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de | ||||
|    * valeur par défaut pour $file | ||||
|    */ | ||||
|   function getWorkfile(string $file, $profile=null, bool $ensureDir=true): string { | ||||
|     $file = path::reljoin($this->vardir, $this->appgroup, $file); | ||||
|     $file = $this->withProfile($file, $profile); | ||||
|     if ($ensureDir) sh::mkdirof($file); | ||||
|     return $file; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir le chemin absolu vers un fichier spécifié par l'utilisateur. | ||||
|    * - si le chemin commence par /, il est laissé en l'état | ||||
|    * - si le chemin commence par ./ ou ../, il est exprimé par rapport à $cwd | ||||
|    * - sinon le chemin est exprimé par rapport à $vardir/$appgroup | ||||
|    * | ||||
|    * la différence est avec {@link self::getVarfile()} est que le fichier peut | ||||
|    * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de | ||||
|    * valeur par défaut pour $file | ||||
|    */ | ||||
|   function getUserfile(string $file): string { | ||||
|     if (path::is_qualified($file)) { | ||||
|       return path::reljoin($this->cwd, $file); | ||||
|     } else { | ||||
|       return path::reljoin($this->vardir, $this->appgroup, $file); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   protected ?RunFile $runfile = null; | ||||
| 
 | ||||
|   function getRunfile(): RunFile { | ||||
|     $name = $this->name; | ||||
|     $runfile = $this->getWorkfile($name); | ||||
|     $logfile = $this->getLogfile("$name.out", false); | ||||
|     return $this->runfile ??= new RunFile($name, $runfile, $logfile); | ||||
|   } | ||||
| 
 | ||||
|   protected ?array $lockFiles = null; | ||||
| 
 | ||||
|   function getLockfile(?string $name=null): LockFile { | ||||
|     $this->lockFiles[$name] ??= $this->getRunfile()->getLockFile($name, $this->title); | ||||
|     return $this->lockFiles[$name]; | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
| 
 | ||||
|   const EC_FORK_CHILD = 250; | ||||
|   const EC_FORK_PARENT = 251; | ||||
|   const EC_DISABLED = 252; | ||||
|   const EC_LOCKED = 253; | ||||
|   const EC_BAD_COMMAND = 254; | ||||
|   const EC_UNEXPECTED = 255; | ||||
| 
 | ||||
|   #############################################################################
 | ||||
| 
 | ||||
|   static bool $dispach_signals = false; | ||||
| 
 | ||||
|   static function install_signal_handler(bool $allow=true): void { | ||||
|     if (!$allow) return; | ||||
|     $signalHandler = function(int $signo, $siginfo) { | ||||
|       throw new ExitError(128 + $signo); | ||||
|     }; | ||||
|     pcntl_signal(SIGHUP, $signalHandler); | ||||
|     pcntl_signal(SIGINT, $signalHandler); | ||||
|     pcntl_signal(SIGQUIT, $signalHandler); | ||||
|     pcntl_signal(SIGTERM, $signalHandler); | ||||
|     self::$dispach_signals = true; | ||||
|   } | ||||
| 
 | ||||
|   static function _dispatch_signals() { | ||||
|     if (self::$dispach_signals) pcntl_signal_dispatch(); | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
| 
 | ||||
|   static ?func $bgapplication_enabled = null; | ||||
| 
 | ||||
|   /** | ||||
|    * spécifier la fonction permettant de vérifier si l'exécution de tâches | ||||
|    * de fond est autorisée. Si cette méthode n'est pas utilisée, par défaut, | ||||
|    * les tâches planifiées sont autorisées | ||||
|    * | ||||
|    * si $func===true, spécifier une fonction qui retourne toujours vrai | ||||
|    * si $func===false, spécifiée une fonction qui retourne toujours faux | ||||
|    * sinon, $func doit être une fonction valide | ||||
|    */ | ||||
|   static function set_bgapplication_enabled($func): void { | ||||
|     if (is_bool($func)) { | ||||
|       $enabled = $func; | ||||
|       $func = function () use ($enabled) { | ||||
|         return $enabled; | ||||
|       }; | ||||
|     } | ||||
|     self::$bgapplication_enabled = func::with($func); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Si les exécutions en tâche de fond sont autorisée, retourner. Sinon | ||||
|    * afficher une erreur et quitter l'application | ||||
|    */ | ||||
|   static function check_bgapplication_enabled(bool $forceEnabled=false): void { | ||||
|     if (self::$bgapplication_enabled === null || $forceEnabled) return; | ||||
|     if (!self::$bgapplication_enabled->invoke()) { | ||||
|       throw new ExitError(self::EC_DISABLED, "Planifications désactivées. La tâche n'a pas été lancée"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
| 
 | ||||
|   static function action(?string $title, ?int $maxSteps=null): void { | ||||
|     self::get()->getRunfile()->action($title, $maxSteps); | ||||
|   } | ||||
| 
 | ||||
|   static function step(int $nbSteps=1): void { | ||||
|     self::get()->getRunfile()->step($nbSteps); | ||||
|   } | ||||
| } | ||||
| @ -1,108 +0,0 @@ | ||||
| <?php | ||||
| 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 | ||||
|    * 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. | ||||
|    */ | ||||
|   protected 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
 | ||||
|     $eoo = false; // l'option a-t-elle été terminée?
 | ||||
|     while ($desti < $destmax && $srci < $srcmax) { | ||||
|       $opt = $src[$srci]; | ||||
|       $srci++; | ||||
|       $desti++; | ||||
|       if ($opt === "--") { | ||||
|         # fin des arguments facultatifs en entrée
 | ||||
|         $eoo = true; | ||||
|         if ($keepsep) $dest[] = $opt; | ||||
|         break; | ||||
|       } | ||||
|       $dest[] = $opt; | ||||
|     } | ||||
|     if (!$eoo && $desti < $destmax) { | ||||
|       # pas assez d'arguments en entrée, terminer avec "--"
 | ||||
|       $dest[] = "--"; | ||||
|     } | ||||
|     return 0; | ||||
|   } | ||||
| 
 | ||||
|   abstract function normalize(array $args): array; | ||||
| 
 | ||||
|   /** @var object|array objet destination */ | ||||
|   protected $dest; | ||||
| 
 | ||||
|   protected function setDest(&$dest): void { | ||||
|     $this->dest =& $dest; | ||||
|   } | ||||
| 
 | ||||
|   protected function unsetDest(): void { | ||||
|     unset($this->dest); | ||||
|   } | ||||
| 
 | ||||
|   abstract function process(array $args); | ||||
| 
 | ||||
|   function parse(&$dest, array $args=null): void { | ||||
|     if ($args === null) { | ||||
|       global $argv; | ||||
|       $args = array_slice($argv, 1); | ||||
|     } | ||||
|     $args = $this->normalize($args); | ||||
|     $dest ??= new stdClass(); | ||||
|     $this->setDest($dest); | ||||
|     $this->process($args); | ||||
|     $this->unsetDest(); | ||||
|   } | ||||
| 
 | ||||
|   abstract function actionPrintHelp(string $arg): void; | ||||
| } | ||||
| @ -1,620 +0,0 @@ | ||||
| <?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"; | ||||
|   } | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| <?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]); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,268 +0,0 @@ | ||||
| <?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); | ||||
|   } | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\cli; | ||||
| 
 | ||||
| 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"; | ||||
|   } | ||||
| } | ||||
| @ -1,378 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\cli; | ||||
| 
 | ||||
| use Exception; | ||||
| use nulib\app\app; | ||||
| use nulib\app\RunFile; | ||||
| use nulib\ExitError; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\output\console; | ||||
| use nulib\output\log; | ||||
| use nulib\output\msg; | ||||
| use nulib\output\std\StdMessenger; | ||||
| use nulib\ValueException; | ||||
| use nur\config; | ||||
| 
 | ||||
| /** | ||||
|  * Class Application: application de base | ||||
|  */ | ||||
| abstract class Application { | ||||
|   /** @var string répertoire du projet (celui qui contient composer.json */ | ||||
|   const PROJDIR = null; | ||||
| 
 | ||||
|   /** | ||||
|    * @var array répertoires vendor exprimés relativement à PROJDIR | ||||
|    * | ||||
|    * les clés suivantes doivent être présentes dans le tableau: | ||||
|    * - autoload (chemin vers vendor/autoload.php) | ||||
|    * - bindir (chemin vers vendor/bin) | ||||
|    */ | ||||
|   const VENDOR = null; | ||||
| 
 | ||||
|   /** | ||||
|    * @var string code du projet, utilisé pour dériver le noms de certains des | ||||
|    * paramètres extraits de l'environnement, e.g XXX_YYY_DATADIR si le projet a | ||||
|    * pour code xxx-yyy | ||||
|    * | ||||
|    * si non définie, cette valeur est calculée automatiquement à partir de | ||||
|    * self::PROJDIR sans le suffixe "-app" | ||||
|    */ | ||||
|   const APPCODE = null; | ||||
| 
 | ||||
|   /** | ||||
|    * @var string|null identifiant d'un groupe auquel l'application appartient. | ||||
|    * les applications du même groupe enregistrent leur fichiers de controle au | ||||
|    * même endroit $VARDIR/$APPGROUP | ||||
|    */ | ||||
|   const APPGROUP = null; | ||||
| 
 | ||||
|   /** | ||||
|    * @var string code de l'application, utilisé pour inférer le nom de certains | ||||
|    * fichiers spécifiques à l'application. | ||||
|    * | ||||
|    * si non définie, cette valeur est calculée automatiquement à partir de | ||||
|    * static::class | ||||
|    */ | ||||
|   const NAME = null; | ||||
| 
 | ||||
|   /** @var string description courte de l'application */ | ||||
|   const TITLE = null; | ||||
| 
 | ||||
|   const DATADIR = null; | ||||
|   const ETCDIR = null; | ||||
|   const VARDIR = null; | ||||
|   const LOGDIR = null; | ||||
| 
 | ||||
|   /** @var bool faut-il activer automatiquement l'écriture dans les logs */ | ||||
|   const USE_LOGFILE = null; | ||||
| 
 | ||||
|   /** @var bool faut-il maintenir un fichier de suivi du process? */ | ||||
|   const USE_RUNFILE = false; | ||||
| 
 | ||||
|   /** | ||||
|    * @var bool faut-il empêcher deux instances de cette application de se lancer | ||||
|    * en même temps? | ||||
|    * | ||||
|    * nécessite USE_RUNFILE==true | ||||
|    */ | ||||
|   const USE_RUNLOCK = false; | ||||
| 
 | ||||
|   /** @var bool faut-il installer le gestionnaire de signaux? */ | ||||
|   const INSTALL_SIGNAL_HANDLER = false; | ||||
| 
 | ||||
|   private static function _info(string $message, int $ec=0): int { | ||||
|     fwrite(STDERR, "INFO: $message\n"); | ||||
|     return $ec; | ||||
|   } | ||||
| 
 | ||||
|   private static function _error(string $message, int $ec=1): int { | ||||
|     fwrite(STDERR, "ERROR: $message\n"); | ||||
|     return $ec; | ||||
|   } | ||||
| 
 | ||||
|   static function _manage_runfile(int &$argc, array &$argv, RunFile $runfile): void { | ||||
|     if ($argc <= 1 || $argv[1] !== "//") return; | ||||
|     array_splice($argv, 1, 1); $argc--; | ||||
|     $ec = 0; | ||||
|     switch ($argv[1] ?? "infos") { | ||||
|     case "help": | ||||
|       self::_info(<<<EOT | ||||
| Valid commands: | ||||
|   infos | ||||
|   dump | ||||
|   reset | ||||
|   release | ||||
|   start | ||||
|   kill | ||||
| 
 | ||||
| EOT); | ||||
|       break; | ||||
|     case "infos": | ||||
|     case "i": | ||||
|       $desc = $runfile->getDesc(); | ||||
|       echo implode("\n", $desc["message"])."\n"; | ||||
|       $ec = $desc["exitcode"] ?? 0; | ||||
|       break; | ||||
|     case "dump": | ||||
|     case "d": | ||||
|       yaml::dump($runfile->read()); | ||||
|       break; | ||||
|     case "reset": | ||||
|     case "z": | ||||
|       if (!$runfile->isRunning()) $runfile->reset(); | ||||
|       else $ec = self::_error("cannot reset while running"); | ||||
|       break; | ||||
|     case "release": | ||||
|     case "rl": | ||||
|       $runfile->release(); | ||||
|       break; | ||||
|     case "start": | ||||
|     case "s": | ||||
|       array_splice($argv, 1, 1); $argc--; | ||||
|       return; | ||||
|     case "kill": | ||||
|     case "k": | ||||
|       if ($runfile->isRunning()) $runfile->wfKill(); | ||||
|       else $ec = self::_error("not running"); | ||||
|       break; | ||||
|     default: | ||||
|       $ec = self::_error("$argv[1]: unexpected command", app::EC_BAD_COMMAND); | ||||
|     } | ||||
|     exit($ec); | ||||
|   } | ||||
| 
 | ||||
|   static function run(?Application $app=null): void { | ||||
|     $unlock = false; | ||||
|     $stop = false; | ||||
|     $shutdown = function () use (&$unlock, &$stop) { | ||||
|       if ($unlock) { | ||||
|         app::get()->getRunfile()->release(); | ||||
|         $unlock = false; | ||||
|       } | ||||
|       if ($stop) { | ||||
|         app::get()->getRunfile()->wfStop(); | ||||
|         $stop = false; | ||||
|       } | ||||
|     }; | ||||
|     register_shutdown_function($shutdown); | ||||
|     app::install_signal_handler(static::INSTALL_SIGNAL_HANDLER); | ||||
|     try { | ||||
|       static::_initialize_app(); | ||||
|       $useRunfile = static::USE_RUNFILE; | ||||
|       $useRunlock = static::USE_RUNLOCK; | ||||
|       if ($useRunfile) { | ||||
|         $runfile = app::get()->getRunfile(); | ||||
| 
 | ||||
|         global $argc, $argv; | ||||
|         self::_manage_runfile($argc, $argv, $runfile); | ||||
|         if ($useRunlock && $runfile->warnIfLocked()) exit(app::EC_LOCKED); | ||||
| 
 | ||||
|         $runfile->wfStart(); | ||||
|         $stop = true; | ||||
|         if ($useRunlock) { | ||||
|           $runfile->lock(); | ||||
|           $unlock = true; | ||||
|         } | ||||
|       } | ||||
|       if ($app === null) $app = new static(); | ||||
|       static::_configure_app($app); | ||||
|       static::_start_app($app); | ||||
|     } catch (ExitError $e) { | ||||
|       if ($e->haveUserMessage()) msg::error($e->getUserMessage()); | ||||
|       exit($e->getCode()); | ||||
|     } catch (Exception $e) { | ||||
|       msg::error($e); | ||||
|       exit(app::EC_UNEXPECTED); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   protected static function _initialize_app(): void { | ||||
|     app::init(static::class); | ||||
|     app::set_fact(app::FACT_CLI_APP); | ||||
|     msg::set_messenger(new StdMessenger([ | ||||
|       "min_level" => msg::DEBUG, | ||||
|     ])); | ||||
|   } | ||||
| 
 | ||||
|   protected static function _configure_app(Application $app): void { | ||||
|     config::configure(config::CONFIGURE_INITIAL_ONLY); | ||||
| 
 | ||||
|     $msgs = null; | ||||
|     $msgs["console"] = new StdMessenger([ | ||||
|       "min_level" => msg::NORMAL, | ||||
|     ]); | ||||
|     if (static::USE_LOGFILE) { | ||||
|       $msgs["log"] = new StdMessenger([ | ||||
|         "output" => app::get()->getLogfile(), | ||||
|         "min_level" => msg::MINOR, | ||||
|         "add_date" => true, | ||||
|       ]); | ||||
|     } | ||||
|     msg::init($msgs); | ||||
| 
 | ||||
|     $app->parseArgs(); | ||||
|     config::configure(); | ||||
|   } | ||||
| 
 | ||||
|   protected static function _start_app(Application $app): void { | ||||
|     $retcode = $app->main(); | ||||
|     if (is_int($retcode)) exit($retcode); | ||||
|     elseif (is_bool($retcode)) exit($retcode? 0: 1); | ||||
|     elseif ($retcode !== null) exit(strval($retcode)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * sortir de l'application avec un code d'erreur, qui est 0 par défaut (i.e | ||||
|    * pas d'erreur) | ||||
|    * | ||||
|    * équivalent à lancer l'exception {@link ExitError} | ||||
|    */ | ||||
|   protected static final function exit(int $exitcode=0, $message=null) { | ||||
|     throw new ExitError($exitcode, $message); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * sortir de l'application avec un code d'erreur, qui vaut 1 par défaut (i.e | ||||
|    * une erreur s'est produite) | ||||
|    * | ||||
|    * équivalent à lancer l'exception {@link ExitError} | ||||
|    */ | ||||
|   protected static final function die($message=null, int $exitcode=1) { | ||||
|     throw new ExitError($exitcode, $message); | ||||
|   } | ||||
| 
 | ||||
|   const PROFILE_SECTION = [ | ||||
|     "title" => "PROFILS D'EXECUTION", | ||||
|     "show" => false, | ||||
|     ["group", | ||||
|       ["-p", "--profile", "--app-profile", | ||||
|         "args" => "profile", | ||||
|         "action" => [app::class, "set_profile"], | ||||
|         "help" => "spécifier le profil d'exécution", | ||||
|       ], | ||||
|       ["-P", "--prod", "action" => [app::class, "set_profile", config::PROD]], | ||||
|       ["-T", "--test", "action" => [app::class, "set_profile", config::TEST]], | ||||
|       ["--devel", "action" => [app::class, "set_profile", config::DEVEL]], | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   const VERBOSITY_SECTION = [ | ||||
|     "title" => "NIVEAU D'INFORMATION", | ||||
|     "show" => false, | ||||
|     ["group", | ||||
|       ["--verbosity", | ||||
|         "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", | ||||
|         "action" => [null, "set_application_verbosity"], | ||||
|         "help" => "spécifier le niveau d'informations affiché", | ||||
|       ], | ||||
|       ["-q", "--quiet", "action" => [null, "set_application_verbosity", "quiet"]], | ||||
|       ["-v", "--verbose", "action" => [null, "set_application_verbosity", "verbose"]], | ||||
|       ["-D", "--debug", "action" => [null, "set_application_verbosity", "debug"]], | ||||
|     ], | ||||
|     ["-L", "--logfile", | ||||
|       "args" => "output", | ||||
|       "action" => [null, "set_application_log_output"], | ||||
|       "help" => "Logger les messages de l'application dans le fichier spécifié", | ||||
|     ], | ||||
|     ["group", | ||||
|       ["--color", | ||||
|         "action" => [null, "set_application_color", true], | ||||
|         "help" => "Afficher (resp. ne pas afficher) la sortie en couleur par défaut", | ||||
|       ], | ||||
|       ["--no-color", "action" => [null, "set_application_color", false]], | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   static function set_application_verbosity(string $verbosity): void { | ||||
|     $console = console::get(); | ||||
|     switch ($verbosity) { | ||||
|     case "Q": | ||||
|     case "silent": | ||||
|       $console->resetParams([ | ||||
|         "min_level" => msg::NONE, | ||||
|       ]); | ||||
|       break; | ||||
|     case "q": | ||||
|     case "quiet": | ||||
|       $console->resetParams([ | ||||
|         "min_level" => msg::MAJOR, | ||||
|       ]); | ||||
|       break; | ||||
|     case "n": | ||||
|     case "normal": | ||||
|       $console->resetParams([ | ||||
|         "min_level" => msg::NORMAL, | ||||
|       ]); | ||||
|       break; | ||||
|     case "v": | ||||
|     case "verbose": | ||||
|       $console->resetParams([ | ||||
|         "min_level" => msg::MINOR, | ||||
|       ]); | ||||
|       break; | ||||
|     case "D": | ||||
|     case "debug": | ||||
|       config::set_debug(); | ||||
|       $console->resetParams([ | ||||
|         "min_level" => msg::DEBUG, | ||||
|       ]); | ||||
|       break; | ||||
|     default: | ||||
|       throw ValueException::invalid_value($verbosity, "verbosity"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static function set_application_log_output(string $logfile): void { | ||||
|     log::create_or_reset_params([ | ||||
|       "output" => $logfile, | ||||
|     ], StdMessenger::class, [ | ||||
|       "add_date" => true, | ||||
|       "min_level" => log::MINOR, | ||||
|     ]); | ||||
|   } | ||||
|   static function set_application_color(bool $color): void { | ||||
|     console::reset_params([ | ||||
|       "color" => $color, | ||||
|     ]); | ||||
|   } | ||||
|   const ARGS = [ | ||||
|     "sections" => [ | ||||
|       self::PROFILE_SECTION, | ||||
|       self::VERBOSITY_SECTION, | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   protected function getArgsParser(): AbstractArgsParser { | ||||
|     return new SimpleArgsParser(static::ARGS); | ||||
|   } | ||||
| 
 | ||||
|   /** @throws ArgsException */ | ||||
|   function parseArgs(array $args=null): void { | ||||
|     $this->getArgsParser()->parse($this, $args); | ||||
|   } | ||||
| 
 | ||||
|   const PROFILE_COLORS = [ | ||||
|     "prod" => "@r", | ||||
|     "test" => "@g", | ||||
|     "devel" => "@w", | ||||
|   ]; | ||||
|   const DEFAULT_PROFILE_COLOR = "y"; | ||||
| 
 | ||||
|   /** retourner le profil courant en couleur */ | ||||
|   static function get_profile(?string $profile=null): string { | ||||
|     if ($profile === null) $profile = app::get_profile(); | ||||
|     foreach (static::PROFILE_COLORS as $text => $color) { | ||||
|       if (strpos($profile, $text) !== false) { | ||||
|         return $color? "<color $color>$profile</color>": $profile; | ||||
|       } | ||||
|     } | ||||
|     $color = static::DEFAULT_PROFILE_COLOR; | ||||
|     return $color? "<color $color>$profile</color>": $profile; | ||||
|   } | ||||
| 
 | ||||
|   abstract function main(); | ||||
| 
 | ||||
|   static function runfile(): RunFile { | ||||
|     return app::with(static::class)->getRunfile(); | ||||
|   } | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| <?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,187 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\cli; | ||||
| 
 | ||||
| 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,247 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\cli; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\ExitError; | ||||
| use nulib\StateException; | ||||
| 
 | ||||
| class SimpleArgsParser extends AbstractArgsParser { | ||||
|   function __construct(array $defs) { | ||||
|     global $argv; | ||||
|     $defs["name"] ??= basename($argv[0]); | ||||
|     $this->aolist = new SimpleAolist($defs); | ||||
|   } | ||||
| 
 | ||||
|   protected SimpleAolist $aolist; | ||||
| 
 | ||||
|   protected function getArgdef(string $option): ?Aodef { | ||||
|     return $this->aolist->get($option); | ||||
|   } | ||||
| 
 | ||||
|   protected function getOptions(): array { | ||||
|     return $this->aolist->getOptions(); | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|         } | ||||
|         $argdef = $this->getArgdef($option); | ||||
|         if ($argdef === null) { | ||||
|           # chercher une correspondance
 | ||||
|           $len = strlen($option); | ||||
|           $candidates = []; | ||||
|           foreach ($this->getOptions() as $candidate) { | ||||
|             if (substr($candidate, 0, $len) === $option) { | ||||
|               $candidates[] = $candidate; | ||||
|             } | ||||
|           } | ||||
|           switch (count($candidates)) { | ||||
|           case 0: throw $this->invalidArg($option); | ||||
|           case 1: $option = $candidates[0]; break; | ||||
|           default: throw $this->ambiguousArg($option, $candidates); | ||||
|           } | ||||
|           $argdef = $this->getArgdef($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->checkEnoughArgs($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 $this->tooManyArgs(1, 0, $option); | ||||
|         } else { | ||||
|           $options[] = $option; | ||||
|         } | ||||
| 
 | ||||
|       } elseif (substr($arg, 0, 1) === "-") { | ||||
|         #######################################################################
 | ||||
|         # option courte
 | ||||
|         $pos = 1; | ||||
|         $len = strlen($arg); | ||||
|         while ($pos < $len) { | ||||
|           $option = "-".substr($arg, $pos, 1); | ||||
|           $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); | ||||
|               $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->checkEnoughArgs($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); | ||||
|   } | ||||
| 
 | ||||
|   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,21 +0,0 @@ | ||||
| # cli | ||||
| 
 | ||||
| * [ ] dans la section "profils", rajouter une option pour spécifier un fichier de configuration | ||||
| * [ ] 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 | ||||
| 
 | ||||
| ## support des commandes | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| ## BUGS | ||||
| 
 | ||||
| -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary | ||||
| @ -1,41 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app; | ||||
| 
 | ||||
| use nulib\app\config\ConfigManager; | ||||
| use nulib\cl; | ||||
| 
 | ||||
| /** | ||||
|  * Class config: gestion de la configuration de l'application | ||||
|  */ | ||||
| class config { | ||||
|   protected static ConfigManager $config; | ||||
| 
 | ||||
|   static function init_configurator($configurators): void { | ||||
|     self::$config->addConfigurator($configurators); | ||||
|   } | ||||
| 
 | ||||
|   # certains types de configurations sont normalisés
 | ||||
|   /** ne configurer que le minimum pour que l'application puisse s'initialiser */ | ||||
|   const CONFIGURE_INITIAL_ONLY = ["include" => "initial"]; | ||||
|   /** ne configurer que les routes */ | ||||
|   const CONFIGURE_ROUTES_ONLY = ["include" => "routes"]; | ||||
|   /** configurer uniquement ce qui ne nécessite pas d'avoir une session */ | ||||
|   const CONFIGURE_NO_SESSION = ["exclude" => "session"]; | ||||
| 
 | ||||
|   static function configure(?array $params=null): void { | ||||
|     self::$config->configure($params); | ||||
|   } | ||||
|    | ||||
|   static final function add($config, string ...$profiles): void { self::$config->addConfig($config, $profiles); } | ||||
|   static final function get(string $pkey, $default=null, ?string $profile=null) { return self::$config->getValue($pkey, $default, $profile); } | ||||
|   static final function k(string $pkey, $default=null) { return self::$config->getValue("app.$pkey", $default); } | ||||
|   static final function db(string $pkey, $default=null) { return self::$config->getValue("dbs.$pkey", $default); } | ||||
|   static final function m(string $pkey, $default=null) { return self::$config->getValue("msgs.$pkey", $default); } | ||||
|   static final function l(string $pkey, $default=null) { return self::$config->getValue("mails.$pkey", $default); } | ||||
| } | ||||
| 
 | ||||
| new class extends config { | ||||
|   function __construct() { | ||||
|     self::$config = new ConfigManager(); | ||||
|   } | ||||
| }; | ||||
| @ -1,50 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| 
 | ||||
| class ArrayConfig implements IConfig { | ||||
|   protected function APP(): array { | ||||
|     return static::APP; | ||||
|   } const APP = []; | ||||
| 
 | ||||
|   protected function DBS(): array { | ||||
|     return static::DBS; | ||||
|   } const DBS = []; | ||||
| 
 | ||||
|   protected function MSGS(): array { | ||||
|     return static::MSGS; | ||||
|   } const MSGS = []; | ||||
| 
 | ||||
|   protected function MAILS(): array { | ||||
|     return static::MAILS; | ||||
|   } const MAILS = []; | ||||
| 
 | ||||
|   function __construct(?array $config) { | ||||
|     foreach (self::CONFIG_KEYS as $key) { | ||||
|       switch ($key) { | ||||
|       case "app": $default = $this->APP(); break; | ||||
|       case "dbs": $default = $this->DBS(); break; | ||||
|       case "msgs": $default = $this->MSGS(); break; | ||||
|       case "mails": $default = $this->MAILS(); break; | ||||
|       default: $default = []; | ||||
|       } | ||||
|       $config[$key] ??= $default; | ||||
|     } | ||||
|     $this->config = $config; | ||||
|   } | ||||
| 
 | ||||
|   protected array $config; | ||||
| 
 | ||||
|   function has(string $pkey, string $profile): bool { | ||||
|     return cl::phas($this->config, $pkey); | ||||
|   } | ||||
| 
 | ||||
|   function get(string $pkey, string $profile) { | ||||
|     return cl::pget($this->config, $pkey); | ||||
|   } | ||||
| 
 | ||||
|   function set(string $pkey, $value, string $profile): void { | ||||
|     cl::pset($this->config, $pkey, $value); | ||||
|   } | ||||
| } | ||||
| @ -1,150 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\app\app; | ||||
| use nulib\cl; | ||||
| use nulib\php\func; | ||||
| use nulib\ValueException; | ||||
| use ReflectionClass; | ||||
| 
 | ||||
| class ConfigManager { | ||||
|   protected array $configurators = []; | ||||
| 
 | ||||
|   /** ajouter une classe ou un objet à la liste des configurateurs */ | ||||
|   function addConfigurator($configurators): void { | ||||
|     A::merge($this->configurators, cl::with($configurators)); | ||||
|   } | ||||
| 
 | ||||
|   protected array $configured = []; | ||||
| 
 | ||||
|   /** | ||||
|    * configurer les objets et les classes qui ne l'ont pas encore été. la liste | ||||
|    * des objets et des classes à configurer est fournie en appelant la méthode | ||||
|    * {@link addConfigurator()} | ||||
|    * | ||||
|    * par défaut, la configuration se fait en appelant toutes les méthodes | ||||
|    * publiques des objets et toutes les méthodes statiques des classes qui | ||||
|    * commencent par 'configure', e.g 'configureThis()' ou 'configure_db()', | ||||
|    * si elles n'ont pas déjà été appelées | ||||
|    * | ||||
|    * Il est possible de modifier la liste des méthodes appelées avec le tableau | ||||
|    * $params, qui doit être conforme au schema de {@link func::CALL_ALL_SCHEMA} | ||||
|    */ | ||||
|   function configure(?array $params=null): void { | ||||
|     $params["prefix"] ??= "configure"; | ||||
|     foreach ($this->configurators as $key => $configurator) { | ||||
|       $configured =& $this->configured[$key]; | ||||
|       /** @var func[] $methods */ | ||||
|       $methods = func::get_all($configurator, $params); | ||||
|       foreach ($methods as $method) { | ||||
|         $name = $method->getName() ?? "(no name)"; | ||||
|         $done = $configured[$name] ?? false; | ||||
|         if (!$done) { | ||||
|           $method->invoke(); | ||||
|           $configured[$name] = true; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   #############################################################################
 | ||||
| 
 | ||||
|   protected $cache = []; | ||||
| 
 | ||||
|   protected function resetCache(): void { | ||||
|     $this->cache = []; | ||||
|   } | ||||
| 
 | ||||
|   protected function cacheHas(string $pkey, string $profile) { | ||||
|     return array_key_exists("$profile.$pkey", $this->cache); | ||||
|   } | ||||
|    | ||||
|   protected function cacheGet(string $pkey, string $profile) { | ||||
|     return cl::get($this->cache, "$profile.$pkey"); | ||||
|   } | ||||
|    | ||||
|   protected function cacheSet(string $pkey, $value, string $profile): void { | ||||
|     $this->cache["$profile.$pkey"] = $value; | ||||
|   } | ||||
| 
 | ||||
|   protected array $profileConfigs = []; | ||||
| 
 | ||||
|   /** | ||||
|    * Ajouter une configuration valide pour le(s) profil(s) spécifié(s) | ||||
|    * | ||||
|    * $config est un objet ou une classe qui définit une ou plusieurs des | ||||
|    * constantes APP, DBS, MSGS, MAILS | ||||
|    * | ||||
|    * si $inProfiles===null, la configuration est valide dans tous les profils | ||||
|    */ | ||||
|   function addConfig($config, ?array $inProfiles=null): void { | ||||
|     if (is_string($config)) { | ||||
|       $c = new ReflectionClass($config); | ||||
|       if ($c->implementsInterface(IConfig::class)) { | ||||
|         $config = $c->newInstance(); | ||||
|       } else { | ||||
|         $config = []; | ||||
|         foreach (IConfig::CONFIG_KEYS as $key) { | ||||
|           $config[$key] = cl::with($c->getConstant(strtoupper($key))); | ||||
|         } | ||||
|         $config = new ArrayConfig($config); | ||||
|       } | ||||
|     } elseif (is_array($config)) { | ||||
|       $config = new ArrayConfig($config); | ||||
|     } elseif (!($config instanceof IConfig)) { | ||||
|       throw ValueException::invalid_type($config, "array|IConfig"); | ||||
|     } | ||||
| 
 | ||||
|     $inProfiles ??= [IConfig::PROFILE_ALL]; | ||||
|     foreach ($inProfiles as $profile) { | ||||
|       $this->profileConfigs[$profile][] = $config; | ||||
|     } | ||||
| 
 | ||||
|     $this->resetCache(); | ||||
|   } | ||||
| 
 | ||||
|   function _getValue(string $pkey, $default, string $inProfile) { | ||||
|     $profiles = [$inProfile]; | ||||
|     if ($inProfile !== IConfig::PROFILE_ALL) $profiles[] = IConfig::PROFILE_ALL; | ||||
|     $value = $default; | ||||
|     foreach ($profiles as $profile) { | ||||
|       /** @var IConfig[] $configs */ | ||||
|       $configs = $this->profileConfigs[$profile] ?? []; | ||||
|       foreach (array_reverse($configs) as $config) { | ||||
|         if ($config->has($pkey, $profile)) { | ||||
|           $value = $config->get($pkey, $profile); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir la valeur au chemin de clé $pkey dans le profil spécifié | ||||
|    * | ||||
|    * le $inProfile===null, prendre le profil par défaut. | ||||
|    */ | ||||
|   function getValue(string $pkey, $default=null, ?string $inProfile=null) { | ||||
|     $inProfile ??= app::get_profile(); | ||||
| 
 | ||||
|     if ($this->cacheHas($pkey, $inProfile)) { | ||||
|       return $this->cacheGet($pkey, $inProfile); | ||||
|     } | ||||
| 
 | ||||
|     $value = $this->_getValue($pkey, $default, $inProfile); | ||||
|     $this->cacheSet($pkey, $default, $inProfile); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   function setValue(string $pkey, $value, ?string $inProfile=null): void { | ||||
|     $inProfile ??= app::get_profile(); | ||||
|     /** @var IConfig[] $configs */ | ||||
|     $configs =& $this->profileConfigs[$inProfile]; | ||||
|     if ($configs === null) $key = 0; | ||||
|     else $key = array_key_last($configs); | ||||
|     $configs[$key] ??= new ArrayConfig([]); | ||||
|     $configs[$key]->set($pkey, $value, $inProfile); | ||||
|   } | ||||
| } | ||||
| @ -1,112 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\ext\json; | ||||
| use nulib\file; | ||||
| use nulib\str; | ||||
| 
 | ||||
| /** | ||||
|  * Class EnvConfig: configuration extraite depuis les variables d'environnement | ||||
|  * | ||||
|  * les variables doivent être de la forme {PREFIX}_{PROFILE}_{PKEY} où: | ||||
|  * - PREFIX vaut CONFIG, FILE_CONFIG, JSON_CONFIG ou JSON_FILE_CONFIG | ||||
|  * - PROFILE est le profil dans lequel la configuration est valide, ou ALL si | ||||
|  *   la valeur doit être valide dans tous les profils | ||||
|  * - PKEY est le chemin de clé dans lequel les caractères '.' sont remplacés | ||||
|  *   par '__' | ||||
|  * | ||||
|  * par exemple, la valeur dbs.my_auth.type du profil par défaut est pris dans | ||||
|  * la variable 'CONFIG_ALL_dbs__my_auth__type'. pour le profil prod c'est la | ||||
|  * variable 'CONFIG_prod_dbs__my_auth__type' | ||||
|  * | ||||
|  * pour représenter le tableau suivant: | ||||
|  *   [ "type" => "mysql", "name" => "mysql:host=authdb;dbname=auth;charset=utf8", | ||||
|  *     "user" => "auth_int", "pass" => "auth" ] | ||||
|  * situé au chemin de clé dbs.auth dans le profil prod, on peut par exemple | ||||
|  * définir les variables suivantes: | ||||
|  *   CONFIG_prod_dbs__auth__type="mysql" | ||||
|  *   CONFIG_prod_dbs__auth__name="mysql:host=authdb;dbname=auth;charset=utf8" | ||||
|  *   CONFIG_prod_dbs__auth__user="auth_int" | ||||
|  *   CONFIG_prod_dbs__auth__pass="auth" | ||||
|  * ou alternativement: | ||||
|  *   JSON_CONFIG_prod_dbs__auth='{"type":"mysql","name":"mysql:host=authdb;dbname=auth;charset=utf8","user":"auth_int","pass":"auth"}' | ||||
|  * | ||||
|  * Les préfixes supportés sont, dans l'ordre de précédence: | ||||
|  * - JSON_FILE_CONFIG -- une valeur au format JSON inscrite dans un fichier | ||||
|  * - JSON_CONFIG -- une valeur au format JSON | ||||
|  * - FILE_CONFIG -- une valeur inscrite dans un fichier | ||||
|  * - CONFIG -- une valeur scalaire | ||||
|  */ | ||||
| class EnvConfig implements IConfig{ | ||||
|   protected ?array $profileConfigs = null; | ||||
| 
 | ||||
|   /** analyser $name et retourner [$pkey, $profile] */ | ||||
|   private static function parse_pkey_profile($name): array { | ||||
|     $i = strpos($name, "_"); | ||||
|     if ($i === false) return [false, false]; | ||||
|     $profile = substr($name, 0, $i); | ||||
|     if ($profile === "ALL") $profile = IConfig::PROFILE_ALL; | ||||
|     $name = substr($name, $i + 1); | ||||
|     $pkey = str_replace("__", ".", $name); | ||||
|     return [$pkey, $profile]; | ||||
|   } | ||||
| 
 | ||||
|   function loadEnvConfig(): void { | ||||
|     if ($this->profileConfigs !== null) return; | ||||
|     $json_files = []; | ||||
|     $jsons = []; | ||||
|     $files = []; | ||||
|     $vars = []; | ||||
|     foreach (getenv() as $name => $value) { | ||||
|       if (str::starts_with("JSON_FILE_CONFIG_", $name)) { | ||||
|         $json_files[str::without_prefix("JSON_FILE_CONFIG_", $name)] = $value; | ||||
|       } elseif (str::starts_with("JSON_CONFIG_", $name)) { | ||||
|         $jsons[str::without_prefix("JSON_CONFIG_", $name)] = $value; | ||||
|       } elseif (str::starts_with("FILE_CONFIG_", $name)) { | ||||
|         $files[str::without_prefix("FILE_CONFIG_", $name)] = $value; | ||||
|       } elseif (str::starts_with("CONFIG_", $name)) { | ||||
|         $vars[str::without_prefix("CONFIG_", $name)] = $value; | ||||
|       } | ||||
|     } | ||||
|     $profileConfigs = []; | ||||
|     foreach ($json_files as $name => $file) { | ||||
|       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||
|       $value = json::load($file); | ||||
|       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||
|     } | ||||
|     foreach ($jsons as $name => $json) { | ||||
|       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||
|       $value = json::decode($json); | ||||
|       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||
|     } | ||||
|     foreach ($files as $name => $file) { | ||||
|       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||
|       $value = file::reader($file)->getContents(); | ||||
|       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||
|     } | ||||
|     foreach ($vars as $name => $value) { | ||||
|       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||
|       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||
|     } | ||||
|     $this->profileConfigs = $profileConfigs; | ||||
|   } | ||||
| 
 | ||||
|   function has(string $pkey, string $profile): bool { | ||||
|     $this->loadEnvConfig(); | ||||
|     $config = $this->profileConfigs[$profile] ?? null; | ||||
|     return cl::phas($config, $pkey); | ||||
|   } | ||||
| 
 | ||||
|   function get(string $pkey, string $profile) { | ||||
|     $this->loadEnvConfig(); | ||||
|     $config = $this->profileConfigs[$profile] ?? null; | ||||
|     return cl::pget($config, $pkey); | ||||
|   } | ||||
| 
 | ||||
|   function set(string $pkey, $value, string $profile): void { | ||||
|     $this->loadEnvConfig(); | ||||
|     $config =& $this->profileConfigs[$profile]; | ||||
|     cl::pset($config, $pkey, $value); | ||||
|   } | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| /** | ||||
|  * Interface IConfig: un objet fournissant une configuration | ||||
|  */ | ||||
| interface IConfig { | ||||
|   /** | ||||
|    * @var string profil indiquant qu'une configuration est valide dans tous les | ||||
|    * profils | ||||
|    */ | ||||
|   const PROFILE_ALL = "-ALL-"; | ||||
| 
 | ||||
|   const CONFIG_KEYS = [ | ||||
|     "app", | ||||
|     "dbs", "msgs", "mails", | ||||
|   ]; | ||||
| 
 | ||||
|   function has(string $pkey, string $profile): bool; | ||||
| 
 | ||||
|   function get(string $pkey, string $profile); | ||||
| 
 | ||||
|   function set(string $pkey, $value, string $profile): void; | ||||
| } | ||||
| @ -1,13 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| use nulib\ext\json; | ||||
| 
 | ||||
| /** | ||||
|  * Class JsonConfig: une configuration chargée depuis un fichier JSON | ||||
|  */ | ||||
| class JsonConfig extends ArrayConfig { | ||||
|   function __construct(string $input) { | ||||
|     parent::__construct(json::load($input)); | ||||
|   } | ||||
| } | ||||
| @ -1,142 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| use nulib\app\app; | ||||
| use nulib\app\config; | ||||
| 
 | ||||
| /** | ||||
|  * Class ProfileManager: gestionnaire de profils | ||||
|  */ | ||||
| class ProfileManager { | ||||
|   /** | ||||
|    * @var string code du système dont on gère le profil | ||||
|    * | ||||
|    * ce code est utilisé pour dériver le nom du paramètre dans la configuration | ||||
|    * ainsi que la variable d'environnement depuis laquelle est chargée la valeur | ||||
|    * du profil | ||||
|    */ | ||||
|   const NAME = null; | ||||
| 
 | ||||
|   /** @var array|null liste des profils valides */ | ||||
|   const PROFILES = null; | ||||
| 
 | ||||
|   /** @var array profils dont le mode production doit être actif */ | ||||
|   const PRODUCTION_MODES = [ | ||||
|     "prod" => true, | ||||
|     "test" => true, | ||||
|   ]; | ||||
| 
 | ||||
|   /** | ||||
|    * @var array mapping profil d'application --> profil effectif | ||||
|    * | ||||
|    * ce mapping est utilisé quand il faut calculer le profil courant s'il n'a | ||||
|    * pas été spécifié par l'utilisateur. il permet de faire correspondre le | ||||
|    * profil courant de l'application avec le profil effectif à sélectionner | ||||
|    */ | ||||
|   const PROFILE_MAP = null; | ||||
| 
 | ||||
|   function __construct(?array $params=null) { | ||||
|     $this->isAppProfile = $params["app"] ?? false; | ||||
|     $this->profiles = static::PROFILES; | ||||
|     $this->productionModes = static::PRODUCTION_MODES; | ||||
|     $this->profileMap = static::PROFILE_MAP; | ||||
|     $name = $params["name"] ?? static::NAME; | ||||
|     if ($name === null) { | ||||
|       $this->configKey = null; | ||||
|       $this->envKeys = ["APP_PROFILE"]; | ||||
|     } else { | ||||
|       $configKey = "${name}_profile"; | ||||
|       $envKey = strtoupper($configKey); | ||||
|       if ($this->isAppProfile) { | ||||
|         $this->configKey = null; | ||||
|         $this->envKeys = [$envKey, "APP_PROFILE"]; | ||||
|       } else { | ||||
|         $this->configKey = $configKey; | ||||
|         $this->envKeys = [$envKey]; | ||||
|       } | ||||
|     } | ||||
|     $this->defaultProfile = $params["default_profile"] ?? null; | ||||
|     $profile = $params["profile"] ?? null; | ||||
|     $productionMode = $params["production_mode"] ?? null; | ||||
|     $productionMode ??= $this->productionModes[$profile] ?? false; | ||||
|     $this->profile = $profile; | ||||
|     $this->productionMode = $productionMode; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @var bool cet objet est-il utilisé pour gérer le profil de l'application? | ||||
|    */ | ||||
|   protected bool $isAppProfile; | ||||
| 
 | ||||
|   protected ?array $profiles; | ||||
| 
 | ||||
|   protected ?array $productionModes; | ||||
| 
 | ||||
|   protected ?array $profileMap; | ||||
| 
 | ||||
|   protected function mapProfile(?string $profile): ?string { | ||||
|     return $this->profileMap[$profile] ?? $profile; | ||||
|   } | ||||
| 
 | ||||
|   protected ?string $configKey; | ||||
| 
 | ||||
|   function getConfigProfile(): ?string { | ||||
|     if ($this->configKey === null) return null; | ||||
|     return config::k($this->configKey); | ||||
|   } | ||||
| 
 | ||||
|   protected array $envKeys; | ||||
| 
 | ||||
|   function getEnvProfile(): ?string { | ||||
|     foreach ($this->envKeys as $envKey) { | ||||
|       $profile = getenv($envKey); | ||||
|       if ($profile !== false) return $profile; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   protected ?string $defaultProfile; | ||||
| 
 | ||||
|   function getDefaultProfile(): ?string { | ||||
|     return $this->defaultProfile; | ||||
|   } | ||||
| 
 | ||||
|   function setDefaultProfile(?string $profile): void { | ||||
|     $this->defaultProfile = $profile; | ||||
|   } | ||||
| 
 | ||||
|   protected ?string $profile; | ||||
| 
 | ||||
|   protected bool $productionMode; | ||||
| 
 | ||||
|   protected function resolveProfile(): void { | ||||
|     $profile ??= $this->getenvProfile(); | ||||
|     $profile ??= $this->getConfigProfile(); | ||||
|     $profile ??= $this->getDefaultProfile(); | ||||
|     if ($this->isAppProfile) { | ||||
|       $profile ??= $this->profiles[0] ?? "prod"; | ||||
|     } else { | ||||
|       $profile ??= $this->mapProfile(app::get_profile()); | ||||
|     } | ||||
|     $this->profile = $profile; | ||||
|     $this->productionMode = $this->productionModes[$profile] ?? false; | ||||
|   } | ||||
| 
 | ||||
|   function getProfile(?bool &$productionMode=null): string { | ||||
|     if ($this->profile === null) $this->resolveProfile(); | ||||
|     $productionMode = $this->productionMode; | ||||
|     return $this->profile; | ||||
|   } | ||||
| 
 | ||||
|   function isProductionMode(): bool { | ||||
|     return $this->productionMode; | ||||
|   } | ||||
| 
 | ||||
|   function setProfile(?string $profile=null, ?bool $productionMode=null): void { | ||||
|     if ($profile === null) $this->profile = null; | ||||
|     $profile ??= $this->getProfile($productionMode); | ||||
|     $productionMode ??= $this->productionModes[$profile] ?? false; | ||||
|     $this->profile = $profile; | ||||
|     $this->productionMode = $productionMode; | ||||
|   } | ||||
| } | ||||
| @ -1,13 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config; | ||||
| 
 | ||||
| use nulib\ext\yaml; | ||||
| 
 | ||||
| /** | ||||
|  * Class YamlConfig: une configuration chargée depuis un fichier yaml | ||||
|  */ | ||||
| class YamlConfig extends ArrayConfig { | ||||
|   function __construct(string $input) { | ||||
|     parent::__construct(yaml::load($input)); | ||||
|   } | ||||
| } | ||||
| @ -1,113 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\db\Capacitor; | ||||
| use nulib\db\CapacitorChannel; | ||||
| use nulib\db\CapacitorStorage; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\file\Stream; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| abstract class AbstractStorageApp extends Application { | ||||
|   const ACTION_RESET = 0, ACTION_QUERY = 1, ACTION_SQL = 2; | ||||
| 
 | ||||
|   protected ?string $tableName = null; | ||||
| 
 | ||||
|   protected ?string $channelClass = null; | ||||
| 
 | ||||
|   protected int $action = self::ACTION_QUERY; | ||||
| 
 | ||||
|   protected bool $recreate = true; | ||||
| 
 | ||||
|   protected ?array $args = null; | ||||
| 
 | ||||
|   protected static function isa_cond(string $arg, ?array &$ms=null): bool { | ||||
|     return preg_match('/^(.+?)\s*(=|<>|<|>|<=|>=|(?:is\s+)?null|(?:is\s+)?not\s+null)\s*(.*)$/', $arg, $ms); | ||||
|   } | ||||
| 
 | ||||
|   protected function storageCtl(CapacitorStorage $storage): void { | ||||
|     $args = $this->args; | ||||
| 
 | ||||
|     $channelClass = $this->channelClass; | ||||
|     $tableName = $this->tableName; | ||||
|     if ($channelClass === null && $tableName === null) { | ||||
|       $name = A::shift($args); | ||||
|       if ($name !== null) { | ||||
|         if (!$storage->channelExists($name, $row)) { | ||||
|           self::die("$name: nom de canal de données introuvable"); | ||||
|         } | ||||
|         if ($row["class_name"] !== "class@anonymous") $channelClass = $row["class_name"]; | ||||
|         else $tableName = $row["table_name"]; | ||||
|       } | ||||
|     } | ||||
|     if ($channelClass !== null) { | ||||
|       $channelClass = str_replace("/", "\\", $channelClass); | ||||
|       $channel = new $channelClass; | ||||
|     } elseif ($tableName !== null) { | ||||
|       $channel = new class($tableName) extends CapacitorChannel { | ||||
|         function __construct(?string $name=null) { | ||||
|           parent::__construct($name); | ||||
|           $this->tableName = $name; | ||||
|         } | ||||
|       }; | ||||
|     } else { | ||||
|       $found = false; | ||||
|       foreach ($storage->getChannels() as $row) { | ||||
|         msg::print($row["name"]); | ||||
|         $found = true; | ||||
|       } | ||||
|       if ($found) self::exit(); | ||||
|       self::die("Vous devez spécifier le canal de données"); | ||||
|     } | ||||
|     $capacitor = new Capacitor($storage, $channel); | ||||
| 
 | ||||
|     switch ($this->action) { | ||||
|     case self::ACTION_RESET: | ||||
|       $capacitor->reset($this->recreate); | ||||
|       break; | ||||
|     case self::ACTION_QUERY: | ||||
|       if (!$args) { | ||||
|         # lister les id
 | ||||
|         $out = new Stream(STDOUT); | ||||
|         $primaryKeys = $storage->getPrimaryKeys($channel); | ||||
|         $rows = $storage->db()->all([ | ||||
|           "select", | ||||
|           "cols" => $primaryKeys, | ||||
|           "from" => $channel->getTableName(), | ||||
|         ]); | ||||
|         $out->fputcsv($primaryKeys); | ||||
|         foreach ($rows as $row) { | ||||
|           $rowIds = $storage->getRowIds($channel, $row); | ||||
|           $out->fputcsv($rowIds); | ||||
|         } | ||||
|       } else { | ||||
|         # afficher les lignes correspondantes
 | ||||
|         if (count($args) == 1 && !self::isa_cond($args[0])) { | ||||
|           $filter = $args[0]; | ||||
|         } else { | ||||
|           $filter = []; | ||||
|           $ms = null; | ||||
|           foreach ($args as $arg) { | ||||
|             if (self::isa_cond($arg, $ms)) { | ||||
|               $filter[$ms[1]] = [$ms[2], $ms[3]]; | ||||
|             } else { | ||||
|               $filter[$arg] = ["not null"]; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         $first = true; | ||||
|         $capacitor->each($filter, function ($row) use (&$first) { | ||||
|           if ($first) $first = false; | ||||
|           else echo "---\n"; | ||||
|           yaml::dump($row); | ||||
|         }); | ||||
|       } | ||||
|       break; | ||||
|     case self::ACTION_SQL: | ||||
|       echo $capacitor->getCreateSql()."\n"; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,124 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\app\app; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\app\RunFile; | ||||
| use nulib\ExitError; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\os\path; | ||||
| use nulib\os\proc\Cmd; | ||||
| use nulib\os\sh; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class BgLauncherApp extends Application { | ||||
|   const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2; | ||||
|   const ARGS = [ | ||||
|     "purpose" => "lancer un script en tâche de fond", | ||||
|     "usage" => "ApplicationClass args...", | ||||
| 
 | ||||
|     "sections" => [ | ||||
|       parent::VERBOSITY_SECTION, | ||||
|     ], | ||||
| 
 | ||||
|     ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, | ||||
|       "help" => "Afficher des informations sur la tâche", | ||||
|     ], | ||||
|     ["-s", "--start", "name" => "action", "value" => self::ACTION_START, | ||||
|       "help" => "Démarrer la tâche", | ||||
|     ], | ||||
|     ["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP, | ||||
|       "help" => "Arrêter la tâche", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   protected int $action = self::ACTION_START; | ||||
| 
 | ||||
|   protected ?array $args = null; | ||||
| 
 | ||||
|   static function show_infos(RunFile $runfile, ?int $level=null): void { | ||||
|     msg::print($runfile->getDesc(), $level); | ||||
|     msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1); | ||||
|   } | ||||
| 
 | ||||
|   function main() { | ||||
|     $args = $this->args; | ||||
| 
 | ||||
|     $appClass = $args[0] ?? null; | ||||
|     if ($appClass === null) { | ||||
|       self::die("Vous devez spécifier la classe de l'application"); | ||||
|     } | ||||
|     $appClass = $args[0] = str_replace("/", "\\", $appClass); | ||||
|     if (!class_exists($appClass)) { | ||||
|       self::die("$appClass: classe non trouvée"); | ||||
|     } | ||||
| 
 | ||||
|     $useRunfile = constant("$appClass::USE_RUNFILE"); | ||||
|     if (!$useRunfile) { | ||||
|       self::die("Cette application ne supporte le lancement en tâche de fond"); | ||||
|     } | ||||
| 
 | ||||
|     $runfile = app::with($appClass)->getRunfile(); | ||||
|     switch ($this->action) { | ||||
|     case self::ACTION_START: | ||||
|       $argc = count($args); | ||||
|       $appClass::_manage_runfile($argc, $args, $runfile); | ||||
|       if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED); | ||||
|       array_splice($args, 0, 0, [ | ||||
|         PHP_BINARY, | ||||
|         path::abspath(NULIB_APP_app_launcher), | ||||
|       ]); | ||||
|       app::params_putenv(); | ||||
|       self::_start($args, $runfile); | ||||
|       break; | ||||
|     case self::ACTION_STOP: | ||||
|       self::_stop($runfile); | ||||
|       self::show_infos($runfile, -1); | ||||
|       break; | ||||
|     case self::ACTION_INFOS: | ||||
|       self::show_infos($runfile); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static function _start(array $args, Runfile $runfile): void { | ||||
|     $pid = pcntl_fork(); | ||||
|     if ($pid == -1) { | ||||
|       # parent, impossible de forker
 | ||||
|       throw new ExitError(app::EC_FORK_PARENT, "Unable to fork"); | ||||
|     } elseif (!$pid) { | ||||
|       # child, fork ok
 | ||||
|       $runfile->wfPrepare($pid); | ||||
|       $outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out"; | ||||
|       $exitcode = app::EC_FORK_CHILD; | ||||
|       try { | ||||
|         # rediriger STDIN, STDOUT et STDERR
 | ||||
|         fclose(fopen($outfile, "wb")); // vider le fichier
 | ||||
|         fclose(STDIN); $in = fopen("/dev/null", "rb"); | ||||
|         fclose(STDOUT); $out = fopen($outfile, "ab"); | ||||
|         fclose(STDERR); $err = fopen($outfile, "ab"); | ||||
|         # puis lancer la commande
 | ||||
|         $cmd = new Cmd($args); | ||||
|         $cmd->addSource("/g/init.env"); | ||||
|         $cmd->addRedir("both", $outfile, true); | ||||
|         $cmd->fork_exec($exitcode, false); | ||||
|         sh::_waitpid(-$pid, $exitcode); | ||||
|       } finally { | ||||
|         $runfile->wfReaped($exitcode); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static function _stop(Runfile $runfile): bool { | ||||
|     $data = $runfile->read(); | ||||
|     $pid = $runfile->_getCid($data); | ||||
|     msg::action("stop $pid"); | ||||
|     if ($runfile->wfKill($reason)) { | ||||
|       msg::asuccess(); | ||||
|       return true; | ||||
|     } else { | ||||
|       msg::afailure($reason); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,33 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\file\SharedFile; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class DumpserApp extends Application { | ||||
|   const ARGS = [ | ||||
|     "merge" => parent::ARGS, | ||||
|     "purpose" => "afficher des données sérialisées", | ||||
|   ]; | ||||
| 
 | ||||
|   protected $args; | ||||
| 
 | ||||
|   function main() { | ||||
|     $files = []; | ||||
|     foreach ($this->args as $arg) { | ||||
|       if (is_file($arg)) { | ||||
|         $files[] = $arg; | ||||
|       } else { | ||||
|         msg::warning("$arg: fichier invalide ou introuvable"); | ||||
|       } | ||||
|     } | ||||
|     $showSection = count($files) > 1; | ||||
|     foreach ($files as $file) { | ||||
|       if ($showSection) msg::section($file); | ||||
|       $sfile = new SharedFile($file); | ||||
|       yaml::dump($sfile->unserialize()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\ext\json; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\os\path; | ||||
| 
 | ||||
| class Json2yamlApp extends Application { | ||||
|   private $args; | ||||
| 
 | ||||
|   function main() { | ||||
|     $input = $this->args[0] ?? null; | ||||
|     if ($input === null || $input === "-") { | ||||
|       $output = null; | ||||
|     } else { | ||||
|       $output = path::ensure_ext($input, ".yml", ".json"); | ||||
|     } | ||||
| 
 | ||||
|     $data = json::load($input); | ||||
|     yaml::dump($data, $output); | ||||
|   } | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\db\mysql\MysqlStorage; | ||||
| use nur\config; | ||||
| 
 | ||||
| class MysqlStorageApp extends AbstractStorageApp { | ||||
|   const ARGS = [ | ||||
|     "merge" => parent::ARGS, | ||||
|     "purpose" => "gestion d'un capacitor mysql", | ||||
|     "usage" => [ | ||||
|       "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", | ||||
|       "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", | ||||
|     ], | ||||
|     ["-t", "--table-name", "args" => 1, | ||||
|       "help" => "nom de la table porteuse du canal de données", | ||||
|     ], | ||||
|     ["-c", "--channel-class", "args" => 1, | ||||
|       "help" => "nom de la classe dérivée de CapacitorChannel", | ||||
|     ], | ||||
|     ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, | ||||
|       "help" => "réinitialiser le canal", | ||||
|     ], | ||||
|     ["-n", "--no-recreate", "name" => "recreate", "value" => false, | ||||
|       "help" => "ne pas recréer la table correspondant au canal" | ||||
|     ], | ||||
|     ["--query", "name" => "action", "value" => self::ACTION_QUERY, | ||||
|       "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", | ||||
|     ], | ||||
|     ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, | ||||
|       "help" => "afficher la requête pour créer la table", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   function main() { | ||||
|     $dbconn = A::shift($this->args); | ||||
|     if ($dbconn === null) self::die("Vous devez spécifier la base de données"); | ||||
|     $tmp = config::db($dbconn); | ||||
|     if ($tmp === null) self::die("$dbconn: base de données invalide"); | ||||
|     $storage = new MysqlStorage($tmp); | ||||
| 
 | ||||
|     $this->storageCtl($storage); | ||||
|   } | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\db\pgsql\PgsqlStorage; | ||||
| use nur\config; | ||||
| 
 | ||||
| class PgsqlStorageApp extends AbstractStorageApp { | ||||
|   const ARGS = [ | ||||
|     "merge" => parent::ARGS, | ||||
|     "purpose" => "gestion d'un capacitor pgsql", | ||||
|     "usage" => [ | ||||
|       "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", | ||||
|       "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", | ||||
|     ], | ||||
|     ["-t", "--table-name", "args" => 1, | ||||
|       "help" => "nom de la table porteuse du canal de données", | ||||
|     ], | ||||
|     ["-c", "--channel-class", "args" => 1, | ||||
|       "help" => "nom de la classe dérivée de CapacitorChannel", | ||||
|     ], | ||||
|     ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, | ||||
|       "help" => "réinitialiser le canal", | ||||
|     ], | ||||
|     ["-n", "--no-recreate", "name" => "recreate", "value" => false, | ||||
|       "help" => "ne pas recréer la table correspondant au canal" | ||||
|     ], | ||||
|     ["--query", "name" => "action", "value" => self::ACTION_QUERY, | ||||
|       "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", | ||||
|     ], | ||||
|     ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, | ||||
|       "help" => "afficher la requête pour créer la table", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   function main() { | ||||
|     $dbconn = A::shift($this->args); | ||||
|     if ($dbconn === null) self::die("Vous devez spécifier la base de données"); | ||||
|     $tmp = config::db($dbconn); | ||||
|     if ($tmp === null) self::die("$dbconn: base de données invalide"); | ||||
|     $storage = new PgsqlStorage($tmp); | ||||
| 
 | ||||
|     $this->storageCtl($storage); | ||||
|   } | ||||
| } | ||||
| @ -1,43 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\db\sqlite\SqliteStorage; | ||||
| 
 | ||||
| class SqliteStorageApp extends AbstractStorageApp { | ||||
|   const ARGS = [ | ||||
|     "merge" => parent::ARGS, | ||||
|     "purpose" => "gestion d'un capacitor sqlite", | ||||
|     "usage" => [ | ||||
|       "DBFILE [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", | ||||
|       "DBFILE [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", | ||||
|     ], | ||||
|     ["-t", "--table-name", "args" => 1, | ||||
|       "help" => "nom de la table porteuse du canal de données", | ||||
|     ], | ||||
|     ["-c", "--channel-class", "args" => 1, | ||||
|       "help" => "nom de la classe dérivée de CapacitorChannel", | ||||
|     ], | ||||
|     ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, | ||||
|       "help" => "réinitialiser le canal", | ||||
|     ], | ||||
|     ["-n", "--no-recreate", "name" => "recreate", "value" => false, | ||||
|       "help" => "ne pas recréer la table correspondant au canal" | ||||
|     ], | ||||
|     ["--query", "name" => "action", "value" => self::ACTION_QUERY, | ||||
|       "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", | ||||
|     ], | ||||
|     ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, | ||||
|       "help" => "afficher la requête pour créer la table", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   function main() { | ||||
|     $dbfile = A::shift($this->args); | ||||
|     if ($dbfile === null) self::die("Vous devez spécifier la base de données"); | ||||
|     if (!file_exists($dbfile)) self::die("$dbfile: fichier introuvable"); | ||||
|     $storage = new SqliteStorage($dbfile); | ||||
| 
 | ||||
|     $this->storageCtl($storage); | ||||
|   } | ||||
| } | ||||
| @ -1,53 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\app\app; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\output\msg; | ||||
| use nulib\php\time\DateTime; | ||||
| use nulib\text\words; | ||||
| 
 | ||||
| class SteamTrainApp extends Application { | ||||
|   const PROJDIR = __DIR__.'/../..'; | ||||
|   const TITLE = "Train à vapeur"; | ||||
|   const USE_LOGFILE = true; | ||||
|   const USE_RUNFILE = true; | ||||
|   const USE_RUNLOCK = true; | ||||
| 
 | ||||
|   const ARGS = [ | ||||
|     "purpose" => self::TITLE, | ||||
|     "description" => <<<EOT | ||||
| Cette application peut être utilisée pour tester le lancement des tâches de fond | ||||
| EOT, | ||||
| 
 | ||||
|     ["-c", "--count", "args" => 1, | ||||
|       "help" => "spécifier le nombre d'étapes", | ||||
|     ], | ||||
|     ["-f", "--force-enabled", "value" => true, | ||||
|       "help" => "lancer la commande même si les tâches planifiées sont désactivées", | ||||
|     ], | ||||
|     ["-n", "--no-install-signal-handler", "value" => false, | ||||
|       "help" => "ne pas installer le gestionnaire de signaux", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   protected $count = 100; | ||||
| 
 | ||||
|   protected bool $forceEnabled = false; | ||||
| 
 | ||||
|   protected bool $installSignalHandler = true; | ||||
| 
 | ||||
|   function main() { | ||||
|     app::check_bgapplication_enabled($this->forceEnabled); | ||||
|     if ($this->installSignalHandler) app::install_signal_handler(); | ||||
|     $count = intval($this->count); | ||||
|     msg::info("Starting train for ".words::q($count, "step#s")); | ||||
|     app::action("Running train...", $count); | ||||
|     for ($i = 1; $i <= $count; $i++) { | ||||
|       msg::print("Tchou-tchou! x $i"); | ||||
|       app::step(); | ||||
|       sleep(1); | ||||
|     } | ||||
|     msg::info("Stopping train at ".new DateTime()); | ||||
|   } | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\cli; | ||||
| 
 | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\ext\json; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\os\path; | ||||
| 
 | ||||
| class Yaml2jsonApp extends Application { | ||||
|   private $args; | ||||
| 
 | ||||
|   function main() { | ||||
|     $input = $this->args[0] ?? null; | ||||
|     if ($input === null || $input === "-") { | ||||
|       $output = null; | ||||
|     } else { | ||||
|       $output = path::ensure_ext($input, ".json", [".yml", ".yaml"]); | ||||
|     } | ||||
| 
 | ||||
|     $data = yaml::load($input); | ||||
|     json::dump($data, $output); | ||||
|   } | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| 
 | ||||
| class varray { | ||||
|   static function ensure(&$array): void { | ||||
|     if (!is_array($array)) $array = cl::with($array); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$array): void { | ||||
|     if ($array !== null) varray::ensure($array); | ||||
|   } | ||||
| 
 | ||||
|   static function with($value): array { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   static function withn($value): ?array { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,54 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vbool { | ||||
|   /** liste de valeurs chaines à considérer comme 'OUI' */ | ||||
|   public const YES_VALUES = [ | ||||
|     "true", "vrai", "yes", "oui", | ||||
|     "t", "v", "y", "o", "1", | ||||
|   ]; | ||||
|   /** liste de valeurs chaines à considérer comme 'NON' */ | ||||
|   public const NO_VALUES = [ | ||||
|     "false", "faux", "non", "no", | ||||
|     "f", "n", "0", | ||||
|   ]; | ||||
| 
 | ||||
|   /** Vérifier si $value est 'OUI' */ | ||||
|   static final function is_yes(?string $value): bool { | ||||
|     if ($value === null) return false; | ||||
|     $value = strtolower(trim($value)); | ||||
|     if (in_array($value, self::YES_VALUES, true)) return true; | ||||
|     // n'importe quelle valeur numérique
 | ||||
|     if (is_numeric($value)) return $value != 0; | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   /** Vérifier si $value est 'NON' */ | ||||
|   static final function is_no(?string $value): bool { | ||||
|     if ($value === null) return true; | ||||
|     $value = strtolower(trim($value)); | ||||
|     return in_array($value, self::NO_VALUES, true); | ||||
|   } | ||||
| 
 | ||||
|   static function ensure(&$bool): void { | ||||
|     if (is_string($bool)) { | ||||
|       if (self::is_yes($bool)) $bool = true; | ||||
|       elseif (self::is_no($bool)) $bool = false; | ||||
|     } | ||||
|     if (!is_bool($bool)) $bool = boolval($bool); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$bool): void { | ||||
|     if ($bool !== null) self::ensure($bool); | ||||
|   } | ||||
| 
 | ||||
|   static function with($value): bool { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   static function withn($value): ?bool { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vcontent { | ||||
|   static function ensure(&$content): void { | ||||
|     if ($content === null || $content === false) $content = []; | ||||
|     elseif (!is_string($content) && !is_array($content)) $content = strval($content); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$content): void { | ||||
|     if ($content !== null) self::ensure($content); | ||||
|   } | ||||
| 
 | ||||
|   /** @return string|array */ | ||||
|   static function with($value) { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   /** @return string|array|null */ | ||||
|   static function withn($value) { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vfloat { | ||||
|   static function ensure(&$float): void { | ||||
|     if (!is_float($float)) $float = floatval($float); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$float): void { | ||||
|     if ($float !== null) self::ensure($float); | ||||
|   } | ||||
| 
 | ||||
|   static function with($value): float { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   static function withn($value): ?float { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| use nulib\php\func; | ||||
| 
 | ||||
| class vfunc { | ||||
|   static function ensure(&$func): void { | ||||
|     $func = func::ensure($func); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$func): void { | ||||
|     if ($func !== null) $func = func::ensure($func); | ||||
|   } | ||||
| 
 | ||||
|   static function with($value): func { | ||||
|     return func::with($value); | ||||
|   } | ||||
| 
 | ||||
|   static function withn($value): ?func { | ||||
|     return func::withn($value); | ||||
|   } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vint { | ||||
|   static function ensure(&$int): void { | ||||
|     if (!is_int($int)) $int = intval($int); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$int): void { | ||||
|     if ($int !== null) self::ensure($int); | ||||
|   } | ||||
| 
 | ||||
|   static function with($value): int { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   static function withn($value): ?int { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vkey { | ||||
|   static function ensure(&$key): void { | ||||
|     if ($key === null) $key = ""; | ||||
|     elseif ($key === false) $key = 0; | ||||
|     elseif (!is_string($key) && !is_int($key)) $key = strval($key); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$key): void { | ||||
|     if ($key !== null) self::ensure($key); | ||||
|   } | ||||
| 
 | ||||
|   /** @return string|int */ | ||||
|   static function with($value) { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   /** @return string|int|null */ | ||||
|   static function withn($value) { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,32 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vpkey { | ||||
|   static function ensure(&$pkey): void { | ||||
|     if ($pkey === null) $pkey = ""; | ||||
|     elseif ($pkey === false) $pkey = 0; | ||||
|     elseif (!is_string($pkey) && !is_int($pkey) && !is_array($pkey)) $pkey = strval($pkey); | ||||
|     if (is_array($pkey)) { | ||||
|       foreach ($pkey as &$key) { | ||||
|         vkey::ensure($key); | ||||
|       }; | ||||
|       unset($key); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$pkey): void { | ||||
|     if ($pkey !== null) self::ensure($pkey); | ||||
|   } | ||||
| 
 | ||||
|   /** @return string|int|array */ | ||||
|   static function with($value) { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   /** @return string|int|array|null */ | ||||
|   static function withn($value) { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| use nulib\str; | ||||
| 
 | ||||
| class vrawstring { | ||||
|   /** @var bool faut-il trimmer la valeur */ | ||||
|   const TRIM = false; | ||||
|   /** @var bool faut-il normaliser les caractères fin de ligne */ | ||||
|   const NORM_NL = false; | ||||
| 
 | ||||
|   static function ensure(&$string): void { | ||||
|     if (!is_string($string)) $string = strval($string); | ||||
|     if (static::TRIM) $string = trim($string); | ||||
|     if (static::NORM_NL) $string = str::norm_nl($string); | ||||
|   } | ||||
| 
 | ||||
|   static function ensuren(&$string): void { | ||||
|     if ($string !== null) self::ensure($string); | ||||
|   } | ||||
| 
 | ||||
|   static function with($value): string { | ||||
|     self::ensure($value); | ||||
|     return $value; | ||||
|   } | ||||
| 
 | ||||
|   static function withn($value): ?string { | ||||
|     self::ensuren($value); | ||||
|     return $value; | ||||
|   } | ||||
| } | ||||
| @ -1,6 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vstring extends vrawstring { | ||||
|   const TRIM = true; | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\php\types; | ||||
| 
 | ||||
| class vtext extends vrawstring { | ||||
|   const TRIM = true; | ||||
|   const NORM_NL = true; | ||||
| } | ||||
| @ -1,132 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app { | ||||
|   use nulib\tests\TestCase; | ||||
|   use nulib\app\impl\config; | ||||
|   use nulib\app\impl\myapp; | ||||
|   use nulib\app\impl\MyApplication1; | ||||
|   use nulib\app\impl\MyApplication2; | ||||
| 
 | ||||
|   class appTest extends TestCase { | ||||
|     function testWith() { | ||||
|       $projdir = config::get_projdir(); | ||||
|       $cwd = getcwd(); | ||||
| 
 | ||||
|       myapp::reset(); | ||||
|       $app1 = myapp::with(MyApplication1::class); | ||||
|       self::assertSame([ | ||||
|         "projdir" => $projdir, | ||||
|         "vendor" => [ | ||||
|           "bindir" => "$projdir/vendor/bin", | ||||
|           "autoload" => "$projdir/vendor/autoload.php", | ||||
|         ], | ||||
|         "appcode" => "nur-ture", | ||||
|         "cwd" => $cwd, | ||||
|         "datadir" => "$projdir/devel", | ||||
|         "etcdir" => "$projdir/devel/etc", | ||||
|         "vardir" => "$projdir/devel/var", | ||||
|         "logdir" => "$projdir/devel/log", | ||||
|         "profile" => "devel", | ||||
|         "appgroup" => null, | ||||
|         "name" => "my-application1", | ||||
|         "title" => null, | ||||
|       ], $app1->getParams()); | ||||
| 
 | ||||
|       $app2 = myapp::with(MyApplication2::class, $app1); | ||||
|       self::assertSame([ | ||||
|         "projdir" => $projdir, | ||||
|         "vendor" => [ | ||||
|           "bindir" => "$projdir/vendor/bin", | ||||
|           "autoload" => "$projdir/vendor/autoload.php", | ||||
|         ], | ||||
|         "appcode" => "nur-ture", | ||||
|         "cwd" => $cwd, | ||||
|         "datadir" => "$projdir/devel", | ||||
|         "etcdir" => "$projdir/devel/etc", | ||||
|         "vardir" => "$projdir/devel/var", | ||||
|         "logdir" => "$projdir/devel/log", | ||||
|         "profile" => "devel", | ||||
|         "appgroup" => null, | ||||
|         "name" => "my-application2", | ||||
|         "title" => null, | ||||
|       ], $app2->getParams()); | ||||
|     } | ||||
| 
 | ||||
|     function testInit() { | ||||
|       $projdir = config::get_projdir(); | ||||
|       $cwd = getcwd(); | ||||
| 
 | ||||
|       myapp::reset(); | ||||
|       myapp::init(MyApplication1::class); | ||||
|       self::assertSame([ | ||||
|         "projdir" => $projdir, | ||||
|         "vendor" => [ | ||||
|           "bindir" => "$projdir/vendor/bin", | ||||
|           "autoload" => "$projdir/vendor/autoload.php", | ||||
|         ], | ||||
|         "appcode" => "nur-ture", | ||||
|         "cwd" => $cwd, | ||||
|         "datadir" => "$projdir/devel", | ||||
|         "etcdir" => "$projdir/devel/etc", | ||||
|         "vardir" => "$projdir/devel/var", | ||||
|         "logdir" => "$projdir/devel/log", | ||||
|         "profile" => "devel", | ||||
|         "appgroup" => null, | ||||
|         "name" => "my-application1", | ||||
|         "title" => null, | ||||
|       ], myapp::get()->getParams()); | ||||
| 
 | ||||
|       myapp::init(MyApplication2::class); | ||||
|       self::assertSame([ | ||||
|         "projdir" => $projdir, | ||||
|         "vendor" => [ | ||||
|           "bindir" => "$projdir/vendor/bin", | ||||
|           "autoload" => "$projdir/vendor/autoload.php", | ||||
|         ], | ||||
|         "appcode" => "nur-ture", | ||||
|         "cwd" => $cwd, | ||||
|         "datadir" => "$projdir/devel", | ||||
|         "etcdir" => "$projdir/devel/etc", | ||||
|         "vardir" => "$projdir/devel/var", | ||||
|         "logdir" => "$projdir/devel/log", | ||||
|         "profile" => "devel", | ||||
|         "appgroup" => null, | ||||
|         "name" => "my-application2", | ||||
|         "title" => null, | ||||
|       ], myapp::get()->getParams()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| namespace nulib\app\impl { | ||||
| 
 | ||||
|   use nulib\app\cli\Application; | ||||
|   use nulib\os\path; | ||||
|   use nulib\app\app; | ||||
| 
 | ||||
|   class config { | ||||
|     const PROJDIR = __DIR__.'/../..'; | ||||
| 
 | ||||
|     static function get_projdir(): string { | ||||
|       return path::abspath(self::PROJDIR); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class myapp extends app { | ||||
|     static function reset(): void { | ||||
|       self::$app = null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class MyApplication1 extends Application { | ||||
|     const PROJDIR = config::PROJDIR; | ||||
| 
 | ||||
|     function main() { | ||||
|     } | ||||
|   } | ||||
|   class MyApplication2 extends Application { | ||||
|     const PROJDIR = null; | ||||
| 
 | ||||
|     function main() { | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,158 +0,0 @@ | ||||
| <?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, ""); | ||||
|   } | ||||
| } | ||||
| @ -1,60 +0,0 @@ | ||||
| <?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,58 +0,0 @@ | ||||
| <?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,174 +0,0 @@ | ||||
| <?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); | ||||
|   } | ||||
| } | ||||
| @ -1,124 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\app\config { | ||||
|   use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
|   use nulib\app\config\impl\result; | ||||
|   use nulib\app\config\impl\config1; | ||||
|   use nulib\app\config\impl\config2; | ||||
| 
 | ||||
|   class ConfigManagerTest extends TestCase { | ||||
|     function testConfigurators() { | ||||
|       $config = new ConfigManager(); | ||||
| 
 | ||||
|       result::reset(); | ||||
|       $config->addConfigurator(config1::class); | ||||
|       $config->configure(); | ||||
|       self::assertSame([ | ||||
|         "config1::static configure1", | ||||
|       ], impl\result::$configured); | ||||
| 
 | ||||
|       result::reset(); | ||||
|       $config->addConfigurator(config1::class); | ||||
|       $config->configure(); | ||||
|       $config->configure(); | ||||
|       $config->configure(); | ||||
|       self::assertSame([ | ||||
|         "config1::static configure1", | ||||
|       ], impl\result::$configured); | ||||
| 
 | ||||
|       result::reset(); | ||||
|       $config->addConfigurator(new config1()); | ||||
|       $config->configure(); | ||||
|       self::assertSame([ | ||||
|         "config1::static configure1", | ||||
|         "config1::configure2", | ||||
|       ], impl\result::$configured); | ||||
| 
 | ||||
|       result::reset(); | ||||
|       $config->addConfigurator(new config1()); | ||||
|       $config->configure(["include" => "2"]); | ||||
|       self::assertSame([ | ||||
|         "config1::configure2", | ||||
|       ], impl\result::$configured); | ||||
|       $config->configure(["include" => "1"]); | ||||
|       self::assertSame([ | ||||
|         "config1::configure2", | ||||
|         "config1::static configure1", | ||||
|       ], impl\result::$configured); | ||||
| 
 | ||||
|       result::reset(); | ||||
|       $config->addConfigurator([ | ||||
|         config1::class, | ||||
|         new config2(), | ||||
|       ]); | ||||
|       $config->configure(); | ||||
|       self::assertSame([ | ||||
|         "config1::static configure1", | ||||
|         "config2::static configure1", | ||||
|         "config2::configure2", | ||||
|       ], impl\result::$configured); | ||||
|     } | ||||
| 
 | ||||
|     function testConfig() { | ||||
|       $config = new ConfigManager(); | ||||
| 
 | ||||
|       $config->addConfig([ | ||||
|         "app" => [ | ||||
|           "var" => "array", | ||||
|         ] | ||||
|       ]); | ||||
|       self::assertSame("array", $config->getValue("app.var")); | ||||
| 
 | ||||
|       $config->addConfig(new ArrayConfig([ | ||||
|         "app" => [ | ||||
|           "var" => "instance", | ||||
|         ] | ||||
|       ])); | ||||
|       self::assertSame("instance", $config->getValue("app.var")); | ||||
| 
 | ||||
|       $config->addConfig(config1::class); | ||||
|       self::assertSame("class1", $config->getValue("app.var")); | ||||
| 
 | ||||
|       $config->addConfig(config2::class); | ||||
|       self::assertSame("class2", $config->getValue("app.var")); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| namespace nulib\app\config\impl { | ||||
|   class result { | ||||
|     static array $configured = []; | ||||
| 
 | ||||
|     static function reset() { | ||||
|       self::$configured = []; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class config1 { | ||||
|     const APP = [ | ||||
|       "var" => "class1", | ||||
|     ]; | ||||
| 
 | ||||
|     static function configure1() { | ||||
|       result::$configured[] = "config1::static configure1"; | ||||
|     } | ||||
| 
 | ||||
|     function configure2() { | ||||
|       result::$configured[] = "config1::configure2"; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class config2 { | ||||
|     const APP = [ | ||||
|       "var" => "class2", | ||||
|     ]; | ||||
| 
 | ||||
|     static function configure1() { | ||||
|       result::$configured[] = "config2::static configure1"; | ||||
|     } | ||||
| 
 | ||||
|     function configure2() { | ||||
|       result::$configured[] = "config2::configure2"; | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user