Merge remote-tracking branch 'refs/remotes/origin/develop'

This commit is contained in:
bzick 2013-08-05 19:26:40 +04:00
commit c1d0268a94
36 changed files with 2620 additions and 1822 deletions

View File

@ -1,6 +1,19 @@
CHANGELOG CHANGELOG
========= =========
## 1.2.0
- Feature #28: macros may be called recursively
- Feature #29: add {unset} tag
- Add hook for loading modifiers and tags
- Add hook for loading modifiers and tags
- Feature #3: Add string operator '~'
- Improve parsers: parserExp, parserVar, parserVariable, parserMacro
- Fix ternary bug
- Bugs--
- Tests++
- Docs++
## 1.1.0 ## 1.1.0
- Bug #19: Bug with "if" expressions starting with "(" - Bug #19: Bug with "if" expressions starting with "("

View File

@ -3,14 +3,14 @@ Fenom - Template Engine for PHP
> Composer package: `{"fenom/fenom": "dev-master"}`. See on [Packagist.org](https://packagist.org/packages/bzick/fenom) > Composer package: `{"fenom/fenom": "dev-master"}`. See on [Packagist.org](https://packagist.org/packages/bzick/fenom)
[![Build Status](https://travis-ci.org/bzick/fenom.png?branch=master)](https://travis-ci.org/bzick/fenom) [![Build Status](https://travis-ci.org/bzick/fenom.png?branch=master)](https://travis-ci.org/fenom/fenom)
## [Usage](./docs/usage.md) :: [Documentation](./docs/readme.md) :: [Benchmark](./docs/benchmark.md) :: [Articles](./docs/articles.md) ## [Usage](./docs/usage.md) :: [Documentation](./docs/readme.md) :: [Benchmark](./docs/benchmark.md) :: [Articles](./docs/articles.md)
* Simple [syntax](./docs/syntax.md) * Simple [syntax](./docs/syntax.md)
* [Fast](./docs/benchmark.md) * [Fast](./docs/benchmark.md)
* [Secure](./docs/settings.md) * [Secure](./docs/settings.md)
* [Simple](./ideology.md) * Simple
* [Flexible](./docs/readme.md#extends) * [Flexible](./docs/ext/extensions.md)
* [Lightweight](./docs/benchmark.md#stats) * [Lightweight](./docs/benchmark.md#stats)
* [Powerful](./docs/readme.md) * [Powerful](./docs/readme.md)
* Easy to use: * Easy to use:

View File

@ -1,9 +1,8 @@
{ {
"name": "fenom/fenom", "name": "fenom/fenom",
"type": "library", "type": "library",
"description": "Fenom - fast template engine for PHP", "description": "Fenom - excellent template engine for PHP",
"homepage": "http://bzick.github.io/fenom/", "keywords": ["fenom", "template", "templating", "templater"],
"keywords": ["fenom", "template", "templating", "cytro"],
"license": "BSD-3", "license": "BSD-3",
"authors": [ "authors": [
{ {

View File

@ -1,5 +1,9 @@
Extensions Extensions
========== ==========
* [Extra pack](https://github.com/bzick/fenom-extra) basic add-ons for web-base project. * [Extra pack](https://github.com/bzick/fenom-extra) of add-ons for Fenom template engine.
* *Smarty pack* (planned) Smarty3 adapter * Tools for static files (css, js).
* Global variables
* Allow more hooks for extending
* Add variable container
* You can only use the necessary add-ons

View File

@ -81,6 +81,10 @@ Operators
* `--$a` - decrement the variable and use it * `--$a` - decrement the variable and use it
* `$a--` - use the variable and decrement it * `$a--` - use the variable and decrement it
### String operator
* `$a ~ $b` - return concatenation of variables `$a` and `$b`
### Ternary operator ### Ternary operator
* `$a ? $b : $c` - returns `$b` if `$a` is not empty, and `$c` otherwise * `$a ? $b : $c` - returns `$b` if `$a` is not empty, and `$c` otherwise

View File

@ -7,16 +7,17 @@
* For the full copyright and license information, please view the license.md * For the full copyright and license information, please view the license.md
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
use Fenom\Template, use Fenom\ProviderInterface;
Fenom\ProviderInterface; use Fenom\Template;
/** /**
* Fenom Template Engine * Fenom Template Engine
* *
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
class Fenom { class Fenom
const VERSION = '1.1'; {
const VERSION = '1.2';
/* Actions */ /* Actions */
const INLINE_COMPILER = 1; const INLINE_COMPILER = 1;
@ -27,7 +28,7 @@ class Fenom {
/* Options */ /* Options */
const DENY_METHODS = 0x10; const DENY_METHODS = 0x10;
const DENY_INLINE_FUNCS = 0x20; const DENY_NATIVE_FUNCS = 0x20;
const FORCE_INCLUDE = 0x40; const FORCE_INCLUDE = 0x40;
const AUTO_RELOAD = 0x80; const AUTO_RELOAD = 0x80;
const FORCE_COMPILE = 0x100; const FORCE_COMPILE = 0x100;
@ -37,6 +38,9 @@ class Fenom {
const AUTO_TRIM = 0x1000; // reserved const AUTO_TRIM = 0x1000; // reserved
const DENY_STATICS = 0x2000; // reserved const DENY_STATICS = 0x2000; // reserved
/* @deprecated */
const DENY_INLINE_FUNCS = 0x20;
/* Default parsers */ /* Default parsers */
const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose'; const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Fenom\Compiler::stdFuncParser'; const DEFAULT_FUNC_PARSER = 'Fenom\Compiler::stdFuncParser';
@ -44,13 +48,15 @@ class Fenom {
const DEFAULT_FUNC_CLOSE = 'Fenom\Compiler::stdFuncClose'; const DEFAULT_FUNC_CLOSE = 'Fenom\Compiler::stdFuncClose';
const SMART_FUNC_PARSER = 'Fenom\Compiler::smartFuncParser'; const SMART_FUNC_PARSER = 'Fenom\Compiler::smartFuncParser';
const MAX_MACRO_RECURSIVE = 32;
/** /**
* @var int[] of possible options, as associative array * @var int[] of possible options, as associative array
* @see setOptions * @see setOptions
*/ */
private static $_options_list = array( private static $_options_list = array(
"disable_methods" => self::DENY_METHODS, "disable_methods" => self::DENY_METHODS,
"disable_native_funcs" => self::DENY_INLINE_FUNCS, "disable_native_funcs" => self::DENY_NATIVE_FUNCS,
"disable_cache" => self::DISABLE_CACHE, "disable_cache" => self::DISABLE_CACHE,
"force_compile" => self::FORCE_COMPILE, "force_compile" => self::FORCE_COMPILE,
"auto_reload" => self::AUTO_RELOAD, "auto_reload" => self::AUTO_RELOAD,
@ -61,10 +67,26 @@ class Fenom {
"disable_statics" => self::DENY_STATICS, "disable_statics" => self::DENY_STATICS,
); );
/**
* @var callable[]
*/
public $pre_filters = array();
/**
* @var callable[]
*/
public $filters = array();
/**
* @var callable[]
*/
public $post_filters = array();
/** /**
* @var Fenom\Render[] Templates storage * @var Fenom\Render[] Templates storage
*/ */
protected $_storage = array(); protected $_storage = array();
/** /**
* @var string compile directory * @var string compile directory
*/ */
@ -75,10 +97,6 @@ class Fenom {
*/ */
protected $_options = 0; protected $_options = 0;
protected $_on_pre_cmp = array();
protected $_on_cmp = array();
protected $_on_post_cmp = array();
/** /**
* @var ProviderInterface * @var ProviderInterface
*/ */
@ -104,7 +122,6 @@ class Fenom {
"unescape" => 'Fenom\Modifier::unescape', "unescape" => 'Fenom\Modifier::unescape',
"strip" => 'Fenom\Modifier::strip', "strip" => 'Fenom\Modifier::strip',
"length" => 'Fenom\Modifier::length', "length" => 'Fenom\Modifier::length',
"default" => 'Fenom\Modifier::defaultValue',
"iterable" => 'Fenom\Modifier::isIterable' "iterable" => 'Fenom\Modifier::isIterable'
); );
@ -230,6 +247,10 @@ class Fenom {
'type' => self::BLOCK_COMPILER, 'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::autoescapeOpen', 'open' => 'Fenom\Compiler::autoescapeOpen',
'close' => 'Fenom\Compiler::autoescapeClose' 'close' => 'Fenom\Compiler::autoescapeClose'
),
'unset' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagUnset'
) )
); );
@ -242,10 +263,11 @@ class Fenom {
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return Fenom * @return Fenom
*/ */
public static function factory($source, $compile_dir = '/tmp', $options = 0) { public static function factory($source, $compile_dir = '/tmp', $options = 0)
if(is_string($source)) { {
if (is_string($source)) {
$provider = new Fenom\Provider($source); $provider = new Fenom\Provider($source);
} elseif($source instanceof ProviderInterface) { } elseif ($source instanceof ProviderInterface) {
$provider = $source; $provider = $source;
} else { } else {
throw new InvalidArgumentException("Source must be a valid path or provider object"); throw new InvalidArgumentException("Source must be a valid path or provider object");
@ -253,7 +275,7 @@ class Fenom {
$fenom = new static($provider); $fenom = new static($provider);
/* @var Fenom $fenom */ /* @var Fenom $fenom */
$fenom->setCompileDir($compile_dir); $fenom->setCompileDir($compile_dir);
if($options) { if ($options) {
$fenom->setOptions($options); $fenom->setOptions($options);
} }
return $fenom; return $fenom;
@ -262,7 +284,8 @@ class Fenom {
/** /**
* @param Fenom\ProviderInterface $provider * @param Fenom\ProviderInterface $provider
*/ */
public function __construct(Fenom\ProviderInterface $provider) { public function __construct(Fenom\ProviderInterface $provider)
{
$this->_provider = $provider; $this->_provider = $provider;
} }
@ -272,7 +295,8 @@ class Fenom {
* @param string $dir directory to store compiled templates in * @param string $dir directory to store compiled templates in
* @return Fenom * @return Fenom
*/ */
public function setCompileDir($dir) { public function setCompileDir($dir)
{
$this->_compile_dir = $dir; $this->_compile_dir = $dir;
return $this; return $this;
} }
@ -280,24 +304,50 @@ class Fenom {
/** /**
* *
* @param callable $cb * @param callable $cb
* @return self
*/ */
public function addPreCompileFilter($cb) { public function addPreFilter($cb)
$this->_on_pre_cmp[] = $cb; {
$this->pre_filters[] = $cb;
return $this;
}
public function getPreFilters()
{
return $this->pre_filters;
} }
/** /**
* *
* @param callable $cb * @param callable $cb
* @return self
*/ */
public function addPostCompileFilter($cb) { public function addPostFilter($cb)
$this->_on_post_cmp[] = $cb; {
$this->post_filters[] = $cb;
return $this;
}
public function getPostFilters()
{
return $this->post_filters;
} }
/** /**
* @param callable $cb * @param callable $cb
* @return self
*/ */
public function addCompileFilter($cb) { public function addFilter($cb)
$this->_on_cmp[] = $cb; {
$this->filters[] = $cb;
return $this;
}
public function getFilters()
{
return $this->filters;
} }
/** /**
@ -307,7 +357,8 @@ class Fenom {
* @param string $callback the modifier callback * @param string $callback the modifier callback
* @return Fenom * @return Fenom
*/ */
public function addModifier($modifier, $callback) { public function addModifier($modifier, $callback)
{
$this->_modifiers[$modifier] = $callback; $this->_modifiers[$modifier] = $callback;
return $this; return $this;
} }
@ -319,7 +370,8 @@ class Fenom {
* @param callable $parser * @param callable $parser
* @return Fenom * @return Fenom
*/ */
public function addCompiler($compiler, $parser) { public function addCompiler($compiler, $parser)
{
$this->_actions[$compiler] = array( $this->_actions[$compiler] = array(
'type' => self::INLINE_COMPILER, 'type' => self::INLINE_COMPILER,
'parser' => $parser 'parser' => $parser
@ -332,11 +384,12 @@ class Fenom {
* @param string|object $storage * @param string|object $storage
* @return $this * @return $this
*/ */
public function addCompilerSmart($compiler, $storage) { public function addCompilerSmart($compiler, $storage)
if(method_exists($storage, "tag".$compiler)) { {
if (method_exists($storage, "tag" . $compiler)) {
$this->_actions[$compiler] = array( $this->_actions[$compiler] = array(
'type' => self::INLINE_COMPILER, 'type' => self::INLINE_COMPILER,
'parser' => array($storage, "tag".$compiler) 'parser' => array($storage, "tag" . $compiler)
); );
} }
return $this; return $this;
@ -351,11 +404,12 @@ class Fenom {
* @param array $tags * @param array $tags
* @return Fenom * @return Fenom
*/ */
public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array()) { public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array())
{
$this->_actions[$compiler] = array( $this->_actions[$compiler] = array(
'type' => self::BLOCK_COMPILER, 'type' => self::BLOCK_COMPILER,
'open' => $open_parser, 'open' => $open_parser,
'close' => $close_parser ?: self::DEFAULT_CLOSE_COMPILER, 'close' => $close_parser ? : self::DEFAULT_CLOSE_COMPILER,
'tags' => $tags, 'tags' => $tags,
); );
return $this; return $this;
@ -369,27 +423,28 @@ class Fenom {
* @throws LogicException * @throws LogicException
* @return Fenom * @return Fenom
*/ */
public function addBlockCompilerSmart($compiler, $storage, array $tags, array $floats = array()) { public function addBlockCompilerSmart($compiler, $storage, array $tags, array $floats = array())
{
$c = array( $c = array(
'type' => self::BLOCK_COMPILER, 'type' => self::BLOCK_COMPILER,
"tags" => array(), "tags" => array(),
"float_tags" => array() "float_tags" => array()
); );
if(method_exists($storage, $compiler."Open")) { if (method_exists($storage, $compiler . "Open")) {
$c["open"] = $compiler."Open"; $c["open"] = $compiler . "Open";
} else { } else {
throw new \LogicException("Open compiler {$compiler}Open not found"); throw new \LogicException("Open compiler {$compiler}Open not found");
} }
if(method_exists($storage, $compiler."Close")) { if (method_exists($storage, $compiler . "Close")) {
$c["close"] = $compiler."Close"; $c["close"] = $compiler . "Close";
} else { } else {
throw new \LogicException("Close compiler {$compiler}Close not found"); throw new \LogicException("Close compiler {$compiler}Close not found");
} }
foreach($tags as $tag) { foreach ($tags as $tag) {
if(method_exists($storage, "tag".$tag)) { if (method_exists($storage, "tag" . $tag)) {
$c["tags"][ $tag ] = "tag".$tag; $c["tags"][$tag] = "tag" . $tag;
if($floats && in_array($tag, $floats)) { if ($floats && in_array($tag, $floats)) {
$c['float_tags'][ $tag ] = 1; $c['float_tags'][$tag] = 1;
} }
} else { } else {
throw new \LogicException("Tag compiler $tag (tag{$compiler}) not found"); throw new \LogicException("Tag compiler $tag (tag{$compiler}) not found");
@ -405,7 +460,8 @@ class Fenom {
* @param callable|string $parser * @param callable|string $parser
* @return Fenom * @return Fenom
*/ */
public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER) { public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER)
{
$this->_actions[$function] = array( $this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION, 'type' => self::INLINE_FUNCTION,
'parser' => $parser, 'parser' => $parser,
@ -419,7 +475,8 @@ class Fenom {
* @param callable $callback * @param callable $callback
* @return Fenom * @return Fenom
*/ */
public function addFunctionSmart($function, $callback) { public function addFunctionSmart($function, $callback)
{
$this->_actions[$function] = array( $this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION, 'type' => self::INLINE_FUNCTION,
'parser' => self::SMART_FUNC_PARSER, 'parser' => self::SMART_FUNC_PARSER,
@ -435,7 +492,8 @@ class Fenom {
* @param callable|string $parser_close * @param callable|string $parser_close
* @return Fenom * @return Fenom
*/ */
public function addBlockFunction($function, $callback, $parser_open = self::DEFAULT_FUNC_OPEN, $parser_close = self::DEFAULT_FUNC_CLOSE) { public function addBlockFunction($function, $callback, $parser_open = self::DEFAULT_FUNC_OPEN, $parser_close = self::DEFAULT_FUNC_CLOSE)
{
$this->_actions[$function] = array( $this->_actions[$function] = array(
'type' => self::BLOCK_FUNCTION, 'type' => self::BLOCK_FUNCTION,
'open' => $parser_open, 'open' => $parser_open,
@ -449,7 +507,8 @@ class Fenom {
* @param array $funcs * @param array $funcs
* @return Fenom * @return Fenom
*/ */
public function addAllowedFunctions(array $funcs) { public function addAllowedFunctions(array $funcs)
{
$this->_allowed_funcs = $this->_allowed_funcs + array_flip($funcs); $this->_allowed_funcs = $this->_allowed_funcs + array_flip($funcs);
return $this; return $this;
} }
@ -457,40 +516,75 @@ class Fenom {
/** /**
* Return modifier function * Return modifier function
* *
* @param $modifier * @param string $modifier
* @param Fenom\Template $template
* @return mixed * @return mixed
* @throws \Exception
*/ */
public function getModifier($modifier) { public function getModifier($modifier, Template $template = null)
if(isset($this->_modifiers[$modifier])) { {
if (isset($this->_modifiers[$modifier])) {
return $this->_modifiers[$modifier]; return $this->_modifiers[$modifier];
} elseif($this->isAllowedFunction($modifier)) { } elseif ($this->isAllowedFunction($modifier)) {
return $modifier; return $modifier;
} else { } else {
throw new \Exception("Modifier $modifier not found"); return $this->_loadModifier($modifier, $template);
} }
} }
/** /**
* Return function * @param string $modifier
* * @param Fenom\Template $template
* @param string $function * @return bool
* @return string|bool
*/ */
public function getFunction($function) { protected function _loadModifier($modifier, $template)
if(isset($this->_actions[$function])) { {
return $this->_actions[$function];
} else {
return false; return false;
} }
/**
* @param string $function
* @param Fenom\Template $template
* @return bool|string
* @deprecated
*/
public function getFunction($function, Template $template = null)
{
return $this->getTag($function, $template);
}
/**
* Returns tag info
*
* @param string $tag
* @param Fenom\Template $template
* @return string|bool
*/
public function getTag($tag, Template $template = null)
{
if (isset($this->_actions[$tag])) {
return $this->_actions[$tag];
} else {
return $this->_loadTag($tag, $template);
}
}
/**
* @param $tag
* @param Fenom\Template $template
* @return bool
*/
protected function _loadTag($tag, $template)
{
return false;
} }
/** /**
* @param string $function * @param string $function
* @return bool * @return bool
*/ */
public function isAllowedFunction($function) { public function isAllowedFunction($function)
if($this->_options & self::DENY_INLINE_FUNCS) { {
if ($this->_options & self::DENY_NATIVE_FUNCS) {
return isset($this->_allowed_funcs[$function]); return isset($this->_allowed_funcs[$function]);
} else { } else {
return is_callable($function); return is_callable($function);
@ -501,10 +595,11 @@ class Fenom {
* @param string $tag * @param string $tag
* @return array * @return array
*/ */
public function getTagOwners($tag) { public function getTagOwners($tag)
{
$tags = array(); $tags = array();
foreach($this->_actions as $owner => $params) { foreach ($this->_actions as $owner => $params) {
if(isset($params["tags"][$tag])) { if (isset($params["tags"][$tag])) {
$tags[] = $owner; $tags[] = $owner;
} }
} }
@ -517,21 +612,18 @@ class Fenom {
* @param string $scm scheme name * @param string $scm scheme name
* @param Fenom\ProviderInterface $provider provider object * @param Fenom\ProviderInterface $provider provider object
*/ */
public function addProvider($scm, \Fenom\ProviderInterface $provider) { public function addProvider($scm, \Fenom\ProviderInterface $provider)
{
$this->_providers[$scm] = $provider; $this->_providers[$scm] = $provider;
} }
/** /**
* Set options. May be bitwise mask of constants DENY_METHODS, DENY_INLINE_FUNCS, DENY_SET_VARS, INCLUDE_SOURCES, * Set options
* FORCE_COMPILE, CHECK_MTIME, or associative array with boolean values:
* disable_methods - disable all calls method in template
* disable_native_funcs - disable all native PHP functions in template
* force_compile - recompile template every time (very slow!)
* compile_check - check template modifications (slow!)
* @param int|array $options * @param int|array $options
*/ */
public function setOptions($options) { public function setOptions($options)
if(is_array($options)) { {
if (is_array($options)) {
$options = self::_makeMask($options, self::$_options_list, $this->_options); $options = self::_makeMask($options, self::$_options_list, $this->_options);
} }
$this->_storage = array(); $this->_storage = array();
@ -542,7 +634,8 @@ class Fenom {
* Get options as bits * Get options as bits
* @return int * @return int
*/ */
public function getOptions() { public function getOptions()
{
return $this->_options; return $this->_options;
} }
@ -551,9 +644,10 @@ class Fenom {
* @return Fenom\ProviderInterface * @return Fenom\ProviderInterface
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function getProvider($scm = false) { public function getProvider($scm = false)
if($scm) { {
if(isset($this->_providers[$scm])) { if ($scm) {
if (isset($this->_providers[$scm])) {
return $this->_providers[$scm]; return $this->_providers[$scm];
} else { } else {
throw new InvalidArgumentException("Provider for '$scm' not found"); throw new InvalidArgumentException("Provider for '$scm' not found");
@ -568,8 +662,9 @@ class Fenom {
* *
* @return Fenom\Template * @return Fenom\Template
*/ */
public function getRawTemplate() { public function getRawTemplate()
return new \Fenom\Template($this, $this->_options); {
return new Template($this, $this->_options);
} }
/** /**
@ -579,7 +674,8 @@ class Fenom {
* @param array $vars array of data for template * @param array $vars array of data for template
* @return Fenom\Render * @return Fenom\Render
*/ */
public function display($template, array $vars = array()) { public function display($template, array $vars = array())
{
return $this->getTemplate($template)->display($vars); return $this->getTemplate($template)->display($vars);
} }
@ -589,7 +685,8 @@ class Fenom {
* @param array $vars array of data for template * @param array $vars array of data for template
* @return mixed * @return mixed
*/ */
public function fetch($template, array $vars = array()) { public function fetch($template, array $vars = array())
{
return $this->getTemplate($template)->fetch($vars); return $this->getTemplate($template)->fetch($vars);
} }
@ -602,7 +699,8 @@ class Fenom {
* @param float $chunk * @param float $chunk
* @return array * @return array
*/ */
public function pipe($template, $callback, array $vars = array(), $chunk = 1e6) { public function pipe($template, $callback, array $vars = array(), $chunk = 1e6)
{
ob_start($callback, $chunk, true); ob_start($callback, $chunk, true);
$data = $this->getTemplate($template)->display($vars); $data = $this->getTemplate($template)->display($vars);
ob_end_flush(); ob_end_flush();
@ -616,21 +714,22 @@ class Fenom {
* @param int $options additional options and flags * @param int $options additional options and flags
* @return Fenom\Template * @return Fenom\Template
*/ */
public function getTemplate($template, $options = 0) { public function getTemplate($template, $options = 0)
{
$options |= $this->_options; $options |= $this->_options;
$key = dechex($options)."@".$template; $key = dechex($options) . "@" . $template;
if(isset($this->_storage[ $key ])) { if (isset($this->_storage[$key])) {
/** @var Fenom\Template $tpl */ /** @var Fenom\Template $tpl */
$tpl = $this->_storage[ $key ]; $tpl = $this->_storage[$key];
if(($this->_options & self::AUTO_RELOAD) && !$tpl->isValid()) { if (($this->_options & self::AUTO_RELOAD) && !$tpl->isValid()) {
return $this->_storage[ $key ] = $this->compile($template, true, $options); return $this->_storage[$key] = $this->compile($template, true, $options);
} else { } else {
return $tpl; return $tpl;
} }
} elseif($this->_options & self::FORCE_COMPILE) { } elseif ($this->_options & self::FORCE_COMPILE) {
return $this->compile($template, $this->_options & self::DISABLE_CACHE & ~self::FORCE_COMPILE, $options); return $this->compile($template, $this->_options & self::DISABLE_CACHE & ~self::FORCE_COMPILE, $options);
} else { } else {
return $this->_storage[ $key ] = $this->_load($template, $options); return $this->_storage[$key] = $this->_load($template, $options);
} }
} }
@ -639,9 +738,10 @@ class Fenom {
* @param string $template * @param string $template
* @return bool * @return bool
*/ */
public function templateExists($template) { public function templateExists($template)
if($provider = strstr($template, ":", true)) { {
if(isset($this->_providers[$provider])) { if ($provider = strstr($template, ":", true)) {
if (isset($this->_providers[$provider])) {
return $this->_providers[$provider]->templateExists(substr($template, strlen($provider) + 1)); return $this->_providers[$provider]->templateExists(substr($template, strlen($provider) + 1));
} }
} else { } else {
@ -657,13 +757,14 @@ class Fenom {
* @param int $opts * @param int $opts
* @return Fenom\Render * @return Fenom\Render
*/ */
protected function _load($tpl, $opts) { protected function _load($tpl, $opts)
{
$file_name = $this->_getCacheName($tpl, $opts); $file_name = $this->_getCacheName($tpl, $opts);
if(!is_file($this->_compile_dir."/".$file_name)) { if (!is_file($this->_compile_dir . "/" . $file_name)) {
return $this->compile($tpl, true, $opts); return $this->compile($tpl, true, $opts);
} else { } else {
$fenom = $this; $fenom = $this;
return include($this->_compile_dir."/".$file_name); return include($this->_compile_dir . "/" . $file_name);
} }
} }
@ -674,8 +775,9 @@ class Fenom {
* @param int $options * @param int $options
* @return string * @return string
*/ */
private function _getCacheName($tpl, $options) { private function _getCacheName($tpl, $options)
$hash = $tpl.":".$options; {
$hash = $tpl . ":" . $options;
return sprintf("%s.%x.%x.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash)); return sprintf("%s.%x.%x.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash));
} }
@ -688,20 +790,21 @@ class Fenom {
* @throws RuntimeException * @throws RuntimeException
* @return \Fenom\Template * @return \Fenom\Template
*/ */
public function compile($tpl, $store = true, $options = 0) { public function compile($tpl, $store = true, $options = 0)
{
$options = $this->_options | $options; $options = $this->_options | $options;
$template = Template::factory($this, $options)->load($tpl); $template = $this->getRawTemplate()->load($tpl);
if($store) { if ($store) {
$cache = $this->_getCacheName($tpl, $options); $cache = $this->_getCacheName($tpl, $options);
$tpl_tmp = tempnam($this->_compile_dir, $cache); $tpl_tmp = tempnam($this->_compile_dir, $cache);
$tpl_fp = fopen($tpl_tmp, "w"); $tpl_fp = fopen($tpl_tmp, "w");
if(!$tpl_fp) { if (!$tpl_fp) {
throw new \RuntimeException("Can't to open temporary file $tpl_tmp. Directory ".$this->_compile_dir." is writable?"); throw new \RuntimeException("Can't to open temporary file $tpl_tmp. Directory " . $this->_compile_dir . " is writable?");
} }
fwrite($tpl_fp, $template->getTemplateCode()); fwrite($tpl_fp, $template->getTemplateCode());
fclose($tpl_fp); fclose($tpl_fp);
$file_name = $this->_compile_dir."/".$cache; $file_name = $this->_compile_dir . "/" . $cache;
if(!rename($tpl_tmp, $file_name)) { if (!rename($tpl_tmp, $file_name)) {
throw new \RuntimeException("Can't to move $tpl_tmp to $tpl"); throw new \RuntimeException("Can't to move $tpl_tmp to $tpl");
} }
} }
@ -711,14 +814,16 @@ class Fenom {
/** /**
* Flush internal memory template cache * Flush internal memory template cache
*/ */
public function flush() { public function flush()
{
$this->_storage = array(); $this->_storage = array();
} }
/** /**
* Remove all compiled templates * Remove all compiled templates
*/ */
public function clearAllCompiles() { public function clearAllCompiles()
{
\Fenom\Provider::clean($this->_compile_dir); \Fenom\Provider::clean($this->_compile_dir);
} }
@ -729,8 +834,9 @@ class Fenom {
* @param string $name * @param string $name
* @return Fenom\Template * @return Fenom\Template
*/ */
public function compileCode($code, $name = 'Runtime compile') { public function compileCode($code, $name = 'Runtime compile')
return Template::factory($this, $this->_options)->source($name, $code); {
return $this->getRawTemplate()->source($name, $code);
} }
@ -743,7 +849,8 @@ class Fenom {
* @return int result, ( $mask | a ) & ~b * @return int result, ( $mask | a ) & ~b
* @throws \RuntimeException if key from custom assoc doesn't exists into possible values * @throws \RuntimeException if key from custom assoc doesn't exists into possible values
*/ */
private static function _makeMask(array $values, array $options, $mask = 0) { private static function _makeMask(array $values, array $options, $mask = 0)
{
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
if (isset($options[$key])) { if (isset($options[$key])) {
if ($options[$key]) { if ($options[$key]) {

View File

@ -8,6 +8,8 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Fenom; namespace Fenom;
use Fenom\Error\InvalidUsageException;
use Fenom\Error\UnexpectedTokenException;
use Fenom\Tokenizer; use Fenom\Tokenizer;
use Fenom\Template; use Fenom\Template;
use Fenom\Scope; use Fenom\Scope;
@ -17,7 +19,8 @@ use Fenom\Scope;
* @package Fenom * @package Fenom
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
class Compiler { class Compiler
{
/** /**
* Tag {include ...} * Tag {include ...}
* *
@ -27,24 +30,34 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagInclude(Tokenizer $tokens, Template $tpl) { public static function tagInclude(Tokenizer $tokens, Template $tpl)
{
$name = false;
// if($tokens->is('[')) {
// $tokens->next();
// if(!$name && $tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
// if($tpl->getStorage()->templateExists($_name = substr($tokens->getAndNext(), 1, -1))) {
// $name = $_name;
// }
// }
// }
$cname = $tpl->parsePlainArg($tokens, $name); $cname = $tpl->parsePlainArg($tokens, $name);
$p = $tpl->parseParams($tokens); $p = $tpl->parseParams($tokens);
if($p) { // if we have additionally variables if ($p) { // if we have additionally variables
if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known if ($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
$inc = $tpl->getStorage()->compile($name, false); $inc = $tpl->getStorage()->compile($name, false);
$tpl->addDepend($inc); $tpl->addDepend($inc);
return '$_tpl = (array)$tpl; $tpl->exchangeArray('.self::toArray($p).'+$_tpl); ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);'; return '$_tpl = (array)$tpl; $tpl->exchangeArray(' . self::toArray($p) . '+$_tpl); ?>' . $inc->getBody() . '<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
} else { } else {
return '$tpl->getStorage()->getTemplate('.$cname.')->display('.self::toArray($p).'+(array)$tpl);'; return '$tpl->getStorage()->getTemplate(' . $cname . ')->display(' . self::toArray($p) . '+(array)$tpl);';
} }
} else { } else {
if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known if ($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
$inc = $tpl->getStorage()->compile($name, false); $inc = $tpl->getStorage()->compile($name, false);
$tpl->addDepend($inc); $tpl->addDepend($inc);
return '$_tpl = (array)$tpl; ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);'; return '$_tpl = (array)$tpl; ?>' . $inc->getBody() . '<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
} else { } else {
return '$tpl->getStorage()->getTemplate('.$cname.')->display((array)$tpl);'; return '$tpl->getStorage()->getTemplate(' . $cname . ')->display((array)$tpl);';
} }
} }
} }
@ -58,9 +71,10 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function ifOpen(Tokenizer $tokens, Scope $scope) { public static function ifOpen(Tokenizer $tokens, Scope $scope)
{
$scope["else"] = false; $scope["else"] = false;
return 'if('.$scope->tpl->parseExp($tokens, true).') {'; return 'if(' . $scope->tpl->parseExp($tokens, true) . ') {';
} }
/** /**
@ -72,11 +86,12 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagElseIf(Tokenizer $tokens, Scope $scope) { public static function tagElseIf(Tokenizer $tokens, Scope $scope)
if($scope["else"]) { {
if ($scope["else"]) {
throw new InvalidUsageException('Incorrect use of the tag {elseif}'); throw new InvalidUsageException('Incorrect use of the tag {elseif}');
} }
return '} elseif('.$scope->tpl->parseExp($tokens, true).') {'; return '} elseif(' . $scope->tpl->parseExp($tokens, true) . ') {';
} }
/** /**
@ -88,7 +103,8 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function tagElse(Tokenizer $tokens, Scope $scope) { public static function tagElse(Tokenizer $tokens, Scope $scope)
{
$scope["else"] = true; $scope["else"] = true;
return '} else {'; return '} else {';
} }
@ -103,17 +119,18 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function foreachOpen(Tokenizer $tokens, Scope $scope) { public static function foreachOpen(Tokenizer $tokens, Scope $scope)
{
$p = array("index" => false, "first" => false, "last" => false); $p = array("index" => false, "first" => false, "last" => false);
$key = null; $key = null;
$before = $body = array(); $before = $body = array();
if($tokens->is(T_VARIABLE)) { if ($tokens->is(T_VARIABLE)) {
$from = $scope->tpl->parseVariable($tokens, Template::DENY_MODS); $from = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
$prepend = ""; $prepend = "";
} elseif($tokens->is('[')) { } elseif ($tokens->is('[')) {
$from = $scope->tpl->parseArray($tokens); $from = $scope->tpl->parseArray($tokens);
$uid = '$v'.$scope->tpl->i++; $uid = '$v' . $scope->tpl->i++;
$prepend = $uid.' = '.$from.';'; $prepend = $uid . ' = ' . $from . ';';
$from = $uid; $from = $uid;
} else { } else {
throw new UnexpectedTokenException($tokens, null, "tag {foreach}"); throw new UnexpectedTokenException($tokens, null, "tag {foreach}");
@ -121,7 +138,7 @@ class Compiler {
$tokens->get(T_AS); $tokens->get(T_AS);
$tokens->next(); $tokens->next();
$value = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY); $value = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
if($tokens->is(T_DOUBLE_ARROW)) { if ($tokens->is(T_DOUBLE_ARROW)) {
$tokens->next(); $tokens->next();
$key = $value; $key = $value;
$value = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY); $value = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
@ -130,35 +147,35 @@ class Compiler {
$scope["after"] = array(); $scope["after"] = array();
$scope["else"] = false; $scope["else"] = false;
while($token = $tokens->key()) { while ($token = $tokens->key()) {
$param = $tokens->get(T_STRING); $param = $tokens->get(T_STRING);
if(!isset($p[ $param ])) { if (!isset($p[$param])) {
throw new InvalidUsageException("Unknown parameter '$param' in {foreach}"); throw new InvalidUsageException("Unknown parameter '$param' in {foreach}");
} }
$tokens->getNext("="); $tokens->getNext("=");
$tokens->next(); $tokens->next();
$p[ $param ] = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY); $p[$param] = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
} }
if($p["index"]) { if ($p["index"]) {
$before[] = $p["index"].' = 0'; $before[] = $p["index"] . ' = 0';
$scope["after"][] = $p["index"].'++'; $scope["after"][] = $p["index"] . '++';
} }
if($p["first"]) { if ($p["first"]) {
$before[] = $p["first"].' = true'; $before[] = $p["first"] . ' = true';
$scope["after"][] = $p["first"] .' && ('. $p["first"].' = false )'; $scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )';
} }
if($p["last"]) { if ($p["last"]) {
$before[] = $p["last"].' = false'; $before[] = $p["last"] . ' = false';
$scope["uid"] = "v".$scope->tpl->i++; $scope["uid"] = "v" . $scope->tpl->i++;
$before[] = '$'.$scope["uid"]." = count($from)"; $before[] = '$' . $scope["uid"] . " = count($from)";
$body[] = 'if(!--$'.$scope["uid"].') '.$p["last"].' = true'; $body[] = 'if(!--$' . $scope["uid"] . ') ' . $p["last"] . ' = true';
} }
$before = $before ? implode("; ", $before).";" : ""; $before = $before ? implode("; ", $before) . ";" : "";
$body = $body ? implode("; ", $body).";" : ""; $body = $body ? implode("; ", $body) . ";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]).";" : ""; $scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
if($key) { if ($key) {
return "$prepend if($from) { $before foreach($from as $key => $value) { $body"; return "$prepend if($from) { $before foreach($from as $key => $value) { $body";
} else { } else {
return "$prepend if($from) { $before foreach($from as $value) { $body"; return "$prepend if($from) { $before foreach($from as $value) { $body";
@ -172,7 +189,8 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function foreachElse($tokens, Scope $scope) { public static function foreachElse($tokens, Scope $scope)
{
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true; $scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
return " {$scope['after']} } } else {"; return " {$scope['after']} } } else {";
} }
@ -185,8 +203,9 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function foreachClose($tokens, Scope $scope) { public static function foreachClose($tokens, Scope $scope)
if($scope["else"]) { {
if ($scope["else"]) {
return '}'; return '}';
} else { } else {
return " {$scope['after']} } }"; return " {$scope['after']} } }";
@ -200,7 +219,8 @@ class Compiler {
* @return string * @return string
* @throws InvalidUsageException * @throws InvalidUsageException
*/ */
public static function forOpen(Tokenizer $tokens, Scope $scope) { public static function forOpen(Tokenizer $tokens, Scope $scope)
{
$p = array("index" => false, "first" => false, "last" => false, "step" => 1, "to" => false, "max" => false, "min" => false); $p = array("index" => false, "first" => false, "last" => false, "step" => 1, "to" => false, "max" => false, "min" => false);
$scope["after"] = $before = $body = array(); $scope["after"] = $before = $body = array();
$i = array('', ''); $i = array('', '');
@ -211,40 +231,40 @@ class Compiler {
$val = $scope->tpl->parseExp($tokens, true); $val = $scope->tpl->parseExp($tokens, true);
$p = $scope->tpl->parseParams($tokens, $p); $p = $scope->tpl->parseParams($tokens, $p);
if(is_numeric($p["step"])) { if (is_numeric($p["step"])) {
if($p["step"] > 0) { if ($p["step"] > 0) {
$condition = "$var <= {$p['to']}"; $condition = "$var <= {$p['to']}";
if($p["last"]) $c = "($var + {$p['step']}) > {$p['to']}"; if ($p["last"]) $c = "($var + {$p['step']}) > {$p['to']}";
} elseif($p["step"] < 0) { } elseif ($p["step"] < 0) {
$condition = "$var >= {$p['to']}"; $condition = "$var >= {$p['to']}";
if($p["last"]) $c = "($var + {$p['step']}) < {$p['to']}"; if ($p["last"]) $c = "($var + {$p['step']}) < {$p['to']}";
} else { } else {
throw new InvalidUsageException("Invalid step value if {for}"); throw new InvalidUsageException("Invalid step value if {for}");
} }
} else { } else {
$condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})"; $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["last"]) $c = "({$p['step']} > 0 && ($var + {$p['step']}) <= {$p['to']} || {$p['step']} < 0 && ($var + {$p['step']}) >= {$p['to']})";
} }
if($p["first"]) { if ($p["first"]) {
$before[] = $p["first"].' = true'; $before[] = $p["first"] . ' = true';
$scope["after"][] = $p["first"] .' && ('. $p["first"].' = false )'; $scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )';
} }
if($p["last"]) { if ($p["last"]) {
$before[] = $p["last"].' = false'; $before[] = $p["last"] . ' = false';
$body[] = "if($c) {$p['last']} = true"; $body[] = "if($c) {$p['last']} = true";
} }
if($p["index"]) { if ($p["index"]) {
$i[0] .= $p["index"].' = 0,'; $i[0] .= $p["index"] . ' = 0,';
$i[1] .= $p["index"].'++,'; $i[1] .= $p["index"] . '++,';
} }
$scope["else"] = false; $scope["else"] = false;
$scope["else_cond"] = "$var==$val"; $scope["else_cond"] = "$var==$val";
$before = $before ? implode("; ", $before).";" : ""; $before = $before ? implode("; ", $before) . ";" : "";
$body = $body ? implode("; ", $body).";" : ""; $body = $body ? implode("; ", $body) . ";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]).";" : ""; $scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return "$before for({$i[0]} $var=$val; $condition;{$i[1]} $var+={$p['step']}) { $body"; return "$before for({$i[0]} $var=$val; $condition;{$i[1]} $var+={$p['step']}) { $body";
} }
@ -255,7 +275,8 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function forElse(Tokenizer $tokens, Scope $scope) { public static function forElse(Tokenizer $tokens, Scope $scope)
{
$scope["no-break"] = $scope["no-continue"] = true; $scope["no-break"] = $scope["no-continue"] = true;
$scope["else"] = true; $scope["else"] = true;
return " } if({$scope['else_cond']}) {"; return " } if({$scope['else_cond']}) {";
@ -267,8 +288,9 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function forClose($tokens, Scope $scope) { public static function forClose($tokens, Scope $scope)
if($scope["else"]) { {
if ($scope["else"]) {
return '}'; return '}';
} else { } else {
return " {$scope['after']} }"; return " {$scope['after']} }";
@ -281,8 +303,9 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function whileOpen(Tokenizer $tokens, Scope $scope) { public static function whileOpen(Tokenizer $tokens, Scope $scope)
return 'while('.$scope->tpl->parseExp($tokens, true).') {'; {
return 'while(' . $scope->tpl->parseExp($tokens, true) . ') {';
} }
/** /**
@ -293,9 +316,10 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function switchOpen(Tokenizer $tokens, Scope $scope) { public static function switchOpen(Tokenizer $tokens, Scope $scope)
{
$scope["no-break"] = $scope["no-continue"] = true; $scope["no-break"] = $scope["no-continue"] = true;
$scope["switch"] = 'switch('.$scope->tpl->parseExp($tokens, true).') {'; $scope["switch"] = 'switch(' . $scope->tpl->parseExp($tokens, true) . ') {';
// lazy init // lazy init
return ''; return '';
} }
@ -308,11 +332,12 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function tagCase(Tokenizer $tokens, Scope $scope) { public static function tagCase(Tokenizer $tokens, Scope $scope)
$code = 'case '.$scope->tpl->parseExp($tokens, true).': '; {
if($scope["switch"]) { $code = 'case ' . $scope->tpl->parseExp($tokens, true) . ': ';
if ($scope["switch"]) {
unset($scope["no-break"], $scope["no-continue"]); unset($scope["no-break"], $scope["no-continue"]);
$code = $scope["switch"]."\n".$code; $code = $scope["switch"] . "\n" . $code;
$scope["switch"] = ""; $scope["switch"] = "";
} }
return $code; return $code;
@ -327,8 +352,9 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagContinue($tokens, Scope $scope) { public static function tagContinue($tokens, Scope $scope)
if(empty($scope["no-continue"])) { {
if (empty($scope["no-continue"])) {
return 'continue;'; return 'continue;';
} else { } else {
throw new InvalidUsageException("Improper usage of the tag {continue}"); throw new InvalidUsageException("Improper usage of the tag {continue}");
@ -343,11 +369,12 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function tagDefault($tokens, Scope $scope) { public static function tagDefault($tokens, Scope $scope)
{
$code = 'default: '; $code = 'default: ';
if($scope["switch"]) { if ($scope["switch"]) {
unset($scope["no-break"], $scope["no-continue"]); unset($scope["no-break"], $scope["no-continue"]);
$code = $scope["switch"]."\n".$code; $code = $scope["switch"] . "\n" . $code;
$scope["switch"] = ""; $scope["switch"] = "";
} }
return $code; return $code;
@ -362,8 +389,9 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagBreak($tokens, Scope $scope) { public static function tagBreak($tokens, Scope $scope)
if(empty($scope["no-break"])) { {
if (empty($scope["no-break"])) {
return 'break;'; return 'break;';
} else { } else {
throw new InvalidUsageException("Improper usage of the tag {break}"); throw new InvalidUsageException("Improper usage of the tag {break}");
@ -377,32 +405,33 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagExtends(Tokenizer $tokens, Template $tpl) { public static function tagExtends(Tokenizer $tokens, Template $tpl)
if(!empty($tpl->_extends)) { {
if (!empty($tpl->_extends)) {
throw new InvalidUsageException("Only one {extends} allowed"); throw new InvalidUsageException("Only one {extends} allowed");
} elseif($tpl->getStackSize()) { } elseif ($tpl->getStackSize()) {
throw new InvalidUsageException("Tags {extends} can not be nested"); throw new InvalidUsageException("Tags {extends} can not be nested");
} }
$tpl_name = $tpl->parsePlainArg($tokens, $name); $tpl_name = $tpl->parsePlainArg($tokens, $name);
if(empty($tpl->_extended)) { if (empty($tpl->_extended)) {
$tpl->addPostCompile(__CLASS__."::extendBody"); $tpl->addPostCompile(__CLASS__ . "::extendBody");
} }
if($tpl->getOptions() & Template::DYNAMIC_EXTEND) { if ($tpl->getOptions() & Template::DYNAMIC_EXTEND) {
$tpl->_compatible = true; $tpl->_compatible = true;
} }
if($name) { // static extends if ($name) { // static extends
$tpl->_extends = $tpl->getStorage()->getRawTemplate()->load($name, false); $tpl->_extends = $tpl->getStorage()->getRawTemplate()->load($name, false);
if(!isset($tpl->_compatible)) { if (!isset($tpl->_compatible)) {
$tpl->_compatible = &$tpl->_extends->_compatible; $tpl->_compatible = & $tpl->_extends->_compatible;
} }
$tpl->addDepend($tpl->_extends); $tpl->addDepend($tpl->_extends);
return ""; return "";
} else { // dynamic extends } else { // dynamic extends
if(!isset($tpl->_compatible)) { if (!isset($tpl->_compatible)) {
$tpl->_compatible = true; $tpl->_compatible = true;
} }
$tpl->_extends = $tpl_name; $tpl->_extends = $tpl_name;
return '$parent = $tpl->getStorage()->getTemplate('.$tpl_name.', \Fenom\Template::EXTENDED);'; return '$parent = $tpl->getStorage()->getTemplate(' . $tpl_name . ', \Fenom\Template::EXTENDED);';
} }
} }
@ -411,35 +440,36 @@ class Compiler {
* @param string $body * @param string $body
* @param Template $tpl * @param Template $tpl
*/ */
public static function extendBody(&$body, $tpl) { public static function extendBody(&$body, $tpl)
{
$t = $tpl; $t = $tpl;
if($tpl->uses) { if ($tpl->uses) {
$tpl->blocks += $tpl->uses; $tpl->blocks += $tpl->uses;
} }
while(isset($t->_extends)) { while (isset($t->_extends)) {
$t = $t->_extends; $t = $t->_extends;
if(is_object($t)) { if (is_object($t)) {
/* @var \Fenom\Template $t */ /* @var \Fenom\Template $t */
$t->_extended = true; $t->_extended = true;
$tpl->addDepend($t); $tpl->addDepend($t);
$t->_compatible = &$tpl->_compatible; $t->_compatible = & $tpl->_compatible;
$t->blocks = &$tpl->blocks; $t->blocks = & $tpl->blocks;
$t->compile(); $t->compile();
if($t->uses) { if ($t->uses) {
$tpl->blocks += $t->uses; $tpl->blocks += $t->uses;
} }
if(!isset($t->_extends)) { // last item => parent if (!isset($t->_extends)) { // last item => parent
if(empty($tpl->_compatible)) { if (empty($tpl->_compatible)) {
$body = $t->getBody(); $body = $t->getBody();
} else { } else {
$body = '<?php ob_start(); ?>'.$body.'<?php ob_end_clean(); ?>'.$t->getBody(); $body = '<?php ob_start(); ?>' . $body . '<?php ob_end_clean(); ?>' . $t->getBody();
} }
return; return;
} else { } else {
$body .= $t->getBody(); $body .= $t->getBody();
} }
} else { } else {
$body = '<?php ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->b = &$tpl->b; $parent->display((array)$tpl); unset($tpl->b, $parent->b); ?>'; $body = '<?php ob_start(); ?>' . $body . '<?php ob_end_clean(); $parent->b = &$tpl->b; $parent->display((array)$tpl); unset($tpl->b, $parent->b); ?>';
return; return;
} }
} }
@ -452,34 +482,35 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagUse(Tokenizer $tokens, Template $tpl) { public static function tagUse(Tokenizer $tokens, Template $tpl)
if($tpl->getStackSize()) { {
if ($tpl->getStackSize()) {
throw new InvalidUsageException("Tags {use} can not be nested"); throw new InvalidUsageException("Tags {use} can not be nested");
} }
$cname = $tpl->parsePlainArg($tokens, $name); $cname = $tpl->parsePlainArg($tokens, $name);
if($name) { if ($name) {
$donor = $tpl->getStorage()->getRawTemplate()->load($name, false); $donor = $tpl->getStorage()->getRawTemplate()->load($name, false);
$donor->_extended = true; $donor->_extended = true;
$donor->_extends = $tpl; $donor->_extends = $tpl;
$donor->_compatible = &$tpl->_compatible; $donor->_compatible = & $tpl->_compatible;
//$donor->blocks = &$tpl->blocks; //$donor->blocks = &$tpl->blocks;
$donor->compile(); $donor->compile();
$blocks = $donor->blocks; $blocks = $donor->blocks;
foreach($blocks as $name => $code) { foreach ($blocks as $name => $code) {
if(isset($tpl->blocks[$name])) { if (isset($tpl->blocks[$name])) {
$tpl->blocks[$name] = $code; $tpl->blocks[$name] = $code;
unset($blocks[$name]); unset($blocks[$name]);
} }
} }
$tpl->uses = $blocks + $tpl->uses; $tpl->uses = $blocks + $tpl->uses;
$tpl->addDepend($donor); $tpl->addDepend($donor);
return '?>'.$donor->getBody().'<?php '; return '?>' . $donor->getBody() . '<?php ';
} else { } else {
// throw new InvalidUsageException('template name must be given explicitly yet'); // throw new InvalidUsageException('template name must be given explicitly yet');
// under construction // under construction
$tpl->_compatible = true; $tpl->_compatible = true;
return '$donor = $tpl->getStorage()->getTemplate('.$cname.', \Fenom\Template::EXTENDED);'.PHP_EOL. return '$donor = $tpl->getStorage()->getTemplate(' . $cname . ', \Fenom\Template::EXTENDED);' . PHP_EOL .
'$donor->fetch((array)$tpl);'.PHP_EOL. '$donor->fetch((array)$tpl);' . PHP_EOL .
'$tpl->b += (array)$donor->b'; '$tpl->b += (array)$donor->b';
} }
} }
@ -491,9 +522,9 @@ class Compiler {
* @return string * @return string
* @throws InvalidUsageException * @throws InvalidUsageException
*/ */
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) { public static function tagBlockOpen(Tokenizer $tokens, Scope $scope)
if($scope->level > 0) { {
var_dump("".$scope->tpl); if ($scope->level > 0) {
$scope->tpl->_compatible = true; $scope->tpl->_compatible = true;
} }
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name); $scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
@ -506,55 +537,56 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function tagBlockClose($tokens, Scope $scope) { public static function tagBlockClose($tokens, Scope $scope)
{
$tpl = $scope->tpl; $tpl = $scope->tpl;
if(isset($tpl->_extends)) { // is child if (isset($tpl->_extends)) { // is child
if($scope["name"]) { // is scalar name if ($scope["name"]) { // is scalar name
if($tpl->_compatible) { // is compatible mode if ($tpl->_compatible) { // is compatible mode
$scope->replaceContent( $scope->replaceContent(
'<?php /* 1) Block '.$tpl.': '.$scope["cname"].' */'.PHP_EOL.' if(empty($tpl->b['.$scope["cname"].'])) { '. '<?php /* 1) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(empty($tpl->b[' . $scope["cname"] . '])) { ' .
'$tpl->b['.$scope["cname"].'] = function($tpl) { ?>'.PHP_EOL. '$tpl->b[' . $scope["cname"] . '] = function($tpl) { ?>' . PHP_EOL .
$scope->getContent(). $scope->getContent() .
"<?php };". "<?php };" .
"} ?>".PHP_EOL "} ?>" . PHP_EOL
); );
} elseif(!isset($tpl->blocks[ $scope["name"] ])) { // is block not registered } elseif (!isset($tpl->blocks[$scope["name"]])) { // is block not registered
$tpl->blocks[ $scope["name"] ] = $scope->getContent(); $tpl->blocks[$scope["name"]] = $scope->getContent();
$scope->replaceContent( $scope->replaceContent(
'<?php /* 2) Block '.$tpl.': '.$scope["cname"].' '.$tpl->_compatible.' */'.PHP_EOL.' $tpl->b['.$scope["cname"].'] = function($tpl) { ?>'.PHP_EOL. '<?php /* 2) Block ' . $tpl . ': ' . $scope["cname"] . ' ' . $tpl->_compatible . ' */' . PHP_EOL . ' $tpl->b[' . $scope["cname"] . '] = function($tpl) { ?>' . PHP_EOL .
$scope->getContent(). $scope->getContent() .
"<?php }; ?>".PHP_EOL "<?php }; ?>" . PHP_EOL
); );
} }
} else { // dynamic name } else { // dynamic name
$tpl->_compatible = true; // enable compatible mode $tpl->_compatible = true; // enable compatible mode
$scope->replaceContent( $scope->replaceContent(
'<?php /* 3) Block '.$tpl.': '.$scope["cname"].' */'.PHP_EOL.' if(empty($tpl->b['.$scope["cname"].'])) { '. '<?php /* 3) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(empty($tpl->b[' . $scope["cname"] . '])) { ' .
'$tpl->b['.$scope["cname"].'] = function($tpl) { ?>'.PHP_EOL. '$tpl->b[' . $scope["cname"] . '] = function($tpl) { ?>' . PHP_EOL .
$scope->getContent(). $scope->getContent() .
"<?php };". "<?php };" .
"} ?>".PHP_EOL "} ?>" . PHP_EOL
); );
} }
} else { // is parent } else { // is parent
if(isset($tpl->blocks[ $scope["name"] ])) { // has block if (isset($tpl->blocks[$scope["name"]])) { // has block
if($tpl->_compatible) { // compatible mode enabled if ($tpl->_compatible) { // compatible mode enabled
$scope->replaceContent( $scope->replaceContent(
'<?php /* 4) Block '.$tpl.': '.$scope["cname"].' */'.PHP_EOL.' if(isset($tpl->b['.$scope["cname"].'])) { echo $tpl->b['.$scope["cname"].']->__invoke($tpl); } else {?>'.PHP_EOL. '<?php /* 4) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(isset($tpl->b[' . $scope["cname"] . '])) { echo $tpl->b[' . $scope["cname"] . ']->__invoke($tpl); } else {?>' . PHP_EOL .
$tpl->blocks[ $scope["name"] ]. $tpl->blocks[$scope["name"]] .
'<?php } ?>'.PHP_EOL '<?php } ?>' . PHP_EOL
); );
} else { } else {
$scope->replaceContent($tpl->blocks[ $scope["name"] ]); $scope->replaceContent($tpl->blocks[$scope["name"]]);
} }
// } elseif(isset($tpl->_extended) || !empty($tpl->_compatible)) { // } elseif(isset($tpl->_extended) || !empty($tpl->_compatible)) {
} elseif(isset($tpl->_extended) && $tpl->_compatible || empty($tpl->_extended)) { } elseif (isset($tpl->_extended) && $tpl->_compatible || empty($tpl->_extended)) {
$scope->replaceContent( $scope->replaceContent(
'<?php /* 5) Block '.$tpl.': '.$scope["cname"].' */'.PHP_EOL.' if(isset($tpl->b['.$scope["cname"].'])) { echo $tpl->b['.$scope["cname"].']->__invoke($tpl); } else {?>'.PHP_EOL. '<?php /* 5) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(isset($tpl->b[' . $scope["cname"] . '])) { echo $tpl->b[' . $scope["cname"] . ']->__invoke($tpl); } else {?>' . PHP_EOL .
$scope->getContent(). $scope->getContent() .
'<?php } ?>'.PHP_EOL '<?php } ?>' . PHP_EOL
); );
} }
} }
@ -562,8 +594,9 @@ class Compiler {
} }
public static function tagParent($tokens, Scope $scope) { public static function tagParent($tokens, Scope $scope)
if(empty($scope->tpl->_extends)) { {
if (empty($scope->tpl->_extends)) {
throw new InvalidUsageException("Tag {parent} may be declared in children"); throw new InvalidUsageException("Tag {parent} may be declared in children");
} }
} }
@ -574,7 +607,8 @@ class Compiler {
* @static * @static
* @return string * @return string
*/ */
public static function stdClose() { public static function stdClose()
{
return '}'; return '}';
} }
@ -587,8 +621,9 @@ class Compiler {
* @param Template $tpl * @param Template $tpl
* @return string * @return string
*/ */
public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) { public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl)
return "$function(".self::toArray($tpl->parseParams($tokens)).', $tpl)'; {
return "$function(" . self::toArray($tpl->parseParams($tokens)) . ', $tpl)';
} }
/** /**
@ -600,8 +635,9 @@ class Compiler {
* @param Template $tpl * @param Template $tpl
* @return string * @return string
*/ */
public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl) { public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl)
if(strpos($function, "::")) { {
if (strpos($function, "::")) {
list($class, $method) = explode("::", $function, 2); list($class, $method) = explode("::", $function, 2);
$ref = new \ReflectionMethod($class, $method); $ref = new \ReflectionMethod($class, $method);
} else { } else {
@ -609,16 +645,16 @@ class Compiler {
} }
$args = array(); $args = array();
$params = $tpl->parseParams($tokens); $params = $tpl->parseParams($tokens);
foreach($ref->getParameters() as $param) { foreach ($ref->getParameters() as $param) {
if(isset($params[ $param->getName() ])) { if (isset($params[$param->getName()])) {
$args[] = $params[ $param->getName() ]; $args[] = $params[$param->getName()];
} elseif(isset($params[ $param->getPosition() ])) { } elseif (isset($params[$param->getPosition()])) {
$args[] = $params[ $param->getPosition() ]; $args[] = $params[$param->getPosition()];
} elseif($param->isOptional()) { } elseif ($param->isOptional()) {
$args[] = var_export($param->getDefaultValue(), true); $args[] = var_export($param->getDefaultValue(), true);
} }
} }
return "$function(".implode(", ", $args).')'; return "$function(" . implode(", ", $args) . ')';
} }
/** /**
@ -629,7 +665,8 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function stdFuncOpen(Tokenizer $tokens, Scope $scope) { public static function stdFuncOpen(Tokenizer $tokens, Scope $scope)
{
$scope["params"] = self::toArray($scope->tpl->parseParams($tokens)); $scope["params"] = self::toArray($scope->tpl->parseParams($tokens));
return 'ob_start();'; return 'ob_start();';
} }
@ -642,8 +679,9 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function stdFuncClose($tokens, Scope $scope) { public static function stdFuncClose($tokens, Scope $scope)
return $scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl)'; {
return $scope["function"] . '(' . $scope["params"] . ', ob_get_clean(), $tpl)';
} }
/** /**
@ -651,13 +689,14 @@ class Compiler {
* @param $params * @param $params
* @return string * @return string
*/ */
public static function toArray($params) { public static function toArray($params)
{
$_code = array(); $_code = array();
foreach($params as $k => $v) { foreach ($params as $k => $v) {
$_code[] = '"'.$k.'" => '.$v; $_code[] = '"' . $k . '" => ' . $v;
} }
return 'array('.implode(",", $_code).')'; return 'array(' . implode(",", $_code) . ')';
} }
/** /**
@ -665,19 +704,20 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function varOpen(Tokenizer $tokens, Scope $scope) { public static function varOpen(Tokenizer $tokens, Scope $scope)
{
$var = $scope->tpl->parseVariable($tokens, Template::DENY_MODS); $var = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
if($tokens->is('=')) { // inline tag {var ...} if ($tokens->is('=')) { // inline tag {var ...}
$scope->is_closed = true; $scope->is_closed = true;
$tokens->next(); $tokens->next();
if($tokens->is("[")) { if ($tokens->is("[")) {
return $var.'='.$scope->tpl->parseArray($tokens); return $var . '=' . $scope->tpl->parseArray($tokens);
} else { } else {
return $var.'='.$scope->tpl->parseExp($tokens, true); return $var . '=' . $scope->tpl->parseExp($tokens, true);
} }
} else { } else {
$scope["name"] = $var; $scope["name"] = $var;
if($tokens->is('|')) { if ($tokens->is('|')) {
$scope["value"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()"); $scope["value"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
} else { } else {
$scope["value"] = "ob_get_clean()"; $scope["value"] = "ob_get_clean()";
@ -691,8 +731,9 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function varClose(Tokenizer $tokens, Scope $scope) { public static function varClose(Tokenizer $tokens, Scope $scope)
return $scope["name"].'='.$scope["value"].';'; {
return $scope["name"] . '=' . $scope["value"] . ';';
} }
@ -701,7 +742,8 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function filterOpen(Tokenizer $tokens, Scope $scope) { public static function filterOpen(Tokenizer $tokens, Scope $scope)
{
$scope["filter"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()"); $scope["filter"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
return "ob_start();"; return "ob_start();";
} }
@ -711,8 +753,9 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @return string * @return string
*/ */
public static function filterClose($tokens, Scope $scope) { public static function filterClose($tokens, Scope $scope)
return "echo ".$scope["filter"].";"; {
return "echo " . $scope["filter"] . ";";
} }
/** /**
@ -723,22 +766,23 @@ class Compiler {
* @return string * @return string
* @throws InvalidUsageException * @throws InvalidUsageException
*/ */
public static function tagCycle(Tokenizer $tokens, Template $tpl) { public static function tagCycle(Tokenizer $tokens, Template $tpl)
if($tokens->is("[")) { {
if ($tokens->is("[")) {
$exp = $tpl->parseArray($tokens); $exp = $tpl->parseArray($tokens);
} else { } else {
$exp = $tpl->parseExp($tokens, true); $exp = $tpl->parseExp($tokens, true);
} }
if($tokens->valid()) { if ($tokens->valid()) {
$p = $tpl->parseParams($tokens); $p = $tpl->parseParams($tokens);
if(empty($p["index"])) { if (empty($p["index"])) {
throw new InvalidUsageException("Cycle may contain only index attribute"); throw new InvalidUsageException("Cycle may contain only index attribute");
} else { } else {
return 'echo '.__CLASS__.'::cycle('.$exp.', '.$p["index"].')'; return 'echo ' . __CLASS__ . '::cycle(' . $exp . ', ' . $p["index"] . ')';
} }
} else { } else {
$var = $tpl->tmpVar(); $var = $tpl->tmpVar();
return 'echo '.__CLASS__.'::cycle('.$exp.", isset($var) ? ++$var : ($var = 0) )"; return 'echo ' . __CLASS__ . '::cycle(' . $exp . ", isset($var) ? ++$var : ($var = 0) )";
} }
} }
@ -748,7 +792,8 @@ class Compiler {
* @param $index * @param $index
* @return mixed * @return mixed
*/ */
public static function cycle($vals, $index) { public static function cycle($vals, $index)
{
return $vals[$index % count($vals)]; return $vals[$index % count($vals)];
} }
@ -761,36 +806,37 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagImport(Tokenizer $tokens, Template $tpl) { public static function tagImport(Tokenizer $tokens, Template $tpl)
{
$import = array(); $import = array();
if($tokens->is('[')) { if ($tokens->is('[')) {
$tokens->next(); $tokens->next();
while($tokens->valid()) { while ($tokens->valid()) {
if($tokens->is(Tokenizer::MACRO_STRING)) { if ($tokens->is(Tokenizer::MACRO_STRING)) {
$import[ $tokens->current() ] = true; $import[$tokens->current()] = true;
$tokens->next(); $tokens->next();
} elseif($tokens->is(']')) { } elseif ($tokens->is(']')) {
$tokens->next(); $tokens->next();
break; break;
} elseif($tokens->is(',')) { } elseif ($tokens->is(',')) {
$tokens->next(); $tokens->next();
} else { } else {
break; break;
} }
} }
if($tokens->current() != "from") { if ($tokens->current() != "from") {
throw new UnexpectedTokenException($tokens); throw new UnexpectedTokenException($tokens);
} }
$tokens->next(); $tokens->next();
} }
$tpl->parsePlainArg($tokens, $name); $tpl->parsePlainArg($tokens, $name);
if(!$name) { if (!$name) {
throw new InvalidUsageException("Invalid usage tag {import}"); throw new InvalidUsageException("Invalid usage tag {import}");
} }
if($tokens->is(T_AS)) { if ($tokens->is(T_AS)) {
$alias = $tokens->next()->get(Tokenizer::MACRO_STRING); $alias = $tokens->next()->get(Tokenizer::MACRO_STRING);
if($alias === "macro") { if ($alias === "macro") {
$alias = ""; $alias = "";
} }
$tokens->next(); $tokens->next();
@ -798,18 +844,17 @@ class Compiler {
$alias = ""; $alias = "";
} }
$donor = $tpl->getStorage()->getRawTemplate()->load($name, true); $donor = $tpl->getStorage()->getRawTemplate()->load($name, true);
if($donor->macros) { if ($donor->macros) {
foreach($donor->macros as $name => $macro) { foreach ($donor->macros as $name => $macro) {
if($p = strpos($name, ".")) { if ($p = strpos($name, ".")) {
$name = substr($name, $p); $name = substr($name, $p);
} }
if($import && !isset($import[$name])) { if ($import && !isset($import[$name])) {
continue; continue;
} }
if($alias) { if ($alias) {
$name = $alias.'.'.$name; $name = $alias . '.' . $name;
} }
$tpl->macros[$name] = $macro; $tpl->macros[$name] = $macro;
} }
$tpl->addDepend($donor); $tpl->addDepend($donor);
@ -825,23 +870,25 @@ class Compiler {
* @param Scope $scope * @param Scope $scope
* @throws InvalidUsageException * @throws InvalidUsageException
*/ */
public static function macroOpen(Tokenizer $tokens, Scope $scope) { public static function macroOpen(Tokenizer $tokens, Scope $scope)
{
$scope["name"] = $tokens->get(Tokenizer::MACRO_STRING); $scope["name"] = $tokens->get(Tokenizer::MACRO_STRING);
$scope["args"] = array(); $scope["recursive"] = false;
$scope["defaults"] = array(); $args = array();
if(!$tokens->valid()) { $defaults = array();
if (!$tokens->valid()) {
return; return;
} }
$tokens->next()->need('(')->next(); $tokens->next()->need('(')->next();
if($tokens->is(')')) { if ($tokens->is(')')) {
return; return;
} }
while($tokens->is(Tokenizer::MACRO_STRING)) { while ($tokens->is(Tokenizer::MACRO_STRING, T_VARIABLE)) {
$scope["args"][] = $param = $tokens->getAndNext(); $args[] = $param = $tokens->getAndNext();
if($tokens->is('=')) { if ($tokens->is('=')) {
$tokens->next(); $tokens->next();
if($tokens->is(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_DNUMBER) || $tokens->isSpecialVal()) { if ($tokens->is(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_DNUMBER) || $tokens->isSpecialVal()) {
$scope["defaults"][ $param ] = $tokens->getAndNext(); $defaults[$param] = $tokens->getAndNext();
} else { } else {
throw new InvalidUsageException("Macro parameters may have only scalar defaults"); throw new InvalidUsageException("Macro parameters may have only scalar defaults");
} }
@ -849,7 +896,13 @@ class Compiler {
$tokens->skipIf(','); $tokens->skipIf(',');
} }
$tokens->skipIf(')'); $tokens->skipIf(')');
$scope["macro"] = array(
"name" => $scope["name"],
"args" => $args,
"defaults" => $defaults,
"body" => "",
"recursive" => false
);
return; return;
} }
@ -857,13 +910,13 @@ class Compiler {
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Scope $scope * @param Scope $scope
*/ */
public static function macroClose(Tokenizer $tokens, Scope $scope) { public static function macroClose(Tokenizer $tokens, Scope $scope)
$scope->tpl->macros[ $scope["name"] ] = array( {
"body" => $content = $scope->getContent(), if ($scope["recursive"]) {
"args" => $scope["args"], $scope["macro"]["recursive"] = true;
"defaults" => $scope["defaults"] }
); $scope["macro"]["body"] = $scope->cutContent();
$scope->tpl->_body = substr($scope->tpl->_body, 0, strlen($scope->tpl->_body) - strlen($content)); $scope->tpl->macros[$scope["name"]] = $scope["macro"];
} }
/** /**
@ -874,13 +927,14 @@ class Compiler {
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagRaw(Tokenizer $tokens, Template $tpl) { public static function tagRaw(Tokenizer $tokens, Template $tpl)
{
$escape = (bool)$tpl->escape; $escape = (bool)$tpl->escape;
$tpl->escape = false; $tpl->escape = false;
if($tokens->is(':')) { if ($tokens->is(':')) {
$func = $tokens->getNext(Tokenizer::MACRO_STRING); $func = $tokens->getNext(Tokenizer::MACRO_STRING);
$tag = $tpl->getStorage()->getFunction($func); $tag = $tpl->getStorage()->getTag($func, $tpl);
if($tag["type"] == \Fenom::INLINE_FUNCTION) { if ($tag["type"] == \Fenom::INLINE_FUNCTION) {
$code = $tpl->parseAct($tokens); $code = $tpl->parseAct($tokens);
} elseif ($tag["type"] == \Fenom::BLOCK_FUNCTION) { } elseif ($tag["type"] == \Fenom::BLOCK_FUNCTION) {
$code = $tpl->parseAct($tokens); $code = $tpl->parseAct($tokens);
@ -900,7 +954,8 @@ class Compiler {
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Scope $scope * @param Scope $scope
*/ */
public static function autoescapeOpen(Tokenizer $tokens, Scope $scope) { public static function autoescapeOpen(Tokenizer $tokens, Scope $scope)
{
$boolean = ($tokens->get(T_STRING) == "true" ? true : false); $boolean = ($tokens->get(T_STRING) == "true" ? true : false);
$scope["escape"] = $scope->tpl->escape; $scope["escape"] = $scope->tpl->escape;
$scope->tpl->escape = $boolean; $scope->tpl->escape = $boolean;
@ -911,7 +966,28 @@ class Compiler {
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Scope $scope * @param Scope $scope
*/ */
public static function autoescapeClose(Tokenizer $tokens, Scope $scope) { public static function autoescapeClose(Tokenizer $tokens, Scope $scope)
{
$scope->tpl->escape = $scope["escape"]; $scope->tpl->escape = $scope["escape"];
} }
/**
* Unset present variables
*
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
* @throws InvalidUsageException
*/
public static function tagUnset(Tokenizer $tokens, Template $tpl)
{
$vars = array();
while ($tokens->valid()) {
$vars[] = $tpl->parseVar($tokens);
}
if (!$vars) {
throw new InvalidUsageException("Unset must accept variable(s)");
}
return 'unset(' . implode(', ', $vars) . ')';
}
} }

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom\Error;
/**
* @package Fenom\Error
*/
class CompileException extends \ErrorException
{
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom\Error;
/**
* @package Fenom\Error
*/
class InvalidUsageException extends \LogicException
{
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom\Error;
/**
* @package Fenom\Error
*/
class SecurityException extends CompileException
{
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom\Error;
/**
* @package Fenom\Error
*/
class TokenizeException extends \RuntimeException
{
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom\Error;
use Fenom\Tokenizer;
/**
* Unexpected token
*/
class UnexpectedTokenException extends \RuntimeException
{
public function __construct(Tokenizer $tokens, $expect = null, $where = null)
{
if ($expect && count($expect) == 1 && is_string($expect[0])) {
$expect = ", expect '" . $expect[0] . "'";
} else {
$expect = "";
}
if (!$tokens->curr) {
$this->message = "Unexpected end of " . ($where ? : "expression") . "$expect";
} elseif ($tokens->curr[0] === T_WHITESPACE) {
$this->message = "Unexpected whitespace$expect";
} elseif ($tokens->curr[0] === T_BAD_CHARACTER) {
$this->message = "Unexpected bad characters (below ASCII 32 except \\t, \\n and \\r) in " . ($where ? : "expression") . "$expect";
} else {
$this->message = "Unexpected token '" . $tokens->current() . "' in " . ($where ? : "expression") . "$expect";
}
}
}
;

View File

@ -13,7 +13,8 @@ namespace Fenom;
* Collection of modifiers * Collection of modifiers
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
class Modifier { class Modifier
{
/** /**
* Date format * Date format
@ -22,10 +23,11 @@ class Modifier {
* @param string $format * @param string $format
* @return string * @return string
*/ */
public static function dateFormat($date, $format = "%b %e, %Y") { public static function dateFormat($date, $format = "%b %e, %Y")
if(is_string($date) && !is_numeric($date)) { {
if (is_string($date) && !is_numeric($date)) {
$date = strtotime($date); $date = strtotime($date);
if(!$date) $date = time(); if (!$date) $date = time();
} }
return strftime($format, $date); return strftime($format, $date);
} }
@ -35,10 +37,11 @@ class Modifier {
* @param string $format * @param string $format
* @return string * @return string
*/ */
public static function date($date, $format = "Y m d") { public static function date($date, $format = "Y m d")
if(is_string($date) && !is_numeric($date)) { {
if (is_string($date) && !is_numeric($date)) {
$date = strtotime($date); $date = strtotime($date);
if(!$date) $date = time(); if (!$date) $date = time();
} }
return date($format, $date); return date($format, $date);
} }
@ -50,8 +53,9 @@ class Modifier {
* @param string $type * @param string $type
* @return string * @return string
*/ */
public static function escape($text, $type = 'html') { public static function escape($text, $type = 'html')
switch(strtolower($type)) { {
switch (strtolower($type)) {
case "url": case "url":
return urlencode($text); return urlencode($text);
case "html"; case "html";
@ -68,8 +72,9 @@ class Modifier {
* @param string $type * @param string $type
* @return string * @return string
*/ */
public static function unescape($text, $type = 'html') { public static function unescape($text, $type = 'html')
switch(strtolower($type)) { {
switch (strtolower($type)) {
case "url": case "url":
return urldecode($text); return urldecode($text);
case "html"; case "html";
@ -89,23 +94,24 @@ class Modifier {
* @param bool $middle * @param bool $middle
* @return string * @return string
*/ */
public static function truncate($string, $length = 80, $etc = '...', $by_words = false, $middle = false) { public static function truncate($string, $length = 80, $etc = '...', $by_words = false, $middle = false)
if($middle) { {
if(preg_match('#^(.{'.$length.'}).*?(.{'.$length.'})?$#usS', $string, $match)) { if ($middle) {
if(count($match) == 3) { if (preg_match('#^(.{' . $length . '}).*?(.{' . $length . '})?$#usS', $string, $match)) {
if($by_words) { if (count($match) == 3) {
return preg_replace('#\s.*$#usS', "", $match[1]).$etc.preg_replace('#^.*\s#usS', "", $match[2]); if ($by_words) {
return preg_replace('#\s.*$#usS', "", $match[1]) . $etc . preg_replace('#^.*\s#usS', "", $match[2]);
} else { } else {
return $match[1].$etc.$match[2]; return $match[1] . $etc . $match[2];
} }
} }
} }
} else { } else {
if(preg_match('#^(.{'.$length.'})#usS', $string, $match)) { if (preg_match('#^(.{' . $length . '})#usS', $string, $match)) {
if($by_words) { if ($by_words) {
return preg_replace('#\s.*$#usS', "", $match[1]).$etc; return preg_replace('#\s.*$#usS', "", $match[1]) . $etc;
} else { } else {
return $match[1].$etc; return $match[1] . $etc;
} }
} }
} }
@ -119,9 +125,10 @@ class Modifier {
* @param bool $to_line strip line ends * @param bool $to_line strip line ends
* @return string * @return string
*/ */
public static function strip($str, $to_line = false) { public static function strip($str, $to_line = false)
{
$str = trim($str); $str = trim($str);
if($to_line) { if ($to_line) {
return preg_replace('#[\s]+#ms', ' ', $str); return preg_replace('#[\s]+#ms', ' ', $str);
} else { } else {
return preg_replace('#[ \t]{2,}#', ' ', $str); return preg_replace('#[ \t]{2,}#', ' ', $str);
@ -133,12 +140,13 @@ class Modifier {
* @param mixed $item * @param mixed $item
* @return int * @return int
*/ */
public static function length($item) { public static function length($item)
if(is_string($item)) { {
if (is_string($item)) {
return strlen(preg_replace('#[\x00-\x7F]|[\x80-\xDF][\x00-\xBF]|[\xE0-\xEF][\x00-\xBF]{2}#s', ' ', $item)); return strlen(preg_replace('#[\x00-\x7F]|[\x80-\xDF][\x00-\xBF]|[\xE0-\xEF][\x00-\xBF]{2}#s', ' ', $item));
} elseif (is_array($item)) { } elseif (is_array($item)) {
return count($item); return count($item);
} elseif($item instanceof \Countable) { } elseif ($item instanceof \Countable) {
return count($item); return count($item);
} else { } else {
return 0; return 0;
@ -151,10 +159,11 @@ class Modifier {
* @param mixed $haystack * @param mixed $haystack
* @return bool * @return bool
*/ */
public static function in($value, $haystack) { public static function in($value, $haystack)
if(is_array($haystack)) { {
if (is_array($haystack)) {
return in_array($value, $haystack) || array_key_exists($value, $haystack); return in_array($value, $haystack) || array_key_exists($value, $haystack);
} elseif(is_string($haystack)) { } elseif (is_string($haystack)) {
return strpos($haystack, $value) !== false; return strpos($haystack, $value) !== false;
} }
return false; return false;
@ -164,7 +173,8 @@ class Modifier {
* @param $value * @param $value
* @return bool * @return bool
*/ */
public static function isIterable($value) { public static function isIterable($value)
{
return is_array($value) || ($value instanceof \Iterator); return is_array($value) || ($value instanceof \Iterator);
} }
} }

View File

@ -10,11 +10,13 @@
namespace Fenom; namespace Fenom;
use Fenom\ProviderInterface; use Fenom\ProviderInterface;
/** /**
* Base template provider * Base template provider
* @author Ivan Shalganov * @author Ivan Shalganov
*/ */
class Provider implements ProviderInterface { class Provider implements ProviderInterface
{
private $_path; private $_path;
/** /**
@ -22,10 +24,11 @@ class Provider implements ProviderInterface {
* *
* @param string $path * @param string $path
*/ */
public static function clean($path) { public static function clean($path)
if(is_file($path)) { {
if (is_file($path)) {
unlink($path); unlink($path);
} elseif(is_dir($path)) { } elseif (is_dir($path)) {
$iterator = iterator_to_array( $iterator = iterator_to_array(
new \RecursiveIteratorIterator( new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path, new \RecursiveDirectoryIterator($path,
@ -33,13 +36,13 @@ class Provider implements ProviderInterface {
\RecursiveIteratorIterator::CHILD_FIRST \RecursiveIteratorIterator::CHILD_FIRST
) )
); );
foreach($iterator as $file) { foreach ($iterator as $file) {
/* @var \splFileInfo $file*/ /* @var \splFileInfo $file */
if($file->isFile()) { if ($file->isFile()) {
if(strpos($file->getBasename(), ".") !== 0) { if (strpos($file->getBasename(), ".") !== 0) {
unlink($file->getRealPath()); unlink($file->getRealPath());
} }
} elseif($file->isDir()) { } elseif ($file->isDir()) {
rmdir($file->getRealPath()); rmdir($file->getRealPath());
} }
} }
@ -51,9 +54,10 @@ class Provider implements ProviderInterface {
* *
* @param string $path * @param string $path
*/ */
public static function rm($path) { public static function rm($path)
{
self::clean($path); self::clean($path);
if(is_dir($path)) { if (is_dir($path)) {
rmdir($path); rmdir($path);
} }
} }
@ -62,8 +66,9 @@ class Provider implements ProviderInterface {
* @param string $template_dir directory of templates * @param string $template_dir directory of templates
* @throws \LogicException if directory doesn't exists * @throws \LogicException if directory doesn't exists
*/ */
public function __construct($template_dir) { public function __construct($template_dir)
if($_dir = realpath($template_dir)) { {
if ($_dir = realpath($template_dir)) {
$this->_path = $_dir; $this->_path = $_dir;
} else { } else {
throw new \LogicException("Template directory {$template_dir} doesn't exists"); throw new \LogicException("Template directory {$template_dir} doesn't exists");
@ -76,7 +81,8 @@ class Provider implements ProviderInterface {
* @param int $time load last modified time * @param int $time load last modified time
* @return string * @return string
*/ */
public function getSource($tpl, &$time) { public function getSource($tpl, &$time)
{
$tpl = $this->_getTemplatePath($tpl); $tpl = $this->_getTemplatePath($tpl);
clearstatcache(null, $tpl); clearstatcache(null, $tpl);
$time = filemtime($tpl); $time = filemtime($tpl);
@ -88,7 +94,8 @@ class Provider implements ProviderInterface {
* @param string $tpl * @param string $tpl
* @return int * @return int
*/ */
public function getLastModified($tpl) { public function getLastModified($tpl)
{
clearstatcache(null, $tpl = $this->_getTemplatePath($tpl)); clearstatcache(null, $tpl = $this->_getTemplatePath($tpl));
return filemtime($tpl); return filemtime($tpl);
} }
@ -99,7 +106,8 @@ class Provider implements ProviderInterface {
* @param string $extension all templates must have this extension, default .tpl * @param string $extension all templates must have this extension, default .tpl
* @return array|\Iterator * @return array|\Iterator
*/ */
public function getList($extension = "tpl") { public function getList($extension = "tpl")
{
$list = array(); $list = array();
$iterator = new \RecursiveIteratorIterator( $iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->_path, new \RecursiveDirectoryIterator($this->_path,
@ -107,9 +115,9 @@ class Provider implements ProviderInterface {
\RecursiveIteratorIterator::CHILD_FIRST \RecursiveIteratorIterator::CHILD_FIRST
); );
$path_len = strlen($this->_path); $path_len = strlen($this->_path);
foreach($iterator as $file) { foreach ($iterator as $file) {
/* @var \SplFileInfo $file */ /* @var \SplFileInfo $file */
if($file->isFile() && $file->getExtension() == $extension) { if ($file->isFile() && $file->getExtension() == $extension) {
$list[] = substr($file->getPathname(), $path_len + 1); $list[] = substr($file->getPathname(), $path_len + 1);
} }
} }
@ -122,8 +130,9 @@ class Provider implements ProviderInterface {
* @return string * @return string
* @throws \RuntimeException * @throws \RuntimeException
*/ */
protected function _getTemplatePath($tpl) { protected function _getTemplatePath($tpl)
if(($path = realpath($this->_path."/".$tpl)) && strpos($path, $this->_path) === 0) { {
if (($path = realpath($this->_path . "/" . $tpl)) && strpos($path, $this->_path) === 0) {
return $path; return $path;
} else { } else {
throw new \RuntimeException("Template $tpl not found"); throw new \RuntimeException("Template $tpl not found");
@ -134,8 +143,9 @@ class Provider implements ProviderInterface {
* @param string $tpl * @param string $tpl
* @return bool * @return bool
*/ */
public function templateExists($tpl) { public function templateExists($tpl)
return file_exists($this->_path."/".$tpl); {
return file_exists($this->_path . "/" . $tpl);
} }
/** /**
@ -144,10 +154,11 @@ class Provider implements ProviderInterface {
* @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name * @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name
* @return bool * @return bool
*/ */
public function verify(array $templates) { public function verify(array $templates)
foreach($templates as $template => $mtime) { {
clearstatcache(null, $template = $this->_path.'/'.$template); foreach ($templates as $template => $mtime) {
if(@filemtime($template) !== $mtime) { clearstatcache(null, $template = $this->_path . '/' . $template);
if (@filemtime($template) !== $mtime) {
return false; return false;
} }

View File

@ -14,7 +14,8 @@ namespace Fenom;
* @package Fenom * @package Fenom
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
interface ProviderInterface { interface ProviderInterface
{
/** /**
* @param string $tpl * @param string $tpl
* @return bool * @return bool

View File

@ -14,13 +14,15 @@ use Fenom;
* Primitive template * Primitive template
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
class Render extends \ArrayObject { class Render extends \ArrayObject
{
private static $_props = array( private static $_props = array(
"name" => "runtime", "name" => "runtime",
"base_name" => "", "base_name" => "",
"scm" => false, "scm" => false,
"time" => 0, "time" => 0,
"depends" => array() "depends" => array(),
"macros" => array()
); );
/** /**
* @var \Closure * @var \Closure
@ -72,49 +74,77 @@ class Render extends \ArrayObject {
* @param callable $code template body * @param callable $code template body
* @param array $props * @param array $props
*/ */
public function __construct(Fenom $fenom, \Closure $code, $props = array()) { public function __construct(Fenom $fenom, \Closure $code, array $props = array())
{
$this->_fenom = $fenom; $this->_fenom = $fenom;
$props += self::$_props; $props += self::$_props;
$this->_name = $props["name"]; $this->_name = $props["name"];
// $this->_provider = $this->_fenom->getProvider($props["scm"]);
$this->_scm = $props["scm"]; $this->_scm = $props["scm"];
$this->_time = $props["time"]; $this->_time = $props["time"];
$this->_depends = $props["depends"]; $this->_depends = $props["depends"];
$this->_macros = $props["macros"];
$this->_code = $code; $this->_code = $code;
} }
/** /**
* Get template storage * Get template storage
* @return Fenom * @return \Fenom
*/ */
public function getStorage() { public function getStorage()
{
return $this->_fenom; return $this->_fenom;
} }
public function getDepends() { /**
* Get depends list
* @return array
*/
public function getDepends()
{
return $this->_depends; return $this->_depends;
} }
public function getScm() { /**
* Get schema name
* @return string
*/
public function getScm()
{
return $this->_scm; return $this->_scm;
} }
public function getProvider() { /**
* Get provider of template source
* @return ProviderInterface
*/
public function getProvider()
{
return $this->_fenom->getProvider($this->_scm); return $this->_fenom->getProvider($this->_scm);
} }
public function getBaseName() { /**
* Get name without schema
* @return string
*/
public function getBaseName()
{
return $this->_base_name; return $this->_base_name;
} }
public function getOptions() { /**
* Get parse options
* @return int
*/
public function getOptions()
{
return $this->_options; return $this->_options;
} }
/** /**
* @return string * @return string
*/ */
public function __toString() { public function __toString()
{
return $this->_name; return $this->_name;
} }
@ -122,11 +152,13 @@ class Render extends \ArrayObject {
* Get template name * Get template name
* @return string * @return string
*/ */
public function getName() { public function getName()
{
return $this->_name; return $this->_name;
} }
public function getTime() { public function getTime()
{
return $this->_time; return $this->_time;
} }
@ -135,16 +167,17 @@ class Render extends \ArrayObject {
* Validate template * Validate template
* @return bool * @return bool
*/ */
public function isValid() { public function isValid()
if(count($this->_depends) === 1) { // if no external dependencies, only self {
if (count($this->_depends) === 1) { // if no external dependencies, only self
$provider = $this->_fenom->getProvider($this->_scm); $provider = $this->_fenom->getProvider($this->_scm);
if($provider->getLastModified($this->_name) !== $this->_time) { if ($provider->getLastModified($this->_name) !== $this->_time) {
return false; return false;
} }
} else { } else {
foreach($this->_depends as $scm => $templates) { foreach ($this->_depends as $scm => $templates) {
$provider = $this->_fenom->getProvider($scm); $provider = $this->_fenom->getProvider($scm);
if(!$provider->verify($templates)) { if (!$provider->verify($templates)) {
return false; return false;
} }
} }
@ -152,12 +185,22 @@ class Render extends \ArrayObject {
return true; return true;
} }
/**
* Get internal macro
* @param $name
* @return mixed
*/
public function getMacro($name) {
return $this->_macros[$name];
}
/** /**
* Execute template and write into output * Execute template and write into output
* @param array $values for template * @param array $values for template
* @return Render * @return Render
*/ */
public function display(array $values) { public function display(array $values)
{
$this->exchangeArray($values); $this->exchangeArray($values);
$this->_code->__invoke($this); $this->_code->__invoke($this);
return $this->exchangeArray(array()); return $this->exchangeArray(array());
@ -169,7 +212,8 @@ class Render extends \ArrayObject {
* @return string * @return string
* @throws \Exception * @throws \Exception
*/ */
public function fetch(array $values) { public function fetch(array $values)
{
ob_start(); ob_start();
try { try {
$this->display($values); $this->display($values);
@ -186,7 +230,8 @@ class Render extends \ArrayObject {
* @param $args * @param $args
* @throws \BadMethodCallException * @throws \BadMethodCallException
*/ */
public function __call($method, $args) { public function __call($method, $args)
throw new \BadMethodCallException("Unknown method ".$method); {
throw new \BadMethodCallException("Unknown method " . $method);
} }
} }

View File

@ -14,7 +14,8 @@ namespace Fenom;
* *
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
class Scope extends \ArrayObject { class Scope extends \ArrayObject
{
public $line = 0; public $line = 0;
public $name; public $name;
@ -40,13 +41,14 @@ class Scope extends \ArrayObject {
* @param int $level * @param int $level
* @param $body * @param $body
*/ */
public function __construct($name, $tpl, $line, $action, $level, &$body) { public function __construct($name, $tpl, $line, $action, $level, &$body)
{
$this->line = $line; $this->line = $line;
$this->name = $name; $this->name = $name;
$this->tpl = $tpl; $this->tpl = $tpl;
$this->_action = $action; $this->_action = $action;
$this->level = $level; $this->level = $level;
$this->_body = &$body; $this->_body = & $body;
$this->_offset = strlen($body); $this->_offset = strlen($body);
} }
@ -54,7 +56,8 @@ class Scope extends \ArrayObject {
* *
* @param string $function * @param string $function
*/ */
public function setFuncName($function) { public function setFuncName($function)
{
$this["function"] = $function; $this["function"] = $function;
$this->is_compiler = false; $this->is_compiler = false;
$this->escape = $this->tpl->escape; $this->escape = $this->tpl->escape;
@ -66,7 +69,8 @@ class Scope extends \ArrayObject {
* @param Tokenizer $tokenizer * @param Tokenizer $tokenizer
* @return mixed * @return mixed
*/ */
public function open($tokenizer) { public function open($tokenizer)
{
return call_user_func($this->_action["open"], $tokenizer, $this); return call_user_func($this->_action["open"], $tokenizer, $this);
} }
@ -77,9 +81,10 @@ class Scope extends \ArrayObject {
* @param int $level * @param int $level
* @return bool * @return bool
*/ */
public function hasTag($tag, $level) { public function hasTag($tag, $level)
if(isset($this->_action["tags"][$tag])) { {
if($level) { if (isset($this->_action["tags"][$tag])) {
if ($level) {
return isset($this->_action["float_tags"][$tag]); return isset($this->_action["float_tags"][$tag]);
} else { } else {
return true; return true;
@ -95,7 +100,8 @@ class Scope extends \ArrayObject {
* @param Tokenizer $tokenizer * @param Tokenizer $tokenizer
* @return string * @return string
*/ */
public function tag($tag, $tokenizer) { public function tag($tag, $tokenizer)
{
return call_user_func($this->_action["tags"][$tag], $tokenizer, $this); return call_user_func($this->_action["tags"][$tag], $tokenizer, $this);
} }
@ -105,7 +111,8 @@ class Scope extends \ArrayObject {
* @param Tokenizer $tokenizer * @param Tokenizer $tokenizer
* @return string * @return string
*/ */
public function close($tokenizer) { public function close($tokenizer)
{
return call_user_func($this->_action["close"], $tokenizer, $this); return call_user_func($this->_action["close"], $tokenizer, $this);
} }
@ -115,7 +122,8 @@ class Scope extends \ArrayObject {
* @throws \LogicException * @throws \LogicException
* @return string * @return string
*/ */
public function getContent() { public function getContent()
{
return substr($this->_body, $this->_offset); return substr($this->_body, $this->_offset);
} }
@ -125,7 +133,8 @@ class Scope extends \ArrayObject {
* @return string * @return string
* @throws \LogicException * @throws \LogicException
*/ */
public function cutContent() { public function cutContent()
{
$content = substr($this->_body, $this->_offset + 1); $content = substr($this->_body, $this->_offset + 1);
$this->_body = substr($this->_body, 0, $this->_offset); $this->_body = substr($this->_body, 0, $this->_offset);
return $content; return $content;
@ -136,12 +145,14 @@ class Scope extends \ArrayObject {
* *
* @param $new_content * @param $new_content
*/ */
public function replaceContent($new_content) { public function replaceContent($new_content)
{
$this->cutContent(); $this->cutContent();
$this->_body .= $new_content; $this->_body .= $new_content;
} }
public function unEscapeContent() { public function unEscapeContent()
{
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,16 +9,18 @@
*/ */
namespace Fenom; namespace Fenom;
use Fenom\Error\UnexpectedTokenException;
/** /**
* for <PHP 5.4 compatible * for PHP <5.4 compatible
*/ */
defined('T_INSTEADOF') || define('T_INSTEADOF', 341); defined('T_INSTEADOF') || define('T_INSTEADOF', 341);
defined('T_TRAIT') || define('T_TRAIT', 355); defined('T_TRAIT') || define('T_TRAIT', 355);
defined('T_TRAIT_C') || define('T_TRAIT_C', 365); defined('T_TRAIT_C') || define('T_TRAIT_C', 365);
/** /**
* for PHP <5.5 * for PHP <5.5 compatible
*/ */
defined('T_YIELD') || define('T_YIELD', 370); defined('T_YIELD') || define('T_YIELD', 390);
/** /**
* Each token have structure * Each token have structure
@ -35,7 +37,8 @@ defined('T_YIELD') || define('T_YIELD', 370);
* @package Fenom * @package Fenom
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
*/ */
class Tokenizer { class Tokenizer
{
const TOKEN = 0; const TOKEN = 0;
const TEXT = 1; const TEXT = 1;
const WHITESPACE = 2; const WHITESPACE = 2;
@ -100,7 +103,7 @@ class Tokenizer {
\T_INSTANCEOF => 1, \T_INSTEADOF => 1, \T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 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_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_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_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_VAR => 1, \T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_VAR => 1,
\T_WHILE => 1, \T_YIELD => 1, \T_USE => 1 \T_WHILE => 1, \T_YIELD => 1, \T_USE => 1
), ),
@ -112,7 +115,7 @@ class Tokenizer {
), ),
self::MACRO_BINARY => array( 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_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_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, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1, \T_SR => 1,
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1 "+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
), ),
@ -124,10 +127,10 @@ class Tokenizer {
), ),
self::MACRO_COND => array( self::MACRO_COND => array(
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1, "<" => 1, \T_SL => 1, \T_SR => 1, \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, \T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
), ),
self::MACRO_EQUALS => array( 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_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_MUL_EQUAL => 1, \T_OR_EQUAL => 1, \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1, '=' => 1 \T_XOR_EQUAL => 1, '=' => 1
), ),
@ -159,15 +162,16 @@ class Tokenizer {
/** /**
* @param $query * @param $query
*/ */
public function __construct($query) { public function __construct($query)
{
$tokens = array(-1 => array(\T_WHITESPACE, '', '', 1)); $tokens = array(-1 => array(\T_WHITESPACE, '', '', 1));
$_tokens = token_get_all("<?php ".$query); $_tokens = token_get_all("<?php " . $query);
$line = 1; $line = 1;
array_shift($_tokens); array_shift($_tokens);
$i = 0; $i = 0;
foreach($_tokens as $token) { foreach ($_tokens as $token) {
if(is_string($token)) { if (is_string($token)) {
if($token === '"' || $token === "'" || $token === "`") { if ($token === '"' || $token === "'" || $token === "`") {
$this->quotes++; $this->quotes++;
} }
$tokens[] = array( $tokens[] = array(
@ -178,7 +182,7 @@ class Tokenizer {
); );
$i++; $i++;
} elseif ($token[0] === \T_WHITESPACE) { } elseif ($token[0] === \T_WHITESPACE) {
$tokens[$i-1][2] = $token[1]; $tokens[$i - 1][2] = $token[1];
} else { } else {
$tokens[] = array( $tokens[] = array(
$token[0], $token[0],
@ -202,7 +206,8 @@ class Tokenizer {
* *
* @return int * @return int
*/ */
public function isIncomplete() { public function isIncomplete()
{
return ($this->quotes % 2) || ($this->tokens[$this->_max][0] === T_ENCAPSED_AND_WHITESPACE); return ($this->quotes % 2) || ($this->tokens[$this->_max][0] === T_ENCAPSED_AND_WHITESPACE);
} }
@ -212,7 +217,8 @@ class Tokenizer {
* @link http://php.net/manual/en/iterator.current.php * @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type. * @return mixed Can return any type.
*/ */
public function current() { public function current()
{
return $this->curr[1]; return $this->curr[1];
} }
@ -222,8 +228,9 @@ class Tokenizer {
* @link http://php.net/manual/en/iterator.next.php * @link http://php.net/manual/en/iterator.next.php
* @return Tokenizer * @return Tokenizer
*/ */
public function next() { public function next()
if($this->p > $this->_max) { {
if ($this->p > $this->_max) {
return $this; return $this;
} }
$this->p++; $this->p++;
@ -238,15 +245,16 @@ class Tokenizer {
* @param string|int $token * @param string|int $token
* @return bool * @return bool
*/ */
private function _valid($expects, $token) { private function _valid($expects, $token)
foreach($expects as $expect) { {
if(is_string($expect) || $expect < 1000) { foreach ($expects as $expect) {
if($expect === $token) { if (is_string($expect) || $expect < 1000) {
if ($expect === $token) {
return true; return true;
} }
} else { } else {
if(isset(self::$_macros[ $expect ][ $token ])) { if (isset(self::$_macros[$expect][$token])) {
return true; return true;
} }
} }
@ -260,13 +268,14 @@ class Tokenizer {
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
* @return mixed * @return mixed
*/ */
public function _next($tokens) { public function _next($tokens)
{
$this->next(); $this->next();
if(!$this->curr) { if (!$this->curr) {
throw new UnexpectedTokenException($this, $tokens); throw new UnexpectedTokenException($this, $tokens);
} }
if($tokens) { if ($tokens) {
if($this->_valid($tokens, $this->key())) { if ($this->_valid($tokens, $this->key())) {
return; return;
} }
} else { } else {
@ -279,7 +288,8 @@ class Tokenizer {
* Fetch next specified token or throw an exception * Fetch next specified token or throw an exception
* @return mixed * @return mixed
*/ */
public function getNext(/*int|string $token1, int|string $token2, ... */) { public function getNext( /*int|string $token1, int|string $token2, ... */)
{
$this->_next(func_get_args()); $this->_next(func_get_args());
return $this->current(); return $this->current();
} }
@ -288,7 +298,8 @@ class Tokenizer {
* @param $token * @param $token
* @return bool * @return bool
*/ */
public function isNextToken($token) { public function isNextToken($token)
{
return $this->next ? $this->next[1] == $token : false; return $this->next ? $this->next[1] == $token : false;
} }
@ -298,15 +309,16 @@ class Tokenizer {
* @param int $limit * @param int $limit
* @return string * @return string
*/ */
public function getSubstr($offset, $limit = 0) { public function getSubstr($offset, $limit = 0)
{
$str = ''; $str = '';
if(!$limit) { if (!$limit) {
$limit = $this->_max; $limit = $this->_max;
} else { } else {
$limit += $offset; $limit += $offset;
} }
for($i = $offset; $i <= $limit; $i++){ for ($i = $offset; $i <= $limit; $i++) {
$str .= $this->tokens[$i][1].$this->tokens[$i][2]; $str .= $this->tokens[$i][1] . $this->tokens[$i][2];
} }
return $str; return $str;
} }
@ -316,8 +328,9 @@ class Tokenizer {
* @return mixed * @return mixed
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
*/ */
public function getAndNext() { public function getAndNext( /* $token1, ... */)
if($this->curr) { {
if ($this->curr) {
$cur = $this->curr[1]; $cur = $this->curr[1];
$this->next(); $this->next();
return $cur; return $cur;
@ -331,7 +344,8 @@ class Tokenizer {
* @param $token1 * @param $token1
* @return bool * @return bool
*/ */
public function isNext($token1/*, ...*/) { public function isNext($token1 /*, ...*/)
{
return $this->next && $this->_valid(func_get_args(), $this->next[0]); return $this->next && $this->_valid(func_get_args(), $this->next[0]);
} }
@ -340,7 +354,8 @@ class Tokenizer {
* @param $token1 * @param $token1
* @return bool * @return bool
*/ */
public function is($token1/*, ...*/) { public function is($token1 /*, ...*/)
{
return $this->curr && $this->_valid(func_get_args(), $this->curr[0]); return $this->curr && $this->_valid(func_get_args(), $this->curr[0]);
} }
@ -349,7 +364,8 @@ class Tokenizer {
* @param $token1 * @param $token1
* @return bool * @return bool
*/ */
public function isPrev($token1/*, ...*/) { public function isPrev($token1 /*, ...*/)
{
return $this->prev && $this->_valid(func_get_args(), $this->prev[0]); return $this->prev && $this->_valid(func_get_args(), $this->prev[0]);
} }
@ -360,8 +376,9 @@ class Tokenizer {
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
* @return mixed * @return mixed
*/ */
public function get($token1 /*, $token2 ...*/) { public function get($token1 /*, $token2 ...*/)
if($this->curr && $this->_valid(func_get_args(), $this->curr[0])) { {
if ($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
return $this->curr[1]; return $this->curr[1];
} else { } else {
throw new UnexpectedTokenException($this, func_get_args()); throw new UnexpectedTokenException($this, func_get_args());
@ -372,8 +389,9 @@ class Tokenizer {
* Step back * Step back
* @return Tokenizer * @return Tokenizer
*/ */
public function back() { public function back()
if($this->p === 0) { {
if ($this->p === 0) {
return $this; return $this;
} }
$this->p--; $this->p--;
@ -385,12 +403,13 @@ class Tokenizer {
* @param $token1 * @param $token1
* @return bool * @return bool
*/ */
public function hasBackList($token1 /*, $token2 ...*/) { public function hasBackList($token1 /*, $token2 ...*/)
{
$tokens = func_get_args(); $tokens = func_get_args();
$c = $this->p; $c = $this->p;
foreach($tokens as $token) { foreach ($tokens as $token) {
$c--; $c--;
if($c < 0 || $this->tokens[$c][0] !== $token) { if ($c < 0 || $this->tokens[$c][0] !== $token) {
return false; return false;
} }
} }
@ -403,8 +422,9 @@ class Tokenizer {
* @param string $key * @param string $key
* @return mixed * @return mixed
*/ */
public function __get($key) { public function __get($key)
switch($key) { {
switch ($key) {
case 'curr': case 'curr':
return $this->curr = ($this->p <= $this->_max) ? $this->tokens[$this->p] : null; return $this->curr = ($this->p <= $this->_max) ? $this->tokens[$this->p] : null;
case 'next': case 'next':
@ -416,7 +436,8 @@ class Tokenizer {
} }
} }
public function count() { public function count()
{
return $this->_max; return $this->_max;
} }
@ -424,7 +445,8 @@ class Tokenizer {
* Return the key of the current element * Return the key of the current element
* @return mixed scalar on success, or null on failure. * @return mixed scalar on success, or null on failure.
*/ */
public function key() { public function key()
{
return $this->curr ? $this->curr[0] : null; return $this->curr ? $this->curr[0] : null;
} }
@ -433,7 +455,8 @@ class Tokenizer {
* @return boolean The return value will be casted to boolean and then evaluated. * @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure. * Returns true on success or false on failure.
*/ */
public function valid() { public function valid()
{
return (bool)$this->curr; return (bool)$this->curr;
} }
@ -443,12 +466,13 @@ class Tokenizer {
* @param int|string $token * @param int|string $token
* @return string * @return string
*/ */
public static function getName($token) { public static function getName($token)
if(is_string($token)) { {
if (is_string($token)) {
return $token; return $token;
} elseif(is_integer($token)) { } elseif (is_integer($token)) {
return token_name($token); return token_name($token);
} elseif(is_array($token)) { } elseif (is_array($token)) {
return token_name($token[0]); return token_name($token[0]);
} else { } else {
return null; return null;
@ -461,9 +485,10 @@ class Tokenizer {
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
* @return Tokenizer * @return Tokenizer
*/ */
public function skip(/*$token1, $token2, ...*/) { public function skip( /*$token1, $token2, ...*/)
if(func_num_args()) { {
if($this->_valid(func_get_args(), $this->curr[0])) { if (func_num_args()) {
if ($this->_valid(func_get_args(), $this->curr[0])) {
$this->next(); $this->next();
return $this; return $this;
} else { } else {
@ -481,8 +506,9 @@ class Tokenizer {
* @param int|string $token1 * @param int|string $token1
* @return Tokenizer * @return Tokenizer
*/ */
public function skipIf($token1/*, $token2, ...*/) { public function skipIf($token1 /*, $token2, ...*/)
if($this->_valid(func_get_args(), $this->curr[0])) { {
if ($this->_valid(func_get_args(), $this->curr[0])) {
$this->next(); $this->next();
} }
return $this; return $this;
@ -495,8 +521,9 @@ class Tokenizer {
* @return Tokenizer * @return Tokenizer
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
*/ */
public function need($token1/*, $token2, ...*/) { public function need($token1 /*, $token2, ...*/)
if($this->_valid(func_get_args(), $this->curr[0])) { {
if ($this->_valid(func_get_args(), $this->curr[0])) {
return $this; return $this;
} else { } else {
throw new UnexpectedTokenException($this, func_get_args()); throw new UnexpectedTokenException($this, func_get_args());
@ -509,37 +536,38 @@ class Tokenizer {
* @param int $after count tokens after current token * @param int $after count tokens after current token
* @return array * @return array
*/ */
public function getSnippet($before = 0, $after = 0) { public function getSnippet($before = 0, $after = 0)
{
$from = 0; $from = 0;
$to = $this->p; $to = $this->p;
if($before > 0) { if ($before > 0) {
if($before > $this->p) { if ($before > $this->p) {
$from = $this->p; $from = $this->p;
} else { } else {
$from = $before; $from = $before;
} }
} elseif($before < 0) { } elseif ($before < 0) {
$from = $this->p + $before; $from = $this->p + $before;
if($from < 0) { if ($from < 0) {
$from = 0; $from = 0;
} }
} }
if($after > 0) { if ($after > 0) {
$to = $this->p + $after; $to = $this->p + $after;
if($to > $this->_max) { if ($to > $this->_max) {
$to = $this->_max; $to = $this->_max;
} }
} elseif($after < 0) { } elseif ($after < 0) {
$to = $this->_max + $after; $to = $this->_max + $after;
if($to < $this->p) { if ($to < $this->p) {
$to = $this->p; $to = $this->p;
} }
} elseif($this->p > $this->_max) { } elseif ($this->p > $this->_max) {
$to = $this->_max; $to = $this->_max;
} }
$code = array(); $code = array();
for($i=$from; $i<=$to; $i++) { for ($i = $from; $i <= $to; $i++) {
$code[] = $this->tokens[ $i ]; $code[] = $this->tokens[$i];
} }
return $code; return $code;
@ -551,10 +579,11 @@ class Tokenizer {
* @param int $after * @param int $after
* @return string * @return string
*/ */
public function getSnippetAsString($before = 0, $after = 0) { public function getSnippetAsString($before = 0, $after = 0)
{
$str = ""; $str = "";
foreach($this->getSnippet($before, $after) as $token) { foreach ($this->getSnippet($before, $after) as $token) {
$str .= $token[1].$token[2]; $str .= $token[1] . $token[2];
} }
return trim(str_replace("\n", '↵', $str)); return trim(str_replace("\n", '↵', $str));
} }
@ -563,7 +592,8 @@ class Tokenizer {
* Check if current is special value: true, TRUE, false, FALSE, null, NULL * Check if current is special value: true, TRUE, false, FALSE, null, NULL
* @return bool * @return bool
*/ */
public function isSpecialVal() { public function isSpecialVal()
{
return isset(self::$spec[$this->current()]); return isset(self::$spec[$this->current()]);
} }
@ -571,14 +601,16 @@ class Tokenizer {
* Check if the token is last * Check if the token is last
* @return bool * @return bool
*/ */
public function isLast() { public function isLast()
{
return $this->p === $this->_max; return $this->p === $this->_max;
} }
/** /**
* Move pointer to the end * Move pointer to the end
*/ */
public function end() { public function end()
{
$this->p = $this->_max; $this->p = $this->_max;
} }
@ -586,7 +618,8 @@ class Tokenizer {
* Return line number of the current token * Return line number of the current token
* @return mixed * @return mixed
*/ */
public function getLine() { public function getLine()
{
return $this->curr ? $this->curr[3] : $this->_last_no; return $this->curr ? $this->curr[3] : $this->_last_no;
} }
@ -594,33 +627,13 @@ class Tokenizer {
* Is current token whitespaced, means previous token has whitespace characters * Is current token whitespaced, means previous token has whitespace characters
* @return bool * @return bool
*/ */
public function isWhiteSpaced() { public function isWhiteSpaced()
{
return $this->prev ? (bool)$this->prev[2] : false; return $this->prev ? (bool)$this->prev[2] : false;
} }
public function getWhitespace() { public function getWhitespace()
{
return $this->curr ? $this->curr[2] : false; return $this->curr ? $this->curr[2] : false;
} }
} }
/**
* Unexpected token
*/
class UnexpectedTokenException extends \RuntimeException {
public function __construct(Tokenizer $tokens, $expect = null, $where = null) {
if($expect && count($expect) == 1 && is_string($expect[0])) {
$expect = ", expect '".$expect[0]."'";
} else {
$expect = "";
}
if(!$tokens->curr) {
$this->message = "Unexpected end of ".($where?:"expression")."$expect";
} elseif($tokens->curr[0] === T_WHITESPACE) {
$this->message = "Unexpected whitespace$expect";
} elseif($tokens->curr[0] === T_BAD_CHARACTER) {
$this->message = "Unexpected bad characters (below ASCII 32 except \\t, \\n and \\r) in ".($where?:"expression")."$expect";
} else {
$this->message = "Unexpected token '".$tokens->current()."' in ".($where?:"expression")."$expect";
}
}
};

View File

@ -2,7 +2,8 @@
namespace Fenom; namespace Fenom;
use Fenom, Fenom\Provider as FS; use Fenom, Fenom\Provider as FS;
class TestCase extends \PHPUnit_Framework_TestCase { class TestCase extends \PHPUnit_Framework_TestCase
{
/** /**
* @var Fenom * @var Fenom
*/ */
@ -21,7 +22,8 @@ class TestCase extends \PHPUnit_Framework_TestCase {
3 => "three value", 3 => "three value",
); );
public static function getVars() { public static function getVars()
{
return array( return array(
"zero" => 0, "zero" => 0,
"one" => 1, "one" => 1,
@ -32,7 +34,9 @@ class TestCase extends \PHPUnit_Framework_TestCase {
"obj" => new \StdClass, "obj" => new \StdClass,
"list" => array( "list" => array(
"a" => 1, "a" => 1,
"b" => 2 "one" => 1,
"b" => 2,
"two" => 2
), ),
0 => "empty value", 0 => "empty value",
1 => "one value", 1 => "one value",
@ -41,50 +45,57 @@ class TestCase extends \PHPUnit_Framework_TestCase {
); );
} }
public function setUp() { public function setUp()
if(!file_exists(FENOM_RESOURCES.'/compile')) { {
mkdir(FENOM_RESOURCES.'/compile', 0777, true); if (!file_exists(FENOM_RESOURCES . '/compile')) {
mkdir(FENOM_RESOURCES . '/compile', 0777, true);
} else { } else {
FS::clean(FENOM_RESOURCES.'/compile/'); FS::clean(FENOM_RESOURCES . '/compile/');
} }
$this->fenom = Fenom::factory(FENOM_RESOURCES.'/template', FENOM_RESOURCES.'/compile'); $this->fenom = Fenom::factory(FENOM_RESOURCES . '/template', FENOM_RESOURCES . '/compile');
$this->fenom->addModifier('dots', __CLASS__.'::dots'); $this->fenom->addModifier('dots', __CLASS__ . '::dots');
$this->fenom->addModifier('concat', __CLASS__.'::concat'); $this->fenom->addModifier('concat', __CLASS__ . '::concat');
$this->fenom->addFunction('test_function', __CLASS__.'::inlineFunction'); $this->fenom->addFunction('test_function', __CLASS__ . '::inlineFunction');
$this->fenom->addBlockFunction('test_block_function', __CLASS__.'::blockFunction'); $this->fenom->addBlockFunction('test_block_function', __CLASS__ . '::blockFunction');
} }
public static function dots($value) { public static function dots($value)
return $value."..."; {
return $value . "...";
} }
public static function concat() { public static function concat()
{
return call_user_func_array('var_export', func_get_args()); return call_user_func_array('var_export', func_get_args());
} }
public static function inlineFunction($params) { public static function inlineFunction($params)
{
return isset($params["text"]) ? $params["text"] : ""; return isset($params["text"]) ? $params["text"] : "";
} }
public static function blockFunction($params, $text) { public static function blockFunction($params, $text)
{
return $text; return $text;
} }
public static function setUpBeforeClass() { public static function setUpBeforeClass()
if(!file_exists(FENOM_RESOURCES.'/template')) { {
mkdir(FENOM_RESOURCES.'/template', 0777, true); if (!file_exists(FENOM_RESOURCES . '/template')) {
mkdir(FENOM_RESOURCES . '/template', 0777, true);
} else { } else {
FS::clean(FENOM_RESOURCES.'/template/'); FS::clean(FENOM_RESOURCES . '/template/');
} }
} }
public function tpl($name, $code) { public function tpl($name, $code)
{
$dir = dirname($name); $dir = dirname($name);
if($dir != "." && !is_dir(FENOM_RESOURCES.'/template/'.$dir)) { if ($dir != "." && !is_dir(FENOM_RESOURCES . '/template/' . $dir)) {
mkdir(FENOM_RESOURCES.'/template/'.$dir, 0777, true); mkdir(FENOM_RESOURCES . '/template/' . $dir, 0777, true);
} }
file_put_contents(FENOM_RESOURCES.'/template/'.$name, $code); file_put_contents(FENOM_RESOURCES . '/template/' . $name, $code);
} }
/** /**
@ -96,20 +107,22 @@ class TestCase extends \PHPUnit_Framework_TestCase {
* @param int $options * @param int $options
* @param bool $dump dump source and result code (for debug) * @param bool $dump dump source and result code (for debug)
*/ */
public function exec($code, $vars, $result, $options = 0, $dump = false) { public function exec($code, $vars, $result, $options = 0, $dump = false)
{
$this->fenom->setOptions($options); $this->fenom->setOptions($options);
$tpl = $this->fenom->compileCode($code, "runtime.tpl"); $tpl = $this->fenom->compileCode($code, "runtime.tpl");
if($dump) { if ($dump) {
echo "\n========= DUMP BEGIN ===========\n".$code."\n--- to ---\n".$tpl->getBody()."\n========= DUMP END =============\n"; echo "\n========= DUMP BEGIN ===========\n" . $code . "\n--- to ---\n" . $tpl->getBody() . "\n========= DUMP END =============\n";
} }
$this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code"); $this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code");
} }
public function execTpl($name, $code, $vars, $result, $dump = false) { public function execTpl($name, $code, $vars, $result, $dump = false)
{
$this->tpl($name, $code); $this->tpl($name, $code);
$tpl = $this->fenom->getTemplate($name); $tpl = $this->fenom->getTemplate($name);
if($dump) { if ($dump) {
echo "\n========= DUMP BEGIN ===========\n".$code."\n--- to ---\n".$tpl->getBody()."\n========= DUMP END =============\n"; echo "\n========= DUMP BEGIN ===========\n" . $code . "\n--- to ---\n" . $tpl->getBody() . "\n========= DUMP END =============\n";
} }
$this->assertSame(Modifier::strip($result, true), Modifier::strip($tpl->fetch($vars), true), "Test tpl $name"); $this->assertSame(Modifier::strip($result, true), Modifier::strip($tpl->fetch($vars), true), "Test tpl $name");
} }
@ -121,12 +134,13 @@ class TestCase extends \PHPUnit_Framework_TestCase {
* @param string $message exception message * @param string $message exception message
* @param int $options Fenom's options * @param int $options Fenom's options
*/ */
public function execError($code, $exception, $message, $options = 0) { public function execError($code, $exception, $message, $options = 0)
{
$opt = $this->fenom->getOptions(); $opt = $this->fenom->getOptions();
$this->fenom->setOptions($options); $this->fenom->setOptions($options);
try { try {
$this->fenom->compileCode($code, "inline.tpl"); $this->fenom->compileCode($code, "inline.tpl");
} catch(\Exception $e) { } catch (\Exception $e) {
$this->assertSame($exception, get_class($e), "Exception $code"); $this->assertSame($exception, get_class($e), "Exception $code");
$this->assertStringStartsWith($message, $e->getMessage()); $this->assertStringStartsWith($message, $e->getMessage());
$this->fenom->setOptions($opt); $this->fenom->setOptions($opt);
@ -136,16 +150,18 @@ class TestCase extends \PHPUnit_Framework_TestCase {
$this->fail("Code $code must be invalid"); $this->fail("Code $code must be invalid");
} }
public function assertRender($tpl, $result, $debug = false) { public function assertRender($tpl, $result, $debug = false)
{
$template = $this->fenom->compileCode($tpl); $template = $this->fenom->compileCode($tpl);
if($debug) { if ($debug) {
print_r("$tpl:\n".$template->getBody()); print_r("$tpl:\n" . $template->getBody());
} }
$this->assertSame($result, $template->fetch($this->values)); $this->assertSame($result, $template->fetch($this->values));
} }
public static function providerNumbers() { public static function providerNumbers()
{
return array( return array(
array('0', 0), array('0', 0),
array('77', 77), array('77', 77),
@ -157,7 +173,8 @@ class TestCase extends \PHPUnit_Framework_TestCase {
); );
} }
public static function providerStrings() { public static function providerStrings()
{
return array( return array(
array('"str"', 'str'), array('"str"', 'str'),
array('"str\nand\nmany\nlines"', "str\nand\nmany\nlines"), array('"str\nand\nmany\nlines"', "str\nand\nmany\nlines"),
@ -187,48 +204,57 @@ class TestCase extends \PHPUnit_Framework_TestCase {
); );
} }
public function providerVariables() { public function providerVariables()
{
return array(
array('$one', 1),
array('$list.one', 1),
array('$list[$$.DEVELOP]', 1),
);
}
public static function providerObjects()
{
return array(); return array();
} }
public static function providerObjects() { public static function providerArrays()
return array(); {
}
public static function providerArrays() {
$scalars = array(); $scalars = array();
$data = array( $data = array(
array('[]', array()), array('[]', array()),
array('[[],[]]', array(array(), array())), array('[[],[]]', array(array(), array())),
); );
foreach(self::providerScalars() as $scalar) { foreach (self::providerScalars() as $scalar) {
$scalars[0][] = $scalar[0]; $scalars[0][] = $scalar[0];
$scalars[1][] = $scalar[1]; $scalars[1][] = $scalar[1];
$data[] = array( $data[] = array(
"[".$scalar[0]."]", "[" . $scalar[0] . "]",
array($scalar[1]) array($scalar[1])
); );
$data[] = array( $data[] = array(
"['some_key' =>".$scalar[0]."]", "['some_key' =>" . $scalar[0] . "]",
array('some_key' => $scalar[1]) array('some_key' => $scalar[1])
); );
} }
$data[] = array( $data[] = array(
"[".implode(", ", $scalars[0])."]", "[" . implode(", ", $scalars[0]) . "]",
$scalars[1] $scalars[1]
); );
return $data; return $data;
} }
public static function providerScalars() { public static function providerScalars()
{
return array_merge( return array_merge(
self::providerNumbers(), self::providerNumbers(),
self::providerStrings() self::providerStrings()
); );
} }
public static function providerValues() { public static function providerValues()
{
return array_merge( return array_merge(
self::providerScalars(), self::providerScalars(),
self::providerArrays(), self::providerArrays(),
@ -238,30 +264,36 @@ class TestCase extends \PHPUnit_Framework_TestCase {
} }
} }
class Fake implements \ArrayAccess { class Fake implements \ArrayAccess
{
public $vars; public $vars;
public function offsetExists($offset) { public function offsetExists($offset)
{
return true; return true;
} }
public function offsetGet($offset) { public function offsetGet($offset)
if($offset == "object") { {
if ($offset == "object") {
return new self(); return new self();
} else { } else {
return new self($offset); return new self($offset);
} }
} }
public function offsetSet($offset, $value) { public function offsetSet($offset, $value)
{
$this->vars[$offset] = $value; $this->vars[$offset] = $value;
} }
public function offsetUnset($offset) { public function offsetUnset($offset)
{
unset($this->vars[$offset]); unset($this->vars[$offset]);
} }
public function proxy() { public function proxy()
{
return implode(", ", func_get_args()); return implode(", ", func_get_args());
} }
} }

View File

@ -1,24 +1,27 @@
<?php <?php
require_once __DIR__."/../vendor/autoload.php"; require_once __DIR__ . "/../vendor/autoload.php";
define('FENOM_RESOURCES', __DIR__ . "/resources");
define('FENOM_RESOURCES', __DIR__."/resources"); require_once FENOM_RESOURCES . "/actions.php";
require_once __DIR__ . "/TestCase.php";
require_once FENOM_RESOURCES."/actions.php"; ini_set('date.timezone', 'Europe/Moscow');
require_once __DIR__."/TestCase.php";
function drop() { function drop()
{
call_user_func_array("var_dump", func_get_args()); call_user_func_array("var_dump", func_get_args());
$e = new Exception(); $e = new Exception();
echo "-------\nDump trace: \n".$e->getTraceAsString()."\n"; echo "-------\nDump trace: \n" . $e->getTraceAsString() . "\n";
exit(); exit();
} }
function dump() { function dump()
foreach(func_get_args() as $arg) { {
fwrite(STDERR, "DUMP: ".call_user_func("print_r", $arg, true)."\n"); foreach (func_get_args() as $arg) {
fwrite(STDERR, "DUMP: " . call_user_func("print_r", $arg, true) . "\n");
} }
} }

View File

@ -3,9 +3,11 @@
namespace Fenom; namespace Fenom;
class AutoEscapeTest extends TestCase { class AutoEscapeTest extends TestCase
{
public static function providerHTML() { public static function providerHTML()
{
$html = "<script>alert('injection');</script>"; $html = "<script>alert('injection');</script>";
$escaped = htmlspecialchars($html, ENT_COMPAT, 'UTF-8'); $escaped = htmlspecialchars($html, ENT_COMPAT, 'UTF-8');
$vars = array( $vars = array(
@ -17,7 +19,7 @@ class AutoEscapeTest extends TestCase {
array('{$html}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{$html}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw $html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{raw $html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw $html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{raw $html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw "{$html|up}"}, {$html}', strtoupper($html).", $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{raw "{$html|up}"}, {$html}', strtoupper($html) . ", $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{$html}{/autoescape}, {$html}', "$escaped, $html", $vars, 0), array('{autoescape true}{$html}{/autoescape}, {$html}', "$escaped, $html", $vars, 0),
array('{autoescape false}{$html}{/autoescape}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{autoescape false}{$html}{/autoescape}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{$html}{/autoescape}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{autoescape true}{$html}{/autoescape}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
@ -31,7 +33,7 @@ class AutoEscapeTest extends TestCase {
array('{test_function text=$html}, {$html}', "$html, $html", $vars, 0), array('{test_function text=$html}, {$html}', "$html, $html", $vars, 0),
array('{test_function text=$html}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{test_function text=$html}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw:test_function text=$html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{raw:test_function text=$html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw:test_function text="{$html|up}"}, {$html}', strtoupper($html).", $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{raw:test_function text="{$html|up}"}, {$html}', strtoupper($html) . ", $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$escaped, $html", $vars, 0), array('{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$escaped, $html", $vars, 0),
array('{autoescape false}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{autoescape false}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE), array('{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
@ -56,7 +58,8 @@ class AutoEscapeTest extends TestCase {
/** /**
* @dataProvider providerHTML * @dataProvider providerHTML
*/ */
public function testEscaping($tpl, $result, $vars, $options) { public function testEscaping($tpl, $result, $vars, $options)
{
$this->values = $vars; $this->values = $vars;
$this->fenom->setOptions($options); $this->fenom->setOptions($options);
$this->assertRender($tpl, $result); $this->assertRender($tpl, $result);

View File

@ -3,25 +3,29 @@
namespace Fenom; namespace Fenom;
class CommentTest extends TestCase { class CommentTest extends TestCase
{
/** /**
* @dataProvider providerScalars * @dataProvider providerScalars
*/ */
public function testInline($tpl_val) { public function testInline($tpl_val)
{
$this->assertRender("before {* $tpl_val *} after", "before after"); $this->assertRender("before {* $tpl_val *} after", "before after");
$this->assertRender("before {* {{$tpl_val}} {{$tpl_val}} *} after", "before after"); $this->assertRender("before {* {{$tpl_val}} {{$tpl_val}} *} after", "before after");
$this->assertRender("before {*{{$tpl_val}}*} after", "before after"); $this->assertRender("before {*{{$tpl_val}}*} after", "before after");
} }
public function testError() { public function testError()
$this->execError('{* ', 'Fenom\CompileException', "Unclosed comment block in line"); {
$this->execError('{* ', 'Fenom\Error\CompileException', "Unclosed comment block in line");
} }
/** /**
* @dataProvider providerScalars * @dataProvider providerScalars
*/ */
public function testMultiLine($tpl_val) { public function testMultiLine($tpl_val)
{
$this->assertRender( $this->assertRender(
"before-1\nbefore-2 {* before-3\nbefore-4 $tpl_val after-1\nafter-2 *} after-3\nafter-4{* dummy *}\nafter-5", "before-1\nbefore-2 {* before-3\nbefore-4 $tpl_val after-1\nafter-2 *} after-3\nafter-4{* dummy *}\nafter-5",
"before-1\nbefore-2 after-3\nafter-4\nafter-5" "before-1\nbefore-2 after-3\nafter-4\nafter-5"

View File

@ -3,14 +3,17 @@
namespace Fenom; namespace Fenom;
class CustomProviderTest extends TestCase { class CustomProviderTest extends TestCase
{
public function setUp() { public function setUp()
{
parent::setUp(); parent::setUp();
$this->fenom->addProvider("my", new Provider(FENOM_RESOURCES.'/provider')); $this->fenom->addProvider("my", new Provider(FENOM_RESOURCES . '/provider'));
} }
public function testCustom() { public function testCustom()
{
$this->assertRender("start: {include 'my:include.tpl'}", 'start: include template'); $this->assertRender("start: {include 'my:include.tpl'}", 'start: include template');
//$this->assertRender("start: {import 'my:macros.tpl' as ops} {ops.add a=3 b=6}"); //$this->assertRender("start: {import 'my:macros.tpl' as ops} {ops.add a=3 b=6}");
} }

View File

@ -2,10 +2,12 @@
namespace Fenom; namespace Fenom;
use Fenom, Fenom\TestCase; use Fenom, Fenom\TestCase;
class ExtendsTemplateTest extends TestCase { class ExtendsTemplateTest extends TestCase
{
public function _testSandbox() { public function _testSandbox()
$this->fenom = Fenom::factory(FENOM_RESOURCES.'/provider', FENOM_RESOURCES.'/compile'); {
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/provider', FENOM_RESOURCES . '/compile');
try { try {
print_r($this->fenom->getTemplate('use/child.tpl')->getBody()); print_r($this->fenom->getTemplate('use/child.tpl')->getBody());
} catch (\Exception $e) { } catch (\Exception $e) {
@ -19,7 +21,8 @@ class ExtendsTemplateTest extends TestCase {
* @param array $vars * @param array $vars
* @return array * @return array
*/ */
public static function templates(array $vars) { public static function templates(array $vars)
{
return array( return array(
array( array(
"name" => "level.0.tpl", "name" => "level.0.tpl",
@ -29,7 +32,7 @@ class ExtendsTemplateTest extends TestCase {
"b2" => "empty 0" "b2" => "empty 0"
), ),
"result" => array( "result" => array(
"b1" => "default ".$vars["default"], "b1" => "default " . $vars["default"],
"b2" => "empty 0" "b2" => "empty 0"
), ),
), ),
@ -82,27 +85,29 @@ class ExtendsTemplateTest extends TestCase {
* @param array $skels * @param array $skels
* @return array * @return array
*/ */
public static function generate($block_mask, $extend_mask, $skels) { public static function generate($block_mask, $extend_mask, $skels)
{
$t = array(); $t = array();
foreach($skels as $level => $tpl) { foreach ($skels as $level => $tpl) {
$src = 'level#'.$level.' '; $src = 'level#' . $level . ' ';
foreach($tpl["blocks"] as $bname => &$bcode) { foreach ($tpl["blocks"] as $bname => &$bcode) {
$src .= sprintf($block_mask, $bname, $bname.': '.$bcode)." level#$level "; $src .= sprintf($block_mask, $bname, $bname . ': ' . $bcode) . " level#$level ";
} }
$dst = "level#0 "; $dst = "level#0 ";
foreach($tpl["result"] as $bname => &$bcode) { foreach ($tpl["result"] as $bname => &$bcode) {
$dst .= $bname.': '.$bcode.' level#0 '; $dst .= $bname . ': ' . $bcode . ' level#0 ';
} }
if($level) { if ($level) {
$src = sprintf($extend_mask, $level-1).' '.$src; $src = sprintf($extend_mask, $level - 1) . ' ' . $src;
} }
$t[ $tpl["name"] ] = array("src" => $src, "dst" => $dst); $t[$tpl["name"]] = array("src" => $src, "dst" => $dst);
} }
return $t; return $t;
} }
public function _testTemplateExtends() { public function _testTemplateExtends()
{
$vars = array( $vars = array(
"b1" => "b1", "b1" => "b1",
"b2" => "b2", "b2" => "b2",
@ -112,7 +117,7 @@ class ExtendsTemplateTest extends TestCase {
"default" => 5 "default" => 5
); );
$tpls = self::generate('{block "%s"}%s{/block}', '{extends "level.%d.tpl"}', self::templates($vars)); $tpls = self::generate('{block "%s"}%s{/block}', '{extends "level.%d.tpl"}', self::templates($vars));
foreach($tpls as $name => $tpl) { foreach ($tpls as $name => $tpl) {
$this->tpl($name, $tpl["src"]); $this->tpl($name, $tpl["src"]);
$this->assertSame($this->fenom->fetch($name, $vars), $tpl["dst"]); $this->assertSame($this->fenom->fetch($name, $vars), $tpl["dst"]);
} }
@ -121,29 +126,31 @@ class ExtendsTemplateTest extends TestCase {
$this->fenom->flush(); $this->fenom->flush();
$tpls = self::generate('{block "{$%s}"}%s{/block}', '{extends "level.%d.tpl"}', self::templates($vars)); $tpls = self::generate('{block "{$%s}"}%s{/block}', '{extends "level.%d.tpl"}', self::templates($vars));
arsort($tpls); arsort($tpls);
foreach($tpls as $name => $tpl) { foreach ($tpls as $name => $tpl) {
$this->tpl("d.".$name, $tpl["src"]); $this->tpl("d." . $name, $tpl["src"]);
$this->assertSame($this->fenom->fetch("d.".$name, $vars), $tpl["dst"]); $this->assertSame($this->fenom->fetch("d." . $name, $vars), $tpl["dst"]);
} }
$vars["default"]++; $vars["default"]++;
$this->fenom->flush(); $this->fenom->flush();
$tpls = self::generate('{block "%s"}%s{/block}', '{extends "$level.%d.tpl"}', self::templates($vars)); $tpls = self::generate('{block "%s"}%s{/block}', '{extends "$level.%d.tpl"}', self::templates($vars));
arsort($tpls); arsort($tpls);
foreach($tpls as $name => $tpl) { foreach ($tpls as $name => $tpl) {
$this->tpl("x.".$name, $tpl["src"]); $this->tpl("x." . $name, $tpl["src"]);
$this->assertSame($this->fenom->fetch("x.".$name, $vars), $tpl["dst"]); $this->assertSame($this->fenom->fetch("x." . $name, $vars), $tpl["dst"]);
} }
} }
/** /**
* @group use * @group use
*/ */
public function testUse() { public function testUse()
$this->fenom = Fenom::factory(FENOM_RESOURCES.'/provider', FENOM_RESOURCES.'/compile'); {
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/provider', FENOM_RESOURCES . '/compile');
$this->assertSame("<html>\n block 1 blocks \n block 2 child \n</html>", $this->fenom->fetch('use/child.tpl')); $this->assertSame("<html>\n block 1 blocks \n block 2 child \n</html>", $this->fenom->fetch('use/child.tpl'));
} }
public function _testParent() { public function _testParent()
{
} }
} }

View File

@ -1,23 +1,28 @@
<?php <?php
namespace Fenom; namespace Fenom;
class FunctionsTest extends TestCase { class FunctionsTest extends TestCase
{
const FUNCTION_ARGUMENT_CONSTANT = 1; const FUNCTION_ARGUMENT_CONSTANT = 1;
public static function functionSum($of = array()) { public static function functionSum($of = array())
{
return array_sum($of); return array_sum($of);
} }
public static function functionPow($a, $n = 2) { public static function functionPow($a, $n = 2)
{
return pow($a, $n); return pow($a, $n);
} }
public static function functionInc($a, $i = self::FUNCTION_ARGUMENT_CONSTANT) { public static function functionInc($a, $i = self::FUNCTION_ARGUMENT_CONSTANT)
{
return $a + $i; return $a + $i;
} }
public function setUp() { public function setUp()
{
parent::setUp(); parent::setUp();
$this->fenom->addFunctionSmart('sum', __CLASS__ . '::functionSum'); $this->fenom->addFunctionSmart('sum', __CLASS__ . '::functionSum');
$this->fenom->addFunctionSmart('pow', __CLASS__ . '::functionPow'); $this->fenom->addFunctionSmart('pow', __CLASS__ . '::functionPow');
@ -32,37 +37,44 @@ class FunctionsTest extends TestCase {
$this->tpl('function_array_param_pos.tpl', '{sum [1, 2, 3, 4, 5]}'); $this->tpl('function_array_param_pos.tpl', '{sum [1, 2, 3, 4, 5]}');
} }
public function testFunctionWithParams() { public function testFunctionWithParams()
{
$output = $this->fenom->fetch('function_params_scalar.tpl'); $output = $this->fenom->fetch('function_params_scalar.tpl');
$this->assertEquals('8', $output); $this->assertEquals('8', $output);
} }
public function testFunctionWithDynamicParams() { public function testFunctionWithDynamicParams()
{
$output = $this->fenom->fetch('function_params_dynamic.tpl', array('a' => 3, 'n' => 4)); $output = $this->fenom->fetch('function_params_dynamic.tpl', array('a' => 3, 'n' => 4));
$this->assertEquals('81', $output); $this->assertEquals('81', $output);
} }
public function testFunctionWithDefaultParamScalar() { public function testFunctionWithDefaultParamScalar()
{
$output = $this->fenom->fetch('function_default_param_scalar.tpl'); $output = $this->fenom->fetch('function_default_param_scalar.tpl');
$this->assertEquals('4', $output); $this->assertEquals('4', $output);
} }
public function testFunctionWithDefaultParamArray() { public function testFunctionWithDefaultParamArray()
{
$output = $this->fenom->fetch('function_default_param_empty_array.tpl'); $output = $this->fenom->fetch('function_default_param_empty_array.tpl');
$this->assertEquals('0', $output); $this->assertEquals('0', $output);
} }
public function testFunctionWithDefaultParamConst() { public function testFunctionWithDefaultParamConst()
{
$output = $this->fenom->fetch('function_default_param_const.tpl'); $output = $this->fenom->fetch('function_default_param_const.tpl');
$this->assertEquals('2', $output); $this->assertEquals('2', $output);
} }
public function testFunctionWithArrayNamedParam() { public function testFunctionWithArrayNamedParam()
{
$output = $this->fenom->fetch('function_array_param.tpl'); $output = $this->fenom->fetch('function_array_param.tpl');
$this->assertEquals('15', $output); $this->assertEquals('15', $output);
} }
public function testFunctionWithArrayPositionalParam() { public function testFunctionWithArrayPositionalParam()
{
$output = $this->fenom->fetch('function_array_param_pos.tpl'); $output = $this->fenom->fetch('function_array_param_pos.tpl');
$this->assertEquals('15', $output); $this->assertEquals('15', $output);
} }

View File

@ -1,9 +1,11 @@
<?php <?php
namespace Fenom; namespace Fenom;
class MacrosTest extends TestCase { class MacrosTest extends TestCase
{
public function setUp() { public function setUp()
{
parent::setUp(); parent::setUp();
$this->tpl("math.tpl", ' $this->tpl("math.tpl", '
{macro plus(x, y)} {macro plus(x, y)}
@ -42,22 +44,46 @@ class MacrosTest extends TestCase {
a: {macro.plus x=5 y=3}. a: {macro.plus x=5 y=3}.
'); ');
$this->tpl("macro_recursive.tpl", '{macro factorial(num)}
{if $num}
{$num} {macro.factorial num=$num-1} {$num}
{/if}
{/macro}
{macro.factorial num=10}');
} }
public function testMacros() { public function _testSandbox()
{
try {
$this->fenom->compile("macro_recursive.tpl");
// $this->fenom->flush();
// var_dump($this->fenom->fetch("macro_recursive.tpl", []));
var_dump( $this->fenom->compile("macro_recursive.tpl")->getTemplateCode());
} catch (\Exception $e) {
var_dump($e->getMessage() . ": " . $e->getTraceAsString());
}
exit;
}
public function testMacros()
{
$tpl = $this->fenom->compile('math.tpl'); $tpl = $this->fenom->compile('math.tpl');
$this->assertStringStartsWith('x + y = ', trim($tpl->macros["plus"]["body"])); $this->assertStringStartsWith('x + y = ', trim($tpl->macros["plus"]["body"]));
$this->assertSame('Math: x + y = 5 , x - y - z = 6', Modifier::strip($tpl->fetch(array()), true)); $this->assertSame('Math: x + y = 5 , x - y - z = 6', Modifier::strip($tpl->fetch(array()), true));
} }
public function testImport() { public function testImport()
{
$tpl = $this->fenom->compile('import.tpl'); $tpl = $this->fenom->compile('import.tpl');
$this->assertSame('Imp: x + y = 3 , x - y - z = 3', Modifier::strip($tpl->fetch(array()), true)); $this->assertSame('Imp: x + y = 3 , x - y - z = 3', Modifier::strip($tpl->fetch(array()), true));
} }
public function testImportCustom() { public function testImportCustom()
{
$tpl = $this->fenom->compile('import_custom.tpl'); $tpl = $this->fenom->compile('import_custom.tpl');
$this->assertSame('a: x + y = 3 , x - y - z = 3 , new minus macros .', Modifier::strip($tpl->fetch(array()), true)); $this->assertSame('a: x + y = 3 , x - y - z = 3 , new minus macros .', Modifier::strip($tpl->fetch(array()), true));
@ -65,11 +91,20 @@ class MacrosTest extends TestCase {
/** /**
* @expectedExceptionMessage Undefined macro 'plus' * @expectedExceptionMessage Undefined macro 'plus'
* @expectedException \Fenom\CompileException * @expectedException \Fenom\Error\CompileException
*/ */
public function testImportMiss() { public function testImportMiss()
{
$tpl = $this->fenom->compile('import_miss.tpl'); $tpl = $this->fenom->compile('import_miss.tpl');
$this->assertSame('a: x + y = 3 , x - y - z = 3 , new minus macros .', Modifier::strip($tpl->fetch(array()), true)); $this->assertSame('a: x + y = 3 , x - y - z = 3 , new minus macros .', Modifier::strip($tpl->fetch(array()), true));
} }
public function testRecursive()
{
$this->fenom->compile('macro_recursive.tpl');
$this->fenom->flush();
$tpl = $this->fenom->getTemplate('macro_recursive.tpl');
$this->assertSame("10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10", Modifier::strip($tpl->fetch(array()), true));
}
} }

View File

@ -2,9 +2,11 @@
namespace Fenom; namespace Fenom;
class ModifiersTest extends TestCase { class ModifiersTest extends TestCase
{
public static function providerTruncate() { public static function providerTruncate()
{
$lorem = 'Lorem ipsum dolor sit amet'; // en $lorem = 'Lorem ipsum dolor sit amet'; // en
$uni = 'Лорем ипсум долор сит амет'; // ru $uni = 'Лорем ипсум долор сит амет'; // ru
return array( return array(
@ -33,7 +35,8 @@ class ModifiersTest extends TestCase {
* @param bool $by_words * @param bool $by_words
* @param bool $middle * @param bool $middle
*/ */
public function testTruncate($in, $out, $count, $delim = '...', $by_words = false, $middle = false) { public function testTruncate($in, $out, $count, $delim = '...', $by_words = false, $middle = false)
{
$tpl = $this->fenom->compileCode('{$text|truncate:$count:$delim:$by_words:$middle}'); $tpl = $this->fenom->compileCode('{$text|truncate:$count:$delim:$by_words:$middle}');
$this->assertEquals($out, $tpl->fetch(array( $this->assertEquals($out, $tpl->fetch(array(
"text" => $in, "text" => $in,
@ -44,7 +47,8 @@ class ModifiersTest extends TestCase {
))); )));
} }
public static function providerUpLow() { public static function providerUpLow()
{
return array( return array(
array("up", "lorem", "LOREM"), array("up", "lorem", "LOREM"),
array("up", "Lorem", "LOREM"), array("up", "Lorem", "LOREM"),
@ -64,14 +68,16 @@ class ModifiersTest extends TestCase {
* @param $in * @param $in
* @param $out * @param $out
*/ */
public function testUpLow($modifier, $in, $out) { public function testUpLow($modifier, $in, $out)
$tpl = $this->fenom->compileCode('{$text|'.$modifier.'}'); {
$tpl = $this->fenom->compileCode('{$text|' . $modifier . '}');
$this->assertEquals($out, $tpl->fetch(array( $this->assertEquals($out, $tpl->fetch(array(
"text" => $in, "text" => $in,
))); )));
} }
public static function providerLength() { public static function providerLength()
{
return array( return array(
array("length", 6), array("length", 6),
array("длина", 5), array("длина", 5),
@ -90,7 +96,8 @@ class ModifiersTest extends TestCase {
* @param $in * @param $in
* @param $out * @param $out
*/ */
public function testLength($in, $out) { public function testLength($in, $out)
{
$tpl = $this->fenom->compileCode('{$data|length}'); $tpl = $this->fenom->compileCode('{$data|length}');
$this->assertEquals($out, $tpl->fetch(array( $this->assertEquals($out, $tpl->fetch(array(
"data" => $in, "data" => $in,

View File

@ -3,87 +3,97 @@ namespace Fenom;
use Fenom; use Fenom;
use Fenom\TestCase; use Fenom\TestCase;
class ProviderTest extends TestCase { class ProviderTest extends TestCase
{
/** /**
* @var Provider * @var Provider
*/ */
public $provider; public $provider;
public function setUp() { public function setUp()
{
parent::setUp(); parent::setUp();
$this->tpl("template1.tpl", 'Template 1 {$a}'); $this->tpl("template1.tpl", 'Template 1 {$a}');
$this->tpl("template2.tpl", 'Template 2 {$a}'); $this->tpl("template2.tpl", 'Template 2 {$a}');
$this->tpl("sub/template3.tpl", 'Template 3 {$a}'); $this->tpl("sub/template3.tpl", 'Template 3 {$a}');
$this->provider = new Provider(FENOM_RESOURCES.'/template'); $this->provider = new Provider(FENOM_RESOURCES . '/template');
clearstatcache(); clearstatcache();
} }
public function testIsTemplateExists() { public function testIsTemplateExists()
{
clearstatcache(); clearstatcache();
$this->assertTrue($this->provider->templateExists("template1.tpl")); $this->assertTrue($this->provider->templateExists("template1.tpl"));
$this->assertFalse($this->provider->templateExists("unexists.tpl")); $this->assertFalse($this->provider->templateExists("unexists.tpl"));
} }
public function testGetSource() { public function testGetSource()
{
clearstatcache(); clearstatcache();
$src = $this->provider->getSource("template1.tpl", $time); $src = $this->provider->getSource("template1.tpl", $time);
clearstatcache(); clearstatcache();
$this->assertEquals(file_get_contents(FENOM_RESOURCES.'/template/template1.tpl'), $src); $this->assertEquals(file_get_contents(FENOM_RESOURCES . '/template/template1.tpl'), $src);
$this->assertEquals(filemtime(FENOM_RESOURCES.'/template/template1.tpl'), $time); $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time);
} }
/** /**
* @expectedException \RuntimeException * @expectedException \RuntimeException
*/ */
public function testGetSourceInvalid() { public function testGetSourceInvalid()
{
$this->provider->getSource("unexists.tpl", $time); $this->provider->getSource("unexists.tpl", $time);
} }
public function testGetLastModified() { public function testGetLastModified()
{
$time = $this->provider->getLastModified("template1.tpl"); $time = $this->provider->getLastModified("template1.tpl");
clearstatcache(); clearstatcache();
$this->assertEquals(filemtime(FENOM_RESOURCES.'/template/template1.tpl'), $time); $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time);
} }
/** /**
* @expectedException \RuntimeException * @expectedException \RuntimeException
*/ */
public function testGetLastModifiedInvalid() { public function testGetLastModifiedInvalid()
{
$this->provider->getLastModified("unexists.tpl"); $this->provider->getLastModified("unexists.tpl");
} }
public function testVerify() { public function testVerify()
{
$templates = array( $templates = array(
"template1.tpl" => filemtime(FENOM_RESOURCES.'/template/template1.tpl'), "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'),
"template2.tpl" => filemtime(FENOM_RESOURCES.'/template/template2.tpl') "template2.tpl" => filemtime(FENOM_RESOURCES . '/template/template2.tpl')
); );
clearstatcache(); clearstatcache();
$this->assertTrue($this->provider->verify($templates)); $this->assertTrue($this->provider->verify($templates));
clearstatcache(); clearstatcache();
$templates = array( $templates = array(
"template2.tpl" => filemtime(FENOM_RESOURCES.'/template/template2.tpl'), "template2.tpl" => filemtime(FENOM_RESOURCES . '/template/template2.tpl'),
"template1.tpl" => filemtime(FENOM_RESOURCES.'/template/template1.tpl') "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl')
); );
clearstatcache(); clearstatcache();
$this->assertTrue($this->provider->verify($templates)); $this->assertTrue($this->provider->verify($templates));
} }
public function testVerifyInvalid() { public function testVerifyInvalid()
{
$templates = array( $templates = array(
"template1.tpl" => filemtime(FENOM_RESOURCES.'/template/template1.tpl'), "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'),
"template2.tpl" => filemtime(FENOM_RESOURCES.'/template/template2.tpl') + 1 "template2.tpl" => filemtime(FENOM_RESOURCES . '/template/template2.tpl') + 1
); );
clearstatcache(); clearstatcache();
$this->assertFalse($this->provider->verify($templates)); $this->assertFalse($this->provider->verify($templates));
clearstatcache(); clearstatcache();
$templates = array( $templates = array(
"template1.tpl" => filemtime(FENOM_RESOURCES.'/template/template1.tpl'), "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'),
"unexists.tpl" => 1234567890 "unexists.tpl" => 1234567890
); );
$this->assertFalse($this->provider->verify($templates)); $this->assertFalse($this->provider->verify($templates));
} }
public function testGetAll() { public function testGetAll()
{
$list = $this->provider->getList(); $list = $this->provider->getList();
sort($list); sort($list);
$this->assertSame(array( $this->assertSame(array(

View File

@ -3,22 +3,25 @@ namespace Fenom;
use Fenom, use Fenom,
Fenom\Render; Fenom\Render;
class RenderTest extends \PHPUnit_Framework_TestCase { class RenderTest extends \PHPUnit_Framework_TestCase
{
/** /**
* @var Render * @var Render
*/ */
public static $render; public static $render;
public static function setUpBeforeClass() { public static function setUpBeforeClass()
{
self::$render = new Render(Fenom::factory("."), function ($tpl) { self::$render = new Render(Fenom::factory("."), function ($tpl) {
echo "It is render's function ".$tpl["render"]; echo "It is render's function " . $tpl["render"];
}, array( }, array(
"name" => "render.tpl" "name" => "render.tpl"
)); ));
} }
public function testCreate() { public function testCreate()
{
$r = new Render(Fenom::factory("."), function () { $r = new Render(Fenom::factory("."), function () {
echo "Test render"; echo "Test render";
}, array( }, array(
@ -27,14 +30,16 @@ class RenderTest extends \PHPUnit_Framework_TestCase {
$this->assertSame("Test render", $r->fetch(array())); $this->assertSame("Test render", $r->fetch(array()));
} }
public function testDisplay() { public function testDisplay()
{
ob_start(); ob_start();
self::$render->display(array("render" => "display")); self::$render->display(array("render" => "display"));
$out = ob_get_clean(); $out = ob_get_clean();
$this->assertSame("It is render's function display", $out); $this->assertSame("It is render's function display", $out);
} }
public function testFetch() { public function testFetch()
{
$this->assertSame("It is render's function fetch", self::$render->fetch(array("render" => "fetch"))); $this->assertSame("It is render's function fetch", self::$render->fetch(array("render" => "fetch")));
} }
@ -42,7 +47,8 @@ class RenderTest extends \PHPUnit_Framework_TestCase {
* @expectedException \RuntimeException * @expectedException \RuntimeException
* @expectedExceptionMessage template error * @expectedExceptionMessage template error
*/ */
public function testFetchException() { public function testFetchException()
{
$render = new Render(Fenom::factory("."), function () { $render = new Render(Fenom::factory("."), function () {
echo "error"; echo "error";
throw new \RuntimeException("template error"); throw new \RuntimeException("template error");

View File

@ -1,22 +1,26 @@
<?php <?php
namespace Fenom; namespace Fenom;
class ScopeTest extends TestCase { class ScopeTest extends TestCase
public function openTag($tokenizer, $scope) { {
public function openTag($tokenizer, $scope)
{
$this->assertInstanceOf('Fenom\Tokenizer', $tokenizer); $this->assertInstanceOf('Fenom\Tokenizer', $tokenizer);
$this->assertInstanceOf('Fenom\Scope', $scope); $this->assertInstanceOf('Fenom\Scope', $scope);
$scope["value"] = true; $scope["value"] = true;
return "open-tag"; return "open-tag";
} }
public function closeTag($tokenizer, $scope) { public function closeTag($tokenizer, $scope)
{
$this->assertInstanceOf('Fenom\Tokenizer', $tokenizer); $this->assertInstanceOf('Fenom\Tokenizer', $tokenizer);
$this->assertInstanceOf('Fenom\Scope', $scope); $this->assertInstanceOf('Fenom\Scope', $scope);
$this->assertTrue($scope["value"]); $this->assertTrue($scope["value"]);
return "close-tag"; return "close-tag";
} }
public function testBlock() { public function testBlock()
{
/*$scope = new Scope($this->fenom, new Template($this->fenom), 1, array( /*$scope = new Scope($this->fenom, new Template($this->fenom), 1, array(
"open" => array($this, "openTag"), "open" => array($this, "openTag"),
"close" => array($this, "closeTag") "close" => array($this, "closeTag")

View File

@ -3,12 +3,14 @@
namespace Fenom; namespace Fenom;
class TagsTest extends TestCase { class TagsTest extends TestCase
{
public function _testSandbox() { public function _testSandbox()
{
try { try {
var_dump($this->fenom->compileCode('{var $a=Fenom\TestCase::dots("asd")}')->getBody()); var_dump($this->fenom->compileCode('{var $a=Fenom\TestCase::dots("asd")}')->getBody());
} catch(\Exception $e) { } catch (\Exception $e) {
echo "$e"; echo "$e";
} }
exit; exit;
@ -17,39 +19,45 @@ class TagsTest extends TestCase {
/** /**
* @dataProvider providerScalars * @dataProvider providerScalars
*/ */
public function testVar($tpl_val, $val) { public function testVar($tpl_val, $val)
$this->assertRender("{var \$a=$tpl_val}\nVar: {\$a}", "\nVar: ".$val); {
$this->assertRender("{var \$a=$tpl_val}\nVar: {\$a}", "\nVar: " . $val);
} }
/** /**
* @dataProvider providerScalars * @dataProvider providerScalars
*/ */
public function testVarBlock($tpl_val, $val) { public function testVarBlock($tpl_val, $val)
$this->assertRender("{var \$a}before {{$tpl_val}} after{/var}\nVar: {\$a}", "\nVar: before ".$val." after"); {
$this->assertRender("{var \$a}before {{$tpl_val}} after{/var}\nVar: {\$a}", "\nVar: before " . $val . " after");
} }
/** /**
* @dataProvider providerScalars * @dataProvider providerScalars
*/ */
public function testVarBlockModified($tpl_val, $val) { public function testVarBlockModified($tpl_val, $val)
$this->assertRender("{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}", "\nVar: ".strtolower("before ".$val." after")."..."); {
$this->assertRender("{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}", "\nVar: " . strtolower("before " . $val . " after") . "...");
} }
public function testCycle() { public function testCycle()
{
$this->assertRender('{for $i=0 to=4}{cycle ["one", "two"]}, {/for}', "one, two, one, two, one, "); $this->assertRender('{for $i=0 to=4}{cycle ["one", "two"]}, {/for}', "one, two, one, two, one, ");
} }
/** /**
* *
*/ */
public function testCycleIndex() { public function testCycleIndex()
{
$this->assertRender('{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}', "two, one, two, one, two, "); $this->assertRender('{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}', "two, one, two, one, two, ");
} }
/** /**
* @dataProvider providerScalars * @dataProvider providerScalars
*/ */
public function testFilter($tpl_val, $val) { public function testFilter($tpl_val, $val)
{
$this->assertRender("{filter|up} before {{$tpl_val}} after {/filter}", strtoupper(" before {$val} after ")); $this->assertRender("{filter|up} before {{$tpl_val}} after {/filter}", strtoupper(" before {$val} after "));
} }

View File

@ -9,14 +9,17 @@ use Fenom\Template,
* *
* @package Fenom * @package Fenom
*/ */
class TemplateTest extends TestCase { class TemplateTest extends TestCase
{
public function setUp() { public function setUp()
{
parent::setUp(); parent::setUp();
$this->tpl('welcome.tpl', '<b>Welcome, {$username} ({$email})</b>'); $this->tpl('welcome.tpl', '<b>Welcome, {$username} ({$email})</b>');
} }
public static function providerVars() { public static function providerVars()
{
$a = array("a" => "World"); $a = array("a" => "World");
$obj = new \stdClass; $obj = new \stdClass;
$obj->name = "Object"; $obj->name = "Object";
@ -75,20 +78,22 @@ class TemplateTest extends TestCase {
} }
public static function providerVarsInvalid() { public static function providerVarsInvalid()
{
return array( return array(
array('hello, {$a.}!', 'Fenom\CompileException', "Unexpected end of expression"), array('hello, {$a.}!', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('hello, {$b[c}!', 'Fenom\CompileException', "Unexpected end of expression"), array('hello, {$b[c}!', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('hello, {$b.c]}!', 'Fenom\CompileException', "Unexpected token ']'"), array('hello, {$b.c]}!', 'Fenom\Error\CompileException', "Unexpected token ']'"),
array('hello, {$b[ ]}!', 'Fenom\CompileException', "Unexpected token ']'"), array('hello, {$b[ ]}!', 'Fenom\Error\CompileException', "Unexpected token ']'"),
array('hello, {$b[9/].c}!', 'Fenom\CompileException', "Unexpected token ']'"), array('hello, {$b[9/].c}!', 'Fenom\Error\CompileException', "Unexpected token ']'"),
array('hello, {$b[3]$c}!', 'Fenom\CompileException', "Unexpected token '\$c'"), array('hello, {$b[3]$c}!', 'Fenom\Error\CompileException', "Unexpected token '\$c'"),
array('hello, {$b[3]c}!', 'Fenom\CompileException', "Unexpected token 'c'"), array('hello, {$b[3]c}!', 'Fenom\Error\CompileException', "Unexpected token 'c'"),
array('hello, {$b.obj->valid()}!', 'Fenom\SecurityException', "Forbidden to call methods", Fenom::DENY_METHODS), array('hello, {$b.obj->valid()}!', 'Fenom\Error\SecurityException', "Forbidden to call methods", Fenom::DENY_METHODS),
); );
} }
public static function providerModifiers() { public static function providerModifiers()
{
$b = array( $b = array(
"a" => "World", "a" => "World",
"b" => array( "b" => array(
@ -123,22 +128,24 @@ class TemplateTest extends TestCase {
array('Mod: {$date|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: {$tags|strip_tags}!', $b, 'Mod: my name is Legion!'),
array('Mod: {$b.c|json_encode}!', $b, 'Mod: "Username"!'), array('Mod: {$b.c|json_encode}!', $b, 'Mod: "Username"!'),
array('Mod: {time()|date:"Y m d"}!', $b, 'Mod: '.date("Y m d").'!'), array('Mod: {time()|date:"Y m d"}!', $b, 'Mod: ' . date("Y m d") . '!'),
); );
} }
public static function providerModifiersInvalid() { public static function providerModifiersInvalid()
{
return array( return array(
array('Mod: {$lorem|}!', 'Fenom\CompileException', "Unexpected end of expression"), array('Mod: {$lorem|}!', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Mod: {$lorem|str_rot13}!', 'Fenom\CompileException', "Modifier str_rot13 not found", Fenom::DENY_INLINE_FUNCS), array('Mod: {$lorem|str_rot13}!', 'Fenom\Error\CompileException', "Modifier str_rot13 not found", Fenom::DENY_INLINE_FUNCS),
array('Mod: {$lorem|my_encode}!', 'Fenom\CompileException', "Modifier my_encode not found"), array('Mod: {$lorem|my_encode}!', 'Fenom\Error\CompileException', "Modifier my_encode not found"),
array('Mod: {$lorem|truncate:}!', 'Fenom\CompileException', "Unexpected end of expression"), array('Mod: {$lorem|truncate:}!', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Mod: {$lorem|truncate:abs}!', 'Fenom\CompileException', "Unexpected token 'abs'"), array('Mod: {$lorem|truncate:abs}!', 'Fenom\Error\CompileException', "Unexpected token 'abs'"),
array('Mod: {$lorem|truncate:80|}!', 'Fenom\CompileException', "Unexpected end of expression"), array('Mod: {$lorem|truncate:80|}!', 'Fenom\Error\CompileException', "Unexpected end of expression"),
); );
} }
public static function providerExpressions() { public static function providerExpressions()
{
$b = array( $b = array(
"x" => $x = 9, "x" => $x = 9,
"y" => 27, "y" => 27,
@ -146,11 +153,11 @@ class TemplateTest extends TestCase {
"k" => array("i" => "") "k" => array("i" => "")
); );
return array( return array(
array('Exp: {'.$x.'+$y} result', $b, 'Exp: 36 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: 3 result'),
array('Exp: {$y-'.$x.'} result', $b, 'Exp: 18 result'), array('Exp: {$y-' . $x . '} result', $b, 'Exp: 18 result'),
array('Exp: {'.$x.'*$y} result', $b, 'Exp: 243 result'), array('Exp: {' . $x . '*$y} result', $b, 'Exp: 243 result'),
array('Exp: {$y^'.$x.'} result', $b, 'Exp: 18 result'), array('Exp: {$y^' . $x . '} result', $b, 'Exp: 18 result'),
array('Exp: {$x+$y} result', $b, 'Exp: 36 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: 3 result'),
@ -175,25 +182,28 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerExpressionsInvalid() { public static function providerExpressionsInvalid()
{
return array( return array(
array('If: {-"hi"} end', 'Fenom\CompileException', "Unexpected token '-'"), array('If: {-"hi"} end', 'Fenom\Error\CompileException', "Unexpected token '-'"),
array('If: {($a++)++} end', 'Fenom\CompileException', "Unexpected token '++'"), array('If: {-[1,2]} end', 'Fenom\Error\CompileException', "Unexpected token '-'"),
array('If: {$a + * $c} end', 'Fenom\CompileException', "Unexpected token '*'"), array('If: {($a++)++} end', 'Fenom\Error\CompileException', "Unexpected token '++'"),
array('If: {$a + } end', 'Fenom\CompileException', "Unexpected token '+'"), array('If: {$a + * $c} end', 'Fenom\Error\CompileException', "Unexpected token '*'"),
array('If: {$a + =} end', 'Fenom\CompileException', "Unexpected token '='"), array('If: {$a + } end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('If: {$a + 1 =} end', 'Fenom\CompileException', "Unexpected token '='"), array('If: {$a + =} end', 'Fenom\Error\CompileException', "Unexpected token '='"),
array('If: {$a + 1 = 6} end', 'Fenom\CompileException', "Unexpected token '='"), array('If: {$a + 1 =} end', 'Fenom\Error\CompileException', "Unexpected token '='"),
array('If: {/$a} end', 'Fenom\CompileException', "Unexpected token '\$a'"), array('If: {$a + 1 = 6} end', 'Fenom\Error\CompileException', "Unexpected token '='"),
array('If: {$a == 5 > 4} end', 'Fenom\CompileException', "Unexpected token '>'"), array('If: {/$a} end', 'Fenom\Error\CompileException', "Unexpected token '\$a'"),
array('If: {$a != 5 <= 4} end', 'Fenom\CompileException', "Unexpected token '<='"), array('If: {$a == 5 > 4} end', 'Fenom\Error\CompileException', "Unexpected token '>'"),
array('If: {$a != 5 => 4} end', 'Fenom\CompileException', "Unexpected token '=>'"), array('If: {$a != 5 <= 4} end', 'Fenom\Error\CompileException', "Unexpected token '<='"),
array('If: {$a + (*6)} end', 'Fenom\CompileException', "Unexpected token '*'"), array('If: {$a != 5 => 4} end', 'Fenom\Error\CompileException', "Unexpected token '=>'"),
array('If: {$a + ( 6} end', 'Fenom\CompileException', "Unexpected end of expression, expect ')'"), array('If: {$a + (*6)} end', 'Fenom\Error\CompileException', "Unexpected token '*'"),
array('If: {$a + ( 6} end', 'Fenom\Error\CompileException', "Unexpected end of expression, expect ')'"),
); );
} }
public static function providerInclude() { public static function providerInclude()
{
$a = array( $a = array(
"name" => "welcome", "name" => "welcome",
"tpl" => "welcome.tpl", "tpl" => "welcome.tpl",
@ -224,14 +234,16 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerIncludeInvalid() { public static function providerIncludeInvalid()
{
return array( return array(
array('Include {include} template', 'Fenom\CompileException', "Unexpected end of expression"), array('Include {include} template', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Include {include another="welcome.tpl"} template', 'Fenom\CompileException', "Unexpected token '='"), array('Include {include another="welcome.tpl"} template', 'Fenom\Error\CompileException', "Unexpected token '='"),
); );
} }
public static function providerIf() { public static function providerIf()
{
$a = array( $a = array(
"val1" => 1, "val1" => 1,
"val0" => 0, "val0" => 0,
@ -268,16 +280,18 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerIfInvalid() { public static function providerIfInvalid()
{
return array( return array(
array('If: {if} block1 {/if} end', 'Fenom\CompileException', "Unexpected end of expression"), array('If: {if} block1 {/if} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('If: {if 1} block1 {elseif} block2 {/if} end', 'Fenom\CompileException', "Unexpected end of expression"), array('If: {if 1} block1 {elseif} block2 {/if} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('If: {if 1} block1 {else} block2 {elseif 0} block3 {/if} end', 'Fenom\CompileException', "Incorrect use of the tag {elseif}"), array('If: {if 1} block1 {else} block2 {elseif 0} block3 {/if} end', 'Fenom\Error\CompileException', "Incorrect use of the tag {elseif}"),
array('If: {if 1} block1 {else} block2 {/if} block3 {elseif 0} end', 'Fenom\CompileException', "Unexpected tag 'elseif' (this tag can be used with 'if')"), array('If: {if 1} block1 {else} block2 {/if} block3 {elseif 0} end', 'Fenom\Error\CompileException', "Unexpected tag 'elseif' (this tag can be used with 'if')"),
); );
} }
public static function providerCreateVar() { public static function providerCreateVar()
{
$a = array( $a = array(
"x" => 9, "x" => 9,
"y" => 27, "y" => 27,
@ -309,27 +323,29 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerCreateVarInvalid() { public static function providerCreateVarInvalid()
{
return array( return array(
array('Create: {var $v} Result: {$v} end', 'Fenom\CompileException', "Unclosed tag: {var} opened"), array('Create: {var $v} Result: {$v} end', 'Fenom\Error\CompileException', "Unclosed tag: {var} opened"),
array('Create: {var $v = } Result: {$v} end', 'Fenom\CompileException', "Unexpected end of expression"), array('Create: {var $v = } Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Create: {var $v = 1++} Result: {$v} end', 'Fenom\CompileException', "Unexpected token '++'"), array('Create: {var $v = 1++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"),
array('Create: {var $v = c} Result: {$v} end', 'Fenom\CompileException', "Unexpected token 'c'"), array('Create: {var $v = c} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token 'c'"),
array('Create: {var $v = ($a)++} Result: {$v} end', 'Fenom\CompileException', "Unexpected token '++'"), array('Create: {var $v = ($a)++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"),
array('Create: {var $v = --$a++} Result: {$v} end', 'Fenom\CompileException', "Can not use two increments and/or decrements for one variable"), array('Create: {var $v = --$a++} Result: {$v} end', 'Fenom\Error\CompileException', "Can not use two increments and/or decrements for one variable"),
array('Create: {var $v = $a|upper++} Result: {$v} end', 'Fenom\CompileException', "Unexpected token '++'"), array('Create: {var $v = $a|upper++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"),
array('Create: {var $v = max($a,2)++} Result: {$v} end', 'Fenom\CompileException', "Unexpected token '++'"), array('Create: {var $v = max($a,2)++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"),
array('Create: {var $v = max($a,2)} Result: {$v} end', 'Fenom\CompileException', "Modifier max not found", Fenom::DENY_INLINE_FUNCS), array('Create: {var $v = max($a,2)} Result: {$v} end', 'Fenom\Error\CompileException', "Function max not found", Fenom::DENY_NATIVE_FUNCS),
array('Create: {var $v = 4*} Result: {$v} end', 'Fenom\CompileException', "Unexpected token '*'"), array('Create: {var $v = 4*} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Create: {var $v = ""$a} Result: {$v} end', 'Fenom\CompileException', "Unexpected token '\$a'"), array('Create: {var $v = ""$a} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '\$a'"),
array('Create: {var $v = [1,2} Result: {$v} end', 'Fenom\CompileException', "Unexpected end of expression"), array('Create: {var $v = [1,2} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Create: {var $v = empty(2)} Result: {$v} end', 'Fenom\CompileException', "Unexpected token 2, isset() and empty() accept only variables"), array('Create: {var $v = empty(2)} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token 2, isset() and empty() accept only variables"),
array('Create: {var $v = isset(2)} Result: {$v} end', 'Fenom\CompileException', "Unexpected token 2, isset() and empty() accept only variables"), array('Create: {var $v = isset(2)} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token 2, isset() and empty() accept only variables"),
); );
} }
public static function providerTernary() { public static function providerTernary()
{
$a = array( $a = array(
"a" => 1, "a" => 1,
"em" => "empty", "em" => "empty",
@ -341,7 +357,7 @@ class TemplateTest extends TestCase {
"bool" => false, "bool" => false,
), ),
"nonempty" => array( "nonempty" => array(
"array" => array(1,2), "array" => array(1, 2),
"int" => 2, "int" => 2,
"string" => "abc", "string" => "abc",
"double" => 0.2, "double" => 0.2,
@ -373,6 +389,8 @@ class TemplateTest extends TestCase {
array('{$empty.bool?:"empty"}', $a, "empty"), array('{$empty.bool?:"empty"}', $a, "empty"),
array('{$empty.unexist?:"empty"}', $a, "empty"), array('{$empty.unexist?:"empty"}', $a, "empty"),
// ? ... : .... // ? ... : ....
array('{$unexists ? "no way" : "right"}', $a),
array('{$a ? "right" : "no way"}', $a),
// ! // !
array('{if $a!} right {/if}', $a), array('{if $a!} right {/if}', $a),
array('{if $unexists!} no way {else} right {/if}', $a), array('{if $unexists!} no way {else} right {/if}', $a),
@ -388,11 +406,16 @@ class TemplateTest extends TestCase {
array('{if $nonempty.double!} right {/if}', $a), array('{if $nonempty.double!} right {/if}', $a),
array('{if $nonempty.bool!} right {/if}', $a), array('{if $nonempty.bool!} right {/if}', $a),
// ! ... : ... // ! ... : ...
array('{$unexists ! "no way" : "right"}', $a),
array('{$a ! "right" : "no way"}', $a),
// !: ... // !: ...
array('{$unexists !: "right"}', $a),
array('{$a !: "right"}', $a, '1'),
); );
} }
public static function providerForeach() { public static function providerForeach()
{
$a = array( $a = array(
"list" => array(1 => "one", 2 => "two", 3 => "three"), "list" => array(1 => "one", 2 => "two", 3 => "three"),
"empty" => array() "empty" => array()
@ -417,33 +440,35 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerForeachInvalid() { public static function providerForeachInvalid()
{
return array( return array(
array('Foreach: {foreach} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected end of tag {foreach}"), array('Foreach: {foreach} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected end of tag {foreach}"),
array('Foreach: {foreach $list} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected end of expression"), array('Foreach: {foreach $list} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '+'"), array('Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach array_random() as $e} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'array_random'"), array('Foreach: {foreach array_random() as $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'array_random'"),
array('Foreach: {foreach $list as $e+1} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '+'"), array('Foreach: {foreach $list as $e+1} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach $list as $k+1 => $e} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '+'"), array('Foreach: {foreach $list as $k+1 => $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach $list as max($i,1) => $e} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'max'"), array('Foreach: {foreach $list as max($i,1) => $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as max($e,1)} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'max'"), array('Foreach: {foreach $list as max($e,1)} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list => $e} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '=>'"), array('Foreach: {foreach $list => $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '=>'"),
array('Foreach: {foreach $list $k => $e} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '\$k'"), array('Foreach: {foreach $list $k => $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '\$k'"),
array('Foreach: {foreach $list as $k =>} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected end of expression"), array('Foreach: {foreach $list as $k =>} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Foreach: {foreach last=$l $list as $e } {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'last' in tag {foreach}"), array('Foreach: {foreach last=$l $list as $e } {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'last' in tag {foreach}"),
array('Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end', 'Fenom\CompileException', "Unknown parameter 'unknown'"), array('Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unknown parameter 'unknown'"),
array('Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '+'"), array('Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach $list as $e first=$f+1} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '+'"), array('Foreach: {foreach $list as $e first=$f+1} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach $list as $e last=$l+1} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token '+'"), array('Foreach: {foreach $list as $e last=$l+1} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('Foreach: {foreach $list as $e index=max($i,1)} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'max'"), array('Foreach: {foreach $list as $e index=max($i,1)} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as $e first=max($i,1)} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'max'"), array('Foreach: {foreach $list as $e first=max($i,1)} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as $e last=max($i,1)} {$e}, {/foreach} end', 'Fenom\CompileException', "Unexpected token 'max'"), array('Foreach: {foreach $list as $e last=max($i,1)} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"),
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {break} {/foreach} end', 'Fenom\CompileException', "Improper usage of the tag {break}"), array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {break} {/foreach} end', 'Fenom\Error\CompileException', "Improper usage of the tag {break}"),
array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {continue} {/foreach} end', 'Fenom\CompileException', "Improper usage of the tag {continue}"), array('Foreach: {foreach $list as $e} {$e}, {foreachelse} {continue} {/foreach} end', 'Fenom\Error\CompileException', "Improper usage of the tag {continue}"),
); );
} }
public static function providerIgnores() { public static function providerIgnores()
{
$a = array("a" => "lit. A"); $a = array("a" => "lit. A");
return array( return array(
array('{if 0}none{/if} literal: {$a} end', $a, 'literal: lit. A end'), array('{if 0}none{/if} literal: {$a} end', $a, 'literal: lit. A end'),
@ -456,7 +481,8 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerSwitch() { public static function providerSwitch()
{
$code1 = 'Switch: {switch $a} $code1 = 'Switch: {switch $a}
{case 1} one {break} {case 1} one {break}
{case 2} two {break} {case 2} two {break}
@ -483,15 +509,17 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerSwitchInvalid() { public static function providerSwitchInvalid()
{
return array( return array(
array('Switch: {switch}{case 1} one {break}{/switch} end', 'Fenom\CompileException', "Unexpected end of expression"), array('Switch: {switch}{case 1} one {break}{/switch} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Switch: {switch 1}{case} one {break}{/switch} end', 'Fenom\CompileException', "Unexpected end of expression"), array('Switch: {switch 1}{case} one {break}{/switch} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('Switch: {switch 1}{break}{case} one {/switch} end', 'Fenom\CompileException', "Improper usage of the tag {break}"), array('Switch: {switch 1}{break}{case} one {/switch} end', 'Fenom\Error\CompileException', "Improper usage of the tag {break}"),
); );
} }
public static function providerWhile() { public static function providerWhile()
{
$a = array("a" => 3); $a = array("a" => 3);
return array( return array(
array('While: {while false} block {/while} end', $a, 'While: end'), array('While: {while false} block {/while} end', $a, 'While: end'),
@ -501,13 +529,15 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerWhileInvalid() { public static function providerWhileInvalid()
{
return array( return array(
array('While: {while} block {/while} end', 'Fenom\CompileException', "Unexpected end of expression"), array('While: {while} block {/while} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
); );
} }
public static function providerFor() { public static function providerFor()
{
$a = array("c" => 1, "s" => 1, "m" => 3); $a = array("c" => 1, "s" => 1, "m" => 3);
return array( 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 to=6} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 5, $a: 6, end'),
@ -529,35 +559,38 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerForInvalid() { public static function providerForInvalid()
{
return array( return array(
array('For: {for} block1 {/for} end', 'Fenom\CompileException', "Unexpected end of expression"), array('For: {for} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('For: {for $a=} block1 {/for} end', 'Fenom\CompileException', "Unexpected end of expression"), array('For: {for $a=} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected end of expression"),
array('For: {for $a+1=3 to=6} block1 {/for} end', 'Fenom\CompileException', "Unexpected token '+'"), array('For: {for $a+1=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token '+'"),
array('For: {for max($a,$b)=3 to=6} block1 {/for} end', 'Fenom\CompileException', "Unexpected token 'max'"), array('For: {for max($a,$b)=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"),
array('For: {for to=6 $a=3} block1 {/for} end', 'Fenom\CompileException', "Unexpected token 'to'"), array('For: {for to=6 $a=3} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'to'"),
array('For: {for index=$i $a=3 to=6} block1 {/for} end', 'Fenom\CompileException', "Unexpected token 'index'"), array('For: {for index=$i $a=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'index'"),
array('For: {for first=$i $a=3 to=6} block1 {/for} end', 'Fenom\CompileException', "Unexpected token 'first'"), array('For: {for first=$i $a=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'first'"),
array('For: {for last=$i $a=3 to=6} block1 {/for} end', 'Fenom\CompileException', "Unexpected token 'last'"), array('For: {for last=$i $a=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'last'"),
array('For: {for $a=4 to=6 unk=4} block1 {/for} end', 'Fenom\CompileException', "Unknown parameter 'unk'"), array('For: {for $a=4 to=6 unk=4} block1 {/for} end', 'Fenom\Error\CompileException', "Unknown parameter 'unk'"),
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {break} {/for} end', 'Fenom\CompileException', "Improper usage of the tag {break}"), array('For: {for $a=4 to=6} $a: {$a}, {forelse} {break} {/for} end', 'Fenom\Error\CompileException', "Improper usage of the tag {break}"),
array('For: {for $a=4 to=6} $a: {$a}, {forelse} {continue} {/for} end', 'Fenom\CompileException', "Improper usage of the tag {continue}"), array('For: {for $a=4 to=6} $a: {$a}, {forelse} {continue} {/for} end', 'Fenom\Error\CompileException', "Improper usage of the tag {continue}"),
); );
} }
public static function providerLayersInvalid() { public static function providerLayersInvalid()
{
return array( return array(
array('Layers: {foreach $list as $e} block1 {if 1} {foreachelse} {/if} {/foreach} end', 'Fenom\CompileException', "Unexpected tag 'foreachelse' (this tag can be used with 'foreach')"), array('Layers: {foreach $list as $e} block1 {if 1} {foreachelse} {/if} {/foreach} end', 'Fenom\Error\CompileException', "Unexpected tag 'foreachelse' (this tag can be used with 'foreach')"),
array('Layers: {foreach $list as $e} block1 {if 1} {/foreach} {/if} end', 'Fenom\CompileException', "Unexpected closing of the tag 'foreach'"), array('Layers: {foreach $list as $e} block1 {if 1} {/foreach} {/if} end', 'Fenom\Error\CompileException', "Unexpected closing of the tag 'foreach'"),
array('Layers: {for $a=4 to=6} block1 {if 1} {forelse} {/if} {/for} end', 'Fenom\CompileException', "Unexpected tag 'forelse' (this tag can be used with 'for')"), array('Layers: {for $a=4 to=6} block1 {if 1} {forelse} {/if} {/for} end', 'Fenom\Error\CompileException', "Unexpected tag 'forelse' (this tag can be used with 'for')"),
array('Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', 'Fenom\CompileException', "Unexpected closing of the tag 'for'"), array('Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', 'Fenom\Error\CompileException', "Unexpected closing of the tag 'for'"),
array('Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', 'Fenom\CompileException', "Unexpected tag 'case' (this tag can be used with 'switch')"), array('Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', 'Fenom\Error\CompileException', "Unexpected tag 'case' (this tag can be used with 'switch')"),
array('Layers: {/switch} end', 'Fenom\CompileException', "Unexpected closing of the tag 'switch'"), array('Layers: {/switch} end', 'Fenom\Error\CompileException', "Unexpected closing of the tag 'switch'"),
array('Layers: {if 1} end', 'Fenom\CompileException', "Unclosed tag: {if}"), array('Layers: {if 1} end', 'Fenom\Error\CompileException', "Unclosed tag: {if}"),
); );
} }
public static function providerExtends() { public static function providerExtends()
{
return array( return array(
array('{extends file="parent.tpl"}{block name="bk1"} block1 {/block}', "Template extended by block1"), array('{extends file="parent.tpl"}{block name="bk1"} block1 {/block}', "Template extended by block1"),
array('{extends "parent.tpl"}{block "bk1"} block1 {/block}', "Template extended by block1"), array('{extends "parent.tpl"}{block "bk1"} block1 {/block}', "Template extended by block1"),
@ -568,7 +601,8 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerIsOperator() { public static function providerIsOperator()
{
return array( return array(
// is {$type} // is {$type}
array('{if $one is int} block1 {else} block2 {/if}', 'block1'), array('{if $one is int} block1 {else} block2 {/if}', 'block1'),
@ -624,7 +658,8 @@ class TemplateTest extends TestCase {
); );
} }
public static function providerInOperator() { public static function providerInOperator()
{
return array( return array(
array('{if $one in "qwertyuiop 1"} block1 {else} block2 {/if}', 'block1'), array('{if $one in "qwertyuiop 1"} block1 {else} block2 {/if}', 'block1'),
array('{if $one in string "qwertyuiop 1"} block1 {else} block2 {/if}', 'block1'), array('{if $one in string "qwertyuiop 1"} block1 {else} block2 {/if}', 'block1'),
@ -641,11 +676,22 @@ class TemplateTest extends TestCase {
); );
} }
public function _testSandbox() { public static function providerConcat() {
return array(
array('{"string" ~ $one ~ up("end")}', "string1END"),
array('{"string" ~ $one++ ~ "end"}', "string1end"),
array('{"string" ~ ++$one ~ "end"}', "string2end"),
array('{"string" ~ "one" ~ "end"}', "stringoneend"),
array('{"string" ~ 1 ~ "end"}', "string1end"),
);
}
public function _testSandbox()
{
try { try {
var_dump($this->fenom->compileCode('{$one.two->three[e]()}')->getBody()); var_dump($this->fenom->compileCode('{$a++~"hi"~time("Y:m:d")}')->getBody());
} catch(\Exception $e) { } catch (\Exception $e) {
print_r($e->getMessage()."\n".$e->getTraceAsString()); print_r($e->getMessage() . "\n" . $e->getTraceAsString());
} }
exit; exit;
} }
@ -653,28 +699,32 @@ class TemplateTest extends TestCase {
/** /**
* @dataProvider providerVars * @dataProvider providerVars
*/ */
public function testVars($code, $vars, $result) { public function testVars($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerVarsInvalid * @dataProvider providerVarsInvalid
*/ */
public function testVarsInvalid($code, $exception, $message, $options = 0) { public function testVarsInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/** /**
* @dataProvider providerModifiers * @dataProvider providerModifiers
*/ */
public function testModifiers($code, $vars, $result) { public function testModifiers($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerModifiersInvalid * @dataProvider providerModifiersInvalid
*/ */
public function testModifiersInvalid($code, $exception, $message, $options = 0) { public function testModifiersInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
@ -682,14 +732,16 @@ class TemplateTest extends TestCase {
* @group expression * @group expression
* @dataProvider providerExpressions * @dataProvider providerExpressions
*/ */
public function testExpressions($code, $vars, $result) { public function testExpressions($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerExpressionsInvalid * @dataProvider providerExpressionsInvalid
*/ */
public function testExpressionsInvalid($code, $exception, $message, $options = 0) { public function testExpressionsInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
@ -697,14 +749,16 @@ class TemplateTest extends TestCase {
* @group include * @group include
* @dataProvider providerInclude * @dataProvider providerInclude
*/ */
public function testInclude($code, $vars, $result) { public function testInclude($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerIncludeInvalid * @dataProvider providerIncludeInvalid
*/ */
public function testIncludeInvalid($code, $exception, $message, $options = 0) { public function testIncludeInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
@ -712,28 +766,32 @@ class TemplateTest extends TestCase {
* @dataProvider providerIf * @dataProvider providerIf
* @group test-if * @group test-if
*/ */
public function testIf($code, $vars, $result, $options = 0) { public function testIf($code, $vars, $result, $options = 0)
{
$this->exec($code, $vars, $result, $options); $this->exec($code, $vars, $result, $options);
} }
/** /**
* @dataProvider providerIfInvalid * @dataProvider providerIfInvalid
*/ */
public function testIfInvalid($code, $exception, $message, $options = 0) { public function testIfInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/** /**
* @dataProvider providerCreateVar * @dataProvider providerCreateVar
*/ */
public function testCreateVar($code, $vars, $result) { public function testCreateVar($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerCreateVarInvalid * @dataProvider providerCreateVarInvalid
*/ */
public function testCreateVarInvalid($code, $exception, $message, $options = 0) { public function testCreateVarInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
@ -741,77 +799,88 @@ class TemplateTest extends TestCase {
* @group ternary * @group ternary
* @dataProvider providerTernary * @dataProvider providerTernary
*/ */
public function testTernary($code, $vars, $result = 'right') { public function testTernary($code, $vars, $result = 'right')
$this->exec(__FUNCTION__.": $code end", $vars, __FUNCTION__.": $result end"); {
$this->exec(__FUNCTION__ . ": $code end", $vars, __FUNCTION__ . ": $result end");
} }
/** /**
* @dataProvider providerForeach * @dataProvider providerForeach
*/ */
public function testForeach($code, $vars, $result) { public function testForeach($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerForeachInvalid * @dataProvider providerForeachInvalid
*/ */
public function testForeachInvalid($code, $exception, $message, $options = 0) { public function testForeachInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/** /**
* @dataProvider providerFor * @dataProvider providerFor
*/ */
public function testFor($code, $vars, $result) { public function testFor($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerForInvalid * @dataProvider providerForInvalid
*/ */
public function testForInvalid($code, $exception, $message, $options = 0) { public function testForInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/** /**
* @dataProvider providerIgnores * @dataProvider providerIgnores
*/ */
public function testIgnores($code, $vars, $result) { public function testIgnores($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerSwitch * @dataProvider providerSwitch
*/ */
public function testSwitch($code, $vars, $result) { public function testSwitch($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerSwitchInvalid * @dataProvider providerSwitchInvalid
*/ */
public function testSwitchInvalid($code, $exception, $message, $options = 0) { public function testSwitchInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/** /**
* @dataProvider providerWhile * @dataProvider providerWhile
*/ */
public function testWhile($code, $vars, $result) { public function testWhile($code, $vars, $result)
{
$this->exec($code, $vars, $result); $this->exec($code, $vars, $result);
} }
/** /**
* @dataProvider providerWhileInvalid * @dataProvider providerWhileInvalid
*/ */
public function testWhileInvalid($code, $exception, $message, $options = 0) { public function testWhileInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
/** /**
* @dataProvider providerLayersInvalid * @dataProvider providerLayersInvalid
*/ */
public function testLayersInvalid($code, $exception, $message, $options = 0) { public function testLayersInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options); $this->execError($code, $exception, $message, $options);
} }
@ -819,7 +888,8 @@ class TemplateTest extends TestCase {
* @group is_operator * @group is_operator
* @dataProvider providerIsOperator * @dataProvider providerIsOperator
*/ */
public function testIsOperator($code, $result) { public function testIsOperator($code, $result)
{
$this->exec($code, self::getVars(), $result); $this->exec($code, self::getVars(), $result);
} }
@ -827,7 +897,16 @@ class TemplateTest extends TestCase {
* @group in_operator * @group in_operator
* @dataProvider providerInOperator * @dataProvider providerInOperator
*/ */
public function testInOperator($code, $result) { public function testInOperator($code, $result)
{
$this->exec($code, self::getVars(), $result);
}
/**
* @dataProvider providerConcat
*/
public function testConcat($code, $result)
{
$this->exec($code, self::getVars(), $result); $this->exec($code, self::getVars(), $result);
} }
} }

View File

@ -2,9 +2,11 @@
namespace Fenom; namespace Fenom;
use Fenom\Tokenizer; use Fenom\Tokenizer;
class TokenizerTest extends \PHPUnit_Framework_TestCase { class TokenizerTest extends \PHPUnit_Framework_TestCase
{
public function testTokens() { public function testTokens()
{
$code = 'hello, please resolve this example: sin($x)+tan($x*$t) = {U|[0,1]}'; $code = 'hello, please resolve this example: sin($x)+tan($x*$t) = {U|[0,1]}';
$tokens = new Tokenizer($code); $tokens = new Tokenizer($code);
$this->assertSame(T_STRING, $tokens->key()); $this->assertSame(T_STRING, $tokens->key());
@ -39,22 +41,23 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase {
$this->assertFalse($tokens->is($tokens::MACRO_EQUALS)); $this->assertFalse($tokens->is($tokens::MACRO_EQUALS));
$this->assertFalse($tokens->is(T_DNUMBER)); $this->assertFalse($tokens->is(T_DNUMBER));
$this->assertFalse($tokens->is(":")); $this->assertFalse($tokens->is(":"));
$this->assertSame("(", $tokens->getNext("(",")")); $this->assertSame("(", $tokens->getNext("(", ")"));
$tokens->next(); $tokens->next();
$tokens->next(); $tokens->next();
$this->assertSame("+", $tokens->getNext($tokens::MACRO_BINARY)); $this->assertSame("+", $tokens->getNext($tokens::MACRO_BINARY));
} }
public function testSkip() { public function testSkip()
{
$text = "1 foo: bar ( 3 + double ) "; $text = "1 foo: bar ( 3 + double ) ";
$tokens = new Tokenizer($text); $tokens = new Tokenizer($text);
$tokens->skip()->skip(T_STRING)->skip(':'); $tokens->skip()->skip(T_STRING)->skip(':');
try { try {
$tokens->skip(T_STRING)->skip('(')->skip(':'); $tokens->skip(T_STRING)->skip('(')->skip(':');
} catch(\Exception $e) { } catch (\Exception $e) {
$this->assertInstanceOf('Fenom\UnexpectedTokenException', $e); $this->assertInstanceOf('Fenom\Error\UnexpectedTokenException', $e);
$this->assertStringStartsWith("Unexpected token '3' in expression, expect ':'", $e->getMessage()); $this->assertStringStartsWith("Unexpected token '3' in expression, expect ':'", $e->getMessage());
} }
$this->assertTrue($tokens->valid()); $this->assertTrue($tokens->valid());

View File

@ -3,9 +3,11 @@
use Fenom\Render, use Fenom\Render,
Fenom\Provider as FS; Fenom\Provider as FS;
class FenomTest extends \Fenom\TestCase { class FenomTest extends \Fenom\TestCase
{
public static function providerOptions() { public static function providerOptions()
{
return array( return array(
array("disable_methods", Fenom::DENY_METHODS), array("disable_methods", Fenom::DENY_METHODS),
array("disable_native_funcs", Fenom::DENY_INLINE_FUNCS), array("disable_native_funcs", Fenom::DENY_INLINE_FUNCS),
@ -18,7 +20,8 @@ class FenomTest extends \Fenom\TestCase {
); );
} }
public function testCompileFile() { public function testCompileFile()
{
$a = array( $a = array(
"a" => "a", "a" => "a",
"b" => "b" "b" => "b"
@ -29,17 +32,19 @@ class FenomTest extends \Fenom\TestCase {
$this->assertSame("Template 2 b", $this->fenom->fetch('template2.tpl', $a)); $this->assertSame("Template 2 b", $this->fenom->fetch('template2.tpl', $a));
$this->assertInstanceOf('Fenom\Render', $this->fenom->getTemplate('template1.tpl')); $this->assertInstanceOf('Fenom\Render', $this->fenom->getTemplate('template1.tpl'));
$this->assertInstanceOf('Fenom\Render', $this->fenom->getTemplate('template2.tpl')); $this->assertInstanceOf('Fenom\Render', $this->fenom->getTemplate('template2.tpl'));
$this->assertSame(3, iterator_count(new FilesystemIterator(FENOM_RESOURCES.'/compile'))); $this->assertSame(3, iterator_count(new FilesystemIterator(FENOM_RESOURCES . '/compile')));
} }
public function testStorage() { public function testStorage()
{
$this->tpl('custom.tpl', 'Custom template'); $this->tpl('custom.tpl', 'Custom template');
$this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array()));
$this->tpl('custom.tpl', 'Custom template 2'); $this->tpl('custom.tpl', 'Custom template 2');
$this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array()));
} }
public function testCheckMTime() { public function testCheckMTime()
{
$this->fenom->setOptions(Fenom::FORCE_COMPILE); $this->fenom->setOptions(Fenom::FORCE_COMPILE);
$this->tpl('custom.tpl', 'Custom template'); $this->tpl('custom.tpl', 'Custom template');
$this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array()));
@ -49,7 +54,8 @@ class FenomTest extends \Fenom\TestCase {
$this->assertSame("Custom template (new)", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom template (new)", $this->fenom->fetch('custom.tpl', array()));
} }
public function testForceCompile() { public function testForceCompile()
{
$this->fenom->setOptions(Fenom::FORCE_COMPILE); $this->fenom->setOptions(Fenom::FORCE_COMPILE);
$this->tpl('custom.tpl', 'Custom template'); $this->tpl('custom.tpl', 'Custom template');
$this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array()));
@ -57,7 +63,8 @@ class FenomTest extends \Fenom\TestCase {
$this->assertSame("Custom template (new)", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom template (new)", $this->fenom->fetch('custom.tpl', array()));
} }
public function testSetModifier() { public function testSetModifier()
{
$this->fenom->addModifier("mymod", "myMod"); $this->fenom->addModifier("mymod", "myMod");
$this->tpl('custom.tpl', 'Custom modifier {$a|mymod}'); $this->tpl('custom.tpl', 'Custom modifier {$a|mymod}');
$this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->fenom->fetch('custom.tpl', array("a" => "Custom"))); $this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->fenom->fetch('custom.tpl', array("a" => "Custom")));
@ -66,7 +73,8 @@ class FenomTest extends \Fenom\TestCase {
/** /**
* @group add_functions * @group add_functions
*/ */
public function testSetFunctions() { public function testSetFunctions()
{
$this->fenom->setOptions(Fenom::FORCE_COMPILE); $this->fenom->setOptions(Fenom::FORCE_COMPILE);
$this->fenom->addFunction("myfunc", "myFunc"); $this->fenom->addFunction("myfunc", "myFunc");
$this->fenom->addBlockFunction("myblockfunc", "myBlockFunc"); $this->fenom->addBlockFunction("myblockfunc", "myBlockFunc");
@ -76,22 +84,24 @@ class FenomTest extends \Fenom\TestCase {
$this->assertSame("Custom function Block:foo:this block1:Block", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom function Block:foo:this block1:Block", $this->fenom->fetch('custom.tpl', array()));
} }
public function testSetCompilers() { public function testSetCompilers()
{
$this->fenom->setOptions(Fenom::FORCE_COMPILE); $this->fenom->setOptions(Fenom::FORCE_COMPILE);
$this->fenom->addCompiler("mycompiler", 'myCompiler'); $this->fenom->addCompiler("mycompiler", 'myCompiler');
$this->fenom->addBlockCompiler("myblockcompiler", 'myBlockCompilerOpen', 'myBlockCompilerClose', array( $this->fenom->addBlockCompiler("myblockcompiler", 'myBlockCompilerOpen', 'myBlockCompilerClose', array(
'tag' => 'myBlockCompilerTag' 'tag' => 'myBlockCompilerTag'
)); ));
$this->tpl('custom.tpl', 'Custom compiler {mycompiler name="bar"}'); $this->tpl('custom.tpl', 'Custom compiler {mycompiler name="bar"}');
$this->assertSame("Custom compiler PHP_VERSION: ".PHP_VERSION." (for bar)", $this->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom compiler PHP_VERSION: " . PHP_VERSION . " (for bar)", $this->fenom->fetch('custom.tpl', array()));
$this->tpl('custom.tpl', 'Custom compiler {myblockcompiler name="bar"} block1 {tag name="baz"} block2 {/myblockcompiler}'); $this->tpl('custom.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->fenom->fetch('custom.tpl', array())); $this->assertSame("Custom compiler PHP_VERSION: " . PHP_VERSION . " (for bar) block1 Tag baz of compiler block2 End of compiler", $this->fenom->fetch('custom.tpl', array()));
} }
/** /**
* @dataProvider providerOptions * @dataProvider providerOptions
*/ */
public function testOptions($code, $option) { public function testOptions($code, $option)
{
static $options = array(); static $options = array();
static $flags = 0; static $flags = 0;
$options[$code] = true; $options[$code] = true;
@ -104,4 +114,25 @@ class FenomTest extends \Fenom\TestCase {
// printf("remove %010b from option %010b, flags %010b\n", $option, $this->fenom->getOptions(), $flags & ~$option); // printf("remove %010b from option %010b, flags %010b\n", $option, $this->fenom->getOptions(), $flags & ~$option);
// $this->assertSame($this->fenom->getOptions(), $flags & ~$option); // $this->assertSame($this->fenom->getOptions(), $flags & ~$option);
} }
public function testFilter()
{
$punit = $this;
$this->fenom->addPreFilter(function ($src, $tpl) use ($punit) {
$this->assertInstanceOf('Fenom\Template', $tpl);
return "== $src ==";
});
$this->fenom->addPostFilter(function ($code, $tpl) use ($punit) {
$this->assertInstanceOf('Fenom\Template', $tpl);
return "+++ $code +++";
});
$this->fenom->addFilter(function ($text, $tpl) use ($punit) {
$this->assertInstanceOf('Fenom\Template', $tpl);
return "|--- $text ---|";
});
$this->assertSame('+++ |--- == hello ---||--- world == ---| +++', $this->fenom->compileCode('hello {var $user} god {/var} world')->fetch(array()));
}
} }

View File

@ -1,31 +1,38 @@
<?php <?php
function myMod($str) { function myMod($str)
return "(myMod)".$str."(/myMod)"; {
return "(myMod)" . $str . "(/myMod)";
} }
function myFunc($params) { function myFunc($params)
return "MyFunc:".$params["name"]; {
return "MyFunc:" . $params["name"];
} }
function myBlockFunc($params, $content) { function myBlockFunc($params, $content)
return "Block:".$params["name"].':'.trim($content).':Block'; {
return "Block:" . $params["name"] . ':' . trim($content) . ':Block';
} }
function myCompiler(Fenom\Tokenizer $tokenizer, Fenom\Template $tpl) { function myCompiler(Fenom\Tokenizer $tokenizer, Fenom\Template $tpl)
{
$p = $tpl->parseParams($tokenizer); $p = $tpl->parseParams($tokenizer);
return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".'.$p["name"].'.")";'; return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".' . $p["name"] . '.")";';
} }
function myBlockCompilerOpen(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope) { function myBlockCompilerOpen(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)
{
return myCompiler($tokenizer, $scope->tpl); return myCompiler($tokenizer, $scope->tpl);
} }
function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope) { function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)
{
return 'echo "End of compiler";'; return 'echo "End of compiler";';
} }
function myBlockCompilerTag(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope) { function myBlockCompilerTag(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)
{
$p = $scope->tpl->parseParams($tokenizer); $p = $scope->tpl->parseParams($tokenizer);
return 'echo "Tag ".'.$p["name"].'." of compiler";'; return 'echo "Tag ".' . $p["name"] . '." of compiler";';
} }