Merge pull request #170 from fenom-template/develop

2.7.0
This commit is contained in:
Ivan Shalganov
2015-06-03 11:57:35 +03:00
11 changed files with 187 additions and 124 deletions

View File

@@ -18,7 +18,7 @@
var data = { "time": obj.ts }; var data = { "time": obj.ts };
``` ```
Так же для игнорирования синтаксиса Fenom можно использовать модификатор `:ignore` для любого блочного тега. Так же для игнорирования синтаксиса Fenom можно использовать опцию `:ignore` для любого блочного тега.
```smarty ```smarty
{if:ignore $cdn.yandex} {if:ignore $cdn.yandex}
var item = {cdn: "//yandex.st/"}; var item = {cdn: "//yandex.st/"};

View File

@@ -6,7 +6,9 @@ require_once __DIR__.'/../tests/tools.php';
\Fenom::registerAutoload(); \Fenom::registerAutoload();
$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled'); $fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled');
$fenom->setOptions(Fenom::AUTO_RELOAD); $fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_COMPILE);
var_dump($fenom->compileCode('{set $z = "A"~~"B"}')->getBody()); $fenom->addAccessorSmart('g', 'App::$q->get', Fenom::ACCESSOR_CALL);
//$fenom->display("blocks/second.tpl", []); var_dump($fenom->compileCode('{$.g("env")}')->getBody());
//var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode());
//var_dump($fenom->display("bug158/main.tpl", []));
// $fenom->getTemplate("problem.tpl"); // $fenom->getTemplate("problem.tpl");

View File

@@ -0,0 +1,3 @@
{* Отображаемый шаблон *}
{import [test] from "bug158/test.tpl" as test}
{test.test}

View File

@@ -0,0 +1,7 @@
{* template:test.tpl *}
{macro test($break = false)}
Test macro recursive
{if $break?}
{macro.test break = true}
{/if}
{/macro}

View File

@@ -18,7 +18,8 @@ use Fenom\Template;
*/ */
class Fenom class Fenom
{ {
const VERSION = '2.6'; const VERSION = '2.7';
const REV = 1;
/* Actions */ /* Actions */
const INLINE_COMPILER = 1; const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 5; const BLOCK_COMPILER = 5;
@@ -53,6 +54,12 @@ class Fenom
const MAX_MACRO_RECURSIVE = 32; const MAX_MACRO_RECURSIVE = 32;
const ACCESSOR_CUSTOM = null;
const ACCESSOR_VAR = 'Fenom\Accessor::parserVar';
const ACCESSOR_CALL = 'Fenom\Accessor::parserCall';
public static $charset = "UTF-8";
/** /**
* @var int[] of possible options, as associative array * @var int[] of possible options, as associative array
* @see setOptions * @see setOptions
@@ -805,6 +812,21 @@ class Fenom
return $this; return $this;
} }
/**
* Add global accessor ($.)
* @param string $name
* @param callable|string $accessor
* @param string $parser
* @return Fenom
*/
public function addAccessorSmart($name, $accessor, $parser) {
$this->_accessors[$name] = array(
"accessor" => $accessor,
"parser" => $parser
);
return $this;
}
/** /**
* Remove accessor * Remove accessor
* @param string $name * @param string $name

View File

@@ -28,10 +28,21 @@ class Accessor {
'env' => '$_ENV' 'env' => '$_ENV'
); );
public static function parserVar($var, Tokenizer $tokens, Template $tpl, &$is_var) {
$is_var = true;
return $tpl->parseVariable($tokens, $var);
}
public static function parserCall($call, Tokenizer $tokens, Template $tpl) {
return $call.$tpl->parseArgs($tokens);
}
/** /**
* Accessor for global variables * Accessor for global variables
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Template $tpl * @param Template $tpl
* @return string
*/ */
public static function getVar(Tokenizer $tokens, Template $tpl) public static function getVar(Tokenizer $tokens, Template $tpl)
{ {
@@ -47,6 +58,7 @@ class Accessor {
/** /**
* Accessor for template information * Accessor for template information
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @return string
*/ */
public static function tpl(Tokenizer $tokens) public static function tpl(Tokenizer $tokens)
{ {

View File

@@ -741,7 +741,16 @@ class Compiler
*/ */
public static function setOpen(Tokenizer $tokens, Tag $scope) public static function setOpen(Tokenizer $tokens, Tag $scope)
{ {
if($tokens->is(T_VARIABLE)) {
$var = $scope->tpl->parseVariable($tokens); $var = $scope->tpl->parseVariable($tokens);
} elseif($tokens->is('$')) {
$var = $scope->tpl->parseAccessor($tokens, $is_var);
if(!$is_var) {
throw new InvalidUsageException("Accessor is not writable");
}
} else {
throw new InvalidUsageException("{set} and {add} accept only variable");
}
$before = $after = ""; $before = $after = "";
if($scope->name == 'add') { if($scope->name == 'add') {
$before = "if(!isset($var)) {\n"; $before = "if(!isset($var)) {\n";

View File

@@ -58,13 +58,13 @@ class Modifier
* @param string $charset * @param string $charset
* @return string * @return string
*/ */
public static function escape($text, $type = 'html', $charset = 'UTF-8') public static function escape($text, $type = 'html', $charset = null)
{ {
switch (strtolower($type)) { switch (strtolower($type)) {
case "url": case "url":
return urlencode($text); return urlencode($text);
case "html"; case "html";
return htmlspecialchars($text, ENT_COMPAT, $charset); return htmlspecialchars($text, ENT_COMPAT, $charset ? $charset : \Fenom::$charset);
case "js": case "js":
return json_encode($text, 64 | 256); // JSON_UNESCAPED_SLASHES = 64, JSON_UNESCAPED_UNICODE = 256 return json_encode($text, 64 | 256); // JSON_UNESCAPED_SLASHES = 64, JSON_UNESCAPED_UNICODE = 256
default: default:

View File

@@ -428,9 +428,9 @@ class Template extends Render
{ {
if ($this->macros) { if ($this->macros) {
$macros = array(); $macros = array();
foreach ($this->macros as $m) { foreach ($this->macros as $name => $m) {
if ($m["recursive"]) { if ($m["recursive"]) {
$macros[] = "\t\t'" . $m["name"] . "' => function (\$var, \$tpl) {\n?>" . $m["body"] . "<?php\n}"; $macros[] = "\t\t'" . $name . "' => function (\$var, \$tpl) {\n?>" . $m["body"] . "<?php\n}";
} }
} }
return "array(\n" . implode(",\n", $macros) . ")"; return "array(\n" . implode(",\n", $macros) . ")";
@@ -490,7 +490,7 @@ class Template extends Render
$escape = $this->_options & Fenom::AUTO_ESCAPE; $escape = $this->_options & Fenom::AUTO_ESCAPE;
} }
if ($escape) { if ($escape) {
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');"; return "echo htmlspecialchars($data, ENT_COMPAT, ".var_export(Fenom::$charset, true).");";
} else { } else {
return "echo $data;"; return "echo $data;";
} }
@@ -782,15 +782,29 @@ class Template extends Render
} else { } else {
$unary = ""; $unary = "";
} }
if ($tokens->is(T_LNUMBER, T_DNUMBER)) { switch($tokens->key()) {
case T_LNUMBER:
case T_DNUMBER:
$code = $unary . $this->parseScalar($tokens, true); $code = $unary . $this->parseScalar($tokens, true);
} elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING, '"', T_ENCAPSED_AND_WHITESPACE)) { break;
case T_CONSTANT_ENCAPSED_STRING:
case '"':
case T_ENCAPSED_AND_WHITESPACE:
if ($unary) { if ($unary) {
throw new UnexpectedTokenException($tokens->back()); throw new UnexpectedTokenException($tokens->back());
} }
$code = $this->parseScalar($tokens, true); $code = $this->parseScalar($tokens, true);
} elseif ($tokens->is(T_VARIABLE)) { break;
case '$':
$code = $this->parseAccessor($tokens, $is_var);
if(!$is_var) {
$code = $unary . $code;
break;
}
case T_VARIABLE:
if(!isset($code)) {
$code = $this->parseVariable($tokens); $code = $this->parseVariable($tokens);
}
if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) { if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
if ($this->_options & Fenom::DENY_METHODS) { if ($this->_options & Fenom::DENY_METHODS) {
throw new \LogicException("Forbidden to call methods"); throw new \LogicException("Forbidden to call methods");
@@ -810,21 +824,22 @@ class Template extends Render
$code = $unary . $code; $code = $unary . $code;
} }
} }
} elseif ($tokens->is('$')) { break;
$is_var = false; case T_DEC:
$code = $unary . $this->parseAccessor($tokens); case T_INC:
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
if($this->_options & Fenom::FORCE_VERIFY) { if($this->_options & Fenom::FORCE_VERIFY) {
$var = $this->parseVariable($tokens); $var = $this->parseVariable($tokens);
$code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)'; $code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)';
} else { } else {
$code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens); $code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
} }
} elseif ($tokens->is("(")) { break;
case '(':
$tokens->next(); $tokens->next();
$code = $unary . "(" . $this->parseExpr($tokens) . ")"; $code = $unary . "(" . $this->parseExpr($tokens) . ")";
$tokens->need(")")->next(); $tokens->need(")")->next();
} elseif ($tokens->is(T_STRING)) { break;
case T_STRING:
if ($tokens->isSpecialVal()) { if ($tokens->isSpecialVal()) {
$code = $unary . $tokens->getAndNext(); $code = $unary . $tokens->getAndNext();
} elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) { } elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) {
@@ -845,7 +860,9 @@ class Template extends Render
} else { } else {
return false; return false;
} }
} elseif ($tokens->is(T_ISSET, T_EMPTY)) { break;
case T_ISSET:
case T_EMPTY:
$func = $tokens->getAndNext(); $func = $tokens->getAndNext();
if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) { if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
$code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")"; $code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")";
@@ -853,16 +870,20 @@ class Template extends Render
} else { } else {
throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables"); throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables");
} }
} elseif ($tokens->is('[')) { break;
case '[':
if ($unary) { if ($unary) {
throw new UnexpectedTokenException($tokens->back()); throw new UnexpectedTokenException($tokens->back());
} }
$code = $this->parseArray($tokens); $code = $this->parseArray($tokens);
} elseif ($unary) { break;
default:
if ($unary) {
throw new UnexpectedTokenException($tokens->back()); throw new UnexpectedTokenException($tokens->back());
} else { } else {
return false; return false;
} }
}
if (($allows & self::TERM_MODS) && $tokens->is('|')) { if (($allows & self::TERM_MODS) && $tokens->is('|')) {
$code = $this->parseModifier($tokens, $code); $code = $this->parseModifier($tokens, $code);
$is_var = false; $is_var = false;
@@ -958,14 +979,20 @@ class Template extends Render
/** /**
* Parse accessor * Parse accessor
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param bool $is_var
* @return string * @return string
*/ */
public function parseAccessor(Tokenizer $tokens) public function parseAccessor(Tokenizer $tokens, &$is_var = false)
{ {
$accessor = $tokens->need('$')->next()->need('.')->next()->current(); $accessor = $tokens->need('$')->next()->need('.')->next()->current();
$callback = $this->getStorage()->getAccessor($accessor); $parser = $this->getStorage()->getAccessor($accessor);
if($callback) { $is_var = false;
return call_user_func($callback, $tokens->next(), $this); if($parser) {
if(is_string($parser)) {
return call_user_func_array($parser, array($tokens->next(), $this, &$is_var));
} else {
return call_user_func_array($parser['parser'], array($parser['accessor'], $tokens->next(), $this, &$is_var));
}
} else { } else {
throw new \RuntimeException("Unknown accessor '$accessor'"); throw new \RuntimeException("Unknown accessor '$accessor'");
} }
@@ -1342,6 +1369,7 @@ class Template extends Render
{ {
$recursive = false; $recursive = false;
$macro = false; $macro = false;
if (isset($this->macros[$name])) { if (isset($this->macros[$name])) {
$macro = $this->macros[$name]; $macro = $this->macros[$name];
$recursive = $macro['recursive']; $recursive = $macro['recursive'];
@@ -1414,7 +1442,6 @@ class Template extends Render
* (1 + 2.3, 'string', $var, [2,4]) * (1 + 2.3, 'string', $var, [2,4])
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param bool $as_string
* @return string * @return string
*/ */
public function parseArgs(Tokenizer $tokens) public function parseArgs(Tokenizer $tokens)

View File

@@ -77,34 +77,12 @@ class MacrosTest extends TestCase
$this->tpl( $this->tpl(
"macro_recursive_import.tpl", "macro_recursive_import.tpl",
' '
{import "macro_recursive.tpl" as math} {import [factorial] from "macro_recursive.tpl" as math}
{math.factorial num=10}' {math.factorial num=10}'
); );
} }
public function _testSandbox()
{
try {
// $this->fenom->compile("macro_recursive.tpl")->display([]);
// $this->fenom->flush();
// var_dump($this->fenom->fetch("macro_recursive.tpl", []));
var_dump(
$this->fenom->compileCode(
'{macro factorial(num)}
{if $num}
{$num} {macro.factorial num=$num-1} {$num}
{/if}
{/macro}'
)->getBody()
);
// var_dump($this->fenom->display("macro_recursive_import.tpl", array()));
} catch (\Exception $e) {
var_dump($e->getMessage() . ": " . $e->getTraceAsString());
}
exit;
}
/** /**
* @throws \Exception * @throws \Exception
* @group macros * @group macros
@@ -162,6 +140,7 @@ class MacrosTest extends TestCase
$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)); $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));
} }
public function testImportRecursive() public function testImportRecursive()
{ {
$this->fenom->compile('macro_recursive_import.tpl'); $this->fenom->compile('macro_recursive_import.tpl');

View File

@@ -10,23 +10,25 @@ class SandboxTest extends TestCase {
*/ */
public function test() public function test()
{ {
return;
$this->fenom->setOptions(\Fenom::FORCE_VERIFY);
$this->fenom->addAccessorSmart('q', 'Navi::$q', \Fenom::ACCESSOR_VAR);
// $this->assertEquals([1, 2, 4, "as" => 767, "df" => ["qert"]], [1, 2, 4, "as" => 767, "df" => ["qet"]]); // $this->assertEquals([1, 2, 4, "as" => 767, "df" => ["qert"]], [1, 2, 4, "as" => 767, "df" => ["qet"]]);
// $this->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) { // $this->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) {
// return '<?php ' . $tag->cutContent(); // return '<?php ' . $tag->cutContent();
// }); // });
// $this->tpl('welcome.tpl', '{$a}'); // $this->tpl('welcome.tpl', '{$a}');
// var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody()); // var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody());
// try { try {
// var_dump($this->fenom->compileCode('{foreach $a as $k => $v} {/foreach}')->getBody()); var_dump($this->fenom->compileCode('{set $.q.ddqd->d() = 333}')->getBody());
// } catch (\Exception $e) { } catch (\Exception $e) {
// print_r($e->getMessage() . "\n" . $e->getTraceAsString()); print_r($e->getMessage() . "\n" . $e->getTraceAsString());
// while ($e->getPrevious()) { while ($e->getPrevious()) {
// $e = $e->getPrevious(); $e = $e->getPrevious();
// print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString()); print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
// } }
// } }
// exit; exit;
} }
} }