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