mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
port
This commit is contained in:
commit
164ab85594
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.idea
|
||||
vendor
|
||||
composer.lock
|
||||
tests/resources/compile/*
|
23
composer.json
Normal file
23
composer.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "megagroup/aspect",
|
||||
"type": "library",
|
||||
"description": "Aspect - faster templater for PHP",
|
||||
"keywords": ["aspect", "templater", "templating"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ivan Shalganov",
|
||||
"email": "a.cobest@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"ext-tokenizer": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "3.7.*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Aspect": "src/" }
|
||||
}
|
||||
}
|
37
phpunit.xml.dist
Normal file
37
phpunit.xml.dist
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="tests/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="memory_limit" value="-1"/>
|
||||
<ini name="display_errors" value="1"/>
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite >
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>benchmark</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
649
src/Aspect.php
Normal file
649
src/Aspect.php
Normal file
@ -0,0 +1,649 @@
|
||||
<?php
|
||||
use Aspect\Template;
|
||||
|
||||
class Aspect {
|
||||
|
||||
const INLINE_COMPILER = 1;
|
||||
const BLOCK_COMPILER = 2;
|
||||
const INLINE_FUNCTION = 3;
|
||||
const BLOCK_FUNCTION = 4;
|
||||
const MODIFIER = 5;
|
||||
|
||||
const DENY_METHODS = 128;
|
||||
const DENY_INLINE_FUNCS = 256;
|
||||
const DENY_SET_VARS = 512;
|
||||
|
||||
const INCLUDE_SOURCES = 1024;
|
||||
|
||||
const CHECK_MTIME = 2048;
|
||||
const FORCE_COMPILE = 4096;
|
||||
|
||||
/**
|
||||
* @var array list of possible options, as associative array
|
||||
* @see setOptions, addOptions, delOptions
|
||||
*/
|
||||
private static $_option_list = array(
|
||||
"disable_methods" => self::DENY_METHODS,
|
||||
"disable_native_funcs" => self::DENY_INLINE_FUNCS,
|
||||
"disable_set_vars" => self::DENY_SET_VARS,
|
||||
"include_sources" => self::INCLUDE_SOURCES,
|
||||
"force_compile" => self::FORCE_COMPILE,
|
||||
"compile_check" => self::CHECK_MTIME,
|
||||
);
|
||||
|
||||
private static $_actions_defaults = array(
|
||||
self::BLOCK_FUNCTION => array(
|
||||
'type' => self::BLOCK_FUNCTION,
|
||||
'open' => 'MF\Aspect\Compiler::stdFuncOpen',
|
||||
'close' => 'MF\Aspect\Compiler::stdFuncClose',
|
||||
'function' => null,
|
||||
),
|
||||
self::INLINE_FUNCTION => array(
|
||||
'type' => self::INLINE_FUNCTION,
|
||||
'parser' => 'MF\Aspect\Compiler::stdFuncParser',
|
||||
'function' => null,
|
||||
),
|
||||
self::INLINE_FUNCTION => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'open' => null,
|
||||
'close' => 'MF\Aspect\Compiler::stdClose',
|
||||
'tags' => array(),
|
||||
'float_tags' => array()
|
||||
),
|
||||
self::BLOCK_FUNCTION => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => null,
|
||||
'close' => null,
|
||||
'tags' => array(),
|
||||
'float_tags' => array()
|
||||
)
|
||||
);
|
||||
|
||||
public $blocks = array();
|
||||
/**
|
||||
* @var array Templates storage
|
||||
*/
|
||||
protected $_storage = array();
|
||||
/**
|
||||
* @var array template directory
|
||||
*/
|
||||
protected $_tpl_path = array();
|
||||
/**
|
||||
* @var string compile directory
|
||||
*/
|
||||
protected $_compile_dir = "/tmp";
|
||||
|
||||
/**
|
||||
* @var int masked options
|
||||
*/
|
||||
protected $_options = 0;
|
||||
|
||||
/**
|
||||
* Modifiers loader
|
||||
* @var callable
|
||||
*/
|
||||
protected $_loader_mod;
|
||||
/**
|
||||
* Functions loader
|
||||
* @var callable
|
||||
*/
|
||||
protected $_loader_func;
|
||||
|
||||
/**
|
||||
* @var array list of modifiers
|
||||
*/
|
||||
protected $_modifiers = array(
|
||||
"upper" => 'strtoupper',
|
||||
"lower" => 'strtolower',
|
||||
"nl2br" => 'nl2br',
|
||||
"date_format" => 'Aspect\Modifier::dateFormat',
|
||||
"date" => 'Aspect\Modifier::date',
|
||||
"truncate" => 'Aspect\Modifier::truncate',
|
||||
"escape" => 'Aspect\Modifier::escape',
|
||||
"e" => 'Aspect\Modifier::escape', // alias of escape
|
||||
"unescape" => 'Aspect\Modifier::unescape',
|
||||
"strip_tags" => 'strip_tags',
|
||||
"strip" => 'Aspect\Modifier::strip',
|
||||
"default" => 'Aspect\Modifier::defaultValue',
|
||||
"isset" => 'isset',
|
||||
"empty" => 'empty'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array list of allowed PHP functions
|
||||
*/
|
||||
protected $_allowed_funcs = array(
|
||||
"empty" => 1, "isset" => 1, "count" => 1, "is_string" => 1, "is_array" => 1, "is_numeric" => 1, "is_int" => 1, "is_object" => 1
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array list of compilers and functions
|
||||
*/
|
||||
protected $_actions = array(
|
||||
'foreach' => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::foreachOpen',
|
||||
'close' => 'Aspect\Compiler::foreachClose',
|
||||
'tags' => array(
|
||||
'foreachelse' => 'Aspect\Compiler::foreachElse',
|
||||
'break' => 'Aspect\Compiler::tagBreak',
|
||||
'continue' => 'Aspect\Compiler::tagContinue',
|
||||
),
|
||||
'float_tags' => array('break' => 1, 'continue' => 1)
|
||||
),
|
||||
'if' => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::ifOpen',
|
||||
'close' => 'Aspect\Compiler::stdClose',
|
||||
'tags' => array(
|
||||
'elseif' => 'Aspect\Compiler::tagElseIf',
|
||||
'else' => 'Aspect\Compiler::tagElse',
|
||||
)
|
||||
),
|
||||
'switch' => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::switchOpen',
|
||||
'close' => 'Aspect\Compiler::stdClose',
|
||||
'tags' => array(
|
||||
'case' => 'Aspect\Compiler::tagCase',
|
||||
'default' => 'Aspect\Compiler::tagDefault',
|
||||
'break' => 'Aspect\Compiler::tagBreak',
|
||||
),
|
||||
'float_tags' => array('break' => 1)
|
||||
),
|
||||
'for' => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::forOpen',
|
||||
'close' => 'Aspect\Compiler::forClose',
|
||||
'tags' => array(
|
||||
'forelse' => 'Aspect\Compiler::forElse',
|
||||
'break' => 'Aspect\Compiler::tagBreak',
|
||||
'continue' => 'Aspect\Compiler::tagContinue',
|
||||
),
|
||||
'float_tags' => array('break' => 1, 'continue' => 1)
|
||||
),
|
||||
'while' => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::whileOpen',
|
||||
'close' => 'Aspect\Compiler::stdClose',
|
||||
'tags' => array(
|
||||
'break' => 'Aspect\Compiler::tagBreak',
|
||||
'continue' => 'Aspect\Compiler::tagContinue',
|
||||
),
|
||||
'float_tags' => array('break' => 1, 'continue' => 1)
|
||||
),
|
||||
'include' => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Aspect\Compiler::tagInclude'
|
||||
),
|
||||
'var' => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Aspect\Compiler::assign'
|
||||
),
|
||||
'block' => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::tagBlockOpen',
|
||||
'close' => 'Aspect\Compiler::tagBlockClose',
|
||||
),
|
||||
'extends' => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Aspect\Compiler::tagExtends'
|
||||
),
|
||||
'capture' => array(
|
||||
'type' => self::BLOCK_FUNCTION,
|
||||
'open' => 'Aspect\Compiler::stdFuncOpen',
|
||||
'close' => 'Aspect\Compiler::stdFuncClose',
|
||||
'function' => 'Aspect\Func::capture',
|
||||
),
|
||||
'mailto' => array(
|
||||
'type' => self::INLINE_FUNCTION,
|
||||
'parser' => 'Aspect\Compiler::stdFuncParser',
|
||||
'function' => 'Aspect\Func::mailto',
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
public static function factory($template_dir, $compile_dir, $options = 0) {
|
||||
$aspect = new static();
|
||||
$aspect->setCompileDir($compile_dir);
|
||||
$aspect->setTemplateDirs($template_dir);
|
||||
if($options) {
|
||||
$aspect->setOptions($options);
|
||||
}
|
||||
return $aspect;
|
||||
}
|
||||
|
||||
public function setCompileCheck($state) {
|
||||
$state && ($this->_options |= self::CHECK_MTIME);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setForceCompile($state) {
|
||||
$state && ($this->_options |= self::FORCE_COMPILE);
|
||||
$this->_storage = $state ? new Aspect\BlackHole() : array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCompileDir($dir) {
|
||||
$this->_compile_dir = $dir;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTemplateDirs($dirs) {
|
||||
$this->_tpl_path = (array)$dirs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*public function addPostCompileFilter($cb) {
|
||||
$this->_post_cmp[] = $cb;
|
||||
}
|
||||
|
||||
public function addCompileFilter($cb) {
|
||||
$this->_cmp[] = $cb;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Add modifier
|
||||
*
|
||||
* @param string $modifier
|
||||
* @param string $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setModifier($modifier, $callback) {
|
||||
$this->_modifiers[$modifier] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $compiler
|
||||
* @param $parser
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setCompiler($compiler, $parser) {
|
||||
$this->_actions[$compiler] = array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => $parser
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $compiler
|
||||
* @param array $parsers
|
||||
* @param array $tags
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setBlockCompiler($compiler, array $parsers, array $tags = array()) {
|
||||
$this->_actions[$compiler] = array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => $parsers["open"],
|
||||
'close' => isset($parsers["close"]) ? $parsers["close"] : 'Aspect\Compiler::stdClose',
|
||||
'tags' => $tags,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $function
|
||||
* @param $callback
|
||||
* @param null $parser
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setFunction($function, $callback, $parser = null) {
|
||||
$this->_actions[$function] = array(
|
||||
'type' => self::INLINE_FUNCTION,
|
||||
'parser' => $parser ?: 'Aspect\Compiler::stdFuncParser',
|
||||
'function' => $callback,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $function
|
||||
* @param $callback
|
||||
* @param null $parser_open
|
||||
* @param null $parser_close
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setBlockFunction($function, $callback, $parser_open = null, $parser_close = null) {
|
||||
$this->_actions[$function] = array(
|
||||
'type' => self::BLOCK_FUNCTION,
|
||||
'open' => $parser_open ?: 'Aspect\Compiler::stdFuncOpen',
|
||||
'close' => $parser_close ?: 'Aspect\Compiler::stdFuncClose',
|
||||
'function' => $callback,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $funcs
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setAllowedFunctions(array $funcs) {
|
||||
$this->_allowed_funcs = $this->_allowed_funcs + array_flip($funcs);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setFunctionsLoader($callback) {
|
||||
$this->_loader_func = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setModifiersLoader($callback) {
|
||||
$this->_loader_mod = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $modifier
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getModifier($modifier) {
|
||||
if(isset($this->_modifiers[$modifier])) {
|
||||
return $this->_modifiers[$modifier];
|
||||
} elseif($this->isAllowedFunction($modifier)) {
|
||||
return $modifier;
|
||||
} elseif($this->_loader_mod && $this->_loadModifier($modifier)) {
|
||||
return $this->_modifiers[$modifier];
|
||||
} else {
|
||||
throw new \Exception("Modifier $modifier not found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getFunction($function) {
|
||||
if(isset($this->_actions[$function])) {
|
||||
return $this->_actions[$function];
|
||||
} elseif($this->_loader_func && $this->_loadFunction($function)) {
|
||||
return $this->_actions[$function];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function _loadModifier($modifier) {
|
||||
$mod = call_user_func($this->_loader_mod, $modifier);
|
||||
if($mod) {
|
||||
$this->_modifiers[$modifier] = $mod;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function _loadFunction($function) {
|
||||
$func = call_user_func($this->_loader_func, $function);
|
||||
if($func && isset(self::$_actions_defaults[ $func["type"] ])) {
|
||||
|
||||
$this->_actions[$function] = $func + self::$_actions_defaults[ $func["type"] ];
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function isAllowedFunction($function) {
|
||||
if($this->_options & self::DENY_INLINE_FUNCS) {
|
||||
return isset($this->_allowed_funcs[$function]);
|
||||
} else {
|
||||
return is_callable($function);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTagOwners($tag) {
|
||||
$tags = array();
|
||||
foreach($this->_actions as $owner => $params) {
|
||||
if(isset($params["tags"][$tag])) {
|
||||
$tags[] = $owner;
|
||||
}
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template directory
|
||||
* @static
|
||||
* @param string $dir
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addTemplateDir($dir) {
|
||||
$_dir = realpath($dir);
|
||||
if(!$_dir) {
|
||||
throw new \InvalidArgumentException("Invalid template dir: $dir");
|
||||
}
|
||||
$this->_tpl_path[] = $_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options. May be bitwise mask of constants DENY_METHODS, DENY_INLINE_FUNCS, DENY_SET_VARS, INCLUDE_SOURCES,
|
||||
* FORCE_COMPILE, CHECK_MTIME, or associative array with boolean values:
|
||||
* disable_methods - disable all call method in template
|
||||
* disable_native_funcs - disable all native PHP functions in template
|
||||
* disable_set_vars - forbidden rewrite variables
|
||||
* include_sources - insert comments with source code into compiled template
|
||||
* force_compile - recompile template every time (very slow!)
|
||||
* compile_check - check template modifications (slow!)
|
||||
* @param int|array $options
|
||||
*/
|
||||
public function setOptions($options) {
|
||||
if(is_array($options)) {
|
||||
$options = Aspect\Misc::makeMask($options, self::$_option_list);
|
||||
}
|
||||
$this->_storage = ($options & self::FORCE_COMPILE) ? new Aspect\BlackHole() : array();
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options as bits
|
||||
* @return int
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and write result into stdout
|
||||
*
|
||||
*
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @return Aspect\Render
|
||||
*/
|
||||
public function display($template, array $vars = array()) {
|
||||
return $this->getTemplate($template)->display($vars);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @internal param int $options
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetch($template, array $vars = array()) {
|
||||
return $this->getTemplate($template)->fetch($vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return template by name
|
||||
*
|
||||
* @param string $template
|
||||
* @return Aspect\Template
|
||||
*/
|
||||
public function getTemplate($template) {
|
||||
if(isset($this->_storage[ $template ])) {
|
||||
if(($this->_options & self::CHECK_MTIME) && !$this->_check($template)) {
|
||||
return $this->_storage[ $template ] = $this->compile($template);
|
||||
} else {
|
||||
return $this->_storage[ $template ];
|
||||
}
|
||||
} elseif($this->_options & self::FORCE_COMPILE) {
|
||||
return $this->compile($template);
|
||||
} else {
|
||||
return $this->_storage[ $template ] = $this->_load($template);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom template into storage
|
||||
* @param Aspect\Render $template
|
||||
*/
|
||||
public function storeTemplate(Aspect\Render $template) {
|
||||
$this->_storage[ $template->getName() ] = $template;
|
||||
$template->setStorage($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return template from storage or create if template doesn't exists.
|
||||
*
|
||||
* @param string $tpl
|
||||
* @throws \RuntimeException
|
||||
* @return Aspect\Template|mixed
|
||||
*/
|
||||
protected function _load($tpl) {
|
||||
$file_name = $this->_getHash($tpl);
|
||||
if(!is_file($this->_compile_dir."/".$file_name) || ($this->_options & self::CHECK_MTIME) && !$this->_check($tpl)) {
|
||||
return $this->compile($tpl);
|
||||
} else {
|
||||
/** @var Aspect\Render $tpl */
|
||||
$tpl = include($this->_compile_dir."/".$file_name);
|
||||
$tpl->setStorage($this);
|
||||
return $tpl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @return bool
|
||||
*/
|
||||
private function _check($template) {
|
||||
return $this->_isActual($template, filemtime($this->_compile_dir."/".$this->_getHash($template)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, if template is actual
|
||||
* @param $template
|
||||
* @param $compiled_time
|
||||
* @return bool
|
||||
*/
|
||||
protected function _isActual($template, $compiled_time) {
|
||||
clearstatcache(false, $template = $this->_getTemplatePath($template));
|
||||
return filemtime($template) < $compiled_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique name of compiled template
|
||||
*
|
||||
* @param string $tpl
|
||||
* @return string
|
||||
*/
|
||||
private function _getHash($tpl) {
|
||||
$hash = $tpl.":".$this->_options;
|
||||
return basename($tpl).".".crc32($hash).".".strlen($hash).".php";
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and save template
|
||||
*
|
||||
*
|
||||
* @param string $tpl
|
||||
* @throws \RuntimeException
|
||||
* @return \Aspect\Template
|
||||
*/
|
||||
public function compile($tpl) {
|
||||
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
|
||||
$template = new Template($this, $this->_loadCode($tpl), $tpl);
|
||||
$tpl_tmp = tempnam($this->_compile_dir, basename($tpl));
|
||||
$tpl_fp = fopen($tpl_tmp, "w");
|
||||
if(!$tpl_fp) {
|
||||
throw new \RuntimeException("Can not open temporary file $tpl_tmp. Directory ".$this->_compile_dir." is writable?");
|
||||
}
|
||||
fwrite($tpl_fp, $template->getTemplateCode());
|
||||
fclose($tpl_fp);
|
||||
if(!rename($tpl_tmp, $file_name)) {
|
||||
throw new \RuntimeException("Can not to move $tpl_tmp to $tpl");
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all compiled templates. Warning! Do cleanup the compiled directory.
|
||||
* @return int
|
||||
* @api
|
||||
*/
|
||||
public function compileAll() {
|
||||
//return FS::rm($this->_compile_dir.'/*');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return bool
|
||||
* @api
|
||||
*/
|
||||
public function clearCompileTemplate($tpl) {
|
||||
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
|
||||
if(file_exists($file_name)) {
|
||||
return unlink($file_name);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @api
|
||||
*/
|
||||
public function clearAllCompiles() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template path
|
||||
* @param $tpl
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function _getTemplatePath($tpl) {
|
||||
foreach($this->_tpl_path as $tpl_path) {
|
||||
if(($path = stream_resolve_include_path($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException("Template $tpl not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Code loader
|
||||
*
|
||||
* @param string $tpl
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function _loadCode(&$tpl) {
|
||||
return file_get_contents($tpl = $this->_getTemplatePath($tpl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile code to template
|
||||
*
|
||||
* @param string $code
|
||||
* @param string $name
|
||||
* @return Aspect\Template
|
||||
*/
|
||||
public function compileCode($code, $name = 'Runtime compile') {
|
||||
return new Template($this, $code, $name);
|
||||
}
|
||||
|
||||
}
|
100
src/Aspect/BlackHole.php
Normal file
100
src/Aspect/BlackHole.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
/**
|
||||
* Class blackhole
|
||||
* @author Ivan Shalganov <bzick@megagroup.ru>
|
||||
* @copyright MegaGroup.ru
|
||||
*/
|
||||
class BlackHole implements \ArrayAccess, \Countable, \Iterator {
|
||||
|
||||
/**
|
||||
* Whether a offset exists
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* @param mixed $offset <p>
|
||||
* An offset to check for.
|
||||
* @return boolean true on success or false on failure.
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param mixed $offset
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value) {}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* @param mixed $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset) {}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* The return value is cast to an integer.
|
||||
*/
|
||||
public function count() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function next() {}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function rewind() {}
|
||||
}
|
493
src/Aspect/Compiler.php
Normal file
493
src/Aspect/Compiler.php
Normal file
@ -0,0 +1,493 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
use Aspect\Tokenizer;
|
||||
use Aspect\Template;
|
||||
use Aspect\Scope;
|
||||
|
||||
class Compiler {
|
||||
/**
|
||||
* Tag {include ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @throws \Exception
|
||||
* @return string
|
||||
*/
|
||||
public static function tagInclude(Tokenizer $tokens, Template $tpl) {
|
||||
|
||||
$p = $tpl->parseParams($tokens);
|
||||
if(isset($p[0])) {
|
||||
$file_name = $p[0];
|
||||
} elseif (isset($p["file"])) {
|
||||
$file_name = $p["file"];
|
||||
} else {
|
||||
throw new \Exception("{include} require 'file' parameter");
|
||||
}
|
||||
unset($p["file"], $p[0]);
|
||||
if($p) {
|
||||
return '$tpl->getStorage()->getTemplate('.$file_name.')->display('.self::_toArray($p).'+(array)$tpl);';
|
||||
} else {
|
||||
return '$tpl->getStorage()->getTemplate('.$file_name.')->display((array)$tpl);';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open tag {if ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function ifOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["else"] = false;
|
||||
return 'if('.$scope->tpl->parseExp($tokens, true).') {';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {elseif ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @internal param \Exception $
|
||||
* @return string
|
||||
*/
|
||||
public static function tagElseIf(Tokenizer $tokens, Scope $scope) {
|
||||
if($scope["else"]) {
|
||||
throw new \Exception('Incorrect use of the tag {else if}');
|
||||
}
|
||||
return '} elseif('.$scope->tpl->parseExp($tokens, true).') {';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {else}
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @internal param $
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function tagElse(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["else"] = true;
|
||||
return '} else {';
|
||||
}
|
||||
|
||||
/**
|
||||
* Open tag {foreach ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @internal param \Exception $
|
||||
* @return string
|
||||
*/
|
||||
public static function foreachOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$p = array("index" => false, "first" => false, "last" => false);
|
||||
$key = null;
|
||||
$before = $body = array();
|
||||
if($tokens->is(T_VARIABLE)) {
|
||||
$from = $scope->tpl->parseVar($tokens, Template::DENY_MODS);
|
||||
$prepend = "";
|
||||
} elseif($tokens->is('[')) {
|
||||
$from = $scope->tpl->parseArray($tokens);
|
||||
$uid = '$v'.$scope->tpl->i++;
|
||||
$prepend = $uid.' = '.$from.';';
|
||||
$from = $uid;
|
||||
} else {
|
||||
|
||||
if($tokens->valid()) {
|
||||
throw new \Exception("Unexpected token '".$tokens->current()."' in 'foreach'");
|
||||
} else {
|
||||
throw new \Exception("Unexpected end of 'foreach'");
|
||||
}
|
||||
}
|
||||
$tokens->get(T_AS);
|
||||
$tokens->next();
|
||||
$value = $scope->tpl->parseVar($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
|
||||
if($tokens->is(T_DOUBLE_ARROW)) {
|
||||
$tokens->next();
|
||||
$key = $value;
|
||||
$value = $scope->tpl->parseVar($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
|
||||
}
|
||||
|
||||
$scope["after"] = array();
|
||||
$scope["else"] = false;
|
||||
|
||||
while($token = $tokens->key()) {
|
||||
$param = $tokens->get(T_STRING);
|
||||
if(!isset($p[ $param ])) {
|
||||
throw new \Exception("Unknown parameter '$param'");
|
||||
}
|
||||
$tokens->getNext("=");
|
||||
$tokens->next();
|
||||
$p[ $param ] = $scope->tpl->parseVar($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
|
||||
}
|
||||
|
||||
if($p["index"]) {
|
||||
$before[] = $p["index"].' = 0';
|
||||
$scope["after"][] = $p["index"].'++';
|
||||
}
|
||||
if($p["first"]) {
|
||||
$before[] = $p["first"].' = true';
|
||||
$scope["after"][] = $p["first"] .' && ('. $p["first"].' = false )';
|
||||
}
|
||||
if($p["last"]) {
|
||||
$before[] = $p["last"].' = false';
|
||||
$scope["uid"] = "v".$scope->tpl->i++;
|
||||
$before[] = '$'.$scope["uid"]." = count($from)";
|
||||
$body[] = 'if(!--$'.$scope["uid"].') '.$p["last"].' = true';
|
||||
}
|
||||
|
||||
$before = $before ? implode("; ", $before).";" : "";
|
||||
$body = $body ? implode("; ", $body).";" : "";
|
||||
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]).";" : "";
|
||||
|
||||
if($key) {
|
||||
return "$prepend if($from) { $before foreach($from as $key => $value) { $body";
|
||||
} else {
|
||||
return "$prepend if($from) { $before foreach($from as $value) { $body";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {foreachelse}
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @internal param $
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function foreachElse(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
|
||||
return " {$scope['after']} } } else {";
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tag {/foreach}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function foreachClose(Tokenizer $tokens, Scope $scope) {
|
||||
if($scope["else"]) {
|
||||
return '}';
|
||||
} else {
|
||||
return " {$scope['after']} } }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function forOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$p = array("index" => false, "first" => false, "last" => false, "step" => 1, "to" => false, "max" => false, "min" => false);
|
||||
$scope["after"] = $before = $body = array();
|
||||
$i = array('', '');
|
||||
$c = "";
|
||||
$var = $scope->tpl->parseVar($tokens, Template::DENY_MODS);
|
||||
$tokens->get("=");
|
||||
$tokens->next();
|
||||
$val = $scope->tpl->parseExp($tokens, true);
|
||||
$p = $scope->tpl->parseParams($tokens, $p);
|
||||
|
||||
if(is_numeric($p["step"])) {
|
||||
if($p["step"] > 0) {
|
||||
$condition = "$var <= {$p['to']}";
|
||||
if($p["last"]) $c = "($var + {$p['step']}) > {$p['to']}";
|
||||
} elseif($p["step"] < 0) {
|
||||
$condition = "$var >= {$p['to']}";
|
||||
if($p["last"]) $c = "($var + {$p['step']}) < {$p['to']}";
|
||||
} else {
|
||||
throw new \Exception("Invalid step value");
|
||||
}
|
||||
} else {
|
||||
$condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})";
|
||||
if($p["last"]) $c = "({$p['step']} > 0 && ($var + {$p['step']}) <= {$p['to']} || {$p['step']} < 0 && ($var + {$p['step']}) >= {$p['to']})";
|
||||
}
|
||||
|
||||
if($p["first"]) {
|
||||
$before[] = $p["first"].' = true';
|
||||
$scope["after"][] = $p["first"] .' && ('. $p["first"].' = false )';
|
||||
}
|
||||
if($p["last"]) {
|
||||
$before[] = $p["last"].' = false';
|
||||
$body[] = "if($c) {$p['last']} = true";
|
||||
}
|
||||
|
||||
if($p["index"]) {
|
||||
$i[0] .= $p["index"].' = 0,';
|
||||
$i[1] .= $p["index"].'++,';
|
||||
}
|
||||
|
||||
$scope["else"] = false;
|
||||
$scope["else_cond"] = "$var==$val";
|
||||
$before = $before ? implode("; ", $before).";" : "";
|
||||
$body = $body ? implode("; ", $body).";" : "";
|
||||
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]).";" : "";
|
||||
|
||||
return "$before for({$i[0]} $var=$val; $condition;{$i[1]} $var+={$p['step']}) { $body";
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function forElse(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["no-break"] = $scope["no-continue"] = true;
|
||||
$scope["else"] = true;
|
||||
return " } if({$scope['else_cond']}) {";
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function forClose(Tokenizer $tokens, Scope $scope) {
|
||||
if($scope["else"]) {
|
||||
return '}';
|
||||
} else {
|
||||
return " {$scope['after']} }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function whileOpen(Tokenizer $tokens, Scope $scope) {
|
||||
return 'while('.$scope->tpl->parseExp($tokens, true).') {';
|
||||
}
|
||||
|
||||
/**
|
||||
* Open tag {switch}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function switchOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["no-break"] = $scope["no-continue"] = true;
|
||||
$scope["switch"] = 'switch('.$scope->tpl->parseExp($tokens, true).') {';
|
||||
// lazy switch init
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {case ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function tagCase(Tokenizer $tokens, Scope $scope) {
|
||||
$code = 'case '.$scope->tpl->parseExp($tokens, true).': ';
|
||||
if($scope["switch"]) {
|
||||
unset($scope["no-break"], $scope["no-continue"]);
|
||||
$code = $scope["switch"]."\n".$code;
|
||||
$scope["switch"] = "";
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {continue}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @return string
|
||||
*/
|
||||
public static function tagContinue(Tokenizer $tokens, Scope $scope) {
|
||||
if(empty($scope["no-continue"])) {
|
||||
return 'continue;';
|
||||
} else {
|
||||
throw new \Exception("Incorrect use of the tag {continue}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {default}
|
||||
*
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
public static function tagDefault(Tokenizer $tokens, Scope $scope) {
|
||||
$code = 'default: ';
|
||||
if($scope["switch"]) {
|
||||
unset($scope["no-break"], $scope["no-continue"]);
|
||||
$code = $scope["switch"]."\n".$code;
|
||||
$scope["switch"] = "";
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {break}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @return string
|
||||
*/
|
||||
public static function tagBreak(Tokenizer $tokens, Scope $scope) {
|
||||
if(empty($scope["no-break"])) {
|
||||
return 'break;';
|
||||
} else {
|
||||
throw new \Exception("Incorrect use of the tag {break}");
|
||||
}
|
||||
}
|
||||
|
||||
public static function tagExtends(Tokenizer $tokens, Template $tpl) {
|
||||
if(!empty($tpl->_extends)) {
|
||||
throw new \Exception("Only one {extends} allowed");
|
||||
}
|
||||
$p = $tpl->parseParams($tokens);
|
||||
if(isset($p[0])) {
|
||||
$tpl_name = $p[0];
|
||||
} elseif (isset($p["file"])) {
|
||||
$tpl_name = $p["file"];
|
||||
} else {
|
||||
throw new \Exception("{extends} require 'file' parameter");
|
||||
}
|
||||
$tpl->addPostCompile(__CLASS__."::extendBody");
|
||||
$tpl->_extends = $tpl_name;
|
||||
return '$parent = $tpl->getStorage()->getTemplate('.$tpl_name.');';
|
||||
}
|
||||
|
||||
public static function extendBody(&$body, Template $tpl) {
|
||||
$body = '<?php if(!isset($tpl->blocks)) {$tpl->blocks = array();} ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';
|
||||
}
|
||||
|
||||
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$p = $scope->tpl->parseParams($tokens);
|
||||
if(isset($p["name"])) {
|
||||
$scope["name"] = $p["name"];
|
||||
} elseif (isset($p[0])) {
|
||||
$scope["name"] = $p[0];
|
||||
} else {
|
||||
throw new \Exception("{block} require name parameter");
|
||||
}
|
||||
|
||||
if($scope->closed) {
|
||||
return 'isset($tpl->blocks['.$scope["name"].']) ? $tpl->blocks[] : "" ;';
|
||||
} else {
|
||||
return 'ob_start();';
|
||||
}
|
||||
}
|
||||
|
||||
public static function tagBlockClose(Tokenizer $tokens, Scope $scope) {
|
||||
if(isset($scope->tpl->_extends)) {
|
||||
$var = '$i'.$scope->tpl->i++;
|
||||
return $var.' = ob_get_clean(); if('.$var.') $tpl->blocks['.$scope["name"].'] = '.$var.';';
|
||||
} else {
|
||||
return 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_end_flush(); } else { print($tpl->blocks['.$scope["name"].']); ob_end_clean(); }';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard close tag {/...}
|
||||
*
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
public static function stdClose() {
|
||||
return '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard function tag parser
|
||||
*
|
||||
* @static
|
||||
* @param $function
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @return string
|
||||
*/
|
||||
public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) {
|
||||
return "echo $function(".self::_toArray($tpl->parseParams($tokens)).', $tpl);';
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard function open tag parser
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function stdFuncOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["params"] = self::_toArray($scope->tpl->parseParams($tokens));
|
||||
return 'ob_start();';
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard function close tag parser
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function stdFuncClose(Tokenizer $tokens, Scope $scope) {
|
||||
return "echo ".$scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl);';
|
||||
}
|
||||
|
||||
private static function _toArray($params) {
|
||||
$_code = array();
|
||||
foreach($params as $k => $v) {
|
||||
$_code[] = '"'.$k.'" => '.$v;
|
||||
}
|
||||
|
||||
return 'array('.implode(",", $_code).')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {var ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @return string
|
||||
*/
|
||||
public static function assign(Tokenizer $tokens, Template $tpl) {
|
||||
return self::setVar($tokens, $tpl).';';
|
||||
}
|
||||
|
||||
public static function setVar(Tokenizer $tokens, Template $tpl, $allow_array = true) {
|
||||
$var = $tpl->parseVar($tokens, $tpl::DENY_MODS);
|
||||
|
||||
$tokens->get('=');
|
||||
$tokens->next();
|
||||
if($tokens->is("[") && $allow_array) {
|
||||
return $var.'='.$tpl->parseArray($tokens);
|
||||
} else {
|
||||
return $var.'='.$tpl->parseExp($tokens, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
17
src/Aspect/Func.php
Normal file
17
src/Aspect/Func.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
|
||||
class Func {
|
||||
public static function mailto($params) {
|
||||
if(empty($params["address"])) {
|
||||
trigger_error(E_USER_WARNING, "Modifier mailto: paramenter 'address' required");
|
||||
return "";
|
||||
}
|
||||
|
||||
if(empty($params["text"])) {
|
||||
$params["text"] = $params["address"];
|
||||
}
|
||||
|
||||
return '<a href="mailto:'.$params["address"].'">'.$params["text"].'</a>';
|
||||
}
|
||||
}
|
59
src/Aspect/Misc.php
Normal file
59
src/Aspect/Misc.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Aspect;
|
||||
|
||||
class Misc {
|
||||
|
||||
/**
|
||||
* Create bit-mask from associative array use fully associative array possible keys with bit values
|
||||
* @static
|
||||
* @param array $values custom assoc array, ["a" => true, "b" => false]
|
||||
* @param array $options possible values, ["a" => 0b001, "b" => 0b010, "c" => 0b100]
|
||||
* @param int $mask the initial value of the mask
|
||||
* @return int result, ( $mask | a ) & ~b
|
||||
* @throws \RuntimeException if key from custom assoc doesn't exists into possible values
|
||||
*/
|
||||
public static function makeMask(array $values, array $options, $mask = 0) {
|
||||
foreach($values as $value) {
|
||||
if(isset($options[$value])) {
|
||||
if($options[$value]) {
|
||||
$mask |= $options[$value];
|
||||
} else {
|
||||
$mask &= ~$options[$value];
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException("Undefined parameter $value");
|
||||
}
|
||||
}
|
||||
return $mask;
|
||||
}
|
||||
|
||||
public static function clean($path) {
|
||||
if(is_file($path)) {
|
||||
unlink($path);
|
||||
} elseif(is_dir($path)) {
|
||||
$iterator = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path,
|
||||
\FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST));
|
||||
foreach($iterator as $file) {
|
||||
/* @var \splFileInfo $file*/
|
||||
if($file->isFile()) {
|
||||
unlink($file->getRealPath());
|
||||
} elseif($file->isDir()) {
|
||||
rmdir($file->getRealPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function rm($path) {
|
||||
self::clean($path);
|
||||
if(is_dir($path)) {
|
||||
rmdir($path);
|
||||
}
|
||||
}
|
||||
|
||||
public static function put($path, $content) {
|
||||
file_put_contents($path, $content);
|
||||
}
|
||||
}
|
76
src/Aspect/Modifier.php
Normal file
76
src/Aspect/Modifier.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
|
||||
|
||||
class Modifier {
|
||||
|
||||
public static function dateFormat($date, $format = "%b %e, %Y") {
|
||||
if(is_string($date) && !is_numeric($date)) {
|
||||
$date = strtotime($date);
|
||||
if(!$date) $date = time();
|
||||
}
|
||||
//dump($format, $date);
|
||||
return strftime($format, $date);
|
||||
}
|
||||
|
||||
public static function date($date, $format = "Y m d") {
|
||||
if(is_string($date) && !is_numeric($date)) {
|
||||
$date = strtotime($date);
|
||||
if(!$date) $date = time();
|
||||
}
|
||||
return date($format, $date);
|
||||
}
|
||||
|
||||
public static function escape($text, $type = 'html') {
|
||||
switch($type) {
|
||||
case "url":
|
||||
return urlencode($text);
|
||||
case "html";
|
||||
return htmlspecialchars($text, ENT_COMPAT, 'UTF-8');
|
||||
default:
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
public static function unescape($text, $type = 'html') {
|
||||
switch($type) {
|
||||
case "url":
|
||||
return urldecode($text);
|
||||
case "html";
|
||||
return htmlspecialchars_decode($text);
|
||||
default:
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
public static function defaultValue(&$value, $default = null) {
|
||||
return ($value === null) ? $default : $value;
|
||||
}
|
||||
|
||||
public static function truncate($string, $length = 80, $etc = '...', $break_words = false, $middle = false) {
|
||||
$length -= min($length, strlen($etc));
|
||||
if (!$break_words && !$middle) {
|
||||
$string = preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, $length + 1));
|
||||
}
|
||||
if (!$middle) {
|
||||
return substr($string, 0, $length) . $etc;
|
||||
}
|
||||
return substr($string, 0, $length / 2) . $etc . substr($string, - $length / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip spaces symbols on edge of string end multiple spaces into string
|
||||
* @static
|
||||
* @param string $str
|
||||
* @param bool $to_line strip line ends
|
||||
* @return string
|
||||
*/
|
||||
public static function strip($str, $to_line = false) {
|
||||
$str = trim($str);
|
||||
if($to_line) {
|
||||
return preg_replace('#[\s]+#ms', ' ', $str);
|
||||
} else {
|
||||
return preg_replace('#[ \t]{2,}#', ' ', $str);
|
||||
}
|
||||
}
|
||||
}
|
122
src/Aspect/Render.php
Normal file
122
src/Aspect/Render.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
use Aspect;
|
||||
|
||||
/**
|
||||
* Primitive template
|
||||
* @author Ivan Shalganov <bzick@megagroup.ru>
|
||||
* @copyright MegaGroup.ru
|
||||
*/
|
||||
class Render extends \ArrayObject {
|
||||
/**
|
||||
* @var \Closure
|
||||
*/
|
||||
protected $_code;
|
||||
/**
|
||||
* Template name
|
||||
* @var string
|
||||
*/
|
||||
protected $_name = 'runtime template';
|
||||
/**
|
||||
* @var Aspect
|
||||
*/
|
||||
protected $_aspect;
|
||||
/**
|
||||
* Signature of the template
|
||||
* @var mixed
|
||||
*/
|
||||
protected $_fingerprint;
|
||||
|
||||
/**
|
||||
* @param string $name template name
|
||||
* @param callable $code template body
|
||||
* @param mixed $fingerprint signature
|
||||
*/
|
||||
public function __construct($name, \Closure $code, $fingerprint = null) {
|
||||
$this->_name = $name;
|
||||
$this->_code = $code;
|
||||
$this->_fingerprint = $fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set template storage
|
||||
* @param Aspect $aspect
|
||||
*/
|
||||
public function setStorage(Aspect $aspect) {
|
||||
$this->_aspect = $aspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template storage
|
||||
* @return Aspect
|
||||
*/
|
||||
public function getStorage() {
|
||||
return $this->_aspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return "Template({$this->_name})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template name
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate template version
|
||||
* @param mixed $fingerprint of the template
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($fingerprint) {
|
||||
if($this->_fingerprint) {
|
||||
return $fingerprint === $this->_fingerprint;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and write into output
|
||||
* @param array $values for template
|
||||
* @return Render
|
||||
*/
|
||||
public function display(array $values) {
|
||||
$this->exchangeArray($values);
|
||||
$this->_code->__invoke($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and return result as string
|
||||
* @param array $values for template
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function fetch(array $values) {
|
||||
ob_start();
|
||||
try {
|
||||
$this->display($values);
|
||||
return ob_get_clean();
|
||||
} catch (\Exception $e) {
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub
|
||||
* @param $method
|
||||
* @param $args
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function __call($method, $args) {
|
||||
throw new \BadMethodCallException("Unknown method ".$method);
|
||||
}
|
||||
}
|
69
src/Aspect/Scope.php
Normal file
69
src/Aspect/Scope.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
|
||||
/**
|
||||
* Scope for blocks tags
|
||||
*/
|
||||
class Scope extends \ArrayObject {
|
||||
|
||||
public $line = 0;
|
||||
public $name;
|
||||
/**
|
||||
* @var Template
|
||||
*/
|
||||
public $tpl;
|
||||
public $closed = false;
|
||||
public $is_next_close = false;
|
||||
public $is_compiler = true;
|
||||
private $_action;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Template $tpl
|
||||
* @param int $line
|
||||
* @param array $action
|
||||
*/
|
||||
public function __construct($name, $tpl, $line, $action) {
|
||||
$this->line = $line;
|
||||
$this->name = $name;
|
||||
$this->tpl = $tpl;
|
||||
$this->_action = $action;
|
||||
}
|
||||
|
||||
public function setFuncName($function) {
|
||||
$this["function"] = $function;
|
||||
$this->is_compiler = false;
|
||||
}
|
||||
|
||||
public function open($tokenizer) {
|
||||
return call_user_func($this->_action["open"], $tokenizer, $this);
|
||||
}
|
||||
|
||||
public function hasTag($tag, $level) {
|
||||
if(isset($this->_action["tags"][$tag])) {
|
||||
if($level) {
|
||||
return isset($this->_action["float_tags"][$tag]);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function tag($tag, $tokenizer) {
|
||||
return call_user_func($this->_action["tags"][$tag], $tokenizer, $this);
|
||||
}
|
||||
|
||||
public function close($tokenizer) {
|
||||
return call_user_func($this->_action["close"], $tokenizer, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count chars to close tag
|
||||
* @todo
|
||||
* @return int
|
||||
*/
|
||||
public function getDistanceToClose() {
|
||||
return 1;
|
||||
}
|
||||
}
|
827
src/Aspect/Template.php
Normal file
827
src/Aspect/Template.php
Normal file
@ -0,0 +1,827 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
|
||||
use Aspect;
|
||||
|
||||
/**
|
||||
* Aspect template compiler
|
||||
*
|
||||
* @author Ivan Shalganov <bzick@megagroup.ru>
|
||||
* @copyright MegaGroup.ru
|
||||
*/
|
||||
class Template extends Render {
|
||||
|
||||
const DENY_ARRAY = 1;
|
||||
const DENY_MODS = 2;
|
||||
|
||||
/**
|
||||
* @var int shared counter
|
||||
*/
|
||||
public $i = 1;
|
||||
/**
|
||||
* Template PHP code
|
||||
* @var string
|
||||
*/
|
||||
private $_body;
|
||||
/**
|
||||
* Call stack
|
||||
* @var Scope[]
|
||||
*/
|
||||
private $_stack = array();
|
||||
|
||||
/**
|
||||
* Template source
|
||||
* @var string
|
||||
*/
|
||||
private $_src;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $_pos = 0;
|
||||
private $_line = 1;
|
||||
private $_trim = false;
|
||||
private $_post = array();
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $_literal = false;
|
||||
/**
|
||||
* Options
|
||||
* @var int
|
||||
*/
|
||||
private $_options = 0;
|
||||
|
||||
/** System variables {$smarty.<...>} or {$aspect.<...>}
|
||||
* @var array
|
||||
*/
|
||||
public static $sysvar = array('$aspect' => 1, '$smarty' => 1);
|
||||
|
||||
/**
|
||||
* @param Aspect $aspect Template storage
|
||||
* @param string $code template source
|
||||
* @param string $name template name
|
||||
* @throws CompileException
|
||||
*/
|
||||
public function __construct(Aspect $aspect, $code, $name = "runtime template") {
|
||||
$this->_src = $code;
|
||||
$this->_name = $name;
|
||||
$this->_aspect = $aspect;
|
||||
$this->_options = $aspect->getOptions();
|
||||
$pos = 0;
|
||||
while(($start = strpos($code, '{', $pos)) !== false) { // search open-char of tags
|
||||
switch($code[$start + 1]) { // check next char
|
||||
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
|
||||
$pos = $start + 1; // trying finding tags after the current char
|
||||
continue 2;
|
||||
case "*": // if comment block
|
||||
$end = strpos($code, '*}', $start); // finding end of the comment block
|
||||
$frag = substr($code, $this->_pos, $start - $end); // read the comment block for precessing
|
||||
$this->_line += substr_count($frag, "\n"); // count skipped lines
|
||||
$pos = $end + 1; // trying finding tags after the comment block
|
||||
continue 2;
|
||||
}
|
||||
$end = strpos($code, '}', $start); // search close-char of the tag
|
||||
if(!$end) { // if unexpected end of template
|
||||
throw new CompileException("Unclosed tag in line $this->_line", 0, 1, $this->_name, $this->_line);
|
||||
}
|
||||
$frag = substr($code, $this->_pos, $start - $this->_pos); // variable $frag contains chars after last '}' and new '{'
|
||||
$tag = substr($code, $start, $end - $start + 1); // variable $tag contains aspect tag '{...}'
|
||||
$this->_line += substr_count($code, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code)
|
||||
$pos = $this->_pos = $end + 1; // move search pointer to end of the tag
|
||||
if($this->_trim) { // if previous tag has trim flag
|
||||
$frag = ltrim($frag);
|
||||
}
|
||||
$tag = $this->_tag($tag, $this->_trim);
|
||||
if($this->_trim) { // if current tag has trim flag
|
||||
$frag = rtrim($frag);
|
||||
}
|
||||
$this->_body .= $frag.$tag;
|
||||
|
||||
}
|
||||
$this->_body .= substr($code, $this->_pos);
|
||||
if($this->_stack) {
|
||||
$_names = array();
|
||||
$_line = 0;
|
||||
foreach($this->_stack as $scope) {
|
||||
if(!$_line) {
|
||||
$_line = $scope->line;
|
||||
}
|
||||
$_names[] = $scope->name.' (line '.$scope->line.')';
|
||||
}
|
||||
throw new CompileException("Unclosed tags: ".implode(", ", $_names), 0, 1, $this->_name, $_line);
|
||||
}
|
||||
unset($this->_src);
|
||||
if($this->_post) {
|
||||
foreach($this->_post as $cb) {
|
||||
call_user_func_array($cb, array(&$this->_body, $this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addPostCompile($cb) {
|
||||
$this->_post[] = $cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PHP code of template
|
||||
* @return string
|
||||
*/
|
||||
public function getBody() {
|
||||
return $this->_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PHP code of PHP file of template
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateCode() {
|
||||
return "<?php \n".
|
||||
"/** Aspect template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\n".
|
||||
"return new Aspect\\Render('{$this->_name}', ".$this->_getClosureCode().", ".$this->_options.");\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return closure code
|
||||
* @return string
|
||||
*/
|
||||
private function _getClosureCode() {
|
||||
return "function (\$tpl) {\n?>{$this->_body}<?php\n}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime execute template.
|
||||
*
|
||||
* @param array $values input values
|
||||
* @throws CompileException
|
||||
* @return Render
|
||||
*/
|
||||
public function display(array $values) {
|
||||
if(!$this->_code) {
|
||||
// evaluate template's code
|
||||
eval("\$this->_code = ".$this->_getClosureCode().";");
|
||||
if(!$this->_code) {
|
||||
throw new CompileException("Fatal error while creating the template");
|
||||
}
|
||||
}
|
||||
return parent::display($values);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and return result as string
|
||||
* @param array $values for template
|
||||
* @throws CompileException
|
||||
* @return string
|
||||
*/
|
||||
public function fetch(array $values) {
|
||||
if(!$this->_code) {
|
||||
eval("\$this->_code = ".$this->_getClosureCode().";");
|
||||
if(!$this->_code) {
|
||||
throw new CompileException("Fatal error while creating the template");
|
||||
}
|
||||
}
|
||||
return parent::fetch($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal tags router
|
||||
* @param string $src
|
||||
* @param bool $trim
|
||||
* @throws UnexpectedException
|
||||
* @throws CompileException
|
||||
* @throws SecurityException
|
||||
* @return string
|
||||
*/
|
||||
private function _tag($src, &$trim = false) {
|
||||
if($src[strlen($src) - 2] === "-") {
|
||||
$token = substr($src, 1, -2);
|
||||
$trim = true;
|
||||
} else {
|
||||
$token = substr($src, 1, -1);
|
||||
$trim = false;
|
||||
}
|
||||
$token = trim($token);
|
||||
if($this->_literal) {
|
||||
if($token === '/literal') {
|
||||
$this->_literal = false;
|
||||
return '';
|
||||
} else {
|
||||
return $src;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens = new Tokenizer($token);
|
||||
try {
|
||||
switch($token[0]) {
|
||||
case '"':
|
||||
case '\'':
|
||||
case '$':
|
||||
$code = "echo ".$this->parseExp($tokens).";";
|
||||
break;
|
||||
case '/':
|
||||
$code = $this->_end($tokens);
|
||||
break;
|
||||
default:
|
||||
$code = $this->_parseAct($tokens);
|
||||
if($code === null) {
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if($tokens->key()) { // if tokenizer still have tokens
|
||||
throw new UnexpectedException($tokens);
|
||||
}
|
||||
if($this->_options & Aspect::INCLUDE_SOURCES) {
|
||||
return "<?php\n/* {$this->_name}:{$this->_line}: {$src} */\n {$code} ?>";
|
||||
} else {
|
||||
return "<?php {$code} ?>";
|
||||
}
|
||||
} catch (\LogicException $e) {
|
||||
throw new SecurityException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, 1, $this->_name, $this->_line, $e);
|
||||
} catch (\Exception $e) {
|
||||
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, 1, $this->_name, $this->_line, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tag handler
|
||||
* @param Tokenizer $tokens
|
||||
* @return mixed
|
||||
* @throws TokenizeException
|
||||
*/
|
||||
private function _end(Tokenizer $tokens) {
|
||||
$name = $tokens->getNext(Tokenizer::MACRO_STRING);
|
||||
$tokens->next();
|
||||
if(!$this->_stack) {
|
||||
throw new TokenizeException("Unexpected closing of the tag '$name', the tag hasn't been opened");
|
||||
}
|
||||
/** @var Scope $scope */
|
||||
$scope = array_pop($this->_stack);
|
||||
if($scope->name !== $name) {
|
||||
throw new TokenizeException("Unexpected closing of the tag '$name' (expecting closing of the tag {$scope->name}, opened on line {$scope->line})");
|
||||
}
|
||||
return $scope->close($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse action {action ...} or {action(...) ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @throws TokenizeException
|
||||
* @return string
|
||||
*/
|
||||
private function _parseAct(Tokenizer $tokens) {
|
||||
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
$action = $tokens->current();
|
||||
} else {
|
||||
return 'echo '.$this->parseExp($tokens).';';
|
||||
}
|
||||
|
||||
if($action === "literal") {
|
||||
$this->_literal = true;
|
||||
$tokens->next();
|
||||
return '';
|
||||
}
|
||||
if($tokens->isNext("(")) {
|
||||
return "echo ".$this->parseExp($tokens).";";
|
||||
}
|
||||
|
||||
if($act = $this->_aspect->getFunction($action)) {
|
||||
$tokens->next();
|
||||
switch($act["type"]) {
|
||||
case Aspect::BLOCK_COMPILER:
|
||||
$scope = new Scope($action, $this, $this->_line, $act);
|
||||
array_push($this->_stack, $scope);
|
||||
return $scope->open($tokens);
|
||||
case Aspect::INLINE_COMPILER:
|
||||
return call_user_func($act["parser"], $tokens, $this);
|
||||
case Aspect::INLINE_FUNCTION:
|
||||
return call_user_func($act["parser"], $act["function"], $tokens, $this);
|
||||
case Aspect::BLOCK_FUNCTION:
|
||||
$scope = new Scope($action, $this, $this->_line, $act);
|
||||
$scope->setFuncName($act["function"]);
|
||||
array_push($this->_stack, $scope);
|
||||
return $scope->open($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
for($j = $i = count($this->_stack)-1; $i>=0; $i--) {
|
||||
if($this->_stack[$i]->hasTag($action, $j - $i)) {
|
||||
$tokens->next();
|
||||
return $this->_stack[$i]->tag($action, $tokens);
|
||||
}
|
||||
}
|
||||
if($tags = $this->_aspect->getTagOwners($action)) {
|
||||
throw new TokenizeException("Unexpected tag '$action' (this tag can be used with '".implode("', '", $tags)."')");
|
||||
} else {
|
||||
throw new TokenizeException("Unexpected tag $action");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse expressions. The mix of math operations, boolean operations, scalars, arrays and variables.
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param bool $required
|
||||
* @throws \LogicException
|
||||
* @throws UnexpectedException
|
||||
* @throws TokenizeException
|
||||
* @return string
|
||||
*/
|
||||
public function parseExp(Tokenizer $tokens, $required = false) {
|
||||
$_exp = "";
|
||||
$brackets = 0;
|
||||
$term = false;
|
||||
$cond = false;
|
||||
while($tokens->valid()) {
|
||||
if(!$term && $tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_ENCAPSED_AND_WHITESPACE)) {
|
||||
|
||||
$_exp .= $this->parseScalar($tokens, true);
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(T_VARIABLE)) {
|
||||
$pp = $tokens->isPrev(Tokenizer::MACRO_INCDEC);
|
||||
$_exp .= $this->parseVar($tokens, 0, $only_var);
|
||||
if($only_var && !$pp) {
|
||||
$term = 2;
|
||||
} else {
|
||||
$term = 1;
|
||||
}
|
||||
} elseif(!$term && $tokens->is("(")) {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
$brackets++;
|
||||
$term = false;
|
||||
} elseif($term && $tokens->is(")")) {
|
||||
if(!$brackets) {
|
||||
break;
|
||||
}
|
||||
$brackets--;
|
||||
$_exp .= $tokens->getAndNext();
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(T_STRING)) {
|
||||
if($tokens->isSpecialVal()) {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
} elseif($tokens->isNext("(")) {
|
||||
$func = $this->_aspect->getModifier($tokens->current());
|
||||
$tokens->next();
|
||||
$_exp .= $func.$this->parseArgs($tokens);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(T_ISSET, T_EMPTY)) {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
if($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
|
||||
$_exp .= $this->parseArgs($tokens);
|
||||
} else {
|
||||
throw new TokenizeException("Unexpected token ".$tokens->getNext().", isset() and empty() accept only variables");
|
||||
}
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(Tokenizer::MACRO_UNARY)) {
|
||||
if(!$tokens->isNext(T_VARIABLE, T_DNUMBER, T_LNUMBER, T_STRING, T_ISSET, T_EMPTY)) {
|
||||
break;
|
||||
}
|
||||
$_exp .= $tokens->getAndNext();
|
||||
$term = 0;
|
||||
} elseif($tokens->is(Tokenizer::MACRO_BINARY)) {
|
||||
if(!$term) {
|
||||
throw new UnexpectedException($tokens);
|
||||
}
|
||||
if($tokens->isLast()) {
|
||||
break;
|
||||
}
|
||||
if($tokens->is(Tokenizer::MACRO_COND)) {
|
||||
if($cond) {
|
||||
break;
|
||||
}
|
||||
$cond = true;
|
||||
} elseif ($tokens->is(Tokenizer::MACRO_BOOLEAN)) {
|
||||
$cond = false;
|
||||
}
|
||||
$_exp .= " ".$tokens->getAndNext()." ";
|
||||
$term = 0;
|
||||
} elseif($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
if($term === 2) {
|
||||
$term = 1;
|
||||
} elseif(!$tokens->isNext(T_VARIABLE)) {
|
||||
break;
|
||||
}
|
||||
$_exp .= $tokens->getAndNext();
|
||||
} elseif($term && !$cond && !$tokens->isLast()) {
|
||||
if($tokens->is(Tokenizer::MACRO_EQUALS) && $term === 2) {
|
||||
if($this->_options & Aspect::DENY_SET_VARS) {
|
||||
throw new \LogicException("Forbidden to set a variable");
|
||||
}
|
||||
$_exp .= ' '.$tokens->getAndNext().' ';
|
||||
$term = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($term === 0) {
|
||||
throw new UnexpectedException($tokens);
|
||||
}
|
||||
if($brackets) {
|
||||
throw new TokenizeException("Brackets don't match");
|
||||
}
|
||||
if($required && $_exp === "") {
|
||||
throw new UnexpectedException($tokens);
|
||||
}
|
||||
return $_exp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse variable
|
||||
* $var.foo[bar]["a"][1+3/$var]|mod:3:"w":$var3|mod3
|
||||
*
|
||||
* @see parseModifier
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param int $deny
|
||||
* @param bool $pure_var
|
||||
* @throws \LogicException
|
||||
* @return string
|
||||
*/
|
||||
public function parseVar(Tokenizer $tokens, $deny = 0, &$pure_var = true) {
|
||||
$var = $tokens->get(T_VARIABLE);
|
||||
$pure_var = true;
|
||||
if(isset(self::$sysvar[ $var ])) {
|
||||
$_var = $this->_parseSystemVar($tokens);
|
||||
} else {
|
||||
$_var = '$tpl["'.ltrim($var,'$').'"]';
|
||||
}
|
||||
$tokens->next();
|
||||
while($t = $tokens->key()) {
|
||||
if($t === "." && !($deny & self::DENY_ARRAY)) {
|
||||
$key = $tokens->getNext();
|
||||
if($tokens->is(T_VARIABLE)) {
|
||||
$key = "[ ".$this->parseVar($tokens, self::DENY_ARRAY)." ]";
|
||||
} elseif($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
if($tokens->isNext("(")) {
|
||||
$key = "[".$this->parseExp($tokens)."]";
|
||||
} else {
|
||||
$key = '["'.$key.'"]';
|
||||
$tokens->next();
|
||||
}
|
||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"')) {
|
||||
$key = "[".$this->parseScalar($tokens, false)."]";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
$_var .= $key;
|
||||
} elseif($t === "[" && !($deny & self::DENY_ARRAY)) {
|
||||
$tokens->next();
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
if($tokens->isNext("(")) {
|
||||
$key = "[".$this->parseExp($tokens)."]";
|
||||
} else {
|
||||
$key = '["'.$tokens->current().'"]';
|
||||
$tokens->next();
|
||||
}
|
||||
} else {
|
||||
$key = "[".$this->parseExp($tokens, true)."]";
|
||||
}
|
||||
$tokens->get("]");
|
||||
$tokens->next();
|
||||
$_var .= $key;
|
||||
} elseif($t === "|" && !($deny & self::DENY_MODS)) {
|
||||
$pure_var = false;
|
||||
return $this->parseModifier($tokens, $_var);
|
||||
} elseif($t === T_OBJECT_OPERATOR) {
|
||||
$prop = $tokens->getNext(T_STRING);
|
||||
if($tokens->isNext("(")) {
|
||||
if($this->_options & Aspect::DENY_METHODS) {
|
||||
throw new \LogicException("Forbidden to call methods");
|
||||
}
|
||||
$pure_var = false;
|
||||
$tokens->next();
|
||||
$_var .= '->'.$prop.$this->parseArgs($tokens);
|
||||
} else {
|
||||
$tokens->next();
|
||||
$_var .= '->'.$prop;
|
||||
}
|
||||
} elseif($t === T_DNUMBER) {
|
||||
$_var .= '['.substr($tokens->getAndNext(), 1).']';
|
||||
} elseif($t === "?") {
|
||||
$pure_var = false;
|
||||
$tokens->next();
|
||||
if($tokens->is(":")) {
|
||||
$tokens->next();
|
||||
return '(empty('.$_var.') ? ('.$this->parseExp($tokens, true).') : '.$_var.')';
|
||||
} else {
|
||||
return '!empty('.$_var.')';
|
||||
}
|
||||
} elseif($t === "!") {
|
||||
$pure_var = false;
|
||||
$tokens->next();
|
||||
return 'isset('.$_var.')';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $_var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse scalar values
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @param bool $allow_mods
|
||||
* @return string
|
||||
* @throws TokenizeException
|
||||
*/
|
||||
public function parseScalar(Tokenizer $tokens, $allow_mods = true) {
|
||||
$_scalar = "";
|
||||
if($token = $tokens->key()) {
|
||||
switch($token) {
|
||||
case T_CONSTANT_ENCAPSED_STRING:
|
||||
case T_LNUMBER:
|
||||
case T_DNUMBER:
|
||||
$_scalar .= $tokens->getAndNext();
|
||||
break;
|
||||
case T_ENCAPSED_AND_WHITESPACE:
|
||||
case '"':
|
||||
$_scalar .= $this->parseSubstr($tokens);
|
||||
break;
|
||||
default:
|
||||
throw new TokenizeException("Unexpected scalar token '".$tokens->current()."'");
|
||||
}
|
||||
if($allow_mods && $tokens->is("|")) {
|
||||
return $this->parseModifier($tokens, $_scalar);
|
||||
}
|
||||
}
|
||||
return $_scalar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse string with or without variable
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @throws UnexpectedException
|
||||
* @return string
|
||||
*/
|
||||
public function parseSubstr(Tokenizer $tokens) {
|
||||
ref: {
|
||||
if($tokens->is('"',"`")) {
|
||||
$p = $tokens->p;
|
||||
$stop = $tokens->current();
|
||||
$_str = '"';
|
||||
$tokens->next();
|
||||
while($t = $tokens->key()) {
|
||||
if($t === T_ENCAPSED_AND_WHITESPACE) {
|
||||
$_str .= $tokens->current();
|
||||
$tokens->next();
|
||||
} elseif($t === T_VARIABLE) {
|
||||
$_str .= '".$tpl["'.substr($tokens->current(), 1).'"]."';
|
||||
$tokens->next();
|
||||
} elseif($t === T_CURLY_OPEN) {
|
||||
$tokens->getNext(T_VARIABLE);
|
||||
$_str .= '".('.$this->parseExp($tokens).')."';
|
||||
} elseif($t === "}") {
|
||||
$tokens->next();
|
||||
} elseif($t === $stop) {
|
||||
$tokens->next();
|
||||
return $_str.'"';
|
||||
} else {
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($more = $this->_getMoreSubstr($stop)) {
|
||||
$tokens->append("}".$more, $p);
|
||||
goto ref;
|
||||
}
|
||||
throw new UnexpectedException($tokens);
|
||||
} elseif($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
return $tokens->getAndNext();
|
||||
} elseif($tokens->is(T_ENCAPSED_AND_WHITESPACE)) {
|
||||
$p = $tokens->p;
|
||||
if($more = $this->_getMoreSubstr($tokens->curr[1][0])) {
|
||||
$tokens->append("}".$more, $p);
|
||||
goto ref;
|
||||
}
|
||||
throw new UnexpectedException($tokens);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function _getMoreSubstr($after) {
|
||||
$end = strpos($this->_src, $after, $this->_pos);
|
||||
$end = strpos($this->_src, "}", $end);
|
||||
if(!$end) {
|
||||
return false;
|
||||
}
|
||||
$fragment = substr($this->_src, $this->_pos, $end - $this->_pos);
|
||||
$this->_pos = $end + 1;
|
||||
return $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse modifiers
|
||||
* |modifier:1:2.3:'string':false:$var:(4+5*$var3)|modifier2:"str {$var+3} ing":$arr.item
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @param $value
|
||||
* @throws \LogicException
|
||||
* @throws \Exception
|
||||
* @return string
|
||||
*/
|
||||
public function parseModifier(Tokenizer $tokens, $value) {
|
||||
while($tokens->is("|")) {
|
||||
$mods = $this->_aspect->getModifier( $tokens->getNext(Tokenizer::MACRO_STRING) );
|
||||
$tokens->next();
|
||||
$args = array();
|
||||
|
||||
while($tokens->is(":")) {
|
||||
$token = $tokens->getNext(Tokenizer::MACRO_SCALAR, T_VARIABLE, '"', Tokenizer::MACRO_STRING, "(", "[");
|
||||
|
||||
if($tokens->is(Tokenizer::MACRO_SCALAR) || $tokens->isSpecialVal()) {
|
||||
$args[] = $token;
|
||||
$tokens->next();
|
||||
} elseif($tokens->is(T_VARIABLE)) {
|
||||
$args[] = $this->parseVar($tokens, self::DENY_MODS);
|
||||
} elseif($tokens->is('"', '`', T_ENCAPSED_AND_WHITESPACE)) {
|
||||
$args[] = $this->parseSubstr($tokens);
|
||||
} elseif($tokens->is('(')) {
|
||||
$args[] = $this->parseExp($tokens);
|
||||
} elseif($tokens->is('[')) {
|
||||
$args[] = $this->parseArray($tokens);
|
||||
} elseif($tokens->is(T_STRING) && $tokens->isNext('(')) {
|
||||
$args[] = $tokens->getAndNext().$this->parseArgs($tokens);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($args) {
|
||||
$value = $mods.'('.$value.', '.implode(", ", $args).')';
|
||||
} else {
|
||||
$value = $mods.'('.$value.')';
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse array
|
||||
* [1, 2.3, 5+7/$var, 'string', "str {$var+3} ing", $var2, []]
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @throws UnexpectedException
|
||||
* @return string
|
||||
*/
|
||||
public function parseArray(Tokenizer $tokens) {
|
||||
if($tokens->is("[")) {
|
||||
$_arr = "array(";
|
||||
$key = $val = false;
|
||||
$tokens->next();
|
||||
while($tokens->valid()) {
|
||||
if($tokens->is(',') && $val) {
|
||||
$key = true;
|
||||
$val = false;
|
||||
$_arr .= $tokens->getAndNext().' ';
|
||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, T_VARIABLE, T_STRING, T_EMPTY, T_ISSET, "(") && !$val) {
|
||||
$_arr .= $this->parseExp($tokens, true);
|
||||
$key = false;
|
||||
$val = true;
|
||||
} elseif($tokens->is('"') && !$val) {
|
||||
$_arr .= $this->parseSubstr($tokens);
|
||||
$key = false;
|
||||
$val = true;
|
||||
} elseif($tokens->is(T_DOUBLE_ARROW) && $val) {
|
||||
$_arr .= ' '.$tokens->getAndNext().' ';
|
||||
$key = true;
|
||||
$val = false;
|
||||
} elseif(!$val && $tokens->is('[')) {
|
||||
$_arr .= $this->parseArray($tokens);
|
||||
$key = false;
|
||||
$val = true;
|
||||
} elseif($tokens->is(']') && !$key) {
|
||||
$tokens->next();
|
||||
return $_arr.')';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnexpectedException($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse system variable, like $aspect, $smarty
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @throws \LogicException
|
||||
* @return mixed|string
|
||||
*/
|
||||
private function _parseSystemVar(Tokenizer $tokens) {
|
||||
$tokens->getNext(".");
|
||||
$key = $tokens->getNext(T_STRING, T_CONST);
|
||||
switch($key) {
|
||||
case 'get': return '$_GET';
|
||||
case 'post': return '$_POST';
|
||||
case 'cookies': return '$_COOKIES';
|
||||
case 'session': return '$_SESSION';
|
||||
case 'request': return '$_REQUEST';
|
||||
case 'now': return 'time()';
|
||||
case 'line': return $this->_line;
|
||||
case 'tpl_name': return '$tpl->getName()';
|
||||
case 'const':
|
||||
$tokens->getNext(".");
|
||||
return $tokens->getNext(T_STRING);
|
||||
default:
|
||||
throw new \LogicException("Unexpected key '".$tokens->current()."' in system variable");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse argument list
|
||||
* (1 + 2.3, 'string', $var, [2,4])
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @throws TokenizeException
|
||||
* @return string
|
||||
*/
|
||||
public function parseArgs(Tokenizer $tokens) {
|
||||
$_args = "(";
|
||||
$tokens->next();
|
||||
$arg = $colon = false;
|
||||
while($tokens->valid()) {
|
||||
if(!$arg && $tokens->is(T_VARIABLE, T_STRING, "(", Tokenizer::MACRO_SCALAR, '"', Tokenizer::MACRO_UNARY, Tokenizer::MACRO_INCDEC)) {
|
||||
$_args .= $this->parseExp($tokens, true);
|
||||
$arg = true;
|
||||
$colon = false;
|
||||
} elseif(!$arg && $tokens->is('[')) {
|
||||
$_args .= $this->parseArray($tokens);
|
||||
$arg = true;
|
||||
$colon = false;
|
||||
} elseif($arg && $tokens->is(',')) {
|
||||
$_args .= $tokens->getAndNext().' ';
|
||||
$arg = false;
|
||||
$colon = true;
|
||||
} elseif(!$colon && $tokens->is(')')) {
|
||||
$tokens->next();
|
||||
return $_args.')';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new TokenizeException("Unexpected token '".$tokens->current()."' in argument list");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse parameters as $key=$value
|
||||
* param1=$var param2=3 ...
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param array $defaults
|
||||
* @throws \Exception
|
||||
* @return array
|
||||
*/
|
||||
public function parseParams(Tokenizer $tokens, array $defaults = null) {
|
||||
$params = array();
|
||||
while($tokens->valid()) {
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
$key = $tokens->getAndNext();
|
||||
if($defaults && !isset($defaults[$key])) {
|
||||
throw new \Exception("Unknown parameter '$key'");
|
||||
}
|
||||
if($tokens->is("=")) {
|
||||
$tokens->next();
|
||||
$params[ $key ] = $this->parseExp($tokens);
|
||||
} else {
|
||||
$params[ $key ] = true;
|
||||
$params[] = "'".$key."'";
|
||||
}
|
||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_VARIABLE, "[", '(')) {
|
||||
$params[] = $this->parseExp($tokens);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($defaults) {
|
||||
$params += $defaults;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CompileException extends \ErrorException {}
|
||||
class SecurityException extends CompileException {}
|
731
src/Aspect/Tokenizer.php
Normal file
731
src/Aspect/Tokenizer.php
Normal file
@ -0,0 +1,731 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
|
||||
defined('T_INSTEADOF') || define('T_INSTEADOF', 341);
|
||||
defined('T_TRAIT') || define('T_TRAIT', 355);
|
||||
defined('T_TRAIT_C') || define('T_TRAIT_C', 365);
|
||||
|
||||
/**
|
||||
* This iterator cannot be rewinded.
|
||||
* Each token have structure
|
||||
* - Token (constant T_* or text)
|
||||
* - Token name (textual representation of the token)
|
||||
* - Whitespace (whitespace symbols after token)
|
||||
* - Line number of the token
|
||||
*
|
||||
* @see http://php.net/tokenizer
|
||||
* @property array $prev the previous token
|
||||
* @property array $curr the current token
|
||||
* @property array $next the next token
|
||||
*/
|
||||
class Tokenizer {
|
||||
const TOKEN = 0;
|
||||
const TEXT = 1;
|
||||
const WHITESPACE = 2;
|
||||
const LINE = 3;
|
||||
|
||||
/**
|
||||
* Strip whitespace tokens (default)
|
||||
*/
|
||||
const DECODE_TEXT = 0;
|
||||
/**
|
||||
* Strip whitespace tokens, exclude newlines
|
||||
*/
|
||||
const DECODE_NEW_LINES = 1;
|
||||
/**
|
||||
* Allow all whitespace tokens
|
||||
*/
|
||||
const DECODE_WHITESPACES = 2;
|
||||
/**
|
||||
* Decode mask
|
||||
*/
|
||||
const DECODE = 3;
|
||||
|
||||
/**
|
||||
* Strip duplicate whitespaces. For example \n\n => \n
|
||||
*/
|
||||
const FILTER_DUP_WHITESPACES = 128;
|
||||
/**
|
||||
* Filter mask
|
||||
*/
|
||||
const FILTERS = 4080;
|
||||
|
||||
/**
|
||||
* Some text value: foo, bar, new, class ...
|
||||
*/
|
||||
const MACRO_STRING = 1000;
|
||||
/**
|
||||
* Unary operation: ~, !, ^
|
||||
*/
|
||||
const MACRO_UNARY = 1001;
|
||||
/**
|
||||
* Binary operation (operation between two values): +, -, *, /, &&, or , ||, >=, !=, ...
|
||||
*/
|
||||
const MACRO_BINARY = 1002;
|
||||
/**
|
||||
* Equal operation
|
||||
*/
|
||||
const MACRO_EQUALS = 1003;
|
||||
/**
|
||||
* Scalar values (such as int, float, escaped strings): 2, 0.5, "foo", 'bar\'s'
|
||||
*/
|
||||
const MACRO_SCALAR = 1004;
|
||||
/**
|
||||
* Increment or decrement: ++ --
|
||||
*/
|
||||
const MACRO_INCDEC = 1005;
|
||||
/**
|
||||
* Boolean operations: &&, ||, or, xor
|
||||
*/
|
||||
const MACRO_BOOLEAN = 1006;
|
||||
/**
|
||||
* Math operation
|
||||
*/
|
||||
const MACRO_MATH = 1007;
|
||||
/**
|
||||
* Condition operation
|
||||
*/
|
||||
const MACRO_COND = 1008;
|
||||
|
||||
public $tokens;
|
||||
public $p = 0;
|
||||
private $_max = 0;
|
||||
private $_last_no = 0;
|
||||
|
||||
/**
|
||||
* @see http://docs.php.net/manual/en/tokens.php
|
||||
* @var array groups of tokens
|
||||
*/
|
||||
private static $_macros = array(
|
||||
self::MACRO_STRING => array(
|
||||
\T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1, \T_BREAK => 1, \T_CASE => 1,
|
||||
\T_CATCH => 1, \T_CLASS => 1, \T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
|
||||
\T_DECLARE => 1, \T_DEFAULT => 1, \T_DIR => 1, \T_DO => 1, \T_ECHO => 1, \T_ELSE => 1,
|
||||
\T_ELSEIF => 1, \T_EMPTY => 1, \T_ENDDECLARE => 1, \T_ENDFOR => 1, \T_ENDFOREACH => 1, \T_ENDIF => 1,
|
||||
\T_ENDSWITCH => 1, \T_ENDWHILE => 1, \T_EVAL => 1, \T_EXIT => 1, \T_EXTENDS => 1, \T_FILE => 1,
|
||||
\T_FINAL => 1, \T_FOR => 1, \T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
|
||||
\T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1, \T_INCLUDE => 1, \T_INCLUDE_ONCE => 1,
|
||||
\T_INSTANCEOF => 1, \T_INSTEADOF => 1, \T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
|
||||
\T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1, \T_NAMESPACE => 1, \T_NS_C => 1,
|
||||
\T_NEW => 1, \T_PRINT => 1, \T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
|
||||
\T_REQUIRE_ONCE => 1,\T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1, \T_SWITCH => 1, \T_THROW => 1,
|
||||
\T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_UNSET => 1, \T_VAR => 1,
|
||||
\T_WHILE => 1
|
||||
),
|
||||
self::MACRO_INCDEC => array(
|
||||
\T_INC => 1, \T_DEC => 1
|
||||
),
|
||||
self::MACRO_UNARY => array(
|
||||
"!" => 1, "~" => 1, "-" => 1
|
||||
),
|
||||
self::MACRO_BINARY => array(
|
||||
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_IS_GREATER_OR_EQUAL => 1, \T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1,
|
||||
\T_IS_NOT_EQUAL => 1,\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
|
||||
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1, \T_SR => 1,
|
||||
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
|
||||
),
|
||||
self::MACRO_BOOLEAN => array(
|
||||
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1
|
||||
),
|
||||
self::MACRO_MATH => array(
|
||||
"+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1
|
||||
),
|
||||
self::MACRO_COND => array(
|
||||
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1, "<" => 1, \T_SL => 1, \T_SR => 1,
|
||||
\T_IS_NOT_EQUAL => 1,\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
|
||||
),
|
||||
self::MACRO_EQUALS => array(
|
||||
\T_AND_EQUAL => 1, \T_CONCAT_EQUAL => 1,\T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1, \T_MOD_EQUAL => 1,
|
||||
\T_MUL_EQUAL => 1, \T_OR_EQUAL => 1, \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
|
||||
\T_XOR_EQUAL => 1, '=' => 1
|
||||
),
|
||||
self::MACRO_SCALAR => array(
|
||||
\T_LNUMBER => 1, \T_DNUMBER => 1, \T_CONSTANT_ENCAPSED_STRING => 1
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Special tokens
|
||||
* @var array
|
||||
*/
|
||||
private static $spec = array(
|
||||
'true' => 1, 'false' => 1, 'null' => 1, 'TRUE' => 1, 'FALSE' => 1, 'NULL' => 1
|
||||
);
|
||||
|
||||
/**
|
||||
* Translate expression to tokens list.
|
||||
*
|
||||
* @static
|
||||
* @param string $query
|
||||
* @param int $options one of DECODE_*, FILTER_* constants
|
||||
* @return array
|
||||
*/
|
||||
public static function decode($query, $options = 0) {
|
||||
$tokens = array(-1 => array(\T_WHITESPACE, '', '', 1));
|
||||
$_tokens = token_get_all("<?php ".$query);
|
||||
$line = 1;
|
||||
$decode = $options & self::DECODE;
|
||||
array_shift($_tokens);
|
||||
$i = 0;
|
||||
foreach($_tokens as &$token) {
|
||||
if(is_string($token)) {
|
||||
$tokens[] = array(
|
||||
$token,
|
||||
$token,
|
||||
"",
|
||||
$line,
|
||||
);
|
||||
$i++;
|
||||
} elseif ($token[0] === \T_WHITESPACE) {
|
||||
if(!$decode) {
|
||||
$tokens[$i-1][2] = $token[1];
|
||||
} elseif($decode == 1) {
|
||||
if(strpos($token[1], "\n") !== false) {
|
||||
$frags = explode("\n", $token[1]);
|
||||
$ws = array_shift($frags);
|
||||
if($ws) {
|
||||
$tokens[$i-1][2] .= $ws;
|
||||
}
|
||||
foreach($frags as $frag) {
|
||||
$tokens[] = array(
|
||||
\T_WHITESPACE,
|
||||
"\n",
|
||||
$frag,
|
||||
$line = $token[2],
|
||||
);
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
$tokens[$i-1][2] .= $token[1];
|
||||
}
|
||||
} else {
|
||||
$tokens[] = array(
|
||||
$token[0],
|
||||
$token[1],
|
||||
"",
|
||||
$line = $token[2],
|
||||
);
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
$tokens[] = array(
|
||||
$token[0],
|
||||
$token[1],
|
||||
"",
|
||||
$line = $token[2],
|
||||
);
|
||||
$i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if($options & self::FILTER_DUP_WHITESPACES) {
|
||||
$prev = null;
|
||||
foreach($tokens as &$token) {
|
||||
if($token[0] === T_WHITESPACE && $prev && $prev[0] === T_WHITESPACE) {
|
||||
$prev = false;
|
||||
}
|
||||
|
||||
$prev = &$token;
|
||||
}
|
||||
$tokens = array_values(array_filter($tokens));
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function __construct($query, $decode = 0) {
|
||||
$this->tokens = self::decode($query, $decode);
|
||||
unset($this->tokens[-1]);
|
||||
$this->_max = count($this->tokens) - 1;
|
||||
$this->_last_no = $this->tokens[$this->_max][3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filter callback. Token may be changed by reference or skipped if callback return false.
|
||||
*
|
||||
* @param $callback
|
||||
*/
|
||||
public function filter(\Closure $callback) {
|
||||
$tokens = array();
|
||||
foreach($this->tokens as $token) {
|
||||
if($callback($token) !== false) {
|
||||
$tokens[] = $token;
|
||||
}
|
||||
}
|
||||
$this->tokens = $tokens;
|
||||
$this->_max = count($this->tokens) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
*
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current() {
|
||||
return $this->curr[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
*
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return Tokenizer
|
||||
*/
|
||||
public function next() {
|
||||
if($this->p > $this->_max) {
|
||||
return $this;
|
||||
}
|
||||
$this->p++;
|
||||
unset($this->prev, $this->curr, $this->next);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check token type. If token type is one of expected types return true. Otherwise return false
|
||||
*
|
||||
* @param array $expects
|
||||
* @param string|int $token
|
||||
* @return bool
|
||||
*/
|
||||
private function _valid($expects, $token) {
|
||||
foreach($expects as $expect) {
|
||||
if(is_string($expect) || $expect < 1000) {
|
||||
if($expect === $token) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
||||
if(isset(self::$_macros[ $expect ][ $token ])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the next token is a valid one, move the position of cursor one step forward. Otherwise throws an exception.
|
||||
* @param array $tokens
|
||||
* @throws TokenizeException
|
||||
* @return mixed
|
||||
*/
|
||||
public function _next($tokens) {
|
||||
$this->next();
|
||||
if(!$this->curr) {
|
||||
throw new TokenizeException("Unexpected end of expression");
|
||||
}
|
||||
if($tokens) {
|
||||
if($this->_valid($tokens, $this->key())) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if(count($tokens) == 1 && is_string($tokens[0])) {
|
||||
$expect = ", expect '".$tokens[0]."'";
|
||||
} else {
|
||||
$expect = "";
|
||||
}
|
||||
throw new TokenizeException("Unexpected token '".$this->current()."'$expect");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch next specified token or throw an exception
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNext(/*int|string $token1, int|string $token2, ... */) {
|
||||
$this->_next(func_get_args());
|
||||
return $this->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate tokens from the current one to one of the specified and returns the string.
|
||||
* @param string|int $token
|
||||
* @param ...
|
||||
* @return string
|
||||
*/
|
||||
public function getStringUntil($token/*, $token2 */) {
|
||||
$str = '';
|
||||
while($this->valid() && !$this->_valid(func_get_args(), $this->curr[0])) {
|
||||
$str .= $this->curr[1].$this->curr[2];
|
||||
$this->next();
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return substring. This method doesn't move pointer.
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @return string
|
||||
*/
|
||||
public function getSubstr($offset, $limit = 0) {
|
||||
$str = '';
|
||||
if(!$limit) {
|
||||
$limit = $this->_max;
|
||||
} else {
|
||||
$limit += $offset;
|
||||
}
|
||||
for($i = $offset; $i <= $limit; $i++){
|
||||
$str .= $this->tokens[$i][1].$this->tokens[$i][2];
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return token and move pointer
|
||||
* @return mixed
|
||||
* @throws UnexpectedException
|
||||
*/
|
||||
public function getAndNext() {
|
||||
if($this->curr) {
|
||||
$cur = $this->curr[1];
|
||||
$this->next();
|
||||
} else {
|
||||
throw new UnexpectedException($this, func_get_args());
|
||||
}
|
||||
|
||||
return $cur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the next token is one of the specified.
|
||||
* @param $token1
|
||||
* @return bool
|
||||
*/
|
||||
public function isNext($token1/*, ...*/) {
|
||||
return $this->next && $this->_valid(func_get_args(), $this->next[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current token is one of the specified.
|
||||
* @param $token1
|
||||
* @return bool
|
||||
*/
|
||||
public function is($token1/*, ...*/) {
|
||||
return $this->curr && $this->_valid(func_get_args(), $this->curr[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the previous token is one of the specified.
|
||||
* @param $token1
|
||||
* @return bool
|
||||
*/
|
||||
public function isPrev($token1/*, ...*/) {
|
||||
return $this->prev && $this->_valid(func_get_args(), $this->prev[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified token
|
||||
*
|
||||
* @param string|int $token1
|
||||
* @throws UnexpectedException
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($token1 /*, $token2 ...*/) {
|
||||
if($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
|
||||
return $this->curr[1];
|
||||
} else {
|
||||
throw new UnexpectedException($this, func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step back
|
||||
* @return Tokenizer
|
||||
*/
|
||||
public function back() {
|
||||
if($this->p === 0) {
|
||||
return $this;
|
||||
}
|
||||
$this->p--;
|
||||
unset($this->prev, $this->curr, $this->next);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load properties
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key) {
|
||||
switch($key) {
|
||||
case 'curr':
|
||||
return $this->curr = ($this->p <= $this->_max) ? $this->tokens[$this->p] : null;
|
||||
case 'next':
|
||||
return $this->next = ($this->p + 1 <= $this->_max) ? $this->tokens[$this->p + 1] : null;
|
||||
case 'prev':
|
||||
return $this->prev = $this->p ? $this->tokens[$this->p - 1] : null;
|
||||
default:
|
||||
return $this->$key = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key() {
|
||||
return $this->curr ? $this->curr[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid() {
|
||||
return (bool)$this->curr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element. Disabled.
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function rewind() {}
|
||||
|
||||
/**
|
||||
* Get token name
|
||||
* @static
|
||||
* @param int|string $token
|
||||
* @return string
|
||||
*/
|
||||
public static function getName($token) {
|
||||
if(is_string($token)) {
|
||||
return $token;
|
||||
} elseif(is_integer($token)) {
|
||||
return token_name($token);
|
||||
} elseif(is_array($token)) {
|
||||
return token_name($token[0]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whitespace of current token
|
||||
* @return null
|
||||
*/
|
||||
public function getWhiteSpace() {
|
||||
if($this->curr) {
|
||||
return $this->curr[2];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip specific token or throw an exception
|
||||
*
|
||||
* @throws UnexpectedException
|
||||
* @return Tokenizer
|
||||
*/
|
||||
public function skip(/*$token1, $token2, ...*/) {
|
||||
if(func_num_args()) {
|
||||
if($this->_valid(func_get_args(), $this->curr[0])) {
|
||||
$this->next();
|
||||
return $this;
|
||||
} else {
|
||||
throw new UnexpectedException($this, func_get_args());
|
||||
}
|
||||
} else {
|
||||
$this->next();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip specific token or do nothing
|
||||
*
|
||||
* @param int|string $token1
|
||||
* @return Tokenizer
|
||||
*/
|
||||
public function skipIf($token1/*, $token2, ...*/) {
|
||||
if($this->_valid(func_get_args(), $this->curr[0])) {
|
||||
$this->next();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check current token's type
|
||||
*
|
||||
* @param int|string $token1
|
||||
* @return Tokenizer
|
||||
* @throws UnexpectedException
|
||||
*/
|
||||
public function need($token1/*, $token2, ...*/) {
|
||||
if($this->_valid(func_get_args(), $this->curr[0])) {
|
||||
return $this;
|
||||
} else {
|
||||
throw new UnexpectedException($this, func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* The return value is cast to an integer.
|
||||
*/
|
||||
public function count() {
|
||||
return $this->_max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tokens near current token
|
||||
* @param int $before count tokens before current token
|
||||
* @param int $after count tokens after current token
|
||||
* @return array
|
||||
*/
|
||||
public function getSnippet($before = 0, $after = 0) {
|
||||
$from = 0;
|
||||
$to = $this->p;
|
||||
if($before > 0) {
|
||||
if($before > $this->p) {
|
||||
$from = $this->p;
|
||||
} else {
|
||||
$from = $before;
|
||||
}
|
||||
} elseif($before < 0) {
|
||||
$from = $this->p + $before;
|
||||
if($from < 0) {
|
||||
$from = 0;
|
||||
}
|
||||
}
|
||||
if($after > 0) {
|
||||
$to = $this->p + $after;
|
||||
if($to > $this->_max) {
|
||||
$to = $this->_max;
|
||||
}
|
||||
} elseif($after < 0) {
|
||||
$to = $this->_max + $after;
|
||||
if($to < $this->p) {
|
||||
$to = $this->p;
|
||||
}
|
||||
} elseif($this->p > $this->_max) {
|
||||
$to = $this->_max;
|
||||
}
|
||||
$code = array();
|
||||
for($i=$from; $i<=$to; $i++) {
|
||||
$code[] = $this->tokens[ $i ];
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return snippet as string
|
||||
* @param int $before
|
||||
* @param int $after
|
||||
* @return string
|
||||
*/
|
||||
public function getSnippetAsString($before = 0, $after = 0) {
|
||||
$str = "";
|
||||
foreach($this->getSnippet($before, $after) as $token) {
|
||||
$str .= $token[1].$token[2];
|
||||
}
|
||||
return trim(str_replace("\n", '↵', $str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current is special value: true, TRUE, false, FALSE, null, NULL
|
||||
* @return bool
|
||||
*/
|
||||
public function isSpecialVal() {
|
||||
return isset(self::$spec[$this->current()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token is last
|
||||
* @return bool
|
||||
*/
|
||||
public function isLast() {
|
||||
return $this->p === $this->_max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move pointer to the end
|
||||
*/
|
||||
public function end() {
|
||||
$this->p = $this->_max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return line number of the current token
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLine() {
|
||||
return $this->curr ? $this->curr[3] : $this->_last_no;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump (append) token into variable
|
||||
*
|
||||
* @param mixed $var
|
||||
* @param bool $whitespace include whitespace
|
||||
*/
|
||||
/*public function appendTo(&$var, $whitespace = false) {
|
||||
$var .= $this->curr[1];
|
||||
if($whitespace && $this->curr[2]) {
|
||||
$var .= $this->curr[2];
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Parse code and append tokens. This method move pointer to offset.
|
||||
* @param string $code
|
||||
* @param int $offset
|
||||
* @return Tokenizer
|
||||
*/
|
||||
public function append($code, $offset = -1) {
|
||||
if($offset != -1) {
|
||||
$code = $this->getSubstr($offset).$code;
|
||||
if($this->p > $offset) {
|
||||
$this->p = $offset;
|
||||
}
|
||||
|
||||
$this->tokens = array_slice($this->tokens, 0, $offset);
|
||||
}
|
||||
$tokens = self::decode($code);
|
||||
unset($tokens[-1], $this->prev, $this->curr, $this->next);
|
||||
$this->tokens = array_merge($this->tokens, $tokens);
|
||||
$this->_max = count($this->tokens) - 1;
|
||||
$this->_last_no = $this->tokens[$this->_max][3];
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize error
|
||||
*/
|
||||
class TokenizeException extends \RuntimeException {}
|
||||
|
||||
/**
|
||||
* Unexpected token
|
||||
*/
|
||||
class UnexpectedException extends TokenizeException {
|
||||
public function __construct(Tokenizer $tokens, $expect = null) {
|
||||
if($expect && count($expect) == 1 && is_string($expect[0])) {
|
||||
$expect = ", expect '".$expect[0]."'";
|
||||
} else {
|
||||
$expect = "";
|
||||
}
|
||||
if(!$tokens->curr) {
|
||||
$this->message = "Unexpected end of expression$expect";
|
||||
} elseif($tokens->curr[1] === "\n") {
|
||||
$this->message = "Unexpected new line$expect";
|
||||
} elseif($tokens->curr[0] === T_WHITESPACE) {
|
||||
$this->message = "Unexpected whitespace$expect";
|
||||
} else {
|
||||
$this->message = "Unexpected token '".$tokens->current()."'$expect";
|
||||
}
|
||||
}
|
||||
};
|
9
tests/autoload.php
Normal file
9
tests/autoload.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__."/../vendor/autoload.php";
|
||||
|
||||
|
||||
|
||||
define('ASPECT_RESOURCES', __DIR__."/resources");
|
||||
|
||||
require_once ASPECT_RESOURCES."/actions.php";
|
50
tests/cases/Aspect/RenderTest.php
Normal file
50
tests/cases/Aspect/RenderTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
use Aspect,
|
||||
Aspect\Render;
|
||||
|
||||
class RenderTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* @var Render
|
||||
*/
|
||||
public static $render;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
self::$render = new Render("render.tpl", function ($tpl) {
|
||||
echo "It is render function ".$tpl["render"];
|
||||
});
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$r = new Render("test.render.tpl", function () {
|
||||
echo "Test render";
|
||||
});
|
||||
$this->assertSame("Test render", $r->fetch(array()));
|
||||
}
|
||||
|
||||
public function testDisplay() {
|
||||
ob_start();
|
||||
self::$render->display(array("render" => "display"));
|
||||
$out = ob_get_clean();
|
||||
$this->assertSame("It is render function display", $out);
|
||||
}
|
||||
|
||||
public function testFetch() {
|
||||
$this->assertSame("It is render function fetch", self::$render->fetch(array("render" => "fetch")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
* @expectedExceptionMessage template error
|
||||
*/
|
||||
public function testFetchException() {
|
||||
$render = new Render("render.tpl", function ($tpl) {
|
||||
echo "error";
|
||||
throw new \RuntimeException("template error");
|
||||
});
|
||||
$render->fetch(array());
|
||||
}
|
||||
|
||||
}
|
||||
|
709
tests/cases/Aspect/TemplateTest.php
Normal file
709
tests/cases/Aspect/TemplateTest.php
Normal file
@ -0,0 +1,709 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
use Aspect\Template,
|
||||
Aspect,
|
||||
Aspect\Render;
|
||||
|
||||
class TemplateTest extends \PHPUnit_Framework_TestCase {
|
||||
/**
|
||||
* @var Aspect
|
||||
*/
|
||||
public static $aspect;
|
||||
|
||||
public function _testSandbox() {
|
||||
//self::$aspect->storeTemplate(self::$aspect->compileCode("<b>Welcome, {block username}{/block} ({block name='email'}{/block})</b>", "parent.tpl"));
|
||||
/*self::$aspect->storeTemplate(new Render("parent.tpl", function () {
|
||||
echo "<b>Welcome, {block name='username'}{/block} ({block name='usermail'}{/block})</b>";
|
||||
}));*/
|
||||
try {
|
||||
drop(self::$aspect->compileCode(<<<EOT
|
||||
{if \$data?}
|
||||
echo lot
|
||||
{/if}
|
||||
<doc></doc>
|
||||
literal: function () { return 1; } end
|
||||
asdasd
|
||||
EOT
|
||||
)->fetch([]));
|
||||
} catch(\Exception $e) {
|
||||
echo $e->getTraceAsString();
|
||||
drop($e->getMessage(), $e->getFile().":".$e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
self::$aspect = new Aspect();
|
||||
self::$aspect->storeTemplate(new Render("welcome.tpl", function ($tpl) {
|
||||
echo "<b>Welcome, ".$tpl["username"]." (".$tpl["email"].")</b>";
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public static function providerVars() {
|
||||
$a = array("a" => "World");
|
||||
$obj = new \stdClass;
|
||||
$obj->name = "Object";
|
||||
$obj->c = "c";
|
||||
$b = array("b" => array("c" => "Username", "c_char" => "c", "mcp" => "Master", 'm{$c}p' => "Unknown", 'obj' => $obj), "c" => "c");
|
||||
$c = array_replace_recursive($b, array("b" => array(3 => $b["b"], 4 => "Mister")));
|
||||
return array(
|
||||
array('hello, {$a}!', $a, 'hello, World!'),
|
||||
array('hello, {$b.c}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b."c"}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b.\'c\'}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[c]}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b["c"]}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[\'c\']}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[ $b.c_char ]}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[ "$c" ]}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b.$c}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b."$c"}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b."{$c}"}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[ "{$c}" ]}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[ "mcp" ]}!', $b, 'hello, Master!'),
|
||||
array('hello, {$b[ "m{$c}p" ]}!', $b, 'hello, Master!'),
|
||||
array('hello, {$b."m{$c}p"}!', $b, 'hello, Master!'),
|
||||
array('hello, {$b[ "m{$b.c_char}p" ]}!',
|
||||
$b, 'hello, Master!'),
|
||||
array('hello, {$b[ \'m{$c}p\' ]}!', $b, 'hello, Unknown!'),
|
||||
array('hello, {$b.4}!', $c, 'hello, Mister!'),
|
||||
array('hello, {$b[4]}!', $c, 'hello, Mister!'),
|
||||
array('hello, {$b.3.c}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b.3.$c}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b.3[$b.c_char]}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b[3].c}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b[2+1].c}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b[9/3].c}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b[3].$c}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b[3][$b.c_char]}!', $c, 'hello, Username!'),
|
||||
array('hello, {$b[ "m{$b.c_char}p" ]} and {$b.3[$b.c_char]}!',
|
||||
$c, 'hello, Master and Username!'),
|
||||
array('hello, {$b.obj->name}!', $c, 'hello, Object!'),
|
||||
array('hello, {$b[obj]->name}!', $c, 'hello, Object!'),
|
||||
array('hello, {$b["obj"]->name}!', $c, 'hello, Object!'),
|
||||
array('hello, {$b."obj"->name}!', $c, 'hello, Object!'),
|
||||
array('hello, {$b.obj->name|upper}!',
|
||||
$c, 'hello, OBJECT!'),
|
||||
array('hello, {$b[ $b.obj->c ]}!', $b, 'hello, Username!'),
|
||||
array('hello, {$b[ "{$b.obj->c}" ]}!',
|
||||
$b, 'hello, Username!'),
|
||||
array('hello, {"World"}!', $a, 'hello, World!'),
|
||||
//array('hello, {"W{$a}d"}!', $a, 'hello, WWorldd!'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerVarsInvalid() {
|
||||
return array(
|
||||
array('hello, {$a.}!', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('hello, {$b[c}!', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('hello, {$b.c]}!', 'Aspect\CompileException', "Unexpected token ']'"),
|
||||
array('hello, {$b[ ]}!', 'Aspect\CompileException', "Unexpected token ']'"),
|
||||
array('hello, {$b[9/].c}!', 'Aspect\CompileException', "Unexpected token ']'"),
|
||||
array('hello, {$b[3]$c}!', 'Aspect\CompileException', "Unexpected token '\$c'"),
|
||||
array('hello, {$b[3]c}!', 'Aspect\CompileException', "Unexpected token 'c'"),
|
||||
array('hello, {$b.obj->valid()}!', 'Aspect\SecurityException', "Forbidden to call methods", Aspect::DENY_METHODS),
|
||||
array('hello, {$b = 5}!', 'Aspect\SecurityException', "Forbidden to set a variable", Aspect::DENY_SET_VARS),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerModifiers() {
|
||||
$b = array(
|
||||
"a" => "World",
|
||||
"b" => array(
|
||||
"c" => "Username",
|
||||
),
|
||||
"c" => "c",
|
||||
"lorem" => "Lorem ipsum dolor sit amet",
|
||||
"next" => " next -->",
|
||||
"rescue" => "Chip & Dale",
|
||||
"rescue_html" => "Chip & Dale",
|
||||
"rescue_url" => "Chip+%26+Dale",
|
||||
"date" => "26-07-2012",
|
||||
"time" => 1343323616,
|
||||
"tags" => "my name is <b>Legion</b>"
|
||||
);
|
||||
return array(
|
||||
array('hello, {$a|upper}!', $b, 'hello, WORLD!'),
|
||||
array('hello, {$b.c|upper}!', $b, 'hello, USERNAME!'),
|
||||
array('hello, {$b."c"|upper}!', $b, 'hello, USERNAME!'),
|
||||
array('hello, {$b["C"|lower]|upper}!', $b, 'hello, USERNAME!'),
|
||||
array('Mod: {$lorem|truncate:16}!', $b, 'Mod: Lorem ipsum...!'),
|
||||
array('Mod: {$lorem|truncate:max(4,16)}!', $b, 'Mod: Lorem ipsum...!'),
|
||||
array('Mod: {$lorem|truncate:16|upper}!', $b, 'Mod: LOREM IPSUM...!'),
|
||||
array('Mod: {$lorem|truncate:16:"->"}!', $b, 'Mod: Lorem ipsum->!'),
|
||||
array('Mod: {$lorem|truncate:20:$next}!', $b, 'Mod: Lorem ipsum next -->!'),
|
||||
array('Mod: {$lorem|truncate:20:$next|upper}!', $b, 'Mod: LOREM IPSUM NEXT -->!'),
|
||||
array('Mod: {$lorem|truncate:(20-5):$next}!', $b, 'Mod: Lorem next -->!'),
|
||||
array('Mod: {$lorem|truncate:20:($next|upper)}!',
|
||||
$b, 'Mod: Lorem ipsum NEXT -->!'),
|
||||
array('Mod: {$lorem|truncate:max(4,20):($next|upper)}!',
|
||||
$b, 'Mod: Lorem ipsum NEXT -->!'),
|
||||
array('Mod: {$rescue|escape}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$rescue|escape:"html"}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$rescue|escape:"url"}!', $b, 'Mod: Chip+%26+Dale!'),
|
||||
array('Mod: {$rescue|escape:"unknown"}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$rescue_html|unescape}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$rescue_html|unescape:"html"}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$rescue_url|unescape:"url"}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$rescue|unescape:"unknown"}!', $b, 'Mod: Chip & Dale!'),
|
||||
array('Mod: {$time|date_format:"%Y %m %d"}!', $b, 'Mod: 2012 07 26!'),
|
||||
array('Mod: {$date|date_format:"%Y %m %d"}!', $b, 'Mod: 2012 07 26!'),
|
||||
array('Mod: {$time|date:"Y m d"}!', $b, 'Mod: 2012 07 26!'),
|
||||
array('Mod: {$date|date:"Y m d"}!', $b, 'Mod: 2012 07 26!'),
|
||||
array('Mod: {$tags|strip_tags}!', $b, 'Mod: my name is Legion!'),
|
||||
array('Mod: {$b.c|json_encode}!', $b, 'Mod: "Username"!'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerModifiersInvalid() {
|
||||
return array(
|
||||
array('Mod: {$lorem|}!', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Mod: {$lorem|json_encode}!', 'Aspect\CompileException', "Modifier json_encode not found", Aspect::DENY_INLINE_FUNCS),
|
||||
array('Mod: {$lorem|my_encode}!', 'Aspect\CompileException', "Modifier my_encode not found"),
|
||||
array('Mod: {$lorem|truncate:}!', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Mod: {$lorem|truncate:abs}!', 'Aspect\CompileException', "Unexpected token 'abs'"),
|
||||
array('Mod: {$lorem|truncate:80|}!', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerExpressions() {
|
||||
$b = array(
|
||||
"x" => $x = 9,
|
||||
"y" => 27,
|
||||
"z" => 8.9,
|
||||
"k" => array("i" => "")
|
||||
);
|
||||
return array(
|
||||
array('Exp: {'.$x.'+$y} result', $b, 'Exp: 36 result'),
|
||||
array('Exp: {$y/'.$x.'} result', $b, 'Exp: 3 result'),
|
||||
array('Exp: {$y-'.$x.'} result', $b, 'Exp: 18 result'),
|
||||
array('Exp: {'.$x.'*$y} result', $b, 'Exp: 243 result'),
|
||||
array('Exp: {$y^'.$x.'} result', $b, 'Exp: 18 result'),
|
||||
|
||||
array('Exp: {$x+$y} result', $b, 'Exp: 36 result'),
|
||||
array('Exp: {$y/$x} result', $b, 'Exp: 3 result'),
|
||||
array('Exp: {$y-$x} result', $b, 'Exp: 18 result'),
|
||||
array('Exp: {$y*$x} result', $b, 'Exp: 243 result'),
|
||||
array('Exp: {$y^$x} result', $b, 'Exp: 18 result'),
|
||||
array('Exp: {-$x} result', $b, 'Exp: -9 result'),
|
||||
array('Exp: {!$x} result', $b, 'Exp: result'),
|
||||
array('Exp: {!5} result', $b, 'Exp: result'),
|
||||
array('Exp: {-1} result', $b, 'Exp: -1 result'),
|
||||
array('Exp: {$z = 5} {$z} result', $b, 'Exp: 5 5 result'),
|
||||
array('Exp: {$k.i = "str"} {$k.i} result', $b, 'Exp: str str result'),
|
||||
|
||||
array('Exp: {($y*$x - (($x+$y) + $y/$x) ^ $y)/4} result',
|
||||
$b, 'Exp: 53.75 result'),
|
||||
array('Exp: {$x+max($x, $y)} result', $b, 'Exp: 36 result'),
|
||||
array('Exp: {max(1,2)} result', $b, 'Exp: 2 result'),
|
||||
array('Exp: {round(sin(pi()), 8)} result', $b, 'Exp: 0 result'),
|
||||
array('Exp: {max($x, $y) + round(sin(pi()), 8) - min($x, $y) +3} result',
|
||||
$b, 'Exp: 21 result'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerExpressionsInvalid() {
|
||||
return array(
|
||||
array('If: {-"hi"} end', 'Aspect\CompileException', "Unexpected token '-'"),
|
||||
array('If: {($a++)++} end', 'Aspect\CompileException', "Unexpected token '++'"),
|
||||
array('If: {$a + * $c} end', 'Aspect\CompileException', "Unexpected token '*'"),
|
||||
array('If: {/$a} end', 'Aspect\CompileException', "Unexpected token '\$a'"),
|
||||
array('If: {$a == 5 > 4} end', 'Aspect\CompileException', "Unexpected token '>'"),
|
||||
array('If: {$a != 5 <= 4} end', 'Aspect\CompileException', "Unexpected token '<='"),
|
||||
array('If: {$a != 5 => 4} end', 'Aspect\CompileException', "Unexpected token '=>'"),
|
||||
array('If: {$a + (*6)} end', 'Aspect\CompileException', "Unexpected token '*'"),
|
||||
array('If: {$a + ( 6} end', 'Aspect\CompileException', "Brackets don't match"),
|
||||
array('If: {$a = 4} end', 'Aspect\SecurityException', "Forbidden to set a variable", Aspect::DENY_SET_VARS),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerInclude() {
|
||||
$a = array(
|
||||
"name" => "welcome",
|
||||
"tpl" => "welcome.tpl",
|
||||
"fragment" => "come",
|
||||
"pr_fragment" => "Come",
|
||||
"pr_name" => "Welcome",
|
||||
"username" => "Master",
|
||||
"email" => "dev@null.net"
|
||||
);
|
||||
$result = 'Include <b>Welcome, Master (dev@null.net)</b> template';
|
||||
$result2 = 'Include <b>Welcome, Flame (dev@null.net)</b> template';
|
||||
$result3 = 'Include <b>Welcome, Master (flame@dev.null)</b> template';
|
||||
$result4 = 'Include <b>Welcome, Flame (flame@dev.null)</b> template';
|
||||
return array(
|
||||
array('Include {include file="welcome.tpl"} template', $a, $result),
|
||||
array('Include {include file=$tpl} template', $a, $result),
|
||||
array('Include {include file="$tpl"} template', $a, $result),
|
||||
array('Include {include file="{$tpl}"} template', $a, $result),
|
||||
array('Include {include file="$name.tpl"} template', $a, $result),
|
||||
array('Include {include file="{$name}.tpl"} template', $a, $result),
|
||||
array('Include {include file="{$pr_name|lower}.tpl"} template', $a, $result),
|
||||
array('Include {include file="wel{$fragment}.tpl"} template', $a, $result),
|
||||
array('Include {include file="wel{$pr_fragment|lower}.tpl"} template', $a, $result),
|
||||
array('Include {include file="welcome.tpl" username="Flame"} template', $a, $result2),
|
||||
array('Include {include file="welcome.tpl" email="flame@dev.null"} template', $a, $result3),
|
||||
array('Include {include file="welcome.tpl" username="Flame" email="flame@dev.null"} template',
|
||||
$a, $result4),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerIncludeInvalid() {
|
||||
return array(
|
||||
array('Include {include} template', 'Aspect\CompileException', "{include} require 'file' parameter"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerIf() {
|
||||
$a = array(
|
||||
"val1" => 1,
|
||||
"val0" => 0,
|
||||
"x" => 9,
|
||||
"y" => 27
|
||||
);
|
||||
return array(
|
||||
array('if: {if 1} block1 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if 1} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if 0} block1 {/if} end', $a, 'if: end'),
|
||||
array('if: {if $val0} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
array('if: {if $val1} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if $val1 || $val0} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if $val1 && $val0} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
array('if: {if $x-9} block1 {elseif $x} block2 {else} block3 {/if} end',
|
||||
$a, 'if: block2 end'),
|
||||
array('if: {if round(sin(pi()), 8)} block1 {elseif $x} block2 {else} block3 {/if} end',
|
||||
$a, 'if: block2 end'),
|
||||
array('if: {if round(sin(pi()), 8)} block1 {elseif $val0} block2 {else} block3 {/if} end',
|
||||
$a, 'if: block3 end'),
|
||||
array('if: {if empty($val0)} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if $val0?} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
array('if: {if $val1?} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if $val0!} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if $val1!} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if $val0.x.y.z?} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
array('if: {if $val0.x.y.z!} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
array('if: {if true} block1 {else} block2 {/if} end', $a, 'if: block1 end'),
|
||||
array('if: {if false} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
array('if: {if null} block1 {else} block2 {/if} end', $a, 'if: block2 end'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerIfInvalid() {
|
||||
return array(
|
||||
array('If: {if} block1 {/if} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('If: {if 1} block1 {elseif} block2 {/if} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('If: {if 1} block1 {else} block2 {elseif 0} block3 {/if} end', 'Aspect\CompileException', "Incorrect use of the tag {else if}"),
|
||||
array('If: {if 1} block1 {else} block2 {/if} block3 {elseif 0} end', 'Aspect\CompileException', "Unexpected tag 'elseif' (this tag can be used with 'if')"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerCreateVar() {
|
||||
$a = array(
|
||||
"x" => 9,
|
||||
"y" => 27,
|
||||
"z" => 99
|
||||
);
|
||||
return array(
|
||||
array('Create: {var $v = $x+$y} Result: {$v} end', $a, 'Create: Result: 36 end'),
|
||||
array('Create: {var $v =
|
||||
$x
|
||||
+
|
||||
$y} Result: {$v} end', $a, 'Create: Result: 36 end'),
|
||||
array('Create: {var $v = $z++} Result: {$v}, {$z} end', $a, 'Create: Result: 99, 100 end'),
|
||||
array('Create: {var $v = $z++ + 1} Result: {$v}, {$z} end', $a, 'Create: Result: 100, 100 end'),
|
||||
array('Create: {var $v = --$z} Result: {$v}, {$z} end', $a, 'Create: Result: 98, 98 end'),
|
||||
array('Create: {var $v = $y/$x} Result: {$v} end', $a, 'Create: Result: 3 end'),
|
||||
array('Create: {var $v = $y-$x} Result: {$v} end', $a, 'Create: Result: 18 end'),
|
||||
array('Create: {var $v = $y*$x-2} Result: {$v} end', $a, 'Create: Result: 241 end'),
|
||||
array('Create: {var $v = ($y^$x)+7} Result: {$v} end', $a, 'Create: Result: 25 end'),
|
||||
|
||||
array('Create: {var $v = [1,2,3]} Result: {$v.1} end', $a, 'Create: Result: 2 end'),
|
||||
array('Create: {var $v = []} Result: {if $v} have items {else} empty {/if} end',
|
||||
$a, 'Create: Result: empty end'),
|
||||
array('Create: {var $v = ["one"|upper => 1, 4 => $x, "three" => 3]} Result: {$v.three}, {$v.4}, {$v.ONE} end',
|
||||
$a, 'Create: Result: 3, 9, 1 end'),
|
||||
array('Create: {var $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]} Result: {$v.key1}, {$v.key2.z} end',
|
||||
$a, 'Create: Result: 241, 99 end'),
|
||||
array('Create: {var $v = count([1,2,3])+7} Result: {$v} end',
|
||||
$a, 'Create: Result: 10 end'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerCreateVarInvalid() {
|
||||
return array(
|
||||
array('Create: {var $v} Result: {$v} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Create: {var $v = } Result: {$v} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Create: {var $v = 1++} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '++'"),
|
||||
array('Create: {var $v = c} Result: {$v} end', 'Aspect\CompileException', "Unexpected token 'c'"),
|
||||
array('Create: {var $v = ($a)++} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '++'"),
|
||||
array('Create: {var $v = --$a++} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '++'"),
|
||||
array('Create: {var $v = $a|upper++} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '++'"),
|
||||
array('Create: {var $v = max($a,2)++} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '++'"),
|
||||
array('Create: {var $v = max($a,2)} Result: {$v} end', 'Aspect\CompileException', "Modifier max not found", Aspect::DENY_INLINE_FUNCS),
|
||||
array('Create: {var $v = 4*} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '*'"),
|
||||
array('Create: {var $v = ""$a} Result: {$v} end', 'Aspect\CompileException', "Unexpected token '\$a'"),
|
||||
array('Create: {var $v = [1,2} Result: {$v} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Create: {var $v = empty(2)} Result: {$v} end', 'Aspect\CompileException', "Unexpected token 2, isset() and empty() accept only variables"),
|
||||
array('Create: {var $v = isset(2)} Result: {$v} end', 'Aspect\CompileException', "Unexpected token 2, isset() and empty() accept only variables"),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerForeach() {
|
||||
$a = array(
|
||||
"list" => array(1 => "one", 2 => "two", 3 => "three"),
|
||||
"empty" => array()
|
||||
);
|
||||
return array(
|
||||
array('Foreach: {foreach $list as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'),
|
||||
array('Foreach: {foreach $list as $e} {$e},{break} break {/foreach} end', $a, 'Foreach: one, end'),
|
||||
array('Foreach: {foreach $list as $e} {$e},{continue} continue {/foreach} end', $a, 'Foreach: one, two, three, end'),
|
||||
array('Foreach: {foreach ["one", "two", "three"] as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'),
|
||||
array('Foreach: {foreach $list as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: 1 => one, 2 => two, 3 => three, end'),
|
||||
array('Foreach: {foreach [1 => "one", 2 => "two", 3 => "three"] as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: 1 => one, 2 => two, 3 => three, end'),
|
||||
array('Foreach: {foreach $empty as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'),
|
||||
array('Foreach: {foreach [] as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'),
|
||||
array('Foreach: {foreach $empty as $k => $e} {$k} => {$e}, {foreachelse} empty {/foreach} end', $a, 'Foreach: empty end'),
|
||||
array('Foreach: {foreach $list as $e index=$i} {$i}: {$e}, {/foreach} end', $a, 'Foreach: 0: one, 1: two, 2: three, end'),
|
||||
array('Foreach: {foreach $list as $k => $e index=$i} {$i}: {$k} => {$e}, {/foreach} end', $a, 'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end'),
|
||||
array('Foreach: {foreach $empty as $k => $e index=$i} {$i}: {$k} => {$e}, {foreachelse} empty {/foreach} end', $a, 'Foreach: empty end'),
|
||||
array('Foreach: {foreach $list as $k => $e first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {/foreach} end', $a, 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end'),
|
||||
array('Foreach: {foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', $a, 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end'),
|
||||
array('Foreach: {foreach $empty as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {foreachelse} empty {/foreach} end', $a, 'Foreach: empty end'),
|
||||
array('Foreach: {foreach [1 => "one", 2 => "two", 3 => "three"] as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', $a, 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerForeachInvalid() {
|
||||
return array(
|
||||
array('Foreach: {foreach} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of 'foreach'"),
|
||||
array('Foreach: {foreach $list} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('Foreach: {foreach array_random() as $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'array_random'"),
|
||||
array('Foreach: {foreach $list as $e+1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('Foreach: {foreach $list as $k+1 => $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('Foreach: {foreach $list as max($i,1) => $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
|
||||
array('Foreach: {foreach $list as max($e,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
|
||||
array('Foreach: {foreach $list => $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '=>'"),
|
||||
array('Foreach: {foreach $list $k => $e} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '\$k'"),
|
||||
array('Foreach: {foreach $list as $k =>} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Foreach: {foreach last=$l $list as $e } {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'last' in 'foreach'"),
|
||||
array('Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unknown parameter 'unknown'"),
|
||||
array('Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('Foreach: {foreach $list as $e first=$f+1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('Foreach: {foreach $list as $e last=$l+1} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('Foreach: {foreach $list as $e index=max($i,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
|
||||
array('Foreach: {foreach $list as $e first=max($i,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
|
||||
array('Foreach: {foreach $list as $e last=max($i,1)} {$e}, {/foreach} end', 'Aspect\CompileException', "Unexpected token 'max'"),
|
||||
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {break} {/foreach} end', 'Aspect\CompileException', "Incorrect use of the tag {break}"),
|
||||
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {continue} {/foreach} end', 'Aspect\CompileException', "Incorrect use of the tag {continue}"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerLiteral() {
|
||||
$a = array("a" => "lit. A");
|
||||
return array(
|
||||
array('{if 0}none{/if} literal: {$a} end', $a, 'literal: lit. A end'),
|
||||
array('{if 0}none{/if} literal:{literal} {$a} {/literal} end', $a, 'literal: {$a} end'),
|
||||
array('{if 0}none{/if} literal: { $a} end', $a, 'literal: { $a} end'),
|
||||
array('{if 0}none{/if} literal: {
|
||||
$a} end', $a, 'literal: { $a} end'),
|
||||
array('{if 0}none{/if}literal: function () { return 1; } end', $a, 'literal: function () { return 1; } end')
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerSwitch() {
|
||||
$code1 = 'Switch: {switch $a}
|
||||
{case 1} one {break}
|
||||
{case 2} two {break}
|
||||
{case "string"} str {break}
|
||||
{default} def
|
||||
{/switch} end';
|
||||
|
||||
$code2 = 'Switch: {switch $a}
|
||||
{case 1} one {break}
|
||||
{case 2} two {break}
|
||||
{case "string"} str {break}
|
||||
{/switch} end';
|
||||
|
||||
$code3 = 'Switch: {switch $a} invalid
|
||||
{case 1} one {break}
|
||||
{/switch} end';
|
||||
|
||||
return array(
|
||||
array($code1, array("a" => 1), 'Switch: one end'),
|
||||
array($code1, array("a" => 2), 'Switch: two end'),
|
||||
array($code1, array("a" => "string"), 'Switch: str end'),
|
||||
array($code2, array("a" => "unk"), 'Switch: end'),
|
||||
array($code3, array("a" => 1), 'Switch: invalid one end'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerSwitchInvalid() {
|
||||
return array(
|
||||
array('Switch: {switch}{case 1} one {break}{/switch} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Switch: {switch 1}{case} one {break}{/switch} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('Switch: {switch 1}{break}{case} one {/switch} end', 'Aspect\CompileException', "Incorrect use of the tag {break}"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerWhile() {
|
||||
$a = array("a" => 3);
|
||||
return array(
|
||||
array('While: {while false} block {/while} end', $a, 'While: end'),
|
||||
array('While: {while --$a} {$a}, {/while} end', $a, 'While: 2, 1, end'),
|
||||
array('While: {while --$a} {$a},{break} break {/while} end', $a, 'While: 2, end'),
|
||||
array('While: {while --$a} {$a},{continue} continue {/while} end', $a, 'While: 2, 1, end'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerWhileInvalid() {
|
||||
return array(
|
||||
array('While: {while} block {/while} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerFor() {
|
||||
$a = array("c" => 1, "s" => 1, "m" => 3);
|
||||
return array(
|
||||
array('For: {for $a=4 to=6} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 5, $a: 6, end'),
|
||||
array('For: {for $a=4 step=2 to=10} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 6, $a: 8, $a: 10, end'),
|
||||
array('For: {for $a=4 step=-2 to=0} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 2, $a: 0, end'),
|
||||
array('For: {for $a=$c step=$s to=$m} $a: {$a}, {/for} end', $a, 'For: $a: 1, $a: 2, $a: 3, end'),
|
||||
array('For: {for $a=-1 step=-max(1,2) to=-5} $a: {$a}, {/for} end', $a, 'For: $a: -1, $a: -3, $a: -5, end'),
|
||||
array('For: {for $a=4 step=2 to=10} $a: {$a}, {break} break {/for} end', $a, 'For: $a: 4, end'),
|
||||
array('For: {for $a=4 step=2 to=8} $a: {$a}, {continue} continue {/for} end',
|
||||
$a, 'For: $a: 4, $a: 6, $a: 8, end'),
|
||||
array('For: {for $a=4 step=2 to=8 index=$i} $a{$i}: {$a}, {/for} end', $a, 'For: $a0: 4, $a1: 6, $a2: 8, end'),
|
||||
array('For: {for $a=4 step=2 to=8 index=$i first=$f} {if $f}first{/if} $a{$i}: {$a}, {/for} end',
|
||||
$a, 'For: first $a0: 4, $a1: 6, $a2: 8, end'),
|
||||
array('For: {for $a=4 step=2 to=8 index=$i first=$f last=$l} {if $f} first {/if} $a{$i}: {$a}, {if $l} last {/if} {/for} end',
|
||||
$a, 'For: first $a0: 4, $a1: 6, $a2: 8, last end'),
|
||||
array('For: {for $a=1 to=-1 } $a: {$a}, {forelse} empty {/for} end', $a, 'For: empty end'),
|
||||
array('For: {for $a=1 to=-1 index=$i first=$f last=$l} {if $f} first {/if} $a{$i}: {$a}, {if $l} last {/if} {forelse} empty {/for} end',
|
||||
$a, 'For: empty end'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerForInvalid() {
|
||||
return array(
|
||||
array('For: {for} block1 {/for} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('For: {for $a=} block1 {/for} end', 'Aspect\CompileException', "Unexpected end of expression"),
|
||||
array('For: {for $a+1=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token '+'"),
|
||||
array('For: {for max($a,$b)=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'max'"),
|
||||
array('For: {for to=6 $a=3} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'to'"),
|
||||
array('For: {for index=$i $a=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'index'"),
|
||||
array('For: {for first=$i $a=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'first'"),
|
||||
array('For: {for last=$i $a=3 to=6} block1 {/for} end', 'Aspect\CompileException', "Unexpected token 'last'"),
|
||||
array('For: {for $a=4 to=6 unk=4} block1 {/for} end', 'Aspect\CompileException', "Unknown parameter 'unk'"),
|
||||
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {break} {/for} end', 'Aspect\CompileException', "Incorrect use of the tag {break}"),
|
||||
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {continue} {/for} end', 'Aspect\CompileException', "Incorrect use of the tag {continue}"),
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerLayersInvalid() {
|
||||
return array(
|
||||
array('Layers: {foreach $list as $e} block1 {if 1} {foreachelse} {/if} {/foreach} end', 'Aspect\CompileException', "Unexpected tag 'foreachelse' (this tag can be used with 'foreach')"),
|
||||
array('Layers: {foreach $list as $e} block1 {if 1} {/foreach} {/if} end', 'Aspect\CompileException', "Unexpected closing of the tag 'foreach'"),
|
||||
array('Layers: {for $a=4 to=6} block1 {if 1} {forelse} {/if} {/for} end', 'Aspect\CompileException', "Unexpected tag 'forelse' (this tag can be used with 'for')"),
|
||||
array('Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', 'Aspect\CompileException', "Unexpected closing of the tag 'for'"),
|
||||
array('Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', 'Aspect\CompileException', "Unexpected tag 'case' (this tag can be used with 'switch')"),
|
||||
array('Layers: {/switch} end', 'Aspect\CompileException', "Unexpected closing of the tag 'switch'"),
|
||||
array('Layers: {if 1} end', 'Aspect\CompileException', "Unclosed tags: if"),
|
||||
);
|
||||
}
|
||||
|
||||
/*public static function providerExtends() {
|
||||
return array(
|
||||
array('{extends file="proto.tpl"}{block name="bk1"} block1 {/block}', "Template extended by block1"),
|
||||
array('{extends file="proto.tpl"}{block name=bk1} block1 {/block}', "Template extended by block1"),
|
||||
array('{extends "proto.tpl"}{block "bk1"} block1 {/block}', "Template extended by block1"),
|
||||
array('{extends "proto.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} garbage', "Template extended by block1"),
|
||||
array('{extends "proto2.tpl"}{block "bk1"} block1 {/block}{block "bk2"} block2 {/block} garbage', "Template multi-extended by block1"),
|
||||
);
|
||||
}*/
|
||||
|
||||
public function exec($code, $vars, $result, $dump = false) {
|
||||
$tpl = self::$aspect->compileCode($code, "inline.tpl");
|
||||
if($dump) {
|
||||
echo "\n===========================\n".$code.": ".$tpl->getBody();
|
||||
}
|
||||
$this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code");
|
||||
}
|
||||
|
||||
public function execError($code, $exception, $message, $options) {
|
||||
self::$aspect->setOptions($options);
|
||||
try {
|
||||
self::$aspect->compileCode($code, "inline.tpl");
|
||||
} catch(\Exception $e) {
|
||||
$this->assertSame($exception, get_class($e), "Exception $code");
|
||||
$this->assertStringStartsWith($message, $e->getMessage());
|
||||
self::$aspect->setOptions(0);
|
||||
return;
|
||||
}
|
||||
self::$aspect->setOptions(0);
|
||||
$this->fail("Code $code must be invalid");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerVars
|
||||
*/
|
||||
public function testVars($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerVarsInvalid
|
||||
*/
|
||||
public function testVarsInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerModifiers
|
||||
*/
|
||||
public function testModifiers($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerModifiersInvalid
|
||||
*/
|
||||
public function testModifiersInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerExpressions
|
||||
*/
|
||||
public function testExpressions($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerExpressionsInvalid
|
||||
*/
|
||||
public function testExpressionsInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerInclude
|
||||
*/
|
||||
public function testInclude($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerIncludeInvalid
|
||||
*/
|
||||
public function testIncludeInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerIf
|
||||
*/
|
||||
public function testIf($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerIfInvalid
|
||||
*/
|
||||
public function testIfInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerCreateVar
|
||||
*/
|
||||
public function testCreateVar($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerCreateVarInvalid
|
||||
*/
|
||||
public function testCreateVarInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerForeach
|
||||
*/
|
||||
public function testForeach($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerForeachInvalid
|
||||
*/
|
||||
public function testForeachInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerFor
|
||||
*/
|
||||
public function testFor($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerForInvalid
|
||||
*/
|
||||
public function testForInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerLiteral
|
||||
*/
|
||||
public function testLiterals($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSwitch
|
||||
*/
|
||||
public function testSwitch($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSwitchInvalid
|
||||
*/
|
||||
public function testSwitchInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerWhile
|
||||
*/
|
||||
public function testWhile($code, $vars, $result) {
|
||||
$this->exec($code, $vars, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerWhileInvalid
|
||||
*/
|
||||
public function testWhileInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerLayersInvalid
|
||||
*/
|
||||
public function testLayersInvalid($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerExtends
|
||||
*/
|
||||
public function _testExtends($code, $exception, $message, $options = 0) {
|
||||
$this->execError($code, $exception, $message, $options);
|
||||
}
|
||||
}
|
||||
|
172
tests/cases/Aspect/TokenizerTest.php
Normal file
172
tests/cases/Aspect/TokenizerTest.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
use Aspect\Tokenizer;
|
||||
|
||||
class TokenizerTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testTokens() {
|
||||
$code = 'hello, please resolve this example: sin($x)+tan($x*$t) = {U|[0,1]}';
|
||||
$tokens = new Tokenizer($code);
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame("hello", $tokens->current());
|
||||
|
||||
$this->assertTrue($tokens->isNext(","));
|
||||
$this->assertFalse($tokens->isNext("="));
|
||||
$this->assertFalse($tokens->isNext(T_STRING));
|
||||
$this->assertFalse($tokens->isNext($tokens::MACRO_UNARY));
|
||||
|
||||
$this->assertFalse($tokens->isNext("=", T_STRING, $tokens::MACRO_UNARY));
|
||||
$this->assertTrue($tokens->isNext("=", T_STRING, $tokens::MACRO_UNARY, ","));
|
||||
|
||||
$this->assertSame(",", $tokens->getNext());
|
||||
$this->assertSame(",", $tokens->key());
|
||||
$this->assertSame("please", $tokens->getNext(T_STRING));
|
||||
$this->assertSame("resolve", $tokens->getNext($tokens::MACRO_UNARY, T_STRING));
|
||||
|
||||
$tokens->next();
|
||||
$tokens->next();
|
||||
$tokens->next();
|
||||
|
||||
$this->assertSame(":", $tokens->current());
|
||||
$this->assertSame(":", $tokens->key());
|
||||
|
||||
|
||||
$this->assertSame("sin", $tokens->getNext($tokens::MACRO_STRING));
|
||||
$this->assertSame("sin", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertTrue($tokens->is(T_STRING));
|
||||
$this->assertTrue($tokens->is($tokens::MACRO_STRING));
|
||||
$this->assertFalse($tokens->is($tokens::MACRO_EQUALS));
|
||||
$this->assertFalse($tokens->is(T_DNUMBER));
|
||||
$this->assertFalse($tokens->is(":"));
|
||||
$this->assertSame("(", $tokens->getNext("(",")"));
|
||||
|
||||
$tokens->next();
|
||||
$tokens->next();
|
||||
$this->assertSame("+", $tokens->getNext($tokens::MACRO_BINARY));
|
||||
}
|
||||
|
||||
public function testWhitespaceSenseLow() {
|
||||
$text = "1 foo \n bar\n \n double ";
|
||||
$tokens = new Tokenizer($text, Tokenizer::DECODE_NEW_LINES);
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("1", $tokens->current());
|
||||
$this->assertSame(T_LNUMBER, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("foo", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("\n", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("bar", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("\n", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("\n", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("double", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertFalse($tokens->valid());
|
||||
$this->assertNull($tokens->key());
|
||||
}
|
||||
|
||||
public function testWhitespaceSenseHi() {
|
||||
$text = "1 foo \n bar\n \n double ";
|
||||
$tokens = new Tokenizer($text, Tokenizer::DECODE_WHITESPACES);
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("1", $tokens->current());
|
||||
$this->assertSame(T_LNUMBER, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame(" ", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("foo", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame(" \n ", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("bar", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("\n \n ", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("double", $tokens->current());
|
||||
$this->assertSame(T_STRING, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame(" ", $tokens->current());
|
||||
$this->assertSame(T_WHITESPACE, $tokens->key());
|
||||
$this->assertSame("", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
|
||||
$this->assertFalse($tokens->valid());
|
||||
$this->assertNull($tokens->key());
|
||||
}
|
||||
|
||||
public function testSkip() {
|
||||
$text = "1 foo: bar ( 3 + double ) ";
|
||||
$tokens = new Tokenizer($text);
|
||||
|
||||
$tokens->skip()->skip(T_STRING)->skip(':');
|
||||
try {
|
||||
$tokens->skip(T_STRING)->skip('(')->skip(':');
|
||||
} catch(\Exception $e) {
|
||||
$this->assertInstanceOf('Aspect\UnexpectedException', $e);
|
||||
$this->assertStringStartsWith("Unexpected token '3', expect ':'", $e->getMessage());
|
||||
}
|
||||
$this->assertTrue($tokens->valid());
|
||||
$this->assertSame("3", $tokens->current());
|
||||
$this->assertSame(T_LNUMBER, $tokens->key());
|
||||
$this->assertSame(" ", $tokens->getWhiteSpace());
|
||||
$tokens->next();
|
||||
}
|
||||
}
|
119
tests/cases/AspectTest.php
Normal file
119
tests/cases/AspectTest.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
use Aspect\Render,
|
||||
Aspect\Misc;
|
||||
|
||||
class AspectTest extends \PHPUnit_Framework_TestCase {
|
||||
/**
|
||||
* @var Aspect
|
||||
*/
|
||||
public $aspect;
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
Misc::clean(ASPECT_RESOURCES.'/compile');
|
||||
Misc::rm(ASPECT_RESOURCES.'/template/custom.tpl');
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
self::tearDownAfterClass();
|
||||
$this->aspect = $aspect = new Aspect();
|
||||
$aspect->setCompileDir(ASPECT_RESOURCES.'/compile');
|
||||
$aspect->addTemplateDir(ASPECT_RESOURCES.'/template');
|
||||
$aspect->setForceCompile(false);
|
||||
$aspect->setCompileCheck(false);
|
||||
}
|
||||
|
||||
public function tpl($code) {
|
||||
Misc::put(ASPECT_RESOURCES.'/template/custom.tpl', $code);
|
||||
}
|
||||
|
||||
public function rmTpl() {
|
||||
Misc::rm(ASPECT_RESOURCES.'/template/custom.tpl');
|
||||
}
|
||||
|
||||
public function testAddRender() {
|
||||
$test = $this;
|
||||
$this->aspect->storeTemplate(new Render('render.tpl', function($tpl) use ($test) {
|
||||
/** @var \PHPUnit_Framework_TestCase $test */
|
||||
$test->assertInstanceOf('Aspect\Render', $tpl);
|
||||
echo "Inline render";
|
||||
}));
|
||||
|
||||
$this->assertSame("Inline render", $this->aspect->fetch('render.tpl', array()));
|
||||
}
|
||||
|
||||
public function testCompileFile() {
|
||||
$a = array(
|
||||
"a" => "a",
|
||||
"b" => "b"
|
||||
);
|
||||
|
||||
$this->assertSame("Template 1 a", $this->aspect->fetch('template1.tpl', $a));
|
||||
$this->assertSame("Template 2 b", $this->aspect->fetch('template2.tpl', $a));
|
||||
$this->assertInstanceOf('Aspect\Render', $this->aspect->getTemplate('template1.tpl'));
|
||||
$this->assertInstanceOf('Aspect\Render', $this->aspect->getTemplate('template2.tpl'));
|
||||
$this->assertSame(2, iterator_count(new FilesystemIterator(ASPECT_RESOURCES.'/compile')));
|
||||
}
|
||||
|
||||
public function testStorage() {
|
||||
$this->tpl('Custom template');
|
||||
$this->assertSame("Custom template", $this->aspect->fetch('custom.tpl', array()));
|
||||
$this->rmTpl();
|
||||
|
||||
$this->assertSame("Custom template", $this->aspect->fetch('custom.tpl', array()));
|
||||
|
||||
$this->tpl('Custom template 2');
|
||||
$this->assertSame("Custom template", $this->aspect->fetch('custom.tpl', array()));
|
||||
}
|
||||
|
||||
public function testCheckMTime() {
|
||||
$this->aspect->setCompileCheck(true);
|
||||
$this->tpl('Custom template');
|
||||
$this->assertSame("Custom template", $this->aspect->fetch('custom.tpl', array()));
|
||||
|
||||
sleep(1);
|
||||
$this->tpl('Custom template (new)');
|
||||
$this->assertSame("Custom template (new)", $this->aspect->fetch('custom.tpl', array()));
|
||||
}
|
||||
|
||||
public function testForceCompile() {
|
||||
$this->aspect->setForceCompile(true);
|
||||
$this->tpl('Custom template');
|
||||
$this->assertSame("Custom template", $this->aspect->fetch('custom.tpl', array()));
|
||||
$this->tpl('Custom template (new)');
|
||||
$this->assertSame("Custom template (new)", $this->aspect->fetch('custom.tpl', array()));
|
||||
}
|
||||
|
||||
public function testSetModifier() {
|
||||
$this->aspect->setModifier("mymod", "myMod");
|
||||
$this->tpl('Custom modifier {$a|mymod}');
|
||||
$this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->aspect->fetch('custom.tpl', array("a" => "Custom")));
|
||||
}
|
||||
|
||||
public function testSetFunctions() {
|
||||
$this->aspect->setForceCompile(true);
|
||||
$this->aspect->setFunction("myfunc", "myFunc");
|
||||
$this->aspect->setBlockFunction("myblockfunc", "myBlockFunc");
|
||||
$this->tpl('Custom function {myfunc name="foo"}');
|
||||
$this->assertSame("Custom function MyFunc:foo", $this->aspect->fetch('custom.tpl', array()));
|
||||
$this->tpl('Custom function {myblockfunc name="foo"} this block1 {/myblockfunc}');
|
||||
$this->assertSame("Custom function Block:foo:this block1:Block", $this->aspect->fetch('custom.tpl', array()));
|
||||
}
|
||||
|
||||
public function testSetCompilers() {
|
||||
$this->aspect->setForceCompile(true);
|
||||
$this->aspect->setCompiler("mycompiler", 'myCompiler');
|
||||
$this->aspect->setBlockCompiler("myblockcompiler", array(
|
||||
'open' => 'myBlockCompilerOpen',
|
||||
'close' => 'myBlockCompilerClose'
|
||||
), array(
|
||||
'tag' => 'myBlockCompilerTag'
|
||||
));
|
||||
$this->tpl('Custom compiler {mycompiler name="bar"}');
|
||||
$this->assertSame("Custom compiler PHP_VERSION: ".PHP_VERSION." (for bar)", $this->aspect->fetch('custom.tpl', array()));
|
||||
$this->tpl('Custom compiler {myblockcompiler name="bar"} block1 {tag name="baz"} block2 {/myblockcompiler}');
|
||||
$this->assertSame("Custom compiler PHP_VERSION: ".PHP_VERSION." (for bar) block1 Tag baz of compiler block2 End of compiler", $this->aspect->fetch('custom.tpl', array()));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
31
tests/resources/actions.php
Normal file
31
tests/resources/actions.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
function myMod($str) {
|
||||
return "(myMod)".$str."(/myMod)";
|
||||
}
|
||||
|
||||
function myFunc($params) {
|
||||
return "MyFunc:".$params["name"];
|
||||
}
|
||||
|
||||
function myBlockFunc($params, $content) {
|
||||
return "Block:".$params["name"].':'.trim($content).':Block';
|
||||
}
|
||||
|
||||
function myCompiler(Aspect\Tokenizer $tokenizer, Aspect\Template $tpl) {
|
||||
$p = $tpl->parseParams($tokenizer);
|
||||
return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".'.$p["name"].'.")";';
|
||||
}
|
||||
|
||||
function myBlockCompilerOpen(Aspect\Tokenizer $tokenizer, Aspect\Scope $scope) {
|
||||
return myCompiler($tokenizer, $scope->tpl);
|
||||
}
|
||||
|
||||
function myBlockCompilerClose(Aspect\Tokenizer $tokenizer, Aspect\Scope $scope) {
|
||||
return 'echo "End of compiler";';
|
||||
}
|
||||
|
||||
function myBlockCompilerTag(Aspect\Tokenizer $tokenizer, Aspect\Scope $scope) {
|
||||
$p = $scope->tpl->parseParams($tokenizer);
|
||||
return 'echo "Tag ".'.$p["name"].'." of compiler";';
|
||||
}
|
1
tests/resources/template/template1.tpl
Normal file
1
tests/resources/template/template1.tpl
Normal file
@ -0,0 +1 @@
|
||||
Template 1 {$a}
|
1
tests/resources/template/template2.tpl
Normal file
1
tests/resources/template/template2.tpl
Normal file
@ -0,0 +1 @@
|
||||
Template 2 {$b}
|
Loading…
Reference in New Issue
Block a user