modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2024-09-23 17:18:00 +04:00
parent 7782b1039c
commit 15f81c8ecf
11 changed files with 1039 additions and 2 deletions

5
.gitignore vendored
View File

@ -1,5 +1,8 @@
/.phpunit.result.cache
/devel/*
!/devel/.keep
.~lock*#
.*.swp
/vendor/
/.phpunit.result.cache

0
devel/.keep Normal file
View File

7
nur_bin/steam-train.php Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/php
<?php
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
use nur\tools\SteamTrainApp;
SteamTrainApp::run();

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

136
src/app/launcher2.php Normal file
View File

@ -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();
}
}

17
tests/wip/app/appTest.php Normal file
View File

@ -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"));
}
}

View File

@ -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, "-"));
}

419
wip/app/app2.php Normal file
View File

@ -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];
}
}