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