mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
Add recursive macros support (#28)
This commit is contained in:
parent
97e891895a
commit
4edd0e1708
@ -9,8 +9,8 @@ Fenom - Template Engine for PHP
|
||||
* Simple [syntax](./docs/syntax.md)
|
||||
* [Fast](./docs/benchmark.md)
|
||||
* [Secure](./docs/settings.md)
|
||||
* [Simple](./ideology.md)
|
||||
* [Flexible](./docs/main.md#extends)
|
||||
* Simple
|
||||
* [Flexible](./docs/ext/extensions.md)
|
||||
* [Lightweight](./docs/benchmark.md#stats)
|
||||
* [Powerful](./docs/main.md)
|
||||
* Easy to use:
|
||||
|
@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "fenom/fenom",
|
||||
"type": "library",
|
||||
"description": "Fenom - fast template engine for PHP",
|
||||
"homepage": "http://bzick.github.io/fenom/",
|
||||
"keywords": ["fenom", "template", "templating", "cytro"],
|
||||
"description": "Fenom - excellent template engine for PHP",
|
||||
"keywords": ["fenom", "template", "templating", "templater"],
|
||||
"license": "BSD-3",
|
||||
"authors": [
|
||||
{
|
||||
|
@ -1,5 +1,9 @@
|
||||
Extensions
|
||||
==========
|
||||
|
||||
* [Extra pack](https://github.com/bzick/fenom-extra) basic add-ons for web-base project.
|
||||
* *Smarty pack* (planned) Smarty3 adapter
|
||||
* [Extra pack](https://github.com/bzick/fenom-extra) of add-ons for Fenom template engine.
|
||||
* Tools for static files (css, js).
|
||||
* Global variables
|
||||
* Allow more hooks for extending
|
||||
* Add variable container
|
||||
* You can only use the necessary add-ons
|
@ -47,6 +47,8 @@ class Fenom {
|
||||
const DEFAULT_FUNC_CLOSE = 'Fenom\Compiler::stdFuncClose';
|
||||
const SMART_FUNC_PARSER = 'Fenom\Compiler::smartFuncParser';
|
||||
|
||||
const MAX_MACRO_RECURSIVE = 32;
|
||||
|
||||
/**
|
||||
* @var int[] of possible options, as associative array
|
||||
* @see setOptions
|
||||
|
@ -34,7 +34,7 @@ class Compiler {
|
||||
if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
|
||||
$inc = $tpl->getStorage()->compile($name, false);
|
||||
$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 {
|
||||
return '$tpl->getStorage()->getTemplate('.$cname.')->display('.self::toArray($p).'+(array)$tpl);';
|
||||
}
|
||||
@ -42,7 +42,7 @@ class Compiler {
|
||||
if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
|
||||
$inc = $tpl->getStorage()->compile($name, false);
|
||||
$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 {
|
||||
return '$tpl->getStorage()->getTemplate('.$cname.')->display((array)$tpl);';
|
||||
}
|
||||
@ -493,7 +493,6 @@ class Compiler {
|
||||
*/
|
||||
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
|
||||
if($scope->level > 0) {
|
||||
var_dump("".$scope->tpl);
|
||||
$scope->tpl->_compatible = true;
|
||||
}
|
||||
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
|
||||
@ -809,7 +808,6 @@ class Compiler {
|
||||
if($alias) {
|
||||
$name = $alias.'.'.$name;
|
||||
}
|
||||
|
||||
$tpl->macros[$name] = $macro;
|
||||
}
|
||||
$tpl->addDepend($donor);
|
||||
@ -827,8 +825,9 @@ class Compiler {
|
||||
*/
|
||||
public static function macroOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["name"] = $tokens->get(Tokenizer::MACRO_STRING);
|
||||
$scope["args"] = array();
|
||||
$scope["defaults"] = array();
|
||||
$scope["recursive"] = array();
|
||||
$args = array();
|
||||
$defaults = array();
|
||||
if(!$tokens->valid()) {
|
||||
return;
|
||||
}
|
||||
@ -836,12 +835,12 @@ class Compiler {
|
||||
if($tokens->is(')')) {
|
||||
return;
|
||||
}
|
||||
while($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
$scope["args"][] = $param = $tokens->getAndNext();
|
||||
while($tokens->is(Tokenizer::MACRO_STRING, T_VARIABLE)) {
|
||||
$args[] = $param = $tokens->getAndNext();
|
||||
if($tokens->is('=')) {
|
||||
$tokens->next();
|
||||
if($tokens->is(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_DNUMBER) || $tokens->isSpecialVal()) {
|
||||
$scope["defaults"][ $param ] = $tokens->getAndNext();
|
||||
$defaults[ $param ] = $tokens->getAndNext();
|
||||
} else {
|
||||
throw new InvalidUsageException("Macro parameters may have only scalar defaults");
|
||||
}
|
||||
@ -849,7 +848,12 @@ class Compiler {
|
||||
$tokens->skipIf(',');
|
||||
}
|
||||
$tokens->skipIf(')');
|
||||
|
||||
$scope["macro"] = array(
|
||||
"id" => $scope->tpl->i++,
|
||||
"args" => $args,
|
||||
"defaults" => $defaults,
|
||||
"body" => ""
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -858,12 +862,23 @@ class Compiler {
|
||||
* @param Scope $scope
|
||||
*/
|
||||
public static function macroClose(Tokenizer $tokens, Scope $scope) {
|
||||
$scope->tpl->macros[ $scope["name"] ] = array(
|
||||
"body" => $content = $scope->getContent(),
|
||||
"args" => $scope["args"],
|
||||
"defaults" => $scope["defaults"]
|
||||
);
|
||||
$scope->tpl->_body = substr($scope->tpl->_body, 0, strlen($scope->tpl->_body) - strlen($content));
|
||||
if($scope["recursive"]) {
|
||||
$switch = "switch(\$call['mark']) {\n";
|
||||
foreach($scope["recursive"] as $mark) {
|
||||
$switch .= "case $mark: goto macro_$mark;\n";
|
||||
}
|
||||
$switch .= "}";
|
||||
$stack = '$stack_'.$scope["macro"]['id'];
|
||||
$scope["macro"]["body"] = '<?php '.$stack.' = array(); macro_'.$scope["macro"]['id'].': ?>'.$scope->cutContent().'<?php if('.$stack.') {'.PHP_EOL.
|
||||
'$call = array_pop('.$stack.');'.PHP_EOL.
|
||||
'$tpl = $call["tpl"];'.PHP_EOL.
|
||||
$switch.PHP_EOL.
|
||||
'unset($call, '.$stack.');'.PHP_EOL.
|
||||
'} ?>';
|
||||
} else {
|
||||
$scope["macro"]["body"] = $scope->cutContent();
|
||||
}
|
||||
$scope->tpl->macros[ $scope["name"] ] = $scope["macro"];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,11 +72,10 @@ class Render extends \ArrayObject {
|
||||
* @param callable $code template body
|
||||
* @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;
|
||||
$props += self::$_props;
|
||||
$this->_name = $props["name"];
|
||||
// $this->_provider = $this->_fenom->getProvider($props["scm"]);
|
||||
$this->_scm = $props["scm"];
|
||||
$this->_time = $props["time"];
|
||||
$this->_depends = $props["depends"];
|
||||
@ -85,28 +84,48 @@ class Render extends \ArrayObject {
|
||||
|
||||
/**
|
||||
* Get template storage
|
||||
* @return Fenom
|
||||
* @return \Fenom
|
||||
*/
|
||||
public function getStorage() {
|
||||
return $this->_fenom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get depends list
|
||||
* @return array
|
||||
*/
|
||||
public function getDepends() {
|
||||
return $this->_depends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema name
|
||||
* @return string
|
||||
*/
|
||||
public function getScm() {
|
||||
return $this->_scm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider of template source
|
||||
* @return ProviderInterface
|
||||
*/
|
||||
public function getProvider() {
|
||||
return $this->_fenom->getProvider($this->_scm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name without schema
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseName() {
|
||||
return $this->_base_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parse options
|
||||
* @return int
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->_options;
|
||||
}
|
||||
|
@ -38,12 +38,6 @@ class Template extends Render {
|
||||
* @var int shared counter
|
||||
*/
|
||||
public $i = 1;
|
||||
/**
|
||||
* Template PHP code
|
||||
* @var string
|
||||
*/
|
||||
public $_body;
|
||||
|
||||
/**
|
||||
* @var array of macros
|
||||
*/
|
||||
@ -63,10 +57,17 @@ class Template extends Render {
|
||||
* @var bool
|
||||
*/
|
||||
public $escape = false;
|
||||
|
||||
public $_extends;
|
||||
public $_extended = false;
|
||||
public $_compatible;
|
||||
|
||||
/**
|
||||
* Template PHP code
|
||||
* @var string
|
||||
*/
|
||||
private $_body;
|
||||
|
||||
/**
|
||||
* Call stack
|
||||
* @var Scope[]
|
||||
@ -362,9 +363,10 @@ class Template extends Render {
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateCode() {
|
||||
$before = $this->_before ? $this->_before."\n" : "";
|
||||
return "<?php \n".
|
||||
"/** Fenom template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\n".
|
||||
($this->_before ? $this->_before."\n" : "").
|
||||
$before. // some code 'before' template
|
||||
"return new Fenom\\Render(\$fenom, ".$this->_getClosureSource().", ".var_export(array(
|
||||
"options" => $this->_options,
|
||||
"provider" => $this->_scm,
|
||||
@ -517,7 +519,7 @@ class Template extends Render {
|
||||
if($action !== "macro") {
|
||||
$name = $action.".".$name;
|
||||
}
|
||||
return $this->parseMacro($tokens, $name);
|
||||
return $this->parseMacroCall($tokens, $name);
|
||||
}
|
||||
|
||||
if($tag = $this->_fenom->getTag($action, $this)) { // call some function
|
||||
@ -839,7 +841,7 @@ class Template extends Render {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse 'is' and 'is not' operator
|
||||
* Parse 'is' and 'is not' operators
|
||||
* @see $_checkers
|
||||
* @param Tokenizer $tokens
|
||||
* @param string $value
|
||||
@ -1095,7 +1097,7 @@ class Template extends Render {
|
||||
}
|
||||
|
||||
if(!is_string($mods)) { // dynamic modifier
|
||||
$mods = 'call_user_func($tpl->getStorage()->getModifier("'.$modifier_name.'"), ';
|
||||
$mods = 'call_user_func($tpl->getStorage()->getModifier("'.$mods.'"), ';
|
||||
} else {
|
||||
$mods .= "(";
|
||||
}
|
||||
@ -1188,24 +1190,42 @@ class Template extends Render {
|
||||
* @return string
|
||||
* @throws InvalidUsageException
|
||||
*/
|
||||
public function parseMacro(Tokenizer $tokens, $name) {
|
||||
public function parseMacroCall(Tokenizer $tokens, $name) {
|
||||
$recursive = false;
|
||||
$macro = false;
|
||||
if(isset($this->macros[ $name ])) {
|
||||
$macro = $this->macros[ $name ];
|
||||
} else {
|
||||
foreach($this->_stack as $scope) {
|
||||
if($scope->name == 'macro' && $scope['name'] == $name) { // invoke recursive
|
||||
$recursive = $scope;
|
||||
$macro = $scope['macro'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!$macro) {
|
||||
throw new InvalidUsageException("Undefined macro '$name'");
|
||||
}
|
||||
}
|
||||
$tokens->next();
|
||||
$p = $this->parseParams($tokens);
|
||||
$args = array();
|
||||
foreach($macro["args"] as $arg) {
|
||||
foreach($macro['args'] as $arg) {
|
||||
if(isset($p[ $arg ])) {
|
||||
$args[ $arg ] = $p[ $arg ];
|
||||
} elseif(isset($macro["defaults"][ $arg ])) {
|
||||
$args[ $arg ] = $macro["defaults"][ $arg ];
|
||||
} elseif(isset($macro['defaults'][ $arg ])) {
|
||||
$args[ $arg ] = $macro['defaults'][ $arg ];
|
||||
} else {
|
||||
throw new InvalidUsageException("Macro '$name' require '$arg' argument");
|
||||
}
|
||||
}
|
||||
$args = $args ? '$tpl = '.Compiler::toArray($args).';' : '';
|
||||
return '$_tpl = $tpl; '.$args.' ?>'.$macro["body"].'<?php $tpl = $_tpl; unset($_tpl);';
|
||||
if($recursive) {
|
||||
$n = $this->i++;
|
||||
$recursive['recursive'][] = $n;
|
||||
return '$stack_'.$macro['id'].'[] = array("tpl" => $tpl, "mark" => '.$n.'); '.$args.' goto macro_'.$macro['id'].'; macro_'.$n.':';
|
||||
} else {
|
||||
throw new InvalidUsageException("Undefined macro '$name'");
|
||||
return '$_tpl = $tpl; '.$args.' ?>'.$macro["body"].'<?php $tpl = $_tpl; unset($_tpl);';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1270,6 +1290,7 @@ class Template extends Render {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse parameters as $key=$value
|
||||
* param1=$var param2=3 ...
|
||||
|
@ -42,6 +42,25 @@ class MacrosTest extends TestCase {
|
||||
|
||||
a: {macro.plus x=5 y=3}.
|
||||
');
|
||||
|
||||
$this->tpl("macro_recursive.tpl", '{macro factorial(num)}
|
||||
{if $num}
|
||||
{$num} {macro.factorial num=$num-1}
|
||||
{/if}
|
||||
{/macro}
|
||||
|
||||
{macro.factorial num=10}');
|
||||
}
|
||||
|
||||
public function _testSandbox() {
|
||||
try {
|
||||
$this->fenom->compile("macro_recursive.tpl");
|
||||
$this->fenom->flush();
|
||||
var_dump($this->fenom->fetch("macro_recursive.tpl", []));
|
||||
} catch(\Exception $e) {
|
||||
var_dump($e->getMessage().": ".$e->getTraceAsString());
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
public function testMacros() {
|
||||
@ -72,4 +91,11 @@ class MacrosTest extends TestCase {
|
||||
|
||||
$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", Modifier::strip($tpl->fetch(array()), true));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user