[ Index ]

PHP Cross Reference of Automap

title

Body

[close]

/Automap/Build/ -> Creator.php (source)

   1  <?php
   2  //=============================================================================
   3  //
   4  // Copyright Francois Laupretre <automap@tekwire.net>
   5  //
   6  //   Licensed under the Apache License, Version 2.0 (the "License");
   7  //   you may not use this file except in compliance with the License.
   8  //   You may obtain a copy of the License at
   9  //
  10  //       http://www.apache.org/licenses/LICENSE-2.0
  11  //
  12  //   Unless required by applicable law or agreed to in writing, software
  13  //   distributed under the License is distributed on an "AS IS" BASIS,
  14  //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15  //   See the License for the specific language governing permissions and
  16  //   limitations under the License.
  17  //
  18  //=============================================================================
  19  /**
  20  * @copyright Francois Laupretre <automap@tekwire.net>
  21  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0
  22  * @category Automap
  23  * @package Automap
  24  *///==========================================================================
  25  
  26  //=============================================================================
  27  /**
  28  * This class creates a map file
  29  *
  30  * Usage:
  31  *    $map=new \Automap\Build\Creator();
  32  *    ...[populate using the different methods]...
  33  *    $map->save(<path>);
  34  *
  35  * API status: Public
  36  * Included in the PHK PHP runtime: No
  37  * Implemented in the extension: No
  38  *///==========================================================================
  39  
  40  namespace Automap\Build {
  41  
  42  if (!class_exists('Automap\Build\Creator',false)) 
  43  {
  44  class Creator
  45  {
  46  const VERSION='3.0.0';        // Version set into the maps I produce
  47  const MIN_RUNTIME_VERSION='3.0.0'; // Minimum version of runtime able to understand the maps I produce
  48  
  49  //---------
  50  
  51  private $symbols=array();    // array($key => array('T' => <symbol type>
  52                              // , 'n' => <case-sensitive symbol name>
  53                              // , 't' => <target type>, 'p' => <target path>))
  54  private $options=array();
  55  
  56  private $php_file_ext=array('php','inc','hh');
  57  
  58  private $parser; // Must implement \Automap\Build\ParserInterface
  59  
  60  //---------
  61  
  62  public function __construct($parser=null)
  63  {
  64  $this->setParser($parser);
  65  }
  66  
  67  //---------
  68  
  69  public function setParser($parser=null)
  70  {
  71  if (is_null($parser)) $parser=new Parser();
  72  $this->parser=$parser;
  73  }
  74  
  75  //---------
  76  
  77  public function option($opt)
  78  {
  79  return (isset($this->options[$opt]) ? $this->options[$opt] : null);
  80  }
  81  
  82  //---------
  83  
  84  public function setOption($option,$value)
  85  {
  86  \Phool\Display::trace("Setting option $option=$value");
  87  
  88  $this->options[$option]=$value;
  89  }
  90  
  91  //---------
  92  
  93  public function unsetOption($option)
  94  {
  95  \Phool\Display::trace("Unsetting option $option");
  96  
  97  if (isset($this->options[$option])) unset($this->options[$option]);
  98  }
  99  
 100  //---------
 101  /**
 102  * Set the list of file suffixes recognized as PHP source scripts
 103  *
 104  * Default list is 'php, 'inc, 'hh'.
 105  *
 106  * @param array|string If array, replace the list, otherwise add a suffix to the list
 107  * @return null
 108  */
 109  
 110  public function setPhpFileExt($a)
 111  {
 112  if (is_array($a)) $this->php_file_ext=$a;
 113  else $this->php_file_ext[]=$a;
 114  }
 115  
 116  //---------
 117  
 118  private function addEntry($va)
 119  {
 120  $key=\Automap\Map::key($va['T'],$va['n']);
 121  
 122  // Filter namespace if filter specified
 123  
 124  if (isset($va['f']))
 125      {
 126      $ns_list=$va['f'];
 127      if (is_string($ns_list)) $ns_list=array($ns_list);
 128      $ns=\Automap\Map::nsKey($va['n']);
 129      $ok=false;
 130      foreach($ns_list as $item)
 131          {
 132          $item=trim($item,'\\');
 133          if ((($item=='')&&($ns==''))||($item!='')&&(strpos($ns.'\\',$item.'\\')===0))
 134              {
 135              $ok=true;
 136              break;
 137              }
 138          }
 139      if (!$ok)
 140          {
 141          \Phool\Display::debug("$key rejected by namespace filter");
 142          return;
 143          }
 144      }
 145  
 146  // Add symbol to map if no conflict
 147  
 148  \Phool\Display::debug("Adding symbol (key=<$key>, name=".$va['n']
 149      .", target=".$va['p'].' ('.$va['t'].')');
 150  
 151  if (isset($this->symbols[$key]))
 152      {
 153      $entry=$this->symbols[$key];
 154      // If same target, it's OK
 155      if (($entry['t']!=$va['t'])||($entry['p']!=$va['p']))
 156          {
 157          echo "** Warning: Symbol multiply defined: "
 158              .\Automap\Mgr::typeToString($va['T'])
 159              .' '.$va['n']."\n    Previous location (kept): "
 160              .\Automap\Mgr::typeToString($entry['t'])
 161              .' '.$entry['p']."\n    New location (discarded): "
 162              .\Automap\Mgr::typeToString($va['t'])
 163              .' '.$va['p']."\n";
 164          }
 165      }
 166  else $this->symbols[$key]=$va;
 167  }
 168  
 169  //---------
 170  
 171  private function addTSEntry($stype,$sname,$va)
 172  {
 173  $va['T']=$stype;
 174  $va['n']=$sname;
 175  $this->addEntry($va);
 176  }
 177  
 178  //---------
 179  
 180  public function symbolCount()
 181  {
 182  return count($this->symbols);
 183  }
 184  
 185  //---------
 186  // Build an array containing only target information
 187  
 188  private static function mkVarray($ftype,$fpath,$ns_filter=null)
 189  {
 190  $a=array('t' => $ftype, 'p' => $fpath);
 191  if (!is_null($ns_filter)) $a['f']=$ns_filter;
 192  return $a;
 193  }
 194  
 195  //---------
 196  
 197  public function addSymbol($stype,$sname,$ftype,$fpath)
 198  {
 199  $va=self::mkVarray($ftype,$fpath);
 200  $this->addTSEntry($stype,$sname,$va);
 201  }
 202  
 203  //---------
 204  // Remove the entries matching a given target
 205  
 206  private function unregisterTarget($va)
 207  {
 208  $type=$va['t'];
 209  $path=$va['p'];
 210  \Phool\Display::debug("Unregistering path (type=$type, path=$path)");
 211  
 212  foreach(array_keys($this->symbols) as $key)
 213      {
 214      if (($this->symbols[$key]['t']===$type)&&($this->symbols[$key]['p']===$path))
 215          {
 216          \Phool\Display::debug("Removing $key from symbol table");
 217          unset($this->symbols[$key]);
 218          }
 219      }
 220  }
 221  
 222  //---------
 223  // Using adler32 as it is supposed to be the fastest algo. That's more than
 224  // enough for a CRC check.
 225  // Symbols are supposed to be normalized (no leading/trailing '\').
 226  
 227  public function serialize()
 228  {
 229  //-- Store symbols in namespace slots
 230  
 231  $slots=array();
 232  foreach($this->symbols as $key => $va)
 233      {
 234      $target=$va['t'].$va['p'];
 235      $ns=\Automap\Map::nsKey($va['n']);
 236      if (!array_key_exists($ns,$slots)) $slots[$ns]=array();
 237      $slots[$ns][$key]=$target;
 238      }
 239  
 240  //-- Serialize
 241  
 242  foreach(array_keys($slots) as $ns)
 243      {
 244      $slots[$ns]=serialize($slots[$ns]);
 245      }
 246  
 247  $data=serialize(array('map' => $slots, 'options' => $this->options));
 248  
 249  //-- Dump to file
 250  
 251  $buf=\Automap\Map::MAGIC
 252      .str_pad(self::MIN_RUNTIME_VERSION,12)
 253      .str_pad(self::VERSION,12)
 254      .str_pad(strlen($data)+70,8)
 255      .'00000000'
 256      .str_pad(count($this->symbols),8)
 257      .str_pad(strlen($data),8)
 258      .$data;
 259  
 260  return substr_replace($buf,hash('adler32',$buf),46,8); // Insert CRC
 261  }
 262  
 263  //---------
 264  
 265  public function save($path)
 266  {
 267  if (is_null($path)) throw new \Exception('No path provided');
 268  
 269  $data=$this->serialize();
 270  
 271  \Phool\Display::trace("$path: Writing map file");
 272  \Phool\File::atomicWrite($path,$data);
 273  }
 274  
 275  //---------
 276  // Register an extension in current map.
 277  // $file=extension file (basename)
 278  
 279  public function registerExtensionFile($file)
 280  {
 281  \Phool\Display::trace("Registering extension : $file");
 282  
 283  $va=self::mkVarray(\Automap\Mgr::F_EXTENSION,$file);
 284  $this->unregisterTarget($va);
 285  
 286  foreach($this->parser->parseExtension($file) as $sym)
 287      {
 288      $this->addTSEntry($sym['type'],$sym['name'],$va);
 289      }
 290  }
 291  
 292  //---------
 293  // Register every extension files in the extension directory
 294  // We do several passes, as there are dependencies between extensions which
 295  // must be loaded in a given order. We stop when a pass cannot load any file.
 296  
 297  public function registerExtensionDir()
 298  {
 299  $ext_dir=ini_get('extension_dir');
 300  \Phool\Display::trace("Scanning extensions directory ($ext_dir)\n");
 301  
 302  //-- Multiple passes because of possible dependencies
 303  //-- Loop until everything is loaded or we cannot load anything more
 304  
 305  $f_to_load=array();
 306  $pattern='/\.'.PHP_SHLIB_SUFFIX.'$/';
 307  foreach(scandir($ext_dir) as $ext_file)
 308      {
 309      if (is_dir($ext_dir.DIRECTORY_SEPARATOR.$ext_file)) continue;
 310      if (preg_match($pattern,$ext_file)) $f_to_load[]=$ext_file;
 311      }
 312  
 313  while(true)
 314      {
 315      $f_failed=array();
 316      foreach($f_to_load as $key => $ext_file)
 317          {
 318          try { $this->registerExtensionFile($ext_file); }
 319          catch (\Exception $e) { $f_failed[]=$ext_file; }
 320          }
 321      //-- If we could load everything or if we didn't load anything, break
 322      if ((count($f_failed)==0)||(count($f_failed)==count($f_to_load))) break;
 323      $f_to_load=$f_failed;
 324      }
 325  
 326  if (count($f_failed))
 327      {
 328      foreach($f_failed as $file)
 329          \Phool\Display::warning("$file: This extension was not registered (load failed)");
 330      }
 331  }
 332  
 333  //---------------------------------
 334  /**
 335  * Normalize a destination path
 336  *
 337  * 1. Replace backslashes with forward slashes.
 338  * 2. Remove trailing slashes
 339  *
 340  * @param string $rpath the path to normalize
 341  * @return string the normalized path
 342  */
 343  
 344  private static function normalizePath($path)
 345  {
 346  $path=rtrim(str_replace('\\','/',$path),'/');
 347  if ($path=='') $path='/';
 348  return $path;
 349  }
 350  
 351  //---------
 352  
 353  public function registerScriptFile($fpath,$rpath,$ns_filter=null)
 354  {
 355  \Phool\Display::trace("Registering script $fpath as $rpath");
 356  
 357  // Force relative path
 358  
 359  $va=self::mkVarray(\Automap\Mgr::F_SCRIPT,self::normalizePath($rpath),$ns_filter);
 360  $this->unregisterTarget($va);
 361  
 362  foreach($this->parser->parseScriptFile($fpath) as $sym)
 363      {
 364      $this->addTSEntry($sym['type'],$sym['name'],$va);
 365      }
 366  }
 367  
 368  //---------
 369  /**
 370  * Recursively scan a path and records symbols
 371  *
 372  * Scan retains PHP source files and phk packages only (based on file suffix)
 373  *
 374  * Only dirs and regular files are considered. Other types are ignored.
 375  *
 376  * @param string $fpath Path to register
 377  * @param string $rpath Path to register to in map for $fpath
 378  * @param string|array|null $ns_filter
 379  *            List of authorized namespaces (empty string means no namespace)
 380  *            If null, no filtering.
 381  * @param string|null $file_pattern
 382  *            File path preg pattern (File paths not matching this pattern are ignored)
 383  */
 384  
 385  public function registerPath($fpath,$rpath,$ns_filter=null,$file_pattern=null)
 386  {
 387  \Phool\Display::trace("Registering path <$fpath> as <$rpath>");
 388  
 389  switch($type=filetype($fpath))
 390      {
 391      case 'dir':
 392          foreach(\Phool\File::scandir($fpath) as $entry)
 393              {
 394              $this->registerPath($fpath.'/'.$entry,$rpath.'/'.$entry,$ns_filter);
 395              }
 396          break;
 397  
 398      case 'file':
 399          if ((!is_null($file_pattern)) && (!preg_match($file_pattern, $fpath))) return;
 400          $suffix=strtolower(\Phool\File::fileSuffix($fpath));
 401          if ($suffix=='phk')
 402              $this->registerPhkPkg($fpath,$rpath);
 403          elseif (array_search($suffix,$this->php_file_ext)!==false)
 404              $this->registerScriptFile($fpath,$rpath,$ns_filter);
 405          else
 406              \Phool\Display::trace("Ignoring file $fpath (not a PHP script)");
 407          break;
 408      }
 409  }
 410  
 411  //---------
 412  
 413  public function readMapFile($fpath)
 414  {
 415  \Phool\Display::trace("Reading map file ($fpath)");
 416  
 417  $map=new \Automap\Map($fpath);
 418  $this->options=$map->options();
 419  $this->symbols=array();
 420  $this->mergeMapSymbols($map);
 421  }
 422  
 423  //---------
 424  /**
 425  * Merge an existing map file into the current map
 426  *
 427  * Import symbols only. Options are ignored (including base path).
 428  *
 429  * @param string $fpath Path of the map to merge (input)
 430  * @param Relative path to prepend to map target paths
 431  * @return null
 432  */
 433  
 434  public function mergeMapFile($fpath,$rpath)
 435  {
 436  \Phool\Display::debug("Merging map file from $fpath (rpath=$rpath)");
 437  
 438  $map=new \Automap\Map($fpath);
 439  $this->mergeMapSymbols($map,$rpath);
 440  }
 441  
 442  //---------
 443  
 444  public function mergeMapSymbols($map,$rpath='.')
 445  {
 446  foreach($map->symbols() as $va)
 447      {
 448      $va['rpath']=\Phool\File::combinePath($rpath,$va['rpath']);
 449      $this->addEntry($va);
 450      }
 451  }
 452  
 453  //---------
 454  // Register a PHK package
 455  
 456  public function registerPhkPkg($fpath,$rpath)
 457  {
 458  \Phool\Display::trace("Registering PHK package $fpath as $rpath");
 459  
 460  $rpath=self::normalizePath($rpath);
 461  \Phool\Display::debug("Registering PHK package (path=$fpath, rpath=$rpath)");
 462  $va=self::mkVarray(\Automap\Mgr::F_PACKAGE,$rpath);
 463  $this->unregisterTarget($va);
 464  
 465  $mnt=\PHK\Mgr::mount($fpath,\PHK::NO_MOUNT_SCRIPT);
 466  $pkg=\PHK\Mgr::instance($mnt);
 467  $id=$pkg->automapID();
 468  if ($id) // If package has an automap
 469      {
 470      foreach(\Automap\Mgr::map($id)->symbols() as $sym)
 471          $this->addTSEntry($sym['stype'],$sym['symbol'],$va);
 472      }
 473  }
 474  
 475  //---------
 476  
 477  public function import($path=null)
 478  {
 479  if (is_null($path)) $path="php://stdin";
 480  
 481  \Phool\Display::trace("Importing map from $path");
 482  
 483  $fp=fopen($path,'r');
 484  if (!$fp) throw new \Exception("$path: Cannot open for reading");
 485  
 486  while(($line=fgets($fp))!==false)
 487      {
 488      if (($line=trim($line))==='') continue;
 489      list($stype,$sname,$ftype,$fname)=explode('|',$line);
 490      $va=self::mkVarray($ftype,$fname);
 491      $this->addTSEntry($stype,$sname,$va);
 492      }
 493  fclose($fp);
 494  }
 495  
 496  //---
 497  } // End of class
 498  //===========================================================================
 499  } // End of class_exists
 500  //===========================================================================
 501  } // End of namespace
 502  //===========================================================================
 503  ?>


Generated: Thu Jun 4 18:32:29 2015 Cross-referenced by PHPXref 0.7.1