modifs.mineures sans commentaires
This commit is contained in:
parent
ccb81d35a6
commit
51f8ae487a
|
@ -2,6 +2,6 @@
|
|||
<?php
|
||||
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use nur\tools\SteamTrainApp;
|
||||
use nur\sery\wip\tools\SteamTrainApp;
|
||||
|
||||
SteamTrainApp::run();
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
namespace nur\cli;
|
||||
|
||||
use nur\sery\wip\app\app2;
|
||||
|
||||
class BgApplication2 extends Application2 {
|
||||
const USE_LOGFILE = true;
|
||||
const USE_RUNFILE = true;
|
||||
const USE_RUNLOCK = true;
|
||||
|
||||
const ARGS = [
|
||||
"merge" => parent::ARGS,
|
||||
|
||||
["--force-enabled", "value" => true,
|
||||
"help" => "lancer la commande même si les tâches planifiées sont désactivées",
|
||||
],
|
||||
];
|
||||
|
||||
protected bool $forceEnabled = false;
|
||||
|
||||
function main() {
|
||||
app2::check_bgapplication_enabled($this->forceEnabled);
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
namespace nur\tools;
|
||||
|
||||
use nur\cli\BgApplication2;
|
||||
use nur\sery\output\msg;
|
||||
|
||||
class SteamTrainApp extends BgApplication2 {
|
||||
const NAME = self::class;
|
||||
const TITLE = "Train à vapeur";
|
||||
//const USE_SIGNAL_HANDLER = true;
|
||||
|
||||
const ARGS = [
|
||||
"merge_arrays" => [BgApplication2::ARGS, parent::ARGS],
|
||||
"purpose" => self::TITLE,
|
||||
];
|
||||
|
||||
function main() {
|
||||
for ($i = 1; $i <= 100; $i++) {
|
||||
msg::print("Tchou-tchou! x $i");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
# s'assurer que le script PHP est lancé avec l'utilisateur www-data
|
||||
|
||||
if [ ! -L "$0" ]; then
|
||||
echo "\
|
||||
$0: ce script
|
||||
- doit être lancé en tant que lien symbolique avec un nom de la forme 'monscript.php'
|
||||
- lance le script PHP du même nom situé dans le même répertoire avec l'utilisateur www-data"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MYNAME="$(basename -- "$0")"
|
||||
MYTRUESELF="$(readlink -f "$0")"
|
||||
MYTRUEDIR="$(dirname -- "$MYTRUESELF")"
|
||||
PROJDIR="$(cd "$MYTRUEDIR/.."; pwd)"
|
||||
|
||||
www_data="${DEVUSER_USERENT%%:*}"
|
||||
[ -n "$www_data" ] || www_data=www-data
|
||||
|
||||
class="$MYTRUEDIR/${MYNAME%.php}.phpc"
|
||||
script="$MYTRUEDIR/${MYNAME%.php}.php"
|
||||
|
||||
cmd=(php "$PROJDIR/src_app/init/cli_cli_launcher.php")
|
||||
[ -n "$MEMPROF_PROFILE" ] && cmd+=(-dextension=memprof.so)
|
||||
if [ -f "$class" ]; then
|
||||
cmd+=("$(<"$class")")
|
||||
else
|
||||
cmd+=("$script")
|
||||
fi
|
||||
cmd+=("$@")
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
su-exec "$www_data" "${cmd[@]}"
|
||||
else
|
||||
exec "${cmd[@]}"
|
||||
fi
|
|
@ -5,7 +5,7 @@ use nur\sery\output\std\ProxyMessenger;
|
|||
use nur\sery\php\nur_func;
|
||||
|
||||
/**
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher sur la console
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
|
||||
*
|
||||
* Cette classe DOIT être initialisée avec {@link set_messenger()} ou
|
||||
* {@link set_messenger_class()} avant d'être utilisée.
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\app;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\file\SharedFile;
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\php\time\DateTime;
|
||||
|
||||
/**
|
||||
* Class LockFile: une classe qui permet à une application de verrouiller
|
||||
* certaines actions
|
||||
*/
|
||||
class LockFile {
|
||||
const NAME = null;
|
||||
|
||||
const TITLE = null;
|
||||
|
||||
function __construct($file, ?string $name=null, ?string $title=null) {
|
||||
$this->file = new SharedFile($file);
|
||||
$this->name = $name ?? static::NAME;
|
||||
$this->title = $title ?? static::TITLE;
|
||||
}
|
||||
|
||||
/** @var SharedFile */
|
||||
protected $file;
|
||||
|
||||
/** @var ?string */
|
||||
protected $name;
|
||||
|
||||
/** @var ?string */
|
||||
protected $title;
|
||||
|
||||
protected function initData(): array {
|
||||
return [
|
||||
"name" => $this->name,
|
||||
"title" => $this->title,
|
||||
"locked" => false,
|
||||
"date_lock" => null,
|
||||
"date_release" => null,
|
||||
];
|
||||
}
|
||||
|
||||
function read(bool $close=true): array {
|
||||
$data = $this->file->unserialize(null, $close);
|
||||
if (!is_array($data)) $data = $this->initData();
|
||||
return $data;
|
||||
}
|
||||
|
||||
function isLocked(?array &$data=null): bool {
|
||||
$data = $this->read();
|
||||
return $data["locked"];
|
||||
}
|
||||
|
||||
function warnIfLocked(?array $data=null): bool {
|
||||
if ($data === null) $data = $this->read();
|
||||
if ($data["locked"]) {
|
||||
msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function lock(?array &$data=null): bool {
|
||||
$file = $this->file;
|
||||
$data = $this->read(false);
|
||||
if ($data["locked"]) {
|
||||
$file->close();
|
||||
return false;
|
||||
} else {
|
||||
$file->ftruncate();
|
||||
$file->serialize(cl::merge($data, [
|
||||
"locked" => true,
|
||||
"date_lock" => new DateTime(),
|
||||
"date_release" => null,
|
||||
]));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function release(?array &$data=null): void {
|
||||
$file = $this->file;
|
||||
$data = $this->read(false);
|
||||
$file->ftruncate();
|
||||
$file->serialize(cl::merge($data, [
|
||||
"locked" => false,
|
||||
"date_release" => new DateTime(),
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\app;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\file\SharedFile;
|
||||
use nur\sery\os\path;
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\php\time\DateTime;
|
||||
use nur\sery\str;
|
||||
|
||||
/**
|
||||
* Class RunFile: une classe permettant de suivre le fonctionnement d'une
|
||||
* application qui tourne en tâche de fond
|
||||
*/
|
||||
class RunFile {
|
||||
const RUN_EXT = ".run";
|
||||
const LOCK_EXT = ".lock";
|
||||
|
||||
const NAME = null;
|
||||
|
||||
function __construct(?string $name, string $file, ?string $logfile=null) {
|
||||
$file = path::ensure_ext($file, self::RUN_EXT);
|
||||
$this->name = $name ?? static::NAME;
|
||||
$this->file = new SharedFile($file);
|
||||
$this->logfile = $logfile;
|
||||
}
|
||||
|
||||
protected ?string $name;
|
||||
|
||||
protected SharedFile $file;
|
||||
|
||||
protected ?string $logfile;
|
||||
|
||||
function getLogfile(): ?string {
|
||||
return $this->logfile;
|
||||
}
|
||||
|
||||
protected static function merge(array $data, array $merge): array {
|
||||
return cl::merge($data, [
|
||||
"serial" => $data["serial"] + 1,
|
||||
], $merge);
|
||||
}
|
||||
|
||||
protected function initData(bool $forStart=true): array {
|
||||
if ($forStart) {
|
||||
$pid = posix_getpid();
|
||||
$dateStart = new DateTime();
|
||||
} else {
|
||||
$pid = $dateStart = null;
|
||||
}
|
||||
return [
|
||||
"name" => $this->name,
|
||||
"id" => bin2hex(random_bytes(16)),
|
||||
"pg_pid" => null,
|
||||
"pid" => $pid,
|
||||
"serial" => 0,
|
||||
# lock
|
||||
"locked" => false,
|
||||
"date_lock" => null,
|
||||
"date_release" => null,
|
||||
# run
|
||||
"logfile" => $this->logfile,
|
||||
"date_start" => $dateStart,
|
||||
"date_stop" => null,
|
||||
"exitcode" => null,
|
||||
"is_done" => null,
|
||||
# action
|
||||
"action" => null,
|
||||
"action_date_start" => null,
|
||||
"action_current_step" => null,
|
||||
"action_max_step" => null,
|
||||
"action_date_step" => null,
|
||||
];
|
||||
}
|
||||
|
||||
function read(): array {
|
||||
$data = $this->file->unserialize();
|
||||
if (!is_array($data)) $data = $this->initData(false);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function willWrite(): array {
|
||||
$file = $this->file;
|
||||
$file->lockWrite();
|
||||
$data = $file->unserialize(null, false, true);
|
||||
if (!is_array($data)) {
|
||||
$data = $this->initData(false);
|
||||
$file->ftruncate();
|
||||
$file->serialize($data, false, true);
|
||||
}
|
||||
return [$file, $data];
|
||||
}
|
||||
|
||||
protected function serialize(SharedFile $file, array $data, ?array $merge=null): void {
|
||||
$file->ftruncate();
|
||||
$file->serialize(self::merge($data, $merge), true, true);
|
||||
}
|
||||
|
||||
protected function update(callable $func): void {
|
||||
/** @var SharedFile$file */
|
||||
[$file, $data] = $this->willWrite();
|
||||
$merge = call_user_func($func, $data);
|
||||
if ($merge !== null && $merge !== false) {
|
||||
$this->serialize($file, $data, $merge);
|
||||
} else {
|
||||
$file->cancelWrite();
|
||||
}
|
||||
}
|
||||
|
||||
function haveWorked(int $serial, ?int &$currentSerial=null, ?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
$currentSerial = $data["serial"];
|
||||
return $serial !== $currentSerial;
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# verrouillage par défaut
|
||||
|
||||
function isLocked(?array &$data=null): bool {
|
||||
$data = $this->read();
|
||||
return $data["locked"];
|
||||
}
|
||||
|
||||
function warnIfLocked(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
if ($data["locked"]) {
|
||||
msg::warning("$data[name]: possède le verrou depuis $data[date_lock]");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function lock(): bool {
|
||||
$this->update(function ($data) use (&$locked) {
|
||||
if ($data["locked"]) {
|
||||
$locked = false;
|
||||
return null;
|
||||
} else {
|
||||
$locked = true;
|
||||
return [
|
||||
"locked" => true,
|
||||
"date_lock" => new DateTime(),
|
||||
"date_release" => null,
|
||||
];
|
||||
}
|
||||
});
|
||||
return $locked;
|
||||
}
|
||||
|
||||
function release(): void {
|
||||
$this->update(function ($data) {
|
||||
return [
|
||||
"locked" => false,
|
||||
"date_release" => new DateTime(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# cycle de vie de l'application
|
||||
|
||||
/**
|
||||
* indiquer que l'application démarre. l'état est entièrement réinitialisé,
|
||||
* sauf le PID du leader qui est laissé en l'état
|
||||
*/
|
||||
function wfStart(): void {
|
||||
$this->update(function (array $data) {
|
||||
return cl::merge($this->initData(), [
|
||||
"pg_pid" => $data["pg_pid"],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/** tester si l'application a déjà été démarrée au moins une fois */
|
||||
function wasStarted(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
return $data["date_start"] !== null;
|
||||
}
|
||||
|
||||
/** tester si l'application est démarrée et non arrêtée */
|
||||
function isStarted(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
return $data["date_start"] !== null && $data["date_stop"] === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* vérifier si l'application marquée comme démarrée tourne réellement
|
||||
*/
|
||||
function isRunning(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
if ($data["date_start"] === null) return false;
|
||||
if ($data["date_stop"] !== null) return false;
|
||||
if (!posix_kill($data["pid"], 0)) {
|
||||
switch (posix_get_last_error()) {
|
||||
case 1: #PCNTL_EPERM:
|
||||
# process auquel on n'a pas accès?! est-ce un autre process qui a
|
||||
# réutilisé le PID?
|
||||
return false;
|
||||
case 3: #PCNTL_ESRCH:
|
||||
# process inexistant
|
||||
return false;
|
||||
case 22: #PCNTL_EINVAL:
|
||||
# ne devrait pas se produire
|
||||
return false;
|
||||
}
|
||||
}
|
||||
# process existant auquel on a accès
|
||||
return true;
|
||||
}
|
||||
|
||||
/** indiquer que l'application s'arrête */
|
||||
function wfStop(): void {
|
||||
$this->update(function (array $data) {
|
||||
return ["date_stop" => new DateTime()];
|
||||
});
|
||||
}
|
||||
|
||||
/** tester si l'application est déjà été stoppée au moins une fois */
|
||||
function wasStopped(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
return $data["date_stop"] !== null;
|
||||
}
|
||||
|
||||
/** tester si l'application a été démarrée puis arrêtée */
|
||||
function isStopped(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
return $data["date_start"] !== null && $data["date_stop"] !== null;
|
||||
}
|
||||
|
||||
/** après l'arrêt de l'application, mettre à jour le code de retour */
|
||||
function wfStopped(int $exitcode): void {
|
||||
$this->update(function (array $data) use ($exitcode) {
|
||||
return [
|
||||
"pg_pid" => null,
|
||||
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
||||
"exitcode" => $exitcode,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si
|
||||
* $updateDone==true
|
||||
*/
|
||||
function isDone(?array &$data=null, bool $updateDone=true): bool {
|
||||
$done = false;
|
||||
$this->update(function (array $ldata) use (&$done, &$data, $updateDone) {
|
||||
$data = $ldata;
|
||||
if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_done"]) {
|
||||
return false;
|
||||
}
|
||||
$done = true;
|
||||
if ($updateDone) return ["is_done" => $done];
|
||||
else return null;
|
||||
});
|
||||
return $done;
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# gestion des actions
|
||||
|
||||
/** indiquer le début d'une action */
|
||||
function action(?string $title, ?int $maxSteps=null): void {
|
||||
$this->update(function (array $data) use ($title, $maxSteps) {
|
||||
return [
|
||||
"action" => $title,
|
||||
"action_date_start" => new DateTime(),
|
||||
"action_max_step" => $maxSteps,
|
||||
"action_current_step" => 0,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/** indiquer qu'une étape est franchie dans l'action en cours */
|
||||
function step(int $nbSteps=1): void {
|
||||
$this->update(function (array $data) use ($nbSteps) {
|
||||
return [
|
||||
"action_date_step" => new DateTime(),
|
||||
"action_current_step" => $data["action_current_step"] + $nbSteps,
|
||||
];
|
||||
});
|
||||
app2::_dispatch_signals();
|
||||
}
|
||||
|
||||
function getActionDesc(?array $data=null): ?string {
|
||||
$data ??= $this->read();
|
||||
$action = $data["action"];
|
||||
if ($action === null) {
|
||||
return "pid $data[pid] [$data[date_start]]";
|
||||
}
|
||||
|
||||
$date ??= $data["action_date_step"];
|
||||
$date ??= $data["action_date_start"];
|
||||
if ($date !== null) $action = "[$date] $action";
|
||||
$current = $data["action_current_step"];
|
||||
$max = $data["action_max_step"];
|
||||
if ($current !== null && $max !== null) {
|
||||
$action .= " ($current / $max)";
|
||||
} elseif ($current !== null) {
|
||||
$action .= " ($current)";
|
||||
}
|
||||
return "pid $data[pid] $action";
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# Divers
|
||||
|
||||
function getLockFile(?string $name=null, ?string $title=null): LockFile {
|
||||
$ext = self::LOCK_EXT;
|
||||
if ($name !== null) $ext = ".$name$ext";
|
||||
$file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT);
|
||||
$name = str::join("/", [$this->name, $name]);
|
||||
return new LockFile($file, $name, $title);
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# Gestionnaire de tâches (tm_*)
|
||||
|
||||
/** démarrer un groupe de process dont le process courant est le leader */
|
||||
function tm_startPg(): void {
|
||||
$this->update(function (array $data) {
|
||||
posix_setsid();
|
||||
return [
|
||||
"pg_pid" => posix_getpid(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* vérifier si on est dans le cas où la tâche devrait tourner mais en réalité
|
||||
* ce n'est pas le cas
|
||||
*/
|
||||
function tm_isUndead(?int $pid=null): bool {
|
||||
$data = $this->read();
|
||||
if ($data["date_start"] === null) return false;
|
||||
if ($data["date_stop"] !== null) return false;
|
||||
$pid ??= $data["pid"];
|
||||
if (!posix_kill($pid, 0)) {
|
||||
switch (posix_get_last_error()) {
|
||||
case 1: #PCNTL_EPERM:
|
||||
# process auquel on n'a pas accès?! est-ce un autre process qui a
|
||||
# réutilisé le PID?
|
||||
return false;
|
||||
case 3: #PCNTL_ESRCH:
|
||||
# process inexistant
|
||||
return true;
|
||||
case 22: #PCNTL_EINVAL:
|
||||
# ne devrait pas se produire
|
||||
return false;
|
||||
}
|
||||
}
|
||||
# process existant auquel on a accès
|
||||
return false;
|
||||
}
|
||||
|
||||
function tm_isReapable(): bool {
|
||||
$data = $this->read();
|
||||
return $data["date_stop"] !== null && $data["exitcode"] === null;
|
||||
}
|
||||
|
||||
/** marquer la tâche comme terminée */
|
||||
function tm_reap(?int $pid=null): void {
|
||||
$data = $this->read();
|
||||
$pid ??= $data["pid"];
|
||||
pcntl_waitpid($pid, $status);
|
||||
$exitcode = pcntl_wifexited($status)? pcntl_wexitstatus($status): 127;
|
||||
$this->update(function (array $data) use ($exitcode) {
|
||||
return [
|
||||
"pg_pid" => null,
|
||||
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
||||
"exitcode" => $data["exitcode"] ?? $exitcode,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
126
wip/app/app2.php
126
wip/app/app2.php
|
@ -1,20 +1,18 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\app;
|
||||
|
||||
use nur\b\ExitError;
|
||||
use nur\cli\Application;
|
||||
use nur\cli\Application2;
|
||||
use nur\sery\A;
|
||||
use nur\sery\app\LockFile;
|
||||
use nur\sery\app\RunFile;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ExitError;
|
||||
use nur\sery\os\path;
|
||||
use nur\sery\os\sh;
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\str;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\wip\app\cli\Application;
|
||||
|
||||
#XXX une réécriture de app, qui remplacera app à terme
|
||||
class app2 {
|
||||
static ?func $bgapplication_enabled = null;
|
||||
|
||||
|
@ -50,18 +48,35 @@ class app2 {
|
|||
}
|
||||
}
|
||||
|
||||
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(255);
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
private static function isa_Application($app): bool {
|
||||
if (!is_string($app)) return false;
|
||||
return $app === Application::class || is_subclass_of($app, Application::class) ||
|
||||
$app === Application2::class || is_subclass_of($app, Application2::class);
|
||||
return $app === Application::class || is_subclass_of($app, Application::class);
|
||||
}
|
||||
|
||||
private static function get_params($app): array {
|
||||
if ($app instanceof self) {
|
||||
$params = $app->getParams();
|
||||
} elseif ($app instanceof Application || $app instanceof Application2) {
|
||||
} elseif ($app instanceof Application) {
|
||||
$class = get_class($app);
|
||||
$params = [
|
||||
"class" => $class,
|
||||
|
@ -107,6 +122,7 @@ class app2 {
|
|||
*/
|
||||
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) {
|
||||
|
@ -130,7 +146,26 @@ class app2 {
|
|||
}
|
||||
|
||||
static function get(): self {
|
||||
return self::$app ??= new self(null);
|
||||
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(): string {
|
||||
return self::get()->getProfile();
|
||||
}
|
||||
|
||||
static function set_profile(?string $profile=null): void {
|
||||
self::get()->setProfile($profile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,6 +272,7 @@ class app2 {
|
|||
# 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;
|
||||
|
@ -305,6 +341,11 @@ class app2 {
|
|||
return $this->profile;
|
||||
}
|
||||
|
||||
function setProfile(?string $profile): void {
|
||||
$profile ??= $this->profile;
|
||||
$this->profile = $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string|false $profile
|
||||
*
|
||||
|
@ -344,8 +385,8 @@ class app2 {
|
|||
return $this->withProfile(path::join($dirs[0], $names[0]), $profile);
|
||||
}
|
||||
|
||||
function fencedJoin(string $basedir, string $path): string {
|
||||
$path = path::reljoin($basedir, $path);
|
||||
function fencedJoin(string $basedir, ?string ...$paths): string {
|
||||
$path = path::reljoin($basedir, ...$paths);
|
||||
if (!path::is_within($path, $basedir)) {
|
||||
throw ValueException::invalid_value($path, "path");
|
||||
}
|
||||
|
@ -355,7 +396,7 @@ class app2 {
|
|||
#############################################################################
|
||||
# Paramètres spécifiques à cette application
|
||||
|
||||
protected string $appgroup;
|
||||
protected ?string $appgroup;
|
||||
|
||||
function getAppgroup(): ?string {
|
||||
return $this->appgroup;
|
||||
|
@ -394,21 +435,42 @@ class app2 {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->withProfile($this->fencedJoin($this->vardir, $name), $profile);
|
||||
$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";
|
||||
$file = $this->withProfile($this->fencedJoin($this->logdir, $name), $profile);
|
||||
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;
|
||||
}
|
||||
|
@ -416,20 +478,16 @@ class app2 {
|
|||
/**
|
||||
* obtenir le chemin absolu vers un fichier de travail
|
||||
* - si le chemin est absolu, il est inchangé
|
||||
* - si le chemin est qualifié (commence par ./ ou ../) ou sans chemin, il est
|
||||
* exprimé par rapport à $vardir
|
||||
* - sinon le chemin est exprimé par rapport au répertoire de travail de base
|
||||
* $datadir
|
||||
* - sinon le chemin est exprimé par rapport à $vardir/$appgroup
|
||||
*
|
||||
* is $ensure_dir, créer le répertoire du fichier s'il n'existe pas déjà
|
||||
* is $ensureDir, créer le répertoire du fichier s'il n'existe pas déjà
|
||||
*
|
||||
* 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 getWorkfile(?string $file, $profile=null, bool $ensureDir=true): ?string {
|
||||
if ($file === null) return null;
|
||||
if (path::is_qualified($file) || !path::have_dir($file)) {
|
||||
$file = path::reljoin($this->vardir, $file);
|
||||
} else {
|
||||
$file = path::reljoin($this->datadir, $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;
|
||||
|
@ -439,14 +497,17 @@ class app2 {
|
|||
* 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 au répertoire de travail $vardir
|
||||
* - 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 ($file === null) return null;
|
||||
function getUserfile(string $file): string {
|
||||
if (path::is_qualified($file)) {
|
||||
return path::reljoin($this->cwd, $file);
|
||||
} else {
|
||||
return path::reljoin($this->vardir, $file);
|
||||
return path::reljoin($this->vardir, $this->appgroup, $file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,7 +516,8 @@ class app2 {
|
|||
function getRunfile(): RunFile {
|
||||
$name = $this->name;
|
||||
$runfile = $this->getWorkfile($name);
|
||||
$logfile = $this->getLogfile($name);
|
||||
# ne pas spécifier $name pour avoir le fichier par défaut sans profil
|
||||
$logfile = $this->getLogfile();
|
||||
return $this->runfile ??= new RunFile($name, $runfile, $logfile);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +1,52 @@
|
|||
<?php
|
||||
namespace nur\cli;
|
||||
namespace nur\sery\wip\app\cli;
|
||||
|
||||
use Exception;
|
||||
use nur\b\ExitError;
|
||||
use nur\b\ValueException;
|
||||
use nur\cli\ArgsException;
|
||||
use nur\cli\ArgsParser;
|
||||
use nur\config;
|
||||
use nur\config\ArrayConfig;
|
||||
use nur\msg;
|
||||
use nur\os;
|
||||
use nur\path;
|
||||
use nur\sery\app\launcher;
|
||||
use nur\sery\app\RunFile;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\output\console as nconsole;
|
||||
use nur\sery\output\log as nlog;
|
||||
use nur\sery\output\msg as nmsg;
|
||||
use nur\sery\ExitError;
|
||||
use nur\sery\output\console;
|
||||
use nur\sery\output\log;
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\output\std\StdMessenger;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\wip\app\app2;
|
||||
use nur\sery\wip\app\RunFile;
|
||||
|
||||
/**
|
||||
* Class Application: application de base
|
||||
*/
|
||||
abstract class Application2 {
|
||||
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_DATADIR si le projet a pour
|
||||
* code xxx
|
||||
* 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.
|
||||
|
@ -74,73 +81,39 @@ abstract class Application2 {
|
|||
/** @var bool faut-il installer le gestionnaire de signaux? */
|
||||
const USE_SIGNAL_HANDLER = false;
|
||||
|
||||
protected static function _app_init(): void {
|
||||
config::set_fact(config::FACT_CLI_APP);
|
||||
|
||||
# avant que l'application soit configurée, configurer le mode debug
|
||||
msg::set_messenger_class(Console::class);
|
||||
msg::get()->setParametrableParams([
|
||||
# En ligne de commande, on peut afficher les messages loggués
|
||||
"display_log" => true,
|
||||
]);
|
||||
msg::get()->setLevels(msg::DEBUG_LEVELS, msg::DEBUG_LEVELS, msg::DEBUG);
|
||||
|
||||
# si un fichier nommé .default-profile-devel existe dans le répertoire de
|
||||
# l'application ou du projet, alors le profil par défaut est devel
|
||||
global $argv;
|
||||
$homedir = os::homedir();
|
||||
$projdir = path::abspath(path::dirname($argv[0]));
|
||||
while (true) {
|
||||
if (file_exists("$projdir/.default-profile-devel")) {
|
||||
config::set_default_profile(config::DEVEL);
|
||||
break;
|
||||
static function _manage_runfile(RunFile $runfile): void {
|
||||
global $argc, $argv;
|
||||
if ($argc <= 1 || $argv[1] !== "//") return;
|
||||
array_splice($argv, 1, 1); $argc--;
|
||||
$ec = 0;
|
||||
switch ($argv[1] ?? "infos") {
|
||||
case "release-lock":
|
||||
case "release":
|
||||
case "rl":
|
||||
$runfile->release();
|
||||
break;
|
||||
case "infos":
|
||||
case "i":
|
||||
if ($runfile->isRunning()) {
|
||||
$action = $runfile->getActionDesc();
|
||||
if ($action !== null) $action = ": $action";
|
||||
echo "running$action\n";
|
||||
} else {
|
||||
echo "not running\n";
|
||||
$ec = 1;
|
||||
}
|
||||
# s'arrêter au répertoire du projet, ou à $HOMEDIR, ou à la racine
|
||||
if (file_exists("$projdir/composer.json")) break;
|
||||
if ($projdir == $homedir) break;
|
||||
$projdir = path::dirname($projdir);
|
||||
if ($projdir == "/") break;
|
||||
break;
|
||||
default:
|
||||
fwrite(STDERR, "$argv[2]: unexpected command\n");
|
||||
$ec = 123;
|
||||
}
|
||||
|
||||
app2::init(static::class);
|
||||
nmsg::set_messenger(new StdMessenger([
|
||||
"min_level" => nmsg::DEBUG,
|
||||
]));
|
||||
exit($ec);
|
||||
}
|
||||
|
||||
protected static function _app_configure(Application2 $app): void {
|
||||
config::configure(config::CONFIGURE_INITIAL_ONLY);
|
||||
# revenir à la configuration par défaut une fois que la configuration
|
||||
# initiale est faite
|
||||
msg::get()->setLevels(msg::PRINT_LEVELS, msg::LOG_LEVELS, msg::TYPE_LEVELS);
|
||||
|
||||
$msgs = ["console" => new StdMessenger([
|
||||
"min_level" => nmsg::NORMAL,
|
||||
])];
|
||||
if (static::USE_LOGFILE) {
|
||||
$msgs["log"] = new StdMessenger([
|
||||
"output" => app2::get()->getLogfile(),
|
||||
"min_level" => nmsg::MINOR,
|
||||
"add_date" => true,
|
||||
]);
|
||||
}
|
||||
nmsg::init($msgs);
|
||||
|
||||
$app->parseArgs();
|
||||
config::configure();
|
||||
}
|
||||
|
||||
protected static function _app_main(Application2 $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));
|
||||
}
|
||||
|
||||
static function run(?Application2 $app=null): void {
|
||||
static function run(?Application $app=null): void {
|
||||
$unlock = false;
|
||||
$stop = false;
|
||||
$shutdownHandler = function () use (&$unlock, &$stop) {
|
||||
register_shutdown_function(function() use (&$unlock, &$stop) {
|
||||
if ($unlock) {
|
||||
app2::get()->getRunfile()->release();
|
||||
$unlock = false;
|
||||
|
@ -149,30 +122,18 @@ abstract class Application2 {
|
|||
app2::get()->getRunfile()->wfStop();
|
||||
$stop = false;
|
||||
}
|
||||
};
|
||||
register_shutdown_function($shutdownHandler);
|
||||
if (static::USE_SIGNAL_HANDLER) {
|
||||
$signalHandler = function(int $signo, $siginfo) {
|
||||
self::exit(255);
|
||||
};
|
||||
pcntl_signal(SIGHUP, $signalHandler);
|
||||
pcntl_signal(SIGINT, $signalHandler);
|
||||
pcntl_signal(SIGQUIT, $signalHandler);
|
||||
pcntl_signal(SIGTERM, $signalHandler);
|
||||
}
|
||||
});
|
||||
app2::install_signal_handler(static::USE_SIGNAL_HANDLER);
|
||||
try {
|
||||
static::_app_init();
|
||||
if (static::USE_RUNFILE) {
|
||||
static::_initialize_app();
|
||||
$useRunfile = static::USE_RUNFILE;
|
||||
$useRunlock = static::USE_RUNLOCK;
|
||||
if ($useRunfile) {
|
||||
$runfile = app2::get()->getRunfile();
|
||||
global $argc, $argv;
|
||||
if ($argc === 2 && ($argv[1] === "--Application-release-runlock" || $argv[1] === "--ARL")) {
|
||||
$runfile->release();
|
||||
exit(0);
|
||||
}
|
||||
$useRunlock = static::USE_RUNLOCK;
|
||||
if ($useRunlock && $runfile->warnIfLocked()) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
self::_manage_runfile($runfile);
|
||||
if ($useRunlock && $runfile->warnIfLocked()) exit(1);
|
||||
|
||||
$runfile->wfStart();
|
||||
$stop = true;
|
||||
if ($useRunlock) {
|
||||
|
@ -181,8 +142,8 @@ abstract class Application2 {
|
|||
}
|
||||
}
|
||||
if ($app === null) $app = new static();
|
||||
static::_app_configure($app);
|
||||
static::_app_main($app);
|
||||
static::_configure_app($app);
|
||||
static::_start_app($app);
|
||||
} catch (ExitError $e) {
|
||||
if ($e->haveMessage()) msg::error($e);
|
||||
exit($e->getCode());
|
||||
|
@ -192,6 +153,40 @@ abstract class Application2 {
|
|||
}
|
||||
}
|
||||
|
||||
protected static function _initialize_app(): void {
|
||||
app2::init(static::class);
|
||||
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" => app2::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)
|
||||
|
@ -217,32 +212,27 @@ abstract class Application2 {
|
|||
["group",
|
||||
["-p", "--profile", "--app-profile",
|
||||
"args" => 1, "argsdesc" => "PROFILE",
|
||||
"action" => [null, "set_application_profile"],
|
||||
"action" => [app2::class, "set_profile"],
|
||||
"help" => "spécifier le profil d'exécution",
|
||||
],
|
||||
["-P", "--prod", "action" => [config::class, "set_profile", config::PROD]],
|
||||
["-T", "--test", "action" => [config::class, "set_profile", config::TEST]],
|
||||
["--devel", "action" => [config::class, "set_profile", config::DEVEL]],
|
||||
["-P", "--prod", "action" => [app2::class, "set_profile", config::PROD]],
|
||||
["-T", "--test", "action" => [app2::class, "set_profile", config::TEST]],
|
||||
["--devel", "action" => [app2::class, "set_profile", config::DEVEL]],
|
||||
],
|
||||
];
|
||||
|
||||
static function set_application_profile(string $profile): void {
|
||||
config::set_profile($profile);
|
||||
}
|
||||
|
||||
const VERBOSITY_SECTION = [
|
||||
"title" => "NIVEAU D'INFORMATION",
|
||||
"show" => false,
|
||||
["group",
|
||||
["--verbosity",
|
||||
"args" => 1, "argsdesc" => "silent|very-quiet|quiet|verbose|debug|trace",
|
||||
"args" => 1, "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"]],
|
||||
["--sql-trace", "action" => [null, "set_application_sql_trace"]],
|
||||
],
|
||||
["-L", "--logfile",
|
||||
"args" => "file", "argsdesc" => "OUTPUT",
|
||||
|
@ -259,75 +249,37 @@ abstract class Application2 {
|
|||
];
|
||||
|
||||
static function set_application_verbosity(string $verbosity): void {
|
||||
$msg = msg::get();
|
||||
$nconsole = nconsole::get();
|
||||
$console = console::get();
|
||||
switch ($verbosity) {
|
||||
case "s":
|
||||
case "silent":
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::NEVER,
|
||||
msg::TECH => msg::NEVER,
|
||||
msg::EXCEPTION => msg::NEVER,
|
||||
]);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::NONE,
|
||||
]);
|
||||
break;
|
||||
case "Q":
|
||||
case "very-quiet":
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::CRITICAL,
|
||||
msg::TECH => msg::CRITICAL,
|
||||
msg::EXCEPTION => msg::NEVER,
|
||||
]);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::MAJOR,
|
||||
case "silent":
|
||||
$console->resetParams([
|
||||
"min_level" => msg::NONE,
|
||||
]);
|
||||
break;
|
||||
case "q":
|
||||
case "quiet":
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::MAJOR,
|
||||
msg::TECH => msg::MAJOR,
|
||||
msg::EXCEPTION => msg::NEVER,
|
||||
$console->resetParams([
|
||||
"min_level" => msg::MAJOR,
|
||||
]);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::MAJOR,
|
||||
break;
|
||||
case "n":
|
||||
case "normal":
|
||||
$console->resetParams([
|
||||
"min_level" => msg::NORMAL,
|
||||
]);
|
||||
break;
|
||||
case "v":
|
||||
case "verbose":
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::MINOR,
|
||||
msg::TECH => msg::MINOR,
|
||||
msg::EXCEPTION => msg::NEVER,
|
||||
]);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::MINOR,
|
||||
$console->resetParams([
|
||||
"min_level" => msg::MINOR,
|
||||
]);
|
||||
break;
|
||||
case "D":
|
||||
case "debug":
|
||||
config::set_debug();
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::MINOR,
|
||||
msg::TECH => msg::MINOR,
|
||||
msg::EXCEPTION => msg::NORMAL,
|
||||
], null, msg::DEBUG);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::DEBUG,
|
||||
]);
|
||||
break;
|
||||
case "T":
|
||||
case "trace":
|
||||
config::set_debug();
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::MINOR,
|
||||
msg::TECH => msg::MINOR,
|
||||
msg::EXCEPTION => msg::MINOR,
|
||||
], null, msg::DEBUG);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::DEBUG,
|
||||
$console->resetParams([
|
||||
"min_level" => msg::DEBUG,
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
|
@ -335,22 +287,16 @@ abstract class Application2 {
|
|||
}
|
||||
}
|
||||
|
||||
static function set_application_sql_trace(): void {
|
||||
config::add(new ArrayConfig(["app" => ["trace_sql" => true]]));
|
||||
}
|
||||
|
||||
static function set_application_log_output(string $logfile): void {
|
||||
msg::get()->setParametrableParams(["log_output" => $logfile]);
|
||||
nlog::create_or_reset_params([
|
||||
log::create_or_reset_params([
|
||||
"output" => $logfile,
|
||||
], StdMessenger::class, [
|
||||
"add_date" => true,
|
||||
"min_level" => nlog::MINOR,
|
||||
"min_level" => log::MINOR,
|
||||
]);
|
||||
}
|
||||
static function set_application_color(bool $color): void {
|
||||
msg::get()->setParametrableParams(["color" => $color]);
|
||||
nconsole::reset_params([
|
||||
console::reset_params([
|
||||
"color" => $color,
|
||||
]);
|
||||
}
|
||||
|
@ -375,8 +321,8 @@ abstract class Application2 {
|
|||
const DEFAULT_PROFILE_COLOR = "y";
|
||||
|
||||
/** retourner le profil courant en couleur */
|
||||
static function profile(?string $profile=null): string {
|
||||
if ($profile === null) $profile = config::get_profile();
|
||||
static function get_profile(?string $profile=null): string {
|
||||
if ($profile === null) $profile = app2::get_profile();
|
||||
foreach (static::PROFILE_COLORS as $text => $color) {
|
||||
if (strpos($profile, $text) !== false) {
|
||||
return $color? "<color $color>$profile</color>": $profile;
|
||||
|
@ -387,15 +333,4 @@ abstract class Application2 {
|
|||
}
|
||||
|
||||
abstract function main();
|
||||
|
||||
const BGLAUNCH_SCRIPT = null;
|
||||
static function runfile(): RunFile {
|
||||
$callerParams = app2::get()->getParams();
|
||||
return app2::with(static::class, $callerParams)->getRunfile();
|
||||
}
|
||||
static function bglaunch(?array $args=null) {
|
||||
launcher::launch(static::class, cl::merge([
|
||||
static::BGLAUNCH_SCRIPT,
|
||||
], $args));
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
* [ ] implémenter les arguments avancés avec le préfixe "++" sur la description
|
||||
* [ ] pour le nombre d'arguments, supporter l'alias `*` pour `0..N` et `+` pour `1..N`
|
||||
|
||||
* [ ] transformer un schéma en définition d'arguments, un tableau en liste d'arguments, et vice-versa
|
||||
|
||||
actuellement, même si la surcharge d'option fonctionne, l'affichage de l'aide est incorrecte
|
||||
* possibilité de merger *une définition d'option*
|
||||
|
@ -11,21 +11,21 @@ actuellement, même si la surcharge d'option fonctionne, l'affichage de l'aide e
|
|||
* cela implique d'avoir un moyen de sélectionner une précédente définition, e.g
|
||||
~~~php
|
||||
const ARGS = [
|
||||
## exemple n°1
|
||||
["-o", "--option"],
|
||||
["extends" => "-o", "disabled" => true],
|
||||
# effet: la définition -o,--option est supprimée
|
||||
## exemple n°1
|
||||
["-o", "--option"],
|
||||
["extends" => "-o", "disabled" => true],
|
||||
# effet: la définition -o,--option est supprimée
|
||||
|
||||
## exemple n°2
|
||||
# étendre une précédente définition
|
||||
["-a", "--first"],
|
||||
["extends" => "-a", "extends_delete" => ["--first"], "--second"],
|
||||
# résultat final: ["-a", "--second"]
|
||||
## exemple n°2
|
||||
# étendre une précédente définition
|
||||
["-a", "--first"],
|
||||
["extends" => "-a", "extends_delete" => ["--first"], "--second"],
|
||||
# résultat final: ["-a", "--second"]
|
||||
|
||||
## exemple n°3
|
||||
# créer une définition à partir d'une autre
|
||||
["merge" => ["-b", "--premier"], "merge_delete" => ["--premier"], "--deuxieme"],
|
||||
# résultat final: ["-b", "--deuxieme"]
|
||||
## exemple n°3
|
||||
# créer une définition à partir d'une autre
|
||||
["merge" => ["-b", "--premier"], "merge_delete" => ["--premier"], "--deuxieme"],
|
||||
# résultat final: ["-b", "--deuxieme"]
|
||||
];
|
||||
~~~
|
||||
* pour faciliter l'implémentation de toutes ces fonctionnalités, faire une classe Option qui contient toutes les méthodes appropriées
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace nur\sery\app;
|
||||
namespace nur\sery\wip\app;
|
||||
|
||||
use nur\sery\app\Runfile;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\file\TmpfileWriter;
|
||||
use nur\sery\os\path;
|
||||
|
@ -10,7 +11,7 @@ use nur\sery\StateException;
|
|||
use nur\sery\str;
|
||||
use nur\sery\wip\app\app2;
|
||||
|
||||
class launcher2 {
|
||||
class launcher {
|
||||
/**
|
||||
* transformer une liste d'argument de la forme
|
||||
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\cli;
|
||||
|
||||
use Exception;
|
||||
use nur\sery\ExitError;
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\output\std\StdMessenger;
|
||||
|
||||
/**
|
||||
* Class Application: une application en ligne de commande
|
||||
*/
|
||||
abstract class Application {
|
||||
protected static function _app_init(): void {
|
||||
msg::set_messenger_class(StdMessenger::class);
|
||||
}
|
||||
|
||||
protected static function _app_configure(Application $app): void {
|
||||
$app->parseArgs();
|
||||
}
|
||||
|
||||
protected static function _app_main(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));
|
||||
}
|
||||
|
||||
static function run(?Application $app=null): void {
|
||||
try {
|
||||
static::_app_init();
|
||||
if ($app === null) $app = new static();
|
||||
static::_app_configure($app);
|
||||
static::_app_main($app);
|
||||
} catch (ExitError $e) {
|
||||
msg::error($e->getUserMessage());
|
||||
exit($e->getCode());
|
||||
} catch (Exception $e) {
|
||||
msg::error($e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ARGS = [];
|
||||
|
||||
/** @throws ArgsException */
|
||||
function parseArgs(array $args=null): void {
|
||||
$parser = new ArgsParser(static::ARGS);
|
||||
$parser->parse($this, $args);
|
||||
}
|
||||
|
||||
abstract function main();
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\cli;
|
||||
|
||||
use nur\sery\UserException;
|
||||
|
||||
/**
|
||||
* Class ArgsException: exception lancée quand il y a une erreur dans l'analyse
|
||||
* des arguments de la ligne de commande
|
||||
*/
|
||||
class ArgsException extends UserException {
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\cli;
|
||||
|
||||
use nur\A;
|
||||
use nur\sery\php\nur_func;
|
||||
|
||||
/**
|
||||
* Class DynamicCommand: implémentation par défaut de {@link IDynamicCommand}
|
||||
*
|
||||
*/
|
||||
class DynamicCommand implements IDynamicCommand {
|
||||
/**
|
||||
* retourner la liste des commandes sous la forme d'un tableau associatif avec
|
||||
* des éléments { $command => $cdef }
|
||||
*/
|
||||
protected function COMMANDS(): array {
|
||||
return static::COMMANDS;
|
||||
} const COMMANDS = null;
|
||||
|
||||
private $commands;
|
||||
private $dcommands;
|
||||
private $aliases;
|
||||
|
||||
protected function buildCommands(): void {
|
||||
if ($this->commands !== null) return;
|
||||
$commands = [];
|
||||
$dcommands = [];
|
||||
$aliases = [];
|
||||
$index = 0;
|
||||
foreach ($this->COMMANDS() as $key => $cdef) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
[$cnames, $assoc] = A::split_assoc($cdef);
|
||||
$cname = $cnames[0];
|
||||
if ($cname === null) {
|
||||
# commande complètement dynamique
|
||||
$dcommands[] = $cnames[2];
|
||||
if ($cnames[1] === null) continue;
|
||||
$cdef = [null, $cnames[1]];
|
||||
$cname = $cnames[1][0];
|
||||
$cnames = [];
|
||||
}
|
||||
} else {
|
||||
$cname = $key;
|
||||
$cnames = [$cname];
|
||||
[$seq, $assoc] = A::split_assoc($cdef);
|
||||
A::merge($cnames, $seq);
|
||||
A::merge_assoc($cdef, $cnames, $assoc, true);
|
||||
}
|
||||
$commands[$cname] = $cdef;
|
||||
foreach ($cnames as $key) {
|
||||
$aliases[$key] = $cname;
|
||||
}
|
||||
}
|
||||
$this->commands = $commands;
|
||||
$this->dcommands = $dcommands;
|
||||
$this->aliases = $aliases;
|
||||
}
|
||||
|
||||
function getCommands(): ?array {
|
||||
$this->buildCommands();
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
|
||||
function getCommandDefs(string $command, bool $virtual): ?array {
|
||||
$this->buildCommands();
|
||||
$command = A::get($this->aliases, $command, $command);
|
||||
$cdef = A::get($this->commands, $command);
|
||||
if ($cdef !== null) {
|
||||
if ($cdef[0] === null) {
|
||||
if ($virtual) $cdef = $cdef[1];
|
||||
else return null;
|
||||
}
|
||||
return $cdef !== null? [$cdef]: null;
|
||||
}
|
||||
# tester les commandes complètement dynamiques
|
||||
foreach ($this->dcommands as $func) {
|
||||
$cdef = nur_func::call($func, $command);
|
||||
if ($cdef !== null) return [$cdef];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\cli;
|
||||
|
||||
use nur\sery\php\nur_func;
|
||||
|
||||
class DynamicCommandMethod implements IDynamicCommand {
|
||||
function __construct($func) {
|
||||
$this->func = $func;
|
||||
}
|
||||
|
||||
/** @var object */
|
||||
private $dest;
|
||||
|
||||
function setDest($dest): void {
|
||||
if (!is_object($dest)) $dest = null;
|
||||
$this->dest = $dest;
|
||||
}
|
||||
|
||||
function getCommands(): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
private $func;
|
||||
|
||||
function getCommandDefs(string $command, bool $virtual): ?array {
|
||||
$func = $this->func;
|
||||
$func_args = [$command];
|
||||
nur_func::check_func($func, $this->dest, $func_args);
|
||||
return nur_func::call($func, ...$func_args);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\cli;
|
||||
|
||||
/**
|
||||
* Class IDynamicCommand: gestionnaire de commandes dynamiques
|
||||
*/
|
||||
interface IDynamicCommand {
|
||||
/**
|
||||
* retourner la liste des commandes valides, ou null si cette liste ne peut
|
||||
* pas être construite
|
||||
*/
|
||||
function getCommands(): ?array;
|
||||
|
||||
/**
|
||||
* retourner les définitions pour la commande spécifiée, ou null si elle n'est
|
||||
* pas valide
|
||||
*/
|
||||
function getCommandDefs(string $command, bool $virtual): ?array;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\tools;
|
||||
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\wip\app\app2;
|
||||
use nur\sery\wip\app\cli\Application;
|
||||
|
||||
class SteamTrainApp extends Application {
|
||||
const TITLE = "Train à vapeur";
|
||||
const USE_SIGNAL_HANDLER = true;
|
||||
const USE_LOGFILE = true;
|
||||
const USE_RUNFILE = true;
|
||||
const USE_RUNLOCK = true;
|
||||
|
||||
const ARGS = [
|
||||
"purpose" => self::TITLE,
|
||||
];
|
||||
|
||||
function main() {
|
||||
$runfile = app2::get()->getRunfile();
|
||||
$runfile->action("Running train...", 100);
|
||||
for ($i = 1; $i <= 100; $i++) {
|
||||
msg::print("Tchou-tchou! x $i");
|
||||
$runfile->step();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue