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)
|
* 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/main.md#extends)
|
* [Flexible](./docs/ext/extensions.md)
|
||||||
* [Lightweight](./docs/benchmark.md#stats)
|
* [Lightweight](./docs/benchmark.md#stats)
|
||||||
* [Powerful](./docs/main.md)
|
* [Powerful](./docs/main.md)
|
||||||
* Easy to use:
|
* Easy to use:
|
||||||
|
@ -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": [
|
||||||
{
|
{
|
||||||
|
@ -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
|
@ -47,6 +47,8 @@ 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
|
||||||
|
@ -34,7 +34,7 @@ class Compiler {
|
|||||||
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);';
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ class Compiler {
|
|||||||
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);';
|
||||||
}
|
}
|
||||||
@ -493,7 +493,6 @@ class Compiler {
|
|||||||
*/
|
*/
|
||||||
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
|
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
|
||||||
if($scope->level > 0) {
|
if($scope->level > 0) {
|
||||||
var_dump("".$scope->tpl);
|
|
||||||
$scope->tpl->_compatible = true;
|
$scope->tpl->_compatible = true;
|
||||||
}
|
}
|
||||||
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
|
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
|
||||||
@ -809,7 +808,6 @@ class Compiler {
|
|||||||
if($alias) {
|
if($alias) {
|
||||||
$name = $alias.'.'.$name;
|
$name = $alias.'.'.$name;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tpl->macros[$name] = $macro;
|
$tpl->macros[$name] = $macro;
|
||||||
}
|
}
|
||||||
$tpl->addDepend($donor);
|
$tpl->addDepend($donor);
|
||||||
@ -827,8 +825,9 @@ class Compiler {
|
|||||||
*/
|
*/
|
||||||
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"] = array();
|
||||||
$scope["defaults"] = array();
|
$args = array();
|
||||||
|
$defaults = array();
|
||||||
if(!$tokens->valid()) {
|
if(!$tokens->valid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -836,12 +835,12 @@ class Compiler {
|
|||||||
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 +848,12 @@ class Compiler {
|
|||||||
$tokens->skipIf(',');
|
$tokens->skipIf(',');
|
||||||
}
|
}
|
||||||
$tokens->skipIf(')');
|
$tokens->skipIf(')');
|
||||||
|
$scope["macro"] = array(
|
||||||
|
"id" => $scope->tpl->i++,
|
||||||
|
"args" => $args,
|
||||||
|
"defaults" => $defaults,
|
||||||
|
"body" => ""
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,12 +862,23 @@ class Compiler {
|
|||||||
* @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(
|
if($scope["recursive"]) {
|
||||||
"body" => $content = $scope->getContent(),
|
$switch = "switch(\$call['mark']) {\n";
|
||||||
"args" => $scope["args"],
|
foreach($scope["recursive"] as $mark) {
|
||||||
"defaults" => $scope["defaults"]
|
$switch .= "case $mark: goto macro_$mark;\n";
|
||||||
);
|
}
|
||||||
$scope->tpl->_body = substr($scope->tpl->_body, 0, strlen($scope->tpl->_body) - strlen($content));
|
$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 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"];
|
||||||
@ -85,28 +84,48 @@ class Render extends \ArrayObject {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get template storage
|
* Get template storage
|
||||||
* @return Fenom
|
* @return \Fenom
|
||||||
*/
|
*/
|
||||||
public function getStorage() {
|
public function getStorage() {
|
||||||
return $this->_fenom;
|
return $this->_fenom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get depends list
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function getDepends() {
|
public function getDepends() {
|
||||||
return $this->_depends;
|
return $this->_depends;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get schema name
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getScm() {
|
public function getScm() {
|
||||||
return $this->_scm;
|
return $this->_scm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get provider of template source
|
||||||
|
* @return ProviderInterface
|
||||||
|
*/
|
||||||
public function getProvider() {
|
public function getProvider() {
|
||||||
return $this->_fenom->getProvider($this->_scm);
|
return $this->_fenom->getProvider($this->_scm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name without schema
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getBaseName() {
|
public function getBaseName() {
|
||||||
return $this->_base_name;
|
return $this->_base_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get parse options
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function getOptions() {
|
public function getOptions() {
|
||||||
return $this->_options;
|
return $this->_options;
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,6 @@ class Template extends Render {
|
|||||||
* @var int shared counter
|
* @var int shared counter
|
||||||
*/
|
*/
|
||||||
public $i = 1;
|
public $i = 1;
|
||||||
/**
|
|
||||||
* Template PHP code
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $_body;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array of macros
|
* @var array of macros
|
||||||
*/
|
*/
|
||||||
@ -63,10 +57,17 @@ class Template extends Render {
|
|||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $escape = false;
|
public $escape = false;
|
||||||
|
|
||||||
public $_extends;
|
public $_extends;
|
||||||
public $_extended = false;
|
public $_extended = false;
|
||||||
public $_compatible;
|
public $_compatible;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template PHP code
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $_body;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call stack
|
* Call stack
|
||||||
* @var Scope[]
|
* @var Scope[]
|
||||||
@ -362,9 +363,10 @@ class Template extends Render {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTemplateCode() {
|
public function getTemplateCode() {
|
||||||
|
$before = $this->_before ? $this->_before."\n" : "";
|
||||||
return "<?php \n".
|
return "<?php \n".
|
||||||
"/** Fenom template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\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(
|
"return new Fenom\\Render(\$fenom, ".$this->_getClosureSource().", ".var_export(array(
|
||||||
"options" => $this->_options,
|
"options" => $this->_options,
|
||||||
"provider" => $this->_scm,
|
"provider" => $this->_scm,
|
||||||
@ -517,7 +519,7 @@ class Template extends Render {
|
|||||||
if($action !== "macro") {
|
if($action !== "macro") {
|
||||||
$name = $action.".".$name;
|
$name = $action.".".$name;
|
||||||
}
|
}
|
||||||
return $this->parseMacro($tokens, $name);
|
return $this->parseMacroCall($tokens, $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($tag = $this->_fenom->getTag($action, $this)) { // call some function
|
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
|
* @see $_checkers
|
||||||
* @param Tokenizer $tokens
|
* @param Tokenizer $tokens
|
||||||
* @param string $value
|
* @param string $value
|
||||||
@ -1095,7 +1097,7 @@ class Template extends Render {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!is_string($mods)) { // dynamic modifier
|
if(!is_string($mods)) { // dynamic modifier
|
||||||
$mods = 'call_user_func($tpl->getStorage()->getModifier("'.$modifier_name.'"), ';
|
$mods = 'call_user_func($tpl->getStorage()->getModifier("'.$mods.'"), ';
|
||||||
} else {
|
} else {
|
||||||
$mods .= "(";
|
$mods .= "(";
|
||||||
}
|
}
|
||||||
@ -1188,24 +1190,42 @@ class Template extends Render {
|
|||||||
* @return string
|
* @return string
|
||||||
* @throws InvalidUsageException
|
* @throws InvalidUsageException
|
||||||
*/
|
*/
|
||||||
public function parseMacro(Tokenizer $tokens, $name) {
|
public function parseMacroCall(Tokenizer $tokens, $name) {
|
||||||
|
$recursive = false;
|
||||||
|
$macro = false;
|
||||||
if(isset($this->macros[ $name ])) {
|
if(isset($this->macros[ $name ])) {
|
||||||
$macro = $this->macros[ $name ];
|
$macro = $this->macros[ $name ];
|
||||||
$p = $this->parseParams($tokens);
|
} else {
|
||||||
$args = array();
|
foreach($this->_stack as $scope) {
|
||||||
foreach($macro["args"] as $arg) {
|
if($scope->name == 'macro' && $scope['name'] == $name) { // invoke recursive
|
||||||
if(isset($p[ $arg ])) {
|
$recursive = $scope;
|
||||||
$args[ $arg ] = $p[ $arg ];
|
$macro = $scope['macro'];
|
||||||
} elseif(isset($macro["defaults"][ $arg ])) {
|
break;
|
||||||
$args[ $arg ] = $macro["defaults"][ $arg ];
|
|
||||||
} else {
|
|
||||||
throw new InvalidUsageException("Macro '$name' require '$arg' argument");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$args = $args ? '$tpl = '.Compiler::toArray($args).';' : '';
|
if(!$macro) {
|
||||||
return '$_tpl = $tpl; '.$args.' ?>'.$macro["body"].'<?php $tpl = $_tpl; unset($_tpl);';
|
throw new InvalidUsageException("Undefined macro '$name'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tokens->next();
|
||||||
|
$p = $this->parseParams($tokens);
|
||||||
|
$args = array();
|
||||||
|
foreach($macro['args'] as $arg) {
|
||||||
|
if(isset($p[ $arg ])) {
|
||||||
|
$args[ $arg ] = $p[ $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).';' : '';
|
||||||
|
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 {
|
} 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
|
* Parse parameters as $key=$value
|
||||||
* param1=$var param2=3 ...
|
* param1=$var param2=3 ...
|
||||||
|
@ -42,6 +42,25 @@ 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}
|
||||||
|
{/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() {
|
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));
|
$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…
x
Reference in New Issue
Block a user