318 lines
9.7 KiB
PHP
318 lines
9.7 KiB
PHP
|
<?php
|
||
|
namespace nur\php;
|
||
|
|
||
|
use nur\A;
|
||
|
use nur\b\io\TmpfileWriter;
|
||
|
use nur\b\ValueException;
|
||
|
use nur\base;
|
||
|
use nur\func;
|
||
|
use nur\log;
|
||
|
use nur\reader;
|
||
|
use ReflectionClass;
|
||
|
|
||
|
/**
|
||
|
* Class Updater: met à jour une fichier source
|
||
|
*/
|
||
|
class SrcUpdater {
|
||
|
protected $allowUndefined = false;
|
||
|
|
||
|
function setAllowUndefined(bool $allowUndefined): void {
|
||
|
$this->allowUndefined = $allowUndefined;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* analyser le fichier $pf et trouver le namespace et la classe. retourner un
|
||
|
* tableau ["namespace" => $namespace, "class" => $class]
|
||
|
*/
|
||
|
function parseFile(string $pf): array {
|
||
|
$lines = reader::read_lines($pf);
|
||
|
|
||
|
$namespace = false;
|
||
|
$class_name = null;
|
||
|
$consts = [];
|
||
|
foreach ($lines as $line) {
|
||
|
if (preg_match('/^\s*namespace\s+(.+);/', $line, $ms)) {
|
||
|
$namespace = $ms[1];
|
||
|
} elseif (preg_match('/^\s*(?:abstract\s*)?class\s+(\w+)/', $line, $ms)) {
|
||
|
$class_name = $ms[1];
|
||
|
} elseif (preg_match('/^\s*const\s+(\w+)/', $line, $ms)) {
|
||
|
$consts[$ms[1]] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$class = $class_name;
|
||
|
if ($namespace) $class = "$namespace\\$class";
|
||
|
return [
|
||
|
"namespace" => $namespace,
|
||
|
"class_name" => $class_name,
|
||
|
"class" => $class,
|
||
|
"defined_consts" => $consts,
|
||
|
"file" => $pf,
|
||
|
"lines" => $lines,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
function _initParams(?array &$params, string $file) {
|
||
|
A::update_n($params, $this->parseFile($file));
|
||
|
if (base::z($params["class"])) throw new ValueException("class is required");
|
||
|
}
|
||
|
|
||
|
protected static function undefined_const_msg(string $name, string $class): string {
|
||
|
return "$name: is not defined in class $class";
|
||
|
}
|
||
|
|
||
|
protected static function undefined_const(string $name, string $class): ValueException {
|
||
|
return new ValueException(self::undefined_const_msg($name, $class));
|
||
|
}
|
||
|
|
||
|
function _updateConsts(array &$params, ?bool $allowUndefined=null) {
|
||
|
if ($allowUndefined === null) $allowUndefined = $this->allowUndefined;
|
||
|
|
||
|
$class = $params["class"];
|
||
|
$rc = new ReflectionClass($class);
|
||
|
$availableConsts = $rc->getConstants();
|
||
|
if (!A::has($availableConsts, "_AUTOGEN_CONSTS")) {
|
||
|
# il n'y a rien à mettre à jour
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
# normaliser _AUTOGEN_CONSTS
|
||
|
# chaque élement séquentiel $value de $autogenConsts est remplacé par
|
||
|
# $value => [$class, "_AUTOGEN_$value"]
|
||
|
$autogenConsts = [];
|
||
|
$index = 0;
|
||
|
foreach ($availableConsts["_AUTOGEN_CONSTS"] as $key => $value) {
|
||
|
if ($key === $index) {
|
||
|
# clé séquentielle
|
||
|
$index++;
|
||
|
$autogenConsts[$value] = [$class, "_AUTOGEN_$value"];
|
||
|
} else {
|
||
|
$autogenConsts[$key] = $value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# calculer la liste dynamique de constantes et leur valeur
|
||
|
if (A::has($autogenConsts, "")) {
|
||
|
# liste dynamique de constantes et leurs valeurs associées
|
||
|
$func = A::getdel($autogenConsts, "");
|
||
|
$args = [];
|
||
|
func::fix_static($func, $class);
|
||
|
func::fix_args($func, $args);
|
||
|
$dynamicConsts = A::with(func::call($func, ...$args));
|
||
|
foreach (array_keys($dynamicConsts) as $name) {
|
||
|
if (!A::has($availableConsts, $name) && !$allowUndefined) {
|
||
|
throw self::undefined_const($name, $class);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
$dynamicConsts = [];
|
||
|
}
|
||
|
# liste de tuples [$value, $literal_value]
|
||
|
$literals = A::get($dynamicConsts, "_AUTOGEN_LITERALS");
|
||
|
if ($literals === null) $literals = A::get($availableConsts, "_AUTOGEN_LITERALS");
|
||
|
|
||
|
# lister les constantes à calculer
|
||
|
# une valeur mentionée dans _AUTOGEN_CONSTS prend le pas sur la même valeur
|
||
|
# retournée dans la liste dynamique de constantes
|
||
|
$definedConsts = $params["defined_consts"];
|
||
|
$consts = [];
|
||
|
foreach (array_keys($autogenConsts) as $name) {
|
||
|
if (A::has($availableConsts, $name)) {
|
||
|
$value = $availableConsts[$name];
|
||
|
A::del($dynamicConsts, $name);
|
||
|
} elseif ($allowUndefined) {
|
||
|
$value = null;
|
||
|
} else {
|
||
|
throw self::undefined_const($name, $class);
|
||
|
}
|
||
|
if (!A::has($definedConsts, $name)) {
|
||
|
log::warning(self::undefined_const_msg($name, $class));
|
||
|
}
|
||
|
$consts[$name] = $value;
|
||
|
}
|
||
|
|
||
|
# calculer les valeurs des constantes
|
||
|
foreach ($consts as $name => &$const) {
|
||
|
if (!A::has($autogenConsts, $name)) continue;
|
||
|
$func = $autogenConsts[$name];
|
||
|
$args = [];
|
||
|
func::fix_static($func, $class);
|
||
|
func::fix_args($func, $args);
|
||
|
$args[] = $const;
|
||
|
$const = func::call($func, ...$args);
|
||
|
}; unset($const);
|
||
|
|
||
|
# puis mettre à jour le fichier
|
||
|
$lines = [];
|
||
|
$rewriteConst = false;
|
||
|
$prefix = null;
|
||
|
foreach ($params["lines"] as $line) {
|
||
|
if (preg_match('/^(\s*)const\s+(\w+)/', $line, $ms)) {
|
||
|
$prefix = $ms[1];
|
||
|
$name = $ms[2];
|
||
|
if (A::has($consts, $name)) {
|
||
|
# cette constante doit être mise à jour
|
||
|
$rewriteConst = true;
|
||
|
} elseif (A::has($dynamicConsts, $name)) {
|
||
|
$consts[$name] = A::getdel($dynamicConsts, $name);
|
||
|
$rewriteConst = true;
|
||
|
}
|
||
|
} elseif (!$rewriteConst && $dynamicConsts &&
|
||
|
preg_match('/^(\s*)#+\s*--autogen-dynamic--/', $line, $ms)) {
|
||
|
# il faut écrire les constantes dynamiques restantes
|
||
|
$prefix = $ms[1];
|
||
|
foreach ($dynamicConsts as $name => $value) {
|
||
|
$generator = new SrcGenerator($prefix);
|
||
|
$generator->genConst($name, $value, null, $literals, false, "/*autogen*/");
|
||
|
$generator->mergeInto($lines);
|
||
|
}
|
||
|
$dynamicConsts = false;
|
||
|
$prefix = null;
|
||
|
}
|
||
|
if ($rewriteConst) {
|
||
|
if (preg_match('/;\s*$/', $line, $ms)) {
|
||
|
# écrire la constante mise à jour
|
||
|
$generator = new SrcGenerator($prefix);
|
||
|
$generator->genConst($name, $consts[$name], null, $literals, false, "/*autogen*/");
|
||
|
$generator->mergeInto($lines);
|
||
|
|
||
|
$rewriteConst = false;
|
||
|
$prefix = null;
|
||
|
}
|
||
|
} else {
|
||
|
$lines[] = $line;
|
||
|
}
|
||
|
}
|
||
|
if ($dynamicConsts) {
|
||
|
$count = count($dynamicConsts);
|
||
|
log::warning("$params[file]: missing #--autogen-dynamic-- section, $count dynamic const(s) skipped", log::NORMAL);
|
||
|
}
|
||
|
if ($rewriteConst) {
|
||
|
throw new ValueException("$params[file]: parse error");
|
||
|
}
|
||
|
|
||
|
$params["lines"] = $lines;
|
||
|
}
|
||
|
|
||
|
private static function call_autogens(?array $autogens, string $class) {
|
||
|
$dest = [];
|
||
|
if ($autogens !== null) {
|
||
|
foreach ($autogens as $func) {
|
||
|
$args = [];
|
||
|
func::fix_static($func, $class);
|
||
|
func::fix_args($func, $args);
|
||
|
$fcontext = func::_prepare($func);
|
||
|
func::_fill($fcontext, $args);
|
||
|
$minArgs = $fcontext[3];
|
||
|
$maxArgs = $fcontext[4];
|
||
|
if ($maxArgs > $minArgs) {
|
||
|
# ne rajouter la classe qu'en tant que dernier argument optionnel
|
||
|
$maxArgs--;
|
||
|
$minArgs = count($args); # prendre le nombre effectif
|
||
|
while ($minArgs < $maxArgs) {
|
||
|
$args[] = null;
|
||
|
$minArgs++;
|
||
|
}
|
||
|
$args[] = $class;
|
||
|
}
|
||
|
A::merge($dest, func::_call($fcontext, $args));
|
||
|
};
|
||
|
}
|
||
|
return $dest;
|
||
|
}
|
||
|
|
||
|
function _updatePropertiesAndMethods(array &$params) {
|
||
|
$class = $params["class"];
|
||
|
$rc = new ReflectionClass($class);
|
||
|
$defined_consts = $rc->getConstants();
|
||
|
$autogenProperties = A::get($defined_consts, "_AUTOGEN_PROPERTIES");
|
||
|
$autogenMethods = A::get($defined_consts, "_AUTOGEN_METHODS");
|
||
|
if ($autogenProperties === null && $autogenMethods === null) {
|
||
|
# il n'y a rien à mettre à jour
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$properties = self::call_autogens($autogenProperties, $class);
|
||
|
$methods = self::call_autogens($autogenMethods, $class);
|
||
|
|
||
|
$lines = [];
|
||
|
$done = false;
|
||
|
$in_comment = false;
|
||
|
$found_section = false;
|
||
|
foreach ($params["lines"] as $line) {
|
||
|
if ($done) {
|
||
|
$lines[] = $line;
|
||
|
continue;
|
||
|
}
|
||
|
if (!$in_comment) {
|
||
|
if (preg_match('/^\s*\/\*\*/', $line)) {
|
||
|
$in_comment = true;
|
||
|
}
|
||
|
$lines[] = $line;
|
||
|
continue;
|
||
|
}
|
||
|
# ici, $in_comment == true
|
||
|
if (preg_match('/^(\s*)\*\//', $line, $ms)) {
|
||
|
$prefix = "$ms[1]* ";
|
||
|
$lines[] = "$prefix--autogen-properties-and-methods--";
|
||
|
foreach ($properties as $property) {
|
||
|
$lines[] = "$prefix@property $property";
|
||
|
}
|
||
|
foreach ($methods as $method) {
|
||
|
$lines[] = "$prefix@method $method";
|
||
|
}
|
||
|
$lines[] = $line;
|
||
|
$in_comment = false;
|
||
|
$done = true;
|
||
|
continue;
|
||
|
}
|
||
|
if (!$found_section) {
|
||
|
if (preg_match('/^\s*\*?\s*--autogen-(?:properties-and-)?methods--\s*$/', $line)) {
|
||
|
$found_section = true;
|
||
|
} else {
|
||
|
$lines[] = $line;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
# ici, $found_section == true
|
||
|
}
|
||
|
if ($in_comment) {
|
||
|
throw new ValueException("$params[file]: parse error: check class comment");
|
||
|
}
|
||
|
|
||
|
$params["lines"] = $lines;
|
||
|
}
|
||
|
|
||
|
function _writeFile(array $params) {
|
||
|
$file = $params["file"];
|
||
|
$outf = new TmpfileWriter(dirname($file));
|
||
|
$outf->writeLines($params["lines"]);
|
||
|
$outf->rename($file);
|
||
|
}
|
||
|
|
||
|
function updateConsts(string $file, ?bool $allowUndefined=null, ?array $params=null) {
|
||
|
$this->_initParams($params, $file);
|
||
|
require($file);
|
||
|
|
||
|
$this->_updateConsts($params, $allowUndefined);
|
||
|
$this->_writeFile($params);
|
||
|
}
|
||
|
|
||
|
function updatePropertiesAndMethods(string $file, ?array $params=null) {
|
||
|
$this->_initParams($params, $file);
|
||
|
require($file);
|
||
|
|
||
|
$this->_updatePropertiesAndMethods($params);
|
||
|
$this->_writeFile($params);
|
||
|
}
|
||
|
|
||
|
function update(string $file, ?array $params=null) {
|
||
|
$this->_initParams($params, $file);
|
||
|
require($file);
|
||
|
|
||
|
$this->_updateConsts($params);
|
||
|
$this->_updatePropertiesAndMethods($params);
|
||
|
$this->_writeFile($params);
|
||
|
}
|
||
|
}
|