<Website>

Automap

PHK Home

File: /src/Automap/Build/Creator.php

Size:12355
Storage flags:

<?php
//=============================================================================
//
// Copyright Francois Laupretre <automap@tekwire.net>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
//=============================================================================
/**
* @copyright Francois Laupretre <automap@tekwire.net>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0
* @category Automap
* @package Automap
*///==========================================================================

//=============================================================================
/**
* This class creates a map file
*
* Usage:
*    $map=new \Automap\Build\Creator();
*    ...[populate using the different methods]...
*    $map->save(<path>);
*
* API status: Public
* Included in the PHK PHP runtime: No
* Implemented in the extension: No
*///==========================================================================

namespace Automap\Build {

if (!
class_exists('Automap\Build\Creator',false)) 
{
class 
Creator
{
const 
VERSION='3.0.0';        // Version set into the maps I produce
const MIN_RUNTIME_VERSION='3.0.0'// Minimum version of runtime able to understand the maps I produce

//---------

private $symbols=array();    // array($key => array('T' => <symbol type>
                            // , 'n' => <case-sensitive symbol name>
                            // , 't' => <target type>, 'p' => <target path>))
private $options=array();

private 
$php_file_ext=array('php','inc','hh');

private 
$parser// Must implement \Automap\Build\ParserInterface

//---------

public function __construct($parser=null)
{
$this->setParser($parser);
}

//---------

public function setParser($parser=null)
{
if (
is_null($parser)) $parser=new Parser();
$this->parser=$parser;
}

//---------

public function option($opt)
{
return (isset(
$this->options[$opt]) ? $this->options[$opt] : null);
}

//---------

public function setOption($option,$value)
{
\
Phool\Display::trace("Setting option $option=$value");

$this->options[$option]=$value;
}

//---------

public function unsetOption($option)
{
\
Phool\Display::trace("Unsetting option $option");

if (isset(
$this->options[$option])) unset($this->options[$option]);
}

//---------
/**
* Set the list of file suffixes recognized as PHP source scripts
*
* Default list is 'php, 'inc, 'hh'.
*
* @param array|string If array, replace the list, otherwise add a suffix to the list
* @return null
*/

public function setPhpFileExt($a)
{
if (
is_array($a)) $this->php_file_ext=$a;
else 
$this->php_file_ext[]=$a;
}

//---------

private function addEntry($va)
{
$key=\Automap\Map::key($va['T'],$va['n']);

// Filter namespace if filter specified

if (isset($va['f']))
    {
    
$ns_list=$va['f'];
    if (
is_string($ns_list)) $ns_list=array($ns_list);
    
$ns=\Automap\Map::nsKey($va['n']);
    
$ok=false;
    foreach(
$ns_list as $item)
        {
        
$item=trim($item,'\\');
        if (((
$item=='')&&($ns==''))||($item!='')&&(strpos($ns.'\\',$item.'\\')===0))
            {
            
$ok=true;
            break;
            }
        }
    if (!
$ok)
        {
        \
Phool\Display::debug("$key rejected by namespace filter");
        return;
        }
    }

// Add symbol to map if no conflict

\Phool\Display::debug("Adding symbol (key=<$key>, name=".$va['n']
    .
", target=".$va['p'].' ('.$va['t'].')');

if (isset(
$this->symbols[$key]))
    {
    
$entry=$this->symbols[$key];
    
// If same target, it's OK
    
if (($entry['t']!=$va['t'])||($entry['p']!=$va['p']))
        {
        echo 
"** Warning: Symbol multiply defined: "
            
.\Automap\Mgr::typeToString($va['T'])
            .
' '.$va['n']."\n    Previous location (kept): "
            
.\Automap\Mgr::typeToString($entry['t'])
            .
' '.$entry['p']."\n    New location (discarded): "
            
.\Automap\Mgr::typeToString($va['t'])
            .
' '.$va['p']."\n";
        }
    }
else 
$this->symbols[$key]=$va;
}

//---------

private function addTSEntry($stype,$sname,$va)
{
$va['T']=$stype;
$va['n']=$sname;
$this->addEntry($va);
}

//---------

public function symbolCount()
{
return 
count($this->symbols);
}

//---------
// Build an array containing only target information

private static function mkVarray($ftype,$fpath,$ns_filter=null)
{
$a=array('t' => $ftype'p' => $fpath);
if (!
is_null($ns_filter)) $a['f']=$ns_filter;
return 
$a;
}

//---------

public function addSymbol($stype,$sname,$ftype,$fpath)
{
$va=self::mkVarray($ftype,$fpath);
$this->addTSEntry($stype,$sname,$va);
}

//---------
// Remove the entries matching a given target

private function unregisterTarget($va)
{
$type=$va['t'];
$path=$va['p'];
\
Phool\Display::debug("Unregistering path (type=$type, path=$path)");

foreach(
array_keys($this->symbols) as $key)
    {
    if ((
$this->symbols[$key]['t']===$type)&&($this->symbols[$key]['p']===$path))
        {
        \
Phool\Display::debug("Removing $key from symbol table");
        unset(
$this->symbols[$key]);
        }
    }
}

//---------
// Using adler32 as it is supposed to be the fastest algo. That's more than
// enough for a CRC check.
// Symbols are supposed to be normalized (no leading/trailing '\').

public function serialize()
{
//-- Store symbols in namespace slots

$slots=array();
foreach(
$this->symbols as $key => $va)
    {
    
$target=$va['t'].$va['p'];
    
$ns=\Automap\Map::nsKey($va['n']);
    if (!
array_key_exists($ns,$slots)) $slots[$ns]=array();
    
$slots[$ns][$key]=$target;
    }

//-- Serialize

foreach(array_keys($slots) as $ns)
    {
    
$slots[$ns]=serialize($slots[$ns]);
    }

$data=serialize(array('map' => $slots'options' => $this->options));

//-- Dump to file

$buf=\Automap\Map::MAGIC
    
.str_pad(self::MIN_RUNTIME_VERSION,12)
    .
str_pad(self::VERSION,12)
    .
str_pad(strlen($data)+70,8)
    .
'00000000'
    
.str_pad(count($this->symbols),8)
    .
str_pad(strlen($data),8)
    .
$data;

return 
substr_replace($buf,hash('adler32',$buf),46,8); // Insert CRC
}

//---------

public function save($path)
{
if (
is_null($path)) throw new \Exception('No path provided');

$data=$this->serialize();

\
Phool\Display::trace("$path: Writing map file");
\
Phool\File::atomicWrite($path,$data);
}

//---------
// Register an extension in current map.
// $file=extension file (basename)

public function registerExtensionFile($file)
{
\
Phool\Display::trace("Registering extension : $file");

$va=self::mkVarray(\Automap\Mgr::F_EXTENSION,$file);
$this->unregisterTarget($va);

foreach(
$this->parser->parseExtension($file) as $sym)
    {
    
$this->addTSEntry($sym['type'],$sym['name'],$va);
    }
}

//---------
// Register every extension files in the extension directory
// We do several passes, as there are dependencies between extensions which
// must be loaded in a given order. We stop when a pass cannot load any file.

public function registerExtensionDir()
{
$ext_dir=ini_get('extension_dir');
\
Phool\Display::trace("Scanning extensions directory ($ext_dir)\n");

//-- Multiple passes because of possible dependencies
//-- Loop until everything is loaded or we cannot load anything more

$f_to_load=array();
$pattern='/\.'.PHP_SHLIB_SUFFIX.'$/';
foreach(
scandir($ext_dir) as $ext_file)
    {
    if (
is_dir($ext_dir.DIRECTORY_SEPARATOR.$ext_file)) continue;
    if (
preg_match($pattern,$ext_file)) $f_to_load[]=$ext_file;
    }

while(
true)
    {
    
$f_failed=array();
    foreach(
$f_to_load as $key => $ext_file)
        {
        try { 
$this->registerExtensionFile($ext_file); }
        catch (\
Exception $e) { $f_failed[]=$ext_file; }
        }
    
//-- If we could load everything or if we didn't load anything, break
    
if ((count($f_failed)==0)||(count($f_failed)==count($f_to_load))) break;
    
$f_to_load=$f_failed;
    }

if (
count($f_failed))
    {
    foreach(
$f_failed as $file)
        \
Phool\Display::warning("$file: This extension was not registered (load failed)");
    }
}

//---------------------------------
/**
* Normalize a destination path
*
* 1. Replace backslashes with forward slashes.
* 2. Remove trailing slashes
*
* @param string $rpath the path to normalize
* @return string the normalized path
*/

private static function normalizePath($path)
{
$path=rtrim(str_replace('\\','/',$path),'/');
if (
$path==''$path='/';
return 
$path;
}

//---------

public function registerScriptFile($fpath,$rpath,$ns_filter=null)
{
\
Phool\Display::trace("Registering script $fpath as $rpath");

// Force relative path

$va=self::mkVarray(\Automap\Mgr::F_SCRIPT,self::normalizePath($rpath),$ns_filter);
$this->unregisterTarget($va);

foreach(
$this->parser->parseScriptFile($fpath) as $sym)
    {
    
$this->addTSEntry($sym['type'],$sym['name'],$va);
    }
}

//---------
/**
* Recursively scan a path and records symbols
*
* Scan retains PHP source files and phk packages only (based on file suffix)
*
* Only dirs and regular files are considered. Other types are ignored.
*
* @param string $fpath Path to register
* @param string $rpath Path to register to in map for $fpath
* @param string|array|null $ns_filter
*            List of authorized namespaces (empty string means no namespace)
*            If null, no filtering.
* @param string|null $file_pattern
*            File path preg pattern (File paths not matching this pattern are ignored)
*/

public function registerPath($fpath,$rpath,$ns_filter=null,$file_pattern=null)
{
\
Phool\Display::trace("Registering path <$fpath> as <$rpath>");

switch(
$type=filetype($fpath))
    {
    case 
'dir':
        foreach(\
Phool\File::scandir($fpath) as $entry)
            {
            
$this->registerPath($fpath.'/'.$entry,$rpath.'/'.$entry,$ns_filter);
            }
        break;

    case 
'file':
        if ((!
is_null($file_pattern)) && (!preg_match($file_pattern$fpath))) return;
        
$suffix=strtolower(\Phool\File::fileSuffix($fpath));
        if (
$suffix=='phk')
            
$this->registerPhkPkg($fpath,$rpath);
        elseif (
array_search($suffix,$this->php_file_ext)!==false)
            
$this->registerScriptFile($fpath,$rpath,$ns_filter);
        else
            \
Phool\Display::trace("Ignoring file $fpath (not a PHP script)");
        break;
    }
}

//---------

public function readMapFile($fpath)
{
\
Phool\Display::trace("Reading map file ($fpath)");

$map=new \Automap\Map($fpath);
$this->options=$map->options();
$this->symbols=array();
$this->mergeMapSymbols($map);
}

//---------
/**
* Merge an existing map file into the current map
*
* Import symbols only. Options are ignored (including base path).
*
* @param string $fpath Path of the map to merge (input)
* @param Relative path to prepend to map target paths
* @return null
*/

public function mergeMapFile($fpath,$rpath)
{
\
Phool\Display::debug("Merging map file from $fpath (rpath=$rpath)");

$map=new \Automap\Map($fpath);
$this->mergeMapSymbols($map,$rpath);
}

//---------

public function mergeMapSymbols($map,$rpath='.')
{
foreach(
$map->symbols() as $va)
    {
    
$va['rpath']=\Phool\File::combinePath($rpath,$va['rpath']);
    
$this->addEntry($va);
    }
}

//---------
// Register a PHK package

public function registerPhkPkg($fpath,$rpath)
{
\
Phool\Display::trace("Registering PHK package $fpath as $rpath");

$rpath=self::normalizePath($rpath);
\
Phool\Display::debug("Registering PHK package (path=$fpath, rpath=$rpath)");
$va=self::mkVarray(\Automap\Mgr::F_PACKAGE,$rpath);
$this->unregisterTarget($va);

$mnt=\PHK\Mgr::mount($fpath,\PHK::NO_MOUNT_SCRIPT);
$pkg=\PHK\Mgr::instance($mnt);
$id=$pkg->automapID();
if (
$id// If package has an automap
    
{
    foreach(\
Automap\Mgr::map($id)->symbols() as $sym)
        
$this->addTSEntry($sym['stype'],$sym['symbol'],$va);
    }
}

//---------

public function import($path=null)
{
if (
is_null($path)) $path="php://stdin";

\
Phool\Display::trace("Importing map from $path");

$fp=fopen($path,'r');
if (!
$fp) throw new \Exception("$path: Cannot open for reading");

while((
$line=fgets($fp))!==false)
    {
    if ((
$line=trim($line))==='') continue;
    list(
$stype,$sname,$ftype,$fname)=explode('|',$line);
    
$va=self::mkVarray($ftype,$fname);
    
$this->addTSEntry($stype,$sname,$va);
    }
fclose($fp);
}

//---
// End of class
//===========================================================================
// End of class_exists
//===========================================================================
// End of namespace
//===========================================================================
?>

For more information about the PHK package format: http://phk.tekwire.net