maj nur-base

This commit is contained in:
Jephté Clain 2024-04-04 15:36:42 +04:00
parent 9415a1eb04
commit fc6963c39e
7 changed files with 341 additions and 14 deletions

5
nur_bin/compdep.php Executable file
View File

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

View File

@ -3,7 +3,6 @@ namespace nur\ref;
use nur\data\types\Metadata;
use nur\md;
use nur\sery\php\content\content;
/**
* Class ref_type: référence des types utilisables dans les schémas

View File

@ -8,26 +8,45 @@ class shutils {
static function ls_all(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
$all = scandir($dir, $sorting_order);
if ($all === false) return [];
return array_filter($all,
return array_values(array_filter($all,
function ($file) use ($pattern) {
if ($file === "." || $file === "..") return false;
return $pattern === null || fnmatch($pattern, $file);
});
}
));
}
static function ls_dirs(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
return array_filter(self::ls_all($dir, $pattern, $sorting_order),
return array_values(array_filter(self::ls_all($dir, $pattern, $sorting_order),
function ($file) use ($dir) {
return path::is_dir(path::join($dir, $file));
}
);
));
}
static function ls_files(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
return array_filter(self::ls_all($dir, $pattern, $sorting_order),
return array_values(array_filter(self::ls_all($dir, $pattern, $sorting_order),
function ($file) use ($dir) {
return path::is_file(path::join($dir, $file));
}
);
));
}
static function ls_pall(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
return array_map(function(string $name) use ($dir) {
return path::join($dir, $name);
}, self::ls_all($dir, $pattern, $sorting_order));
}
static function ls_pdirs(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
return array_map(function(string $name) use ($dir) {
return path::join($dir, $name);
}, self::ls_dirs($dir, $pattern, $sorting_order));
}
static function ls_pfiles(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
return array_map(function(string $name) use ($dir) {
return path::join($dir, $name);
}, self::ls_files($dir, $pattern, $sorting_order));
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace nur\tools\compctl;
use nur\A;
use nur\cli\Application;
use nur\msg;
use nur\shutils;
class CompdepApp extends Application {
private static function split_pkey_value(string $pv): array {
if (preg_match('/([a-zA-Z0-9._-]+)=(.*)/', $pv, $ms)) {
return [$ms[1], $ms[2]];
} else {
return [$pv, true];
}
}
const SHOW_NAMES = "names", SHOW_DEPS = "deps", SHOW_DIRS = "dirs", SHOW_COMPOSER = "composer";
const ARGS = [
"purpose" => "Lister les dépendances d'un projet PHP",
["-B", "--projects-dir", "args" => "dir",
"help" => "Spécifier le répertoire de base des projets",
],
["-w", "--rewrite",
"help" => "modifier le fichier composer.json au lieu d'afficher le nouveau contenu",
],
["--force-rewrite",
"help" => "en cas de modification, forcer la ré-écriture du fichier composer.json même s'il est inchangé",
],
["-s", "--set-pkey", "args" => 1, "argsdesc" => "PKEY=VALUE",
"help" => "créer ou modifier le chemin de clé avec la valeur spécifiée",
],
["-d", "--del-pkey", "args" => 1, "argsdesc" => "PKEY",
"help" => "supprimer le chemin de clé spécifié",
],
["-r", "--filter-dep", "args" => 1, "argsdesc" => "DEP",
"help" => "sélectionner tous les projets qui ont une dépendance sur la librairie spécifiée",
],
["-u", "--update-dep", "args" => "value", "argsdesc" => "VERSION",
"help" => "mettre à jour la dépendance avec la nouvelle cible/version.
requière l'utilisation de --filter-dep",
],
["--sn", "--show-names", "name" => "show", "value" => self::SHOW_NAMES,
"help" => "afficher les noms des projets",
],
["--sp", "--show-deps", "name" => "show", "value" => self::SHOW_DEPS,
"help" => "afficher les branches",
],
["--sb", "--show-branches",
"help" => "avec --show-deps, afficher aussi les branches",
],
["--sf", "--show-filtered",
"help" => "avec --show-deps, n'afficher que la dépendances sélectionnée
implique --show-branches",
],
["--sd", "--show-dirs", "name" => "show", "value" => self::SHOW_DIRS,
"help" => "afficher les répertoires des projets",
],
["--sc", "--show-composer", "name" => "show", "value" => self::SHOW_COMPOSER,
"help" => "afficher les chemins des fichiers composer.json de chaque projet",
],
];
private $projectsDir;
private $rewrite = false, $forceRewrite = false;
private $setPkey, $delPkey;
private $filterDep, $updateDep;
private $show = self::SHOW_DEPS, $showBranches, $showFiltered;
private $args;
function main() {
$projectDirs = $this->args;
$rewrite = $this->rewrite;
$forceRewrite = $this->forceRewrite;
$setPkey = $this->setPkey;
$delPkey = $this->delPkey;
$filterDep = $this->filterDep;
$updateDep = $this->updateDep;
$projectsDir = $this->projectsDir;
$show = $this->show;
$showBranches = $this->showBranches;
$showFiltered = $this->showFiltered;
if ($updateDep && !$filterDep) {
self::die("--update-dep requière --filter-dep");
}
if (!$projectDirs) {
if ($projectsDir !== null) {
$projectDirs = shutils::ls_pdirs($projectsDir);
} else {
if (file_exists("composer.json")) {
$projectDirs = ["."];
} else {
$projectsDir = ".";
$projectDirs = shutils::ls_pdirs($projectsDir);
}
}
}
$projects = ComposerWalker::build_projects($projectDirs, $projectsDir);
$oprojects = ComposerWalker::order_deps(null, $projects);
foreach ($oprojects as $name) {
$project = A::get($projects, $name);
if ($project === null) continue;
$requires = ComposerWalker::order_deps($project["requires"], $projects);
$branches = $project["branches"];
if ($filterDep && !in_array($filterDep, $requires)) {
continue;
}
if ($setPkey || $delPkey || $updateDep) {
/** @var ComposerFile $composer */
$composer = $project["composer"];
msg::step($composer->getComposerFile());
if ($setPkey) {
[$pkey, $value] = self::split_pkey_value($setPkey);
$composer->setPkey($pkey, $value, $rewrite, $forceRewrite);
} elseif ($delPkey) {
[$pkey, $value] = self::split_pkey_value($delPkey);
$composer->delPkey($pkey, $rewrite, $forceRewrite);
} elseif ($updateDep) {
$composer->updateDep($filterDep, $updateDep, $rewrite, $forceRewrite);
}
} else {
switch ($show) {
case self::SHOW_NAMES:
echo $name;
break;
case self::SHOW_DEPS:
echo $name; $pos = strlen($name);
$sep = ": ";
foreach ($requires as $require) {
if ($showFiltered && $require !== $filterDep) continue;
$text = $require;
if ($showBranches || $showFiltered) {
$branch = A::get($branches, $require);
if ($branch !== null) $text .= "[$branch]";
}
$size = strlen("$sep$text");
if ($pos + $size > 80) {
$sep = "\n".str_repeat(" ", 4); $pos = 4;
$size = strlen($text);
}
echo "$sep$text"; $pos += $size;
$sep = ", ";
}
break;
case self::SHOW_DIRS:
echo $project["dir"];
break;
case self::SHOW_COMPOSER:
echo "$project[dir]/composer.json";
break;
}
echo "\n";
}
}
}
}

View File

@ -15,7 +15,7 @@ class ComposerFile {
function __construct(string $composerFile, ?string $projdir=null, bool $ensureExists=true) {
if ($ensureExists && !file_exists($composerFile)) {
if ($projdir === null) $projdir = dirname($composerFile);
$message = path::ppath($projdir).": aucune composeruration n'a été trouvée";
$message = path::ppath($projdir).": aucune configuration n'a été trouvée";
throw new ValueException($message);
}
$this->composerFile = $composerFile;
@ -24,6 +24,10 @@ class ComposerFile {
/** @var string */
protected $composerFile;
function getComposerFile(): string {
return $this->composerFile;
}
/** @var array */
protected $data;
@ -196,15 +200,36 @@ class ComposerFile {
return [$data != $orig, $data];
}
function sync(ConfigFile $config, string $branch, bool $inplace=false): bool {
[$modified, $json] = $this->computeComposer($config, $branch);
$contents = json::with($json, json::INDENT_TABS);
if ($inplace) {
if ($modified) file::with($this->composerFile)->putContents($contents);
protected function _save(array $data, bool $modified, bool $rewrite, bool $forceRewrite): bool {
$contents = json::with($data, json::INDENT_TABS);
if ($rewrite) {
$contents .= "\n";
if ($modified || $forceRewrite) file::with($this->composerFile)->putContents($contents);
else msg::info("Aucune modifications effectuée");
} else {
if ($contents !== null) echo "$contents\n";
if ($contents) echo "$contents\n";
}
return $modified;
}
function sync(ConfigFile $config, string $branch, bool $rewrite=false, bool $forceRewrite=false): bool {
[$modified, $data] = $this->computeComposer($config, $branch);
return $this->_save($data, $modified, $rewrite, $forceRewrite);
}
function setPkey(string $pkey, $value, bool $rewrite=false, bool $forceRewrite=false): bool {
$data = $orig = $this->load();
A::pset($data, $pkey, $value);
return $this->_save($data, $data != $orig, $rewrite, $forceRewrite);
}
function delPkey(string $pkey, bool $rewrite=false, bool $forceRewrite=false): bool {
$data = $orig = $this->load();
A::pdel($data, $pkey);
return $this->_save($data, $data != $orig, $rewrite, $forceRewrite);
}
function updateDep(string $dep, string $version, bool $rewrite=false, bool $forceRewrite=false): bool {
return $this->setPkey("require.$dep", $version, $rewrite, $forceRewrite);
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace nur\tools\compctl;
use Exception;
use nur\A;
class ComposerWalker {
protected static function get_project_dir($name, string $projects_dir) {
$project_dir = A::get(ConfigFile::PROJNAMES, $name);
if ($project_dir === null) {
$project_dir = preg_replace('/\//', "-", $name, 1);
}
return "$projects_dir/$project_dir";
}
static function parse_project(string $dir, array &$projects, ?array &$new_projects=null, bool $throw=false): ?array {
try {
$composer = new ComposerFile("$dir/composer.json");
} catch (Exception $e) {
if ($throw) throw $e;
else return null;
}
$pname = $composer->getv("name");
if (array_key_exists($pname, $projects) && $projects[$pname] !== null) {
# projet déjà analysé
return $projects[$pname];
}
$project = [
"composer" => $composer,
"name" => $pname,
"requires" => [],
"branches" => [],
"dir" => realpath($dir),
];
$requires = $composer->geta("require");
if ($requires !== null) {
foreach ($requires as $dep => $version) {
# ignorer les projets qui n'ont pas le préfixe requis
$include_project = false;
foreach (ConfigFile::PREFIXES as $prefix) {
if (substr($dep, 0, strlen($prefix)) === $prefix) {
$include_project = true;
break;
}
}
if (!$include_project) continue;
# sinon, ajouter le projet aux dépendances
$project["requires"][] = $dep;
if (!array_key_exists($dep, $projects)) {
$projects[$dep] = null;
$new_projects[$dep] = true;
}
if (substr($version, 0, 4) === "dev-") {
$project["branches"][$dep] = substr($version, 4);
} else {
$project["branches"][$dep] = $version;
}
}
}
$projects[$pname] = $project;
return $project;
}
static function build_projects(array $project_dirs, ?string &$projects_dir): array {
$projects = [];
foreach ($project_dirs as $project_dir) {
if ($projects_dir === null) $projects_dir = dirname($project_dir);
# analyser le projet initial
$new_projects = [];
if (self::parse_project($project_dir, $projects, $new_projects) === null) {
continue;
}
# puis analyser récursivement tous les projets dépendants
while (true) {
$have_new = false;
foreach ($new_projects as $name => &$new) {
if ($new) {
$new = false;
$have_new = true;
$project_dir = self::get_project_dir($name, $projects_dir);
self::parse_project($project_dir, $projects, $new_projects);
}
}; unset($new);
if (!$have_new) break;
}
}
return $projects;
}
static function order_deps(?array $deps, array $projects): array {
if ($deps === null) $deps = array_keys($projects);
$pi = 0;
$max = count($deps);
while ($pi < $max) {
$modified = false;
$project = $projects[$deps[$pi]];
if ($project !== null) {
# dépendance dont on a trouvé la correspondance sur disque
foreach ($project["requires"] as $dep) {
$di = array_search($dep, $deps);
if ($di > $pi) {
# si la dépendance se situe après le projet, la placer avant
$deps = array_splice($deps, $di, 1);
$deps = array_splice($deps, $pi, 0, [$dep]);
$modified = true;
}
}
}
if (!$modified) $pi++;
}
return $deps;
}
}

View File

@ -10,6 +10,9 @@ use nur\str;
use nur\yaml;
class ConfigFile {
# préfixes de projets dont on tient compte
const PREFIXES = ["ur/", "nur/", "lib/", "nulib/"];
const DEFAULT_DEP = "dev-master";
const DEFAULT_DEPS = [
"wip" => "dev-wip",