Add tag entry

This commit is contained in:
Ivan Shalganov
2014-04-12 01:00:58 +04:00
parent 11ae49f187
commit 1437a13bd1
8 changed files with 151 additions and 94 deletions

View File

@ -23,9 +23,9 @@ Tag {macro} [RU]
Во время рекурсивного вызова используйте суффикс macro что бы обратиться к текущему макросу: Во время рекурсивного вызова используйте суффикс macro что бы обратиться к текущему макросу:
```smarty ```smarty
{macro plus(x, y, z=0)} {macro plus($x, $y, $z=0)}
... ...
{macro.plus x=2 y=4} {macro.plus x=2 y=$y}
... ...
{/macro} {/macro}
``` ```

View File

@ -259,6 +259,39 @@ class Fenom
) )
); );
/**
* List of tests
* @see https://github.com/bzick/fenom/blob/develop/docs/operators.md#test-operator
* @var array
*/
protected $_tests = array(
'integer' => 'is_int(%s)',
'int' => 'is_int(%s)',
'float' => 'is_float(%s)',
'double' => 'is_float(%s)',
'decimal' => 'is_float(%s)',
'string' => 'is_string(%s)',
'bool' => 'is_bool(%s)',
'boolean' => 'is_bool(%s)',
'number' => 'is_numeric(%s)',
'numeric' => 'is_numeric(%s)',
'scalar' => 'is_scalar(%s)',
'object' => 'is_object(%s)',
'callable' => 'is_callable(%s)',
'callback' => 'is_callable(%s)',
'array' => 'is_array(%s)',
'iterable' => '\Fenom\Modifier::isIterable(%s)',
'const' => 'defined(%s)',
'template' => '$tpl->getStorage()->templateExists(%s)',
'empty' => 'empty(%s)',
'set' => 'isset(%s)',
'_empty' => '!%s', // for none variable
'_set' => '(%s !== null)', // for none variable
'odd' => '(%s & 1)',
'even' => '!(%s %% 2)',
'third' => '!(%s %% 3)'
);
/** /**
* Just factory * Just factory
* *
@ -538,6 +571,24 @@ class Fenom
return $this; return $this;
} }
/**
* Add custom test
* @param string $name test name
* @param string $code test PHP code. Code may contains placeholder %s, which will be replaced by test-value. For example: is_callable(%s)
*/
public function addTest($name, $code) {
$this->_tests[$name] = $code;
}
/**
* Get test code by name
* @param string $name
* @return string|bool
*/
public function getTest($name) {
return isset($this->_tests[$name]) ? $this->_tests[$name] : false;
}
/** /**
* Return modifier function * Return modifier function
* *

View File

@ -27,12 +27,13 @@ class Compiler
* *
* @static * @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @throws \LogicException * @throws \LogicException
* @return string * @return string
*/ */
public static function tagInclude(Tokenizer $tokens, Template $tpl) public static function tagInclude(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
$name = false; $name = false;
$cname = $tpl->parsePlainArg($tokens, $name); $cname = $tpl->parsePlainArg($tokens, $name);
$p = $tpl->parseParams($tokens); $p = $tpl->parseParams($tokens);
@ -60,12 +61,13 @@ class Compiler
/** /**
* Tag {insert ...} * Tag {insert ...}
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @return string
* @throws Error\InvalidUsageException * @throws Error\InvalidUsageException
* @return string
*/ */
public static function tagInsert(Tokenizer $tokens, Template $tpl) public static function tagInsert(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
$tpl->parsePlainArg($tokens, $name); $tpl->parsePlainArg($tokens, $name);
if (!$name) { if (!$name) {
throw new InvalidUsageException("Tag {insert} accept only static template name"); throw new InvalidUsageException("Tag {insert} accept only static template name");
@ -466,13 +468,13 @@ class Compiler
/** /**
* Dispatch {extends} tag * Dispatch {extends} tag
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @throws \RuntimeException
* @throws Error\InvalidUsageException * @throws Error\InvalidUsageException
* @return string * @return string
*/ */
public static function tagExtends(Tokenizer $tokens, Template $tpl) public static function tagExtends(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
if ($tpl->extends) { if ($tpl->extends) {
throw new InvalidUsageException("Only one {extends} allowed"); throw new InvalidUsageException("Only one {extends} allowed");
} elseif ($tpl->getStackSize()) { } elseif ($tpl->getStackSize()) {
@ -520,12 +522,13 @@ class Compiler
/** /**
* Tag {use ...} * Tag {use ...}
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @throws InvalidUsageException * @throws Error\InvalidUsageException
* @return string * @return string
*/ */
public static function tagUse(Tokenizer $tokens, Template $tpl) public static function tagUse(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
if ($tpl->getStackSize()) { if ($tpl->getStackSize()) {
throw new InvalidUsageException("Tag {use} can not be nested"); throw new InvalidUsageException("Tag {use} can not be nested");
} }
@ -623,24 +626,24 @@ class Compiler
* @static * @static
* @param mixed $function * @param mixed $function
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @return string * @return string
*/ */
public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) public static function stdFuncParser($function, Tokenizer $tokens, Tag $tag)
{ {
return "$function(" . self::toArray($tpl->parseParams($tokens)) . ', $tpl)'; return "$function(" . self::toArray($tag->tpl->parseParams($tokens)) . ', $tpl)';
} }
/** /**
* Smart function parser * Smart function parser
* *
* @static * @static
* @param $function * @param string $function
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @return string * @return string
*/ */
public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl) public static function smartFuncParser($function, Tokenizer $tokens, Tag $tag)
{ {
if (strpos($function, "::")) { if (strpos($function, "::")) {
list($class, $method) = explode("::", $function, 2); list($class, $method) = explode("::", $function, 2);
@ -649,7 +652,7 @@ class Compiler
$ref = new \ReflectionFunction($function); $ref = new \ReflectionFunction($function);
} }
$args = array(); $args = array();
$params = $tpl->parseParams($tokens); $params = $tag->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()];
@ -767,12 +770,13 @@ class Compiler
* Tag {cycle} * Tag {cycle}
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @throws Error\InvalidUsageException
* @return string * @return string
* @throws InvalidUsageException
*/ */
public static function tagCycle(Tokenizer $tokens, Template $tpl) public static function tagCycle(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
if ($tokens->is("[")) { if ($tokens->is("[")) {
$exp = $tpl->parseArray($tokens); $exp = $tpl->parseArray($tokens);
} else { } else {
@ -806,13 +810,14 @@ class Compiler
* Import macros from templates * Import macros from templates
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @throws UnexpectedTokenException * @throws Error\UnexpectedTokenException
* @throws InvalidUsageException * @throws Error\InvalidUsageException
* @return string * @return string
*/ */
public static function tagImport(Tokenizer $tokens, Template $tpl) public static function tagImport(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
$import = array(); $import = array();
if ($tokens->is('[')) { if ($tokens->is('[')) {
$tokens->next(); $tokens->next();
@ -933,29 +938,15 @@ class Compiler
* Output value as is, without escaping * Output value as is, without escaping
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Tag $tag
* @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagRaw(Tokenizer $tokens, Template $tpl) public static function tagRaw(Tokenizer $tokens, Tag $tag)
{ {
$tpl = $tag->tpl;
$escape = (bool)$tpl->escape; $escape = (bool)$tpl->escape;
$tpl->escape = false; $tpl->escape = false;
if ($tokens->is(':')) {
$func = $tokens->getNext(Tokenizer::MACRO_STRING);
$tag = $tpl->getStorage()->getTag($func, $tpl);
if ($tag["type"] == \Fenom::INLINE_FUNCTION) {
$code = $tpl->parseAct($tokens);
// } elseif ($tag["type"] == \Fenom::BLOCK_FUNCTION) {
// $code = $tpl->parseAct($tokens);
// $tpl->getLastScope()->escape = false;
// return $code;
} else {
throw new InvalidUsageException("Raw mode allow for expressions or functions");
}
} else {
$code = $tpl->out($tpl->parseExpr($tokens)); $code = $tpl->out($tpl->parseExpr($tokens));
}
$tpl->escape = $escape; $tpl->escape = $escape;
return $code; return $code;
} }

View File

@ -26,8 +26,7 @@ class Scope extends \ArrayObject
public $tpl; public $tpl;
public $is_compiler = true; public $is_compiler = true;
public $is_closed = false; public $is_closed = false;
public $escape = false; public $options;
public $options = array();
private $_action; private $_action;
private $_body; private $_body;
private $_offset; private $_offset;

46
src/Fenom/Tag.php Normal file
View File

@ -0,0 +1,46 @@
<?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;
class Tag {
/**
* @var Template
*/
public $tpl;
public $name;
public $options;
public function __construct($name, Template $tpl) {
$this->name = $name;
$this->tpl = $tpl;
}
public function optLtrim() {
}
public function optRtrim() {
}
public function optTrim() {
}
public function optRaw() {
}
public function optEscape() {
}
}

View File

@ -109,34 +109,6 @@ class Template extends Render
*/ */
private $_crc = 0; private $_crc = 0;
protected static $_tests = array(
'integer' => 'is_int(%s)',
'int' => 'is_int(%s)',
'float' => 'is_float(%s)',
'double' => 'is_float(%s)',
'decimal' => 'is_float(%s)',
'string' => 'is_string(%s)',
'bool' => 'is_bool(%s)',
'boolean' => 'is_bool(%s)',
'number' => 'is_numeric(%s)',
'numeric' => 'is_numeric(%s)',
'scalar' => 'is_scalar(%s)',
'object' => 'is_object(%s)',
'callable' => 'is_callable(%s)',
'callback' => 'is_callable(%s)',
'array' => 'is_array(%s)',
'iterable' => '\Fenom\Modifier::isIterable(%s)',
'const' => 'defined(%s)',
'template' => '$tpl->getStorage()->templateExists(%s)',
'empty' => 'empty(%s)',
'set' => 'isset(%s)',
'_empty' => '!%s', // for none variable
'_set' => '(%s !== null)', // for none variable
'odd' => '(%s & 1)',
'even' => '!(%s %% 2)',
'third' => '!(%s %% 3)'
);
/** /**
* @param Fenom $fenom Template storage * @param Fenom $fenom Template storage
* @param int $options * @param int $options
@ -609,6 +581,7 @@ class Template extends Render
*/ */
public function parseAct(Tokenizer $tokens) public function parseAct(Tokenizer $tokens)
{ {
$options = array();
if ($tokens->is(Tokenizer::MACRO_STRING)) { if ($tokens->is(Tokenizer::MACRO_STRING)) {
$action = $tokens->getAndNext(); $action = $tokens->getAndNext();
} else { } else {
@ -632,38 +605,35 @@ class Template extends Render
if ($tokens->is("(")) { if ($tokens->is("(")) {
return $this->out($this->parseExpr($tokens->seek($p))); return $this->out($this->parseExpr($tokens->seek($p)));
} else { } else {
return $this->out(Compiler::smartFuncParser($static, $tokens, $this)); return $this->out(Compiler::smartFuncParser($static, $tokens, new Tag($static, $this)));
} }
} elseif($tokens->is(':')) { // parse tag options } elseif($tokens->is(':')) { // parse tag options
do { do {
$tokens->options[ $tokens->next()->need(T_STRING)->getAndNext() ] = true; $options[ $tokens->next()->need(T_STRING)->getAndNext() ] = true;
} while($tokens->is(':')); } while($tokens->is(':'));
} }
if ($tag = $this->_fenom->getTag($action, $this)) { if ($tag = $this->_fenom->getTag($action, $this)) {
if(isset($tokens->options['ignore']) && ($tag["type"] & Fenom::BLOCK_COMPILER)) { if($tag["type"] == Fenom::BLOCK_COMPILER || $tag["type"] == Fenom::BLOCK_FUNCTION) {
$this->_ignore = $action; $scope = new Scope($action, $this, $this->_line, $tag, count($this->_stack), $this->_body);
} else {
$scope = new Tag($action, $this);
} }
$scope->options = $options;
switch ($tag["type"]) { switch ($tag["type"]) {
case Fenom::BLOCK_COMPILER: case Fenom::BLOCK_COMPILER:
$scope = new Scope($action, $this, $this->_line, $tag, count($this->_stack), $this->_body);
$scope->options = &$tokens->options;
$code = $scope->open($tokens); $code = $scope->open($tokens);
if (!$scope->is_closed) { if (!$scope->is_closed) {
array_push($this->_stack, $scope); array_push($this->_stack, $scope);
} }
return $code; return $code;
case Fenom::INLINE_COMPILER: case Fenom::INLINE_COMPILER:
return call_user_func($tag["parser"], $tokens, $this); return call_user_func($tag["parser"], $tokens, $scope);
case Fenom::INLINE_FUNCTION: case Fenom::INLINE_FUNCTION:
return $this->out(call_user_func($tag["parser"], $tag["function"], $tokens, $this)); return $this->out(call_user_func($tag["parser"], $tag["function"], $tokens, $scope));
case Fenom::BLOCK_FUNCTION: case Fenom::BLOCK_FUNCTION:
$scope = new Scope($action, $this, $this->_line, $tag, count($this->_stack), $this->_body);
$scope->options = &$tokens->options;
$scope->setFuncName($tag["function"]); $scope->setFuncName($tag["function"]);
array_push($this->_stack, $scope); array_push($this->_stack, $scope);
$scope->escape = $this->escape;
$this->escape = false;
return $scope->open($tokens); return $scope->open($tokens);
default: default:
throw new \LogicException("Unknown function type"); throw new \LogicException("Unknown function type");
@ -1052,10 +1022,10 @@ class Template extends Render
if (!$variable && ($action == "set" || $action == "empty")) { if (!$variable && ($action == "set" || $action == "empty")) {
$action = "_$action"; $action = "_$action";
$tokens->next(); $tokens->next();
return $invert . sprintf(self::$_tests[$action], $value); return $invert . sprintf($this->_fenom->getTest($action), $value);
} elseif (isset(self::$_tests[$action])) { } elseif ($test = $this->_fenom->getTest($action)) {
$tokens->next(); $tokens->next();
return $invert . sprintf(self::$_tests[$action], $value); return $invert . sprintf($test, $value);
} elseif ($tokens->isSpecialVal()) { } elseif ($tokens->isSpecialVal()) {
$tokens->next(); $tokens->next();
return '(' . $value . ' ' . $equal . '= ' . $action . ')'; return '(' . $value . ' ' . $equal . '= ' . $action . ')';

View File

@ -84,7 +84,6 @@ class Tokenizer
public $tokens; public $tokens;
public $p = 0; public $p = 0;
public $quotes = 0; public $quotes = 0;
public $options;
private $_max = 0; private $_max = 0;
private $_last_no = 0; private $_last_no = 0;

View File

@ -15,15 +15,16 @@ 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\Tag $tag)
{ {
$p = $tpl->parseParams($tokenizer); $p = $tag->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); $p = $scope->tpl->parseParams($tokenizer);
return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".' . $p["name"] . '.")";';
} }
function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope) function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)