modifs.mineures sans commentaires
This commit is contained in:
parent
7782b1039c
commit
15f81c8ecf
|
@ -1,5 +1,8 @@
|
|||
/.phpunit.result.cache
|
||||
/devel/*
|
||||
!/devel/.keep
|
||||
|
||||
.~lock*#
|
||||
.*.swp
|
||||
/vendor/
|
||||
|
||||
/.phpunit.result.cache
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/php
|
||||
<?php
|
||||
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use nur\tools\SteamTrainApp;
|
||||
|
||||
SteamTrainApp::run();
|
|
@ -40,12 +40,18 @@ abstract class Application {
|
|||
* @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
|
||||
*
|
||||
* si non définie, cette valeur est calculée automatiquement à partir de
|
||||
* self::PROJDIR sans le suffixe "-app"
|
||||
*/
|
||||
const APPCODE = null;
|
||||
|
||||
/**
|
||||
* @var string code de l'application, utilisé pour inférer le nom de certains
|
||||
* fichiers spécifiques à l'application
|
||||
* fichiers spécifiques à l'application.
|
||||
*
|
||||
* si non définie, cette valeur est calculée automatiquement à partir de
|
||||
* static::class
|
||||
*/
|
||||
const NAME = null;
|
||||
|
||||
|
|
|
@ -0,0 +1,401 @@
|
|||
<?php
|
||||
namespace nur\cli;
|
||||
|
||||
use Exception;
|
||||
use nur\b\ExitError;
|
||||
use nur\b\ValueException;
|
||||
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\output\std\StdMessenger;
|
||||
use nur\sery\wip\app\app2;
|
||||
|
||||
/**
|
||||
* Class Application: application de base
|
||||
*/
|
||||
abstract class Application2 {
|
||||
/** @var string répertoire du projet (celui qui contient composer.json */
|
||||
const PROJDIR = null;
|
||||
|
||||
/**
|
||||
* @var array répertoires vendor exprimés relativement à PROJDIR
|
||||
*/
|
||||
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
|
||||
*
|
||||
* si non définie, cette valeur est calculée automatiquement à partir de
|
||||
* self::PROJDIR sans le suffixe "-app"
|
||||
*/
|
||||
const APPCODE = 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 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;
|
||||
}
|
||||
# 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;
|
||||
}
|
||||
|
||||
app2::init(static::class);
|
||||
nmsg::set_messenger(new StdMessenger([
|
||||
"min_level" => nmsg::DEBUG,
|
||||
]));
|
||||
}
|
||||
|
||||
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 {
|
||||
$unlock = false;
|
||||
$stop = false;
|
||||
$shutdownHandler = function () use (&$unlock, &$stop) {
|
||||
if ($unlock) {
|
||||
app2::get()->getRunfile()->release();
|
||||
$unlock = false;
|
||||
}
|
||||
if ($stop) {
|
||||
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);
|
||||
}
|
||||
try {
|
||||
static::_app_init();
|
||||
if (static::USE_RUNFILE) {
|
||||
$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);
|
||||
}
|
||||
$runfile->wfStart();
|
||||
$stop = true;
|
||||
if ($useRunlock) {
|
||||
$runfile->lock();
|
||||
$unlock = true;
|
||||
}
|
||||
}
|
||||
if ($app === null) $app = new static();
|
||||
static::_app_configure($app);
|
||||
static::_app_main($app);
|
||||
} catch (ExitError $e) {
|
||||
if ($e->haveMessage()) msg::error($e);
|
||||
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 PROFILE_SECTION = [
|
||||
"title" => "PROFILS D'EXECUTION",
|
||||
["group",
|
||||
["-p", "--profile", "--app-profile",
|
||||
"args" => 1, "argsdesc" => "PROFILE",
|
||||
"action" => [null, "set_application_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]],
|
||||
],
|
||||
];
|
||||
|
||||
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",
|
||||
"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",
|
||||
"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 {
|
||||
$msg = msg::get();
|
||||
$nconsole = nconsole::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,
|
||||
]);
|
||||
break;
|
||||
case "q":
|
||||
case "quiet":
|
||||
$msg->setLevels([
|
||||
msg::USER => msg::MAJOR,
|
||||
msg::TECH => msg::MAJOR,
|
||||
msg::EXCEPTION => msg::NEVER,
|
||||
]);
|
||||
$nconsole->resetParams([
|
||||
"min_level" => nmsg::MAJOR,
|
||||
]);
|
||||
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,
|
||||
]);
|
||||
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,
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
throw ValueException::invalid_value($verbosity, "verbosity");
|
||||
}
|
||||
}
|
||||
|
||||
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([
|
||||
"output" => $logfile,
|
||||
], StdMessenger::class, [
|
||||
"add_date" => true,
|
||||
"min_level" => nlog::MINOR,
|
||||
]);
|
||||
}
|
||||
static function set_application_color(bool $color): void {
|
||||
msg::get()->setParametrableParams(["color" => $color]);
|
||||
nconsole::reset_params([
|
||||
"color" => $color,
|
||||
]);
|
||||
}
|
||||
const ARGS = [
|
||||
"sections" => [
|
||||
self::PROFILE_SECTION,
|
||||
self::VERBOSITY_SECTION,
|
||||
],
|
||||
];
|
||||
|
||||
/** @throws ArgsException */
|
||||
function parseArgs(array $args=null): void {
|
||||
$parser = new ArgsParser(static::ARGS);
|
||||
$parser->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 profile(?string $profile=null): string {
|
||||
if ($profile === null) $profile = config::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();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?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,136 @@
|
|||
<?php
|
||||
namespace nur\sery\app;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\file\TmpfileWriter;
|
||||
use nur\sery\os\path;
|
||||
use nur\sery\os\proc\Cmd;
|
||||
use nur\sery\output\msg;
|
||||
use nur\sery\StateException;
|
||||
use nur\sery\str;
|
||||
use nur\sery\wip\app\app2;
|
||||
|
||||
class launcher2 {
|
||||
/**
|
||||
* transformer une liste d'argument de la forme
|
||||
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
||||
* - ["myOpt" => true] devient ["--my-opt"]
|
||||
* - ["myOpt" => false] est momis
|
||||
* - les valeurs séquentielles sont prises telles quelles
|
||||
*/
|
||||
static function verifix_args(array $args): array {
|
||||
if (!cl::is_list($args)) {
|
||||
$fixedArgs = [];
|
||||
$index = 0;
|
||||
foreach ($args as $arg => $value) {
|
||||
if ($arg === $index) {
|
||||
$index++;
|
||||
$fixedArgs[] = $value;
|
||||
continue;
|
||||
} elseif ($value === false) {
|
||||
continue;
|
||||
}
|
||||
$arg = str::us2camel($arg);
|
||||
$arg = str::camel2us($arg, false, "-");
|
||||
$arg = str_replace("_", "-", $arg);
|
||||
$fixedArgs[] = "--$arg";
|
||||
if ($value !== true) $fixedArgs[] = "$value";
|
||||
}
|
||||
$args = $fixedArgs;
|
||||
}
|
||||
# corriger le chemin de l'application pour qu'il soit absolu et normalisé
|
||||
$args[0] = path::abspath($args[0]);
|
||||
return $args;
|
||||
}
|
||||
|
||||
static function launch(string $appClass, array $args): int {
|
||||
$app = app2::get();
|
||||
$vendorBindir = $app->getVendorbindir();
|
||||
$launch_php = "$vendorBindir/_launch.php";
|
||||
if (!file_exists($launch_php)) {
|
||||
$launch_php = __DIR__."/../../lib/_launch.php";
|
||||
}
|
||||
$tmpfile = new TmpfileWriter();
|
||||
$tmpfile->keep()->serialize($app->getParams());
|
||||
|
||||
$args = self::verifix_args($args);
|
||||
$cmd = new Cmd([
|
||||
$launch_php,
|
||||
"--internal-use", $tmpfile->getFile(),
|
||||
$appClass, "--", ...$args,
|
||||
]);
|
||||
$cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
|
||||
$cmd->passthru($exitcode);
|
||||
|
||||
# attendre un peu que la commande aie le temps de s'initialiser
|
||||
sleep(1);
|
||||
|
||||
$tmpfile->close();
|
||||
return $exitcode;
|
||||
}
|
||||
|
||||
static function _start(array $args, Runfile $runfile): bool {
|
||||
if ($runfile->warnIfLocked()) return false;
|
||||
$pid = pcntl_fork();
|
||||
if ($pid == -1) {
|
||||
# parent, impossible de forker
|
||||
throw new StateException("unable to fork");
|
||||
} elseif ($pid) {
|
||||
# parent, fork ok
|
||||
return true;
|
||||
} else {
|
||||
## child, fork ok
|
||||
# Créer un groupe de process, pour pouvoir tuer tous les enfants en même temps
|
||||
$runfile->tm_startPg();
|
||||
$logfile = $runfile->getLogfile() ?? "/tmp/nulib_app_launcher-_start.log";
|
||||
$pid = posix_getpid();
|
||||
$exitcode = -776;
|
||||
try {
|
||||
# puis lancer la commande
|
||||
$cmd = new Cmd($args);
|
||||
$cmd->addSource("/g/init.env");
|
||||
$cmd->addRedir("both", $logfile, true);
|
||||
msg::debug("$pid: launching\n".$cmd->getCmd());
|
||||
$cmd->fork_exec($exitcode);
|
||||
msg::debug("$pid: exitcode=$exitcode");
|
||||
return true;
|
||||
} finally {
|
||||
$runfile->wfStopped($exitcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function _stop(Runfile $runfile): void {
|
||||
$data = $runfile->read();
|
||||
$pid = $data["pg_pid"];
|
||||
if ($pid === null) {
|
||||
msg::warning("$data[name]: groupe de process inconnu");
|
||||
return;
|
||||
}
|
||||
msg::action("kill $pid");
|
||||
if (!posix_kill(-$pid, SIGKILL)) {
|
||||
switch (posix_get_last_error()) {
|
||||
case PCNTL_ESRCH:
|
||||
msg::afailure("process inexistant");
|
||||
break;
|
||||
case PCNTL_EPERM:
|
||||
msg::afailure("process non accessible");
|
||||
break;
|
||||
case PCNTL_EINVAL:
|
||||
msg::afailure("signal invalide");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$timeout = 10;
|
||||
while ($runfile->tm_isUndead($pid)) {
|
||||
sleep(1);
|
||||
if (--$timeout == 0) {
|
||||
msg::afailure("impossible d'arrêter la tâche");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$runfile->wfStopped(-778);
|
||||
msg::asuccess();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace nur\sery\wip\app;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class appTest extends TestCase {
|
||||
function testVerifix_name() {
|
||||
$name = 'my\package\MyApplication';
|
||||
$verifix_name = function($name) {
|
||||
self::verifix_name($name);
|
||||
return $name;
|
||||
};
|
||||
$app = new app(null);
|
||||
self::assertSame("my-application", $verifix_name->call($app, "$name"));
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ class app {
|
|||
|
||||
private static function verifix_name(string &$name): void {
|
||||
# si $name est une classe, enlever le package et normaliser
|
||||
# my\package\MyApplication --> my-application
|
||||
$name = preg_replace('/.*\\\\/', "", $name);
|
||||
$name = str::without_suffix("-app", str::camel2us($name, false, "-"));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,419 @@
|
|||
<?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\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;
|
||||
|
||||
class app2 {
|
||||
static ?func $is_bgapplication_enabled = null;
|
||||
|
||||
/**
|
||||
* spécifier la fonction permettant de vérifier si l'exécution de tâches
|
||||
* planifiées est autorisé. Par défaut, les tâches planifiées sont autorisées
|
||||
*/
|
||||
static function set_bgapplication_enabled_checker($func): void {
|
||||
self::$is_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::$is_bgapplication_enabled === null) return;
|
||||
$enabled = boolval(self::$is_bgapplication_enabled->invoke());
|
||||
if (!$forceEnabled && !$enabled) {
|
||||
msg::debug("Planifications désactivées. L'application n'a pas été lancée");
|
||||
throw new ExitError();
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static function get_params($app): array {
|
||||
if ($app instanceof self) {
|
||||
$params = $app->getParams();
|
||||
} elseif ($app instanceof Application || $app instanceof Application2) {
|
||||
$params = [
|
||||
"projdir" => $app::PROJDIR,
|
||||
"vendor" => $app::VENDOR,
|
||||
"appcode" => $app::APPCODE,
|
||||
"datadir" => $app::DATADIR,
|
||||
"etcdir" => $app::ETCDIR,
|
||||
"vardir" => $app::VARDIR,
|
||||
"logdir" => $app::LOGDIR,
|
||||
"apptype" => "cli",
|
||||
"name" => $app::NAME,
|
||||
"title" => $app::TITLE,
|
||||
];
|
||||
} elseif (self::isa_Application($app)) {
|
||||
$params = [
|
||||
"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"),
|
||||
"apptype" => "cli",
|
||||
"name" => constant("$app::NAME"),
|
||||
"title" => constant("$app::TITLE"),
|
||||
];
|
||||
} elseif (is_array($app)) {
|
||||
$params = $app;
|
||||
} else {
|
||||
throw ValueException::invalid_type($app, Application::class);
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Application|string $app
|
||||
* @param Application|string $proj
|
||||
*/
|
||||
static function with($app, $proj=null): self {
|
||||
$params = self::get_params($app);
|
||||
$proj_params = $proj !== null? self::get_params($proj): null;
|
||||
if ($proj_params !== null) {
|
||||
A::merge($params, cl::select($proj_params, [
|
||||
"projdir",
|
||||
"vendor",
|
||||
"appcode",
|
||||
"datadir",
|
||||
"etcdir",
|
||||
"vardir",
|
||||
"logdir",
|
||||
]));
|
||||
}
|
||||
return new static($params, $proj_params !== null);
|
||||
}
|
||||
|
||||
protected static ?self $app = null;
|
||||
|
||||
static function init($app, $proj=null): void {
|
||||
self::$app = static::with($app, $proj);
|
||||
}
|
||||
|
||||
static function get(): self {
|
||||
return self::$app ??= new self(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
"cwd" => $cwd,
|
||||
"datadir" => $datadir,
|
||||
"etcdir" => $etcdir,
|
||||
"vardir" => $vardir,
|
||||
"logdir" => $logdir,
|
||||
"profile" => $profile,
|
||||
] = $params;
|
||||
} else {
|
||||
$projdir = path::abspath($params["projdir"] ?? ".");
|
||||
$vendor = $params["vendor"] ?? self::DEFAULT_VENDOR;
|
||||
$vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]);
|
||||
$vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]);
|
||||
$appcode = $params["appcode"] ?? "app";
|
||||
$APPCODE = str_replace("-", "_", strtoupper($appcode));
|
||||
# cwd
|
||||
$cwd = getcwd();
|
||||
# datadir
|
||||
$datadir = getenv("${APPCODE}_DATADIR");
|
||||
$datadirIsDefined = $datadir !== false;
|
||||
if ($datadir === false) $datadir = $params["datadir"] ?? null;
|
||||
if ($datadir === null) $datadir = "devel/data";
|
||||
$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);
|
||||
# profile
|
||||
$profile = getenv("${APPCODE}_PROFILE");
|
||||
if ($profile === false) $profile = getenv("APP_PROFILE");
|
||||
if ($profile === false) $profile = $params["profile"] ?? null;
|
||||
if ($profile === null) $profile = $datadirIsDefined? "prod": "devel";
|
||||
}
|
||||
$this->projdir = $projdir;
|
||||
$this->vendor = $vendor;
|
||||
$this->appcode = $appcode;
|
||||
$this->cwd = $cwd;
|
||||
$this->datadir = $datadir;
|
||||
$this->etcdir = $etcdir;
|
||||
$this->vardir = $vardir;
|
||||
$this->logdir = $logdir;
|
||||
$this->profile = $profile;
|
||||
|
||||
# apptype, name, title
|
||||
$this->apptype = $params["apptype"] ?? "cli";
|
||||
$name = $params["name"] ?? 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::without_suffix("-app", str::camel2us($name, false, "-"));
|
||||
}
|
||||
$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 string $profile;
|
||||
|
||||
function getProfile(): string {
|
||||
return $this->profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string|false $profile
|
||||
*/
|
||||
function withProfile(string $file, $profile): string {
|
||||
if ($profile !== false) {
|
||||
if ($profile === null) $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 $path): string {
|
||||
$path = path::reljoin($basedir, $path);
|
||||
if (!path::is_within($path, $basedir)) {
|
||||
throw ValueException::invalid_value($path, "path");
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Paramètres spécifiques à cette application
|
||||
|
||||
protected string $apptype;
|
||||
|
||||
function getApptype(): string {
|
||||
return $this->apptype;
|
||||
}
|
||||
|
||||
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->profile,
|
||||
"apptype" => $this->apptype,
|
||||
"name" => $this->name,
|
||||
"title" => $this->title,
|
||||
];
|
||||
}
|
||||
|
||||
function getEtcfile(?string $name=null, $profile=null): string {
|
||||
if ($name === null) $name = "{$this->name}.conf";
|
||||
return $this->findFile([$this->etcdir], [$name], $profile);
|
||||
}
|
||||
|
||||
function getVarfile(?string $name=null, $profile=null): string {
|
||||
if ($name === null) $name = "{$this->name}.tmp";
|
||||
$file = $this->withProfile($this->fencedJoin($this->vardir, $name), $profile);
|
||||
sh::mkdirof($file);
|
||||
return $file;
|
||||
}
|
||||
|
||||
function getLogfile(?string $name=null, $profile=null): string {
|
||||
if ($name === null) $name = "{$this->name}.log";
|
||||
$file = $this->withProfile($this->fencedJoin($this->logdir, $name), $profile);
|
||||
sh::mkdirof($file);
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* is $ensure_dir, créer le répertoire du fichier s'il n'existe pas déjà
|
||||
*/
|
||||
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);
|
||||
}
|
||||
$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 au répertoire de travail $vardir
|
||||
*/
|
||||
function getUserfile(?string $file): ?string {
|
||||
if ($file === null) return null;
|
||||
if (path::is_qualified($file)) {
|
||||
return path::reljoin($this->cwd, $file);
|
||||
} else {
|
||||
return path::reljoin($this->vardir, $file);
|
||||
}
|
||||
}
|
||||
|
||||
protected ?RunFile $runfile = null;
|
||||
|
||||
function getRunfile(): RunFile {
|
||||
$name = $this->name;
|
||||
$runfile = $this->getWorkfile($name);
|
||||
$logfile = $this->getLogfile($name);
|
||||
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];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue