From 027075d7b502ed1db27808b443cd0c78d492e982 Mon Sep 17 00:00:00 2001 From: bzick Date: Mon, 11 Apr 2016 19:44:59 +0300 Subject: [PATCH 01/10] Add #209: block's accessor $.blocks --- src/Fenom.php | 6 ++++-- src/Fenom/Accessor.php | 44 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/Fenom.php b/src/Fenom.php index 31d36c4..eff9651 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -379,9 +379,11 @@ class Fenom 'tpl' => 'Fenom\Accessor::tpl', 'version' => 'Fenom\Accessor::version', 'const' => 'Fenom\Accessor::constant', - 'php' => 'Fenom\Accessor::php', + 'php' => 'Fenom\Accessor::call', + 'call' => 'Fenom\Accessor::call', 'tag' => 'Fenom\Accessor::Tag', - 'fetch' => 'Fenom\Accessor::Fetch', + 'fetch' => 'Fenom\Accessor::fetch', + 'blocks' => 'Fenom\Accessor::blocks', ); /** diff --git a/src/Fenom/Accessor.php b/src/Fenom/Accessor.php index fc80b83..6c66d16 100644 --- a/src/Fenom/Accessor.php +++ b/src/Fenom/Accessor.php @@ -138,7 +138,7 @@ class Accessor { * @param Template $tpl * @return string */ - public static function php(Tokenizer $tokens, Template $tpl) + public static function call(Tokenizer $tokens, Template $tpl) { $callable = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()); while($tokens->is('.')) { @@ -192,4 +192,46 @@ class Accessor { $tokens->skip(')'); return '$tpl->getStorage()->fetch('.$name.', '.$vars.')'; } + + /** + * Accessor {$.blocks.name} + * Accessor {$.blocks.name.from} + * Accessor {$.blocks.name.code} + * Accessor {$.blocks.name.use_parent} + * Accessor {$.blocks.name.import} + * Accessor {$.blocks} + * @param Tokenizer $tokens + * @param Template $tpl + * @return mixed + */ + public static function blocks(Tokenizer $tokens, Template $tpl) + { + if($tokens->is('.')) { + $name = $tokens->next()->get(Tokenizer::MACRO_STRING); + if($tokens->isNext('.')) { + $part = $tokens->next()->next()->get(Tokenizer::MACRO_STRING); + $tokens->next(); + if(!isset($tpl->blocks[$name])) { + return 'NULL'; + } + switch($part) { + case 'code': + return var_export($tpl->blocks[$name]["block"], true); + case 'from': + return var_export($tpl->blocks[$name]["from"], true); + case 'use_parent': + return var_export($tpl->blocks[$name]["use_parent"], true); + case 'import': + return var_export($tpl->blocks[$name]["import"], true); + default: + throw new UnexpectedTokenException($tokens->back()); + } + } else { + $tokens->next(); + return isset($tpl->blocks[$name]) ? 'true' : 'false'; + } + } else { + return "array(".implode(",", array_keys($tpl->blocks)).")"; + } + } } \ No newline at end of file From d6aa777fd07c73f6f33592790a664136bf0352db Mon Sep 17 00:00:00 2001 From: bzick Date: Mon, 11 Apr 2016 20:19:31 +0300 Subject: [PATCH 02/10] Docs #209 [ru] --- docs/ru/configuration.md | 5 +++-- docs/ru/syntax.md | 7 ++++--- docs/ru/tags/extends.md | 10 ++++++++++ example/templates/main.tpl | 7 +++++++ sandbox/fenom.php | 7 +++++-- sandbox/templates/bug215/favicon.tpl | 2 ++ sandbox/templates/bug215/index.tpl | 8 ++++++++ src/Fenom.php | 6 +++++- src/Fenom/Compiler.php | 11 +++++++++++ 9 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 example/templates/main.tpl create mode 100644 sandbox/templates/bug215/favicon.tpl create mode 100644 sandbox/templates/bug215/index.tpl diff --git a/docs/ru/configuration.md b/docs/ru/configuration.md index 82bd9a0..3b48913 100644 --- a/docs/ru/configuration.md +++ b/docs/ru/configuration.md @@ -36,8 +36,9 @@ $fenom->setOptions($options); | *force_include* | `Fenom::FORCE_INCLUDE` | стараться по возможности вставить код дочернего шаблона в родительский при подключении шаблона | повышает производительность, увеличивает размер файлов в кеше, уменьшает количество файлов в кеше | | *auto_escape* | `Fenom::AUTO_ESCAPE` | автоматически экранировать HTML сущности при выводе переменных в шаблон | понижает производительность | | *force_verify* | `Fenom::FORCE_VERIFY` | автоматически проверять существование переменной перед использованием в шаблоне | понижает производительность | -| *disable_php_calls* | `Fenom::DENY_PHP_CALLS` | отключает возможность вызова статических методов и функций в шаблоне | | -| *disable_statics* | `Fenom::DENY_STATICS` | устаревшее название disable_php_calls | | +| *disable_call* | `Fenom::DENY_CALL` | отключает возможность вызова статических методов и функций в шаблоне | | +| *disable_php_calls* | `Fenom::DENY_PHP_CALLS` | устаревшее название disable_call | | +| *disable_statics* | `Fenom::DENY_STATICS` | устаревшее название disable_call | | | *strip* | `Fenom::AUTO_STRIP` | удаляет лишиние пробелы в шаблоне | уменьшает размер кеша | ```php diff --git a/docs/ru/syntax.md b/docs/ru/syntax.md index 42b5789..ef082b6 100644 --- a/docs/ru/syntax.md +++ b/docs/ru/syntax.md @@ -100,9 +100,10 @@ * `$.const` обращение к PHP константе: `$.const.PHP_EOL` обращение к константе `PHP_EOL`. Поддерживается пространство имен которое разделяется через точку: `$.const.Storage.FS::DIR_SEPARATOR` обращение к PHP константе `Storage\FS::DIR_SEPARATOR` если такой констатнты нет будет взята константа `Storage\FS\DIR_SEPARATOR`. -* `$.php` обращение к статическомому методу. `$.php.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`. - `$.php.Storage.FS.put($filename, $data)` `Storage\FS\put($filename, $data)` -* так же вы можете [добавить](./ext/extend.md#Расширение-глобальной-переменной) свои системные переменные и функции +* `$.call` обращение к статическомому методу. `$.call.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`. + Настройка `disable_call` отключает возможность обращения к `$.call`. Так же можно ограничить и указать список доступных к вызову классов и функций. +* `$.blocks` проверка на сущестование блоков которые были определены до момента обращения к акцессору. Например, `{$.blocks.BLOCK_NAME}`. +* так же вы можете [добавить](./ext/extend.md#Расширение-глобальной-переменной) свои или [удалить](./ext/extend.md#Расширение-глобальной-переменной) существующие системные переменные и функции ## Скалярные значения diff --git a/docs/ru/tags/extends.md b/docs/ru/tags/extends.md index 44124c3..9780248 100644 --- a/docs/ru/tags/extends.md +++ b/docs/ru/tags/extends.md @@ -44,4 +44,14 @@ {parent} content ... {/block} +``` + +### {paste} + +```smarty +{block 'b1'} + ... +{/block} + +{paste 'b1'} ``` \ No newline at end of file diff --git a/example/templates/main.tpl b/example/templates/main.tpl new file mode 100644 index 0000000..ef729dc --- /dev/null +++ b/example/templates/main.tpl @@ -0,0 +1,7 @@ + + +{foreach $list as $item} + +{/foreach} \ No newline at end of file diff --git a/sandbox/fenom.php b/sandbox/fenom.php index 8554dce..df2c50e 100644 --- a/sandbox/fenom.php +++ b/sandbox/fenom.php @@ -5,9 +5,12 @@ require_once __DIR__.'/../tests/tools.php'; \Fenom::registerAutoload(); -$fenom = Fenom::factory(__DIR__.'/../tests/resources/provider', __DIR__.'/../tests/resources/compile'); +$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/../tests/resources/compile'); $fenom->setOptions(Fenom::AUTO_RELOAD); -var_dump($fenom->fetch('extends/auto/parent.tpl')); +$fenom->addModifier('firstimg', function ($img) { + return $img; +}); +var_dump($fenom->compileCode('{block "pb"}- {$a} -{/block} =={paste "pb"}==')->getTemplateCode()); //var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode()); //var_dump($fenom->display("bug158/main.tpl", [])); // $fenom->getTemplate("problem.tpl"); \ No newline at end of file diff --git a/sandbox/templates/bug215/favicon.tpl b/sandbox/templates/bug215/favicon.tpl new file mode 100644 index 0000000..82672e9 --- /dev/null +++ b/sandbox/templates/bug215/favicon.tpl @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/sandbox/templates/bug215/index.tpl b/sandbox/templates/bug215/index.tpl new file mode 100644 index 0000000..998aa89 --- /dev/null +++ b/sandbox/templates/bug215/index.tpl @@ -0,0 +1,8 @@ + + + + {include "bug215/favicon.tpl"} + + + + \ No newline at end of file diff --git a/src/Fenom.php b/src/Fenom.php index eff9651..98d0af9 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -330,7 +330,11 @@ class Fenom 'unset' => array( 'type' => self::INLINE_COMPILER, 'parser' => 'Fenom\Compiler::tagUnset' - ) + ), + 'paste' => array( // {include ...} + 'type' => self::INLINE_COMPILER, + 'parser' => 'Fenom\Compiler::tagPaste' + ), ); /** diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index 0edaf22..d7569df 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -1044,4 +1044,15 @@ class Compiler } return 'unset('.implode(", ", $unset).')'; } + + public static function tagPaste(Tokenizer $tokens, Tag $tag) + { + $name = $tokens->get(T_CONSTANT_ENCAPSED_STRING); + $tokens->next(); + if(isset($tag->tpl->blocks[$name])) { + return "?>".substr($tag->tpl->blocks[$name]["block"], 1, -1)." Date: Mon, 11 Apr 2016 20:23:28 +0300 Subject: [PATCH 03/10] Cleanup extending algorithm --- src/Fenom/Compiler.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index d7569df..d221d8b 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -573,9 +573,6 @@ class Compiler */ public static function tagBlockOpen(Tokenizer $tokens, Tag $scope) { - if ($scope->level > 0) { - $scope->tpl->_compatible = true; - } $scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name); if (!$name) { throw new \RuntimeException("Invalid block name"); From 497f2e5b4bf7e3a6548659ee1c5c58cb366cd8a7 Mon Sep 17 00:00:00 2001 From: bzick Date: Tue, 12 Apr 2016 09:37:40 +0300 Subject: [PATCH 04/10] Docs++ --- docs/ru/readme.md | 20 ++++++++++++-------- docs/ru/tags/extends.md | 24 ++++++++++++++++++------ src/Fenom.php | 2 +- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/docs/ru/readme.md b/docs/ru/readme.md index 7609390..8c26369 100644 --- a/docs/ru/readme.md +++ b/docs/ru/readme.md @@ -26,23 +26,27 @@ [Использование](./syntax.md#Теги) тегов. -* [set](./tags/set.md), `add` и `var` — определение значения переменной -* [if](./tags/if.md), `elseif` и `else` — условный оператор -* [foreach](./tags/foreach.md), `foreachelse`, `break` and `continue` — перебор элементов массива или объекта -* [for](./tags/for.md), `forelse`, `break` and `continue` — цикл -* [switch](./tags/switch.md), `case` — групповой условный оператор +* [set](./tags/set.md), [add](./tags/set.md#add) и [var](./tags/set.md#var) — определение значения переменной +* [if](./tags/if.md), [elseif](./tags/if.md#elseif) и [else](./tags/if.md#else) — условный оператор +* [foreach](./tags/foreach.md), [foreachelse](./tags/foreach.md#foreachelse), + [break](./tags/foreach.md#break) и [continue](./tags/foreach.md#continue) — перебор элементов массива или объекта +* [switch](./tags/switch.md) и [case](./tags/switch.md#case) — групповой условный оператор * [cycle](./tags/cycle.md) — циклицеский перебор массива значений -* [include](./tags/include.md), `insert` — вставляет и исполняет указанный шаблон -* [extends](./tags/extends.md), `use`, `block` и `parent` — [наследование](./inheritance.md) шаблонов +* [include](./tags/include.md), [insert](./tags/include.md#insert) — вставляет и исполняет указанный шаблон +* [extends](./tags/extends.md), [use](./tags/extends.md#use), + [block](./tags/extends.md#block), [parent](./tags/extends.md#parent) и + [paste](./tags/extends.md#paste) — [наследование](./inheritance.md) шаблонов * [filter](./tags/filter.md) — применение модификаторов к фрагменту шаблона * [ignore](./tags/ignore.md) — игнорирование тегов Fenom -* [macro](./tags/macro.md) и `import` — пользовательские функции шаблонов +* [macro](./tags/macro.md) и [import](./tags/macro.md#macro) — пользовательские функции шаблонов * [autoescape](./tags/autoescape.md) — экранирует фрагмент шаблона * [raw](./tags/raw.md) — отключает экранирование фрагмента шаблона * [unset](./tags/unset.md) — удаляет переменные * или [добавьте](./ext/extend.md#Добавление-тегов) свои +Устаревшие теги +* [for](./tags/for.md), `forelse`, `break` and `continue` — цикл *** ### Модификаторы diff --git a/docs/ru/tags/extends.md b/docs/ru/tags/extends.md index 9780248..fd2933a 100644 --- a/docs/ru/tags/extends.md +++ b/docs/ru/tags/extends.md @@ -1,9 +1,9 @@ -Тег {extends} +Тег `{extends}` ============= Тег `{extends}` реализует [наследование](../inheritance.md) шаблонов, иерархия, обратная {include}. То есть шаблон сам выбирает своего родителя. -### {extends} +### `{extends}` Родительский шаблон можно задать единожды и до объявления какого-либо блока. @@ -17,7 +17,7 @@ {extends $parent_tpl} ``` -### {block} +### `{block}` Блок указывает фрагмент шаблона, который будет передан родителю. Имя блока должно быть задано явно: @@ -28,7 +28,7 @@ ``` -### {use} +### `{use}` Что бы импортировать блоки из другого шаблона используйте тег {use}: @@ -36,7 +36,7 @@ {use 'blocks.tpl'} ``` -### {parent} +### `{parent}` ```smarty {block 'block1'} @@ -46,7 +46,9 @@ {/block} ``` -### {paste} +### `{paste}` + +Иставка кода блока в любое место через тег `{paste}` ```smarty {block 'b1'} @@ -54,4 +56,14 @@ {/block} {paste 'b1'} +``` + +### `{$.block}` + +Проверка наличия блока череж глобальную переменную `$.block` + +```smarty +{if $.block.header} + ... +{/if} ``` \ No newline at end of file diff --git a/src/Fenom.php b/src/Fenom.php index 98d0af9..f8f1282 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -18,7 +18,7 @@ use Fenom\Template; */ class Fenom { - const VERSION = '2.8'; + const VERSION = '2.9'; const REV = 1; /* Actions */ const INLINE_COMPILER = 1; From eb75ae0bbb537897801cc8be865a4cbc9038fcef Mon Sep 17 00:00:00 2001 From: bzick Date: Tue, 12 Apr 2016 12:28:57 +0300 Subject: [PATCH 05/10] Docs++ --- docs/en/readme.md | 20 ++++++---- docs/en/tags/cycle.md | 10 +++-- docs/en/tags/extends.md | 86 +++++++++++++++++------------------------ docs/en/tags/filter.md | 6 ++- docs/en/tags/foreach.md | 36 ++++++++--------- docs/en/tags/if.md | 17 +++----- docs/en/tags/macro.md | 25 ++++++------ docs/en/tags/set.md | 84 ++++++++++++++++++++++++++++++++++++++++ docs/en/tags/var.md | 64 ------------------------------ docs/ru/operators.md | 14 ++++++- docs/ru/readme.md | 1 + docs/ru/syntax.md | 4 +- docs/ru/tags/foreach.md | 2 +- src/Fenom.php | 2 +- src/Fenom/Accessor.php | 33 ++-------------- 15 files changed, 199 insertions(+), 205 deletions(-) create mode 100644 docs/en/tags/set.md delete mode 100644 docs/en/tags/var.md diff --git a/docs/en/readme.md b/docs/en/readme.md index 00e0920..70efc7e 100644 --- a/docs/en/readme.md +++ b/docs/en/readme.md @@ -23,22 +23,26 @@ Documentation [Usage](./syntax.md#tags) -* [set](./tags/var.md), `add` and `var` — define variables -* [if](./tags/if.md), `elseif` and `else` — conditional statement -* [foreach](./tags/foreach.md), `foreaelse`, `break` and `continue` — traversing items in an array or object -* [for](./tags/for.md), `forelse`, `break` and `continue` — loop statement -* [switch](./tags/switch.md), `case`, `default` — +* [set](./tags/set.md), [add](./tags/set.md#add) and `var` — define variables +* [if](./tags/if.md), [elseif](./tags/if.md#elseif) and [else](./tags/if.md#else) — conditional statement +* [foreach](./tags/foreach.md), [foreaelse](./tags/foreach.md#foreaelse), + [break](./tags/foreach.md#break) and [continue](./tags/foreach.md#continue) — traversing items in an array or object +* [switch](./tags/switch.md), [case](./tags/switch.md#case) — complex condition statement * [cycle](./tags/cycle.md) — cycles on an array of values -* [include](./tags/include.md), `insert` — includes and evaluates the specified template -* [extends](./tags/extends.md), `use`, `block` and `parent` — template inheritance +* [include](./tags/include.md), [insert](./tags/include.md#insert) — includes and evaluates the specified template +* [extends](./tags/extends.md), [use](./tags/extends.md#use), + [block](./tags/extends.md#block), [parent](./tags/extends.md#parent) and [paste](./tags/extends.md#paste) — template inheritance * [filter](./tags/filter.md) — apply modifier on a block of template data * [ignore](./tags/ignore.md) — ignore Fenom syntax -* [macro](./tags/macro.md) and `import` — template functions +* [macro](./tags/macro.md) and [import](./tags/macro.md#import) — template functions * [autoescape](./tags/autoescape.md) — escape template fragment * [raw](./tags/raw.md) — unescape template fragment * [unset](./tags/unset.md) — unset a given variables * or [add](./ext/extend.md#add-tags) yours +Deprecated tags + +* [for](./tags/for.md), `forelse`, `break` and `continue` — loop statement *** diff --git a/docs/en/tags/cycle.md b/docs/en/tags/cycle.md index c47a8fb..f4892e2 100644 --- a/docs/en/tags/cycle.md +++ b/docs/en/tags/cycle.md @@ -1,13 +1,15 @@ Tag {cycle} =========== +`{cycle}` is used to alternate a set of values. + ```smarty -{for $i=$a.c..} +{foreach 1..10}
-{/for} +{/foreach} -{for $i=$a.c..} +{foreach 1..10}
-{/for} +{/foreach} ``` \ No newline at end of file diff --git a/docs/en/tags/extends.md b/docs/en/tags/extends.md index b89d439..83b16cc 100644 --- a/docs/en/tags/extends.md +++ b/docs/en/tags/extends.md @@ -1,82 +1,68 @@ -Tag {extends} [RU] -================== +Tag {extends}] +============= -Тег `{extends}` реализует наследование шаблонов, иерархия, обратная {include}. То есть шаблон сам выбирает своего родителя. +`{extends}` tags are used in child templates in template inheritance for extending parent templates. +The `{extends}` tag must be on before any block. +Also if a child template extends a parent template with the `{extends}` tag it may contain only `{block}` tags. Any other template content is ignored. ### {extends} -Родительский шаблон можно задать единожды и до объявления какого-либо блока. - ```smarty {extends 'parent.tpl'} ``` -Имя родительского шаблона может быть задан динамически, в этом случае производительность отрисовки может снизиться. - -```smarty -{extends $parent_tpl} -``` - ### {block} -Блок указывает фрагмент шаблона, который будет передан родителю. Имя блока может быть задано как явно - ```smarty -{block bk1}content 1{/block} -... {block 'bk2'}content 2{/block} ``` -так и не явно, но в данном случае пострадает производительность - -```smarty -{block "bk{$number}"}content {$number}{/block} -... -{if $condition} - {block "bk-if"}content, then 'if' is true{/block} -{else} - {block "bk{$fail}"}content, then 'if' is false{/block} -{/if} -``` - ### {use} Что бы импортировать блоки из другого шаблона используйте тег {use}: ```smarty -{use 'blocks.tpl'} -``` +{use 'blocks.tpl'} merge blocks from blocks.tpl template +{block 'alpha'} rewrite block alpha from blocks.tpl template, if it exists + ... +{/block} +``` ### {parent} -Planned. Not supported yet. Feature #5. - ```smarty -{block 'block1'} +{extends 'parent.tpl'} + +{block 'header'} content ... - {parent} + {parent} pase code from block 'header' from parent.tpl content ... {/block} ``` -### Performance +### {paste} -Алгоритм реализации наследования шаблонов может работать в разных режимах, в зависимости от условий. -Каждый режим имеет свою производительность. +Paste code of any block -1. **Максимальная** производительность: - * Имена шаблонов в теге {extends } заданы явно, без использования переменных и условий. - * Имена блоков заданы явно, без использования переменных, условий и не вложены ни в какой другой тег. -2. **Средняя** производительность: - * Имена шаблонов в теге {extends } заданы явно, без использования переменных и условий. - * Имена блоков заданы **не** явно, с использованием переменныч, условий или могут быть вложенные в другие теги. -3. **Низкая** производительность: - * Имена шаблонов в теге {extends } заданы **не** явно, с использованием переменных и условий. - * Имена блоков заданы явно, без использования переменных, условий и не вложены ни в какой другой тег. -4. **Минимальная** производительность: - * Имена шаблонов в теге {extends } заданы **не** явно, с использованием переменных и условий. - * Имена блоков заданы **не** явно, с использованием переменных, условий или могут быть вложенные в другие теги. +```smarty +{block 'b1'} + ... +{/block} -Режим может идти только на понижение, при изменении условий во время прохождения по иерархии шаблонов. -При любом режиме работы не используется буферизация данных, то есть данные выводятся сразу. +{block 'b2'} + ... + {paste 'b1'} paste code from b1 +{/block} + +``` + +### {$.block} + +Checks if clock exists + +```smarty +{if $.block.header} + block header exists +{/if} +``` \ No newline at end of file diff --git a/docs/en/tags/filter.md b/docs/en/tags/filter.md index 35a8bb0..48b063a 100644 --- a/docs/en/tags/filter.md +++ b/docs/en/tags/filter.md @@ -1,10 +1,12 @@ Tags {filter} ============= -Позволяет применить модификаторы на фрагмент шаблона +Apply modifier to template area. ```smarty {filter|strip_tags|truncate:20} Remove all HTML tags and truncate {$text} to 20 symbols {/filter} -``` \ No newline at end of file +``` + +**Note**: output buffering used. May be used a lot of memory if you output a lot of data. \ No newline at end of file diff --git a/docs/en/tags/foreach.md b/docs/en/tags/foreach.md index 8fde3f1..375f974 100644 --- a/docs/en/tags/foreach.md +++ b/docs/en/tags/foreach.md @@ -1,5 +1,7 @@ -Tag {foreach} [RU] -================== +Tag {foreach} +============= + +The tag `{foreach}` construct provides an easy way to iterate over arrays and ranges. ```smarty {foreach $list as [$key =>] $value [index=$index] [first=$first] [last=$last]} @@ -15,7 +17,8 @@ Tag {foreach} [RU] ### {foreach} -Перебор значений массива $list +On each iteration, the value of the current element is assigned to `$value` and the internal array pointer is +advanced by one (so on the next iteration, you'll be looking at the next element). ```smarty {foreach $list as $value} @@ -23,7 +26,7 @@ Tag {foreach} [RU] {/foreach} ``` -Перебор ключей и значений массива $list +The next form will additionally assign the current element's key to the `$key` variable on each iteration. ```smarty {foreach $list as $key => $value} @@ -31,7 +34,7 @@ Tag {foreach} [RU] {/foreach} ``` -Получение номера (индекса) итерации +Gets the current array index, starting with zero. ```smarty {foreach $list as $value index=$index} @@ -39,7 +42,7 @@ Tag {foreach} [RU] {/foreach} ``` -Определение первого элемента +Detect first iteration: ```smarty {foreach $list as $value first=$first} @@ -47,8 +50,9 @@ Tag {foreach} [RU] {/foreach} ``` -Переменная `$first` будет иметь значение **TRUE**, если текущая итерация является первой. -Определение последнего элемента +`$first` is `TRUE` if the current `{foreach}` iteration is the initial one. + +Detect last iteration: ```smarty {foreach $list as $value last=$last} @@ -56,22 +60,22 @@ Tag {foreach} [RU] {/foreach} ``` -Переменная `$last` будет иметь значение **TRUE**, если текущая итерация является последней. +`$last` is set to `TRUE` if the current `{foreach}` iteration is the final one. ### {break} -Тег `{break}` используется для выхода из цикла до достижения последней итерации. Если в цикле встречается тег `{break}`, цикл завершает свою работу, и далее, выполняется код, следующий сразу за блоком цикла +Tag `{break}` aborts the iteration. ### {continue} -Тег `{continue}` используется для прерывания текущей итерации. Если в цикле встречается тег `{continue}`, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. Если текущая итерация была последней, цикл завершается. +Tag `{continue}` leaves the current iteration and begins with the next iteration. ### {foreachelse} -Тег {foreachelse} ограничивает код, который должен быть выполнен, если итерируемый объект пуст. +`{foreachelse}` is executed when there are no values in the array variable. ```smarty -{var $list = []} +{set $list = []} {foreach $list as $value}
{if $last} last item {/if} {$value}
{foreachelse} @@ -79,8 +83,4 @@ Tag {foreach} [RU] {/foreach} ``` -В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции - -### Notice - -Использование last требует от `$list` быть **countable**. \ No newline at end of file +`{foreachelse}` does not support tags `{break}` and `{continue}`. \ No newline at end of file diff --git a/docs/en/tags/if.md b/docs/en/tags/if.md index 72069d3..de9dc8b 100644 --- a/docs/en/tags/if.md +++ b/docs/en/tags/if.md @@ -1,7 +1,9 @@ -Tag {if} [RU] -============= +Tag {if} +======== -Реализация оператора [if](http://docs.php.net/if) из PHP +Tag {if} have much the same flexibility as PHP [if](http://docs.php.net/if) statements, +with a few added features for the template engine. +All operators, allowed functions and variables are recognized in conditions. ```smarty {if } @@ -21,8 +23,6 @@ Tag {if} [RU] {/if} ``` -Код, расположенный в теге `{if}` будет выполнен/выведен если выражение ** возвращает значение приводимое к **TRUE** - ### {elseif} ```smarty @@ -33,8 +33,6 @@ Tag {if} [RU] {/if} ``` -Код, расположенный после тега `{elseif}` будет выполнен/выведен, если выражение вернуло значение приводимое к **FALSE**, - приводимое к **TRUE** - ### {else} ```smarty @@ -43,7 +41,4 @@ Tag {if} [RU] {else} {*...some code...*} {/if} -``` - -Код, расположенный после тега `{else}` будет выполнен/выведен, если выражение вернуло значение приводимое к **FALSE** -В тестируемых выражениях могут быть использованы логические операторы , что позволяет обрабатывать сочетания нескольких условий. \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/en/tags/macro.md b/docs/en/tags/macro.md index b6d3a05..f780ee0 100644 --- a/docs/en/tags/macro.md +++ b/docs/en/tags/macro.md @@ -1,12 +1,12 @@ -Tag {macro} [RU] -================ +Tag {macro} +=========== -Макросы - фрагмент шаблона который можно повторить сколь угодно раз и в каком угодно месте. -Макросы не имеют общего пространства имен с шаблоном и могут оперировать только переданными переменными. +Macros are comparable with functions in regular programming languages. +They are useful to put often used HTML idioms into reusable elements to not repeat yourself. ### {macro} -Обявление макроса происходит при помощи блочного тега `{macro}` +Macros can be defined in any template using tag `{macro}`. ```smarty {macro plus($x, $y, $z=0)} @@ -14,14 +14,10 @@ Tag {macro} [RU] {/macro} ``` -Вызов макроса происходит при помощи строкового тега `{macro}`. Аргументы передаются стандартно, как атрибуты в HTML тегах - ```smarty {macro.plus x=$num y=100} ``` -Во время рекурсивного вызова используйте суффикс macro что бы обратиться к текущему макросу: - ```smarty {macro plus($x, $y, $z=0)} ... @@ -32,13 +28,17 @@ Tag {macro} [RU] ### {import} -Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}` +Macros can be defined in any template, and need to be "imported" before being used. +The above import call imports the "math.tpl" file (which can contain only macros, or a template and some macros), +and import the functions as items of the `macro` namespace. ```smarty {import 'math.tpl'} + +{macro.plus x=1 y=3} ``` -При импорте можно указать дргое пространство имен что бы можно было использовать одноименные макросы из разных шаблонов +Use another namespace instead of `macro` ```smarty {import 'math.tpl' as math} @@ -46,9 +46,6 @@ Tag {macro} [RU] {math.plus x=5 y=100} ``` -Пространство имен макросов может совпадать с названием какого-либо тега, в данном случае ничего плохого не произойдет: будет вызван макрос, а тег не исчезнит -При необходимости можно импортировать только необходимые макросы, явно указав в теге `{import}` - ```smarty {import [plus, minus, exp] from 'math.tpl' as math} ``` \ No newline at end of file diff --git a/docs/en/tags/set.md b/docs/en/tags/set.md new file mode 100644 index 0000000..78c85f5 --- /dev/null +++ b/docs/en/tags/set.md @@ -0,0 +1,84 @@ +Tag {set} +========= + +The tag {set} is used for assigning template variables during the execution of a template. + +```smarty +{set $var=EXPR} +``` + +```smarty +{set $var} + ... any content ... +{/set} +``` + +```smarty +{set $var|modifiers} + ... any content ... +{/set} +``` + +Variable names follow the same rules as other labels in PHP. +A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. + +```smarty +{set $v = 5} +{set $v = "value"} + +{set $v = $x+$y} +{set $v = 4} +{set $v = $z++ + 1} +{set $v = --$z} +{set $v = $y/$x} +{set $v = $y-$x} +{set $v = $y*$x-2} +{set $v = ($y^$x)+7} +``` + +Works this array too + +```smarty +{set $v = [1,2,3]} +{set $v = []} +{set $v = ["one"|upper => 1, 4 => $x, "three" => 3]} +{set $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]} +``` + +Getting function result into variable + +```smarty +{set $v = count([1,2,3])+7} +``` + +Fetch the output of the template into variable + +```smarty +{set $v} + Some long {$text|trim} +{/set} + +{set $v|escape} {* apply modifier to variable*} + Some long {$text|trim} +{/set} +``` + +### {add} + +The tag {add} the same tag as {set} except that sets the value of the variable if it does not exist. + +```smarty +{add $var = 'value'} +``` + +instead of + +```smarty +{if $var is not set} + {set $var = 'value'} +{/if} +``` + +### {var} + +Old name of tag {set}. Currently tag {var} the same tag as {set}. \ No newline at end of file diff --git a/docs/en/tags/var.md b/docs/en/tags/var.md deleted file mode 100644 index 879e317..0000000 --- a/docs/en/tags/var.md +++ /dev/null @@ -1,64 +0,0 @@ -Tag {var} -========= - -The tag {var} is used for assigning template variables during the execution of a template. - -```smarty -{var $var=EXPR} -``` - -```smarty -{var $var} - ... any content ... -{/var} -``` - -```smarty -{var $var|modifiers} - ... any content ... -{/var} -``` - -Variable names follow the same rules as other labels in PHP. -A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. - -```smarty -{var $v = 5} -{var $v = "value"} - -{var $v = $x+$y} -{var $v = 4} -{var $v = $z++ + 1} -{var $v = --$z} -{var $v = $y/$x} -{var $v = $y-$x} -{var $v = $y*$x-2} -{var $v = ($y^$x)+7} -``` - -Creating array - -```smarty -{var $v = [1,2,3]} -{var $v = []} -{var $v = ["one"|upper => 1, 4 => $x, "three" => 3]} -{var $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]} -``` - -Getting function result into variable - -```smarty -{var $v = count([1,2,3])+7} -``` - -Collect the output of the template into a variable - -```smarty -{var $v} - Some long {$text|trim} -{/var} - -{var $v|escape} {* apply modifier to variable*} - Some long {$text|trim} -{/var} -``` diff --git a/docs/ru/operators.md b/docs/ru/operators.md index 1afd933..c3cf40a 100644 --- a/docs/ru/operators.md +++ b/docs/ru/operators.md @@ -135,9 +135,21 @@ Fenom поддерживает префиксные и постфиксные о * `$a ~~ $b` - возвращает результат объединения сток `$a` и `$b` через пробел * `$a ~= $b` - присвоение с объединением +Примеры + +```smarty +{"A" ~ "B"} -> AB + +{"A" ~~ "B"} -> A B + +{add $v = "A"} +{set $v ~= "B"} +{$v} -> AB +``` + ### Оператор интервала -Оператор `..` позволяет создать массив данных, не выходящие за указанные ограничения. +Оператор `..` позволяет создать массив данных, не выходящие за указанные пределы. ```smarty {set $a = 1..4} diff --git a/docs/ru/readme.md b/docs/ru/readme.md index 8c26369..1ef16f7 100644 --- a/docs/ru/readme.md +++ b/docs/ru/readme.md @@ -47,6 +47,7 @@ Устаревшие теги * [for](./tags/for.md), `forelse`, `break` and `continue` — цикл + *** ### Модификаторы diff --git a/docs/ru/syntax.md b/docs/ru/syntax.md index ef082b6..e616b93 100644 --- a/docs/ru/syntax.md +++ b/docs/ru/syntax.md @@ -102,7 +102,7 @@ если такой констатнты нет будет взята константа `Storage\FS\DIR_SEPARATOR`. * `$.call` обращение к статическомому методу. `$.call.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`. Настройка `disable_call` отключает возможность обращения к `$.call`. Так же можно ограничить и указать список доступных к вызову классов и функций. -* `$.blocks` проверка на сущестование блоков которые были определены до момента обращения к акцессору. Например, `{$.blocks.BLOCK_NAME}`. +* `$.block` проверка на сущестование блоков которые были определены до момента обращения к акцессору. Например, `{$.blocks.BLOCK_NAME}`. * так же вы можете [добавить](./ext/extend.md#Расширение-глобальной-переменной) свои или [удалить](./ext/extend.md#Расширение-глобальной-переменной) существующие системные переменные и функции @@ -157,7 +157,7 @@ {"Hi, {$user.name}!"} выводит: Hi, Username! {"Hi, {$user->name}!"} выводит: Hi, Username! {"Hi, {$user->getName()}!"} выводит: Hi, Username! -{"Hi, {\$user->name}!"} выводит: Hi, {\$user->name}! +{"Hi, {\$user->name}!"} выводит: Hi, {$user->name}! ``` Допускаются также различные операции и модификаторы: diff --git a/docs/ru/tags/foreach.md b/docs/ru/tags/foreach.md index 2e3d3a3..9dcac01 100644 --- a/docs/ru/tags/foreach.md +++ b/docs/ru/tags/foreach.md @@ -5,7 +5,7 @@ `Foreach` работает только с массивами, объектами и интервалами. ```smarty -{foreach $list as [$key =>] $value [index=$index] [first=$first] [last=$last]} +{foreach $list [as [$key =>] $value] [index=$index] [first=$first] [last=$last]} {* ...code... *} {break} {* ...code... *} diff --git a/src/Fenom.php b/src/Fenom.php index f8f1282..3d21b2f 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -387,7 +387,7 @@ class Fenom 'call' => 'Fenom\Accessor::call', 'tag' => 'Fenom\Accessor::Tag', 'fetch' => 'Fenom\Accessor::fetch', - 'blocks' => 'Fenom\Accessor::blocks', + 'block' => 'Fenom\Accessor::block', ); /** diff --git a/src/Fenom/Accessor.php b/src/Fenom/Accessor.php index 6c66d16..bd841b1 100644 --- a/src/Fenom/Accessor.php +++ b/src/Fenom/Accessor.php @@ -194,42 +194,17 @@ class Accessor { } /** - * Accessor {$.blocks.name} - * Accessor {$.blocks.name.from} - * Accessor {$.blocks.name.code} - * Accessor {$.blocks.name.use_parent} - * Accessor {$.blocks.name.import} - * Accessor {$.blocks} + * Accessor {$.block.NAME} * @param Tokenizer $tokens * @param Template $tpl * @return mixed */ - public static function blocks(Tokenizer $tokens, Template $tpl) + public static function block(Tokenizer $tokens, Template $tpl) { if($tokens->is('.')) { $name = $tokens->next()->get(Tokenizer::MACRO_STRING); - if($tokens->isNext('.')) { - $part = $tokens->next()->next()->get(Tokenizer::MACRO_STRING); - $tokens->next(); - if(!isset($tpl->blocks[$name])) { - return 'NULL'; - } - switch($part) { - case 'code': - return var_export($tpl->blocks[$name]["block"], true); - case 'from': - return var_export($tpl->blocks[$name]["from"], true); - case 'use_parent': - return var_export($tpl->blocks[$name]["use_parent"], true); - case 'import': - return var_export($tpl->blocks[$name]["import"], true); - default: - throw new UnexpectedTokenException($tokens->back()); - } - } else { - $tokens->next(); - return isset($tpl->blocks[$name]) ? 'true' : 'false'; - } + $tokens->next(); + return isset($tpl->blocks[$name]) ? 'true' : 'false'; } else { return "array(".implode(",", array_keys($tpl->blocks)).")"; } From 45c9f27ae657f04dab2a4ea4882cdef76d83b935 Mon Sep 17 00:00:00 2001 From: bzick Date: Wed, 13 Apr 2016 11:34:38 +0300 Subject: [PATCH 06/10] Docs++ --- README.md | 40 ++++++++++++++++++---------------------- docs/en/operators.md | 8 +++++--- docs/en/tags/extends.md | 2 +- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6b689e0..565028e 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,27 @@ Fenom - Template Engine for PHP =============================== -> Composer [package](https://packagist.org/packages/fenom/fenom): `{"fenom/fenom": "2.*"}`.
-> For old version: `{"fenom/fenom": "1.*"}`.
-> [List](https://github.com/fenom-template/fenom/wiki/Migrate-from-1.4.9-to-2.0) of incompatibilities between **1** and **2** versions. +**Fenóm** - lightweight and fast template engine for PHP. -[![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom) -[![Build Status](https://travis-ci.org/fenom-template/fenom.svg?branch=master)](https://travis-ci.org/fenom-template/fenom) -[![Coverage Status](https://coveralls.io/repos/fenom-template/fenom/badge.svg?branch=master)](https://coveralls.io/r/fenom-template/fenom?branch=master) -[![Total Downloads](https://poser.pugx.org/fenom/fenom/downloads.png)](https://packagist.org/packages/fenom/fenom) +* **Subject:** Template engine +* **Syntax:** Smarty-like +* **Documentation:** [english](./docs/en/readme.md), [russian](./docs/ru/readme.md) +* **PHP version:** 5.3+ +* **State:** [![Build Status](https://travis-ci.org/fenom-template/fenom.svg?branch=master)](https://travis-ci.org/fenom-template/fenom) [![Coverage Status](https://coveralls.io/repos/fenom-template/fenom/badge.svg?branch=master)](https://coveralls.io/r/fenom-template/fenom?branch=master) +* **Version:** [![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom) +* **Packagist:** [fenom/fenom](https://packagist.org/packages/fenom/fenom) [![Total Downloads](https://poser.pugx.org/fenom/fenom/downloads.png)](https://packagist.org/packages/fenom/fenom) +* **Composer:** `composer require fenom/fenom` +* **Discussion:** [Fenom Forum](https://groups.google.com/forum/#!forum/php-ion) +* **Versioning:** [semver2](http://semver.org/) +* **Performance:** see [benchmark](./docs/en/benchmark.md) -## [Quick start](./docs/en/start.md) :: [Documentation](./docs/readme.md) [[en](./docs/en/readme.md)|[ru](./docs/ru/readme.md)] :: [Benchmark](./docs/en/benchmark.md) - +*** -### What is it +## Quick Start -**Fenóm** — lightweight template engine for PHP. +### Install -It means: - -* Known Smarty-like [syntax](./docs/en/syntax.md) with improvements. -* Very [fast](./docs/en/benchmark.md). -* [Lightweight](./docs/en/benchmark.md). -* Very [flexible](./docs/en/configuration.md#extends). -* Progressive parser without regular expressions. -* High [code coverage](https://coveralls.io/r/bzick/fenom?branch=master). -* Easy to understand [how it works](./docs/en/dev/readme.md). -* Easy to [use](./docs/en/start.md). -* Maximum [protection](./docs/en/configuration.md#configure). +If you use composer in your project then you can to install Fenom to project as package. +However, if you are not using composer you have to configure autoloader to work with Fenom. +### Usage \ No newline at end of file diff --git a/docs/en/operators.md b/docs/en/operators.md index 22fa228..267093d 100644 --- a/docs/en/operators.md +++ b/docs/en/operators.md @@ -55,7 +55,7 @@ Operators {if $a & 1} {var $b = 4 | $flags} {/if} ``` -### Assignment Operators +### Assignment operators * `$a = $b` - assignment * `$a += $b` - assignment with addition @@ -81,9 +81,11 @@ Operators * `--$a` - decrement the variable and use it * `$a--` - use the variable and decrement it -### String operator +### String operators -* `$a ~ $b` - return concatenation of variables `$a` and `$b` +* `$a ~ $b` - return concatenation of variables `$a` and `$b` +* `$a ~~ $b` - return concatenation of variables `$a` and `$b` separated by a space +* `$a ~= $b` - assignment with concatenation ### Ternary operators diff --git a/docs/en/tags/extends.md b/docs/en/tags/extends.md index 83b16cc..54310b9 100644 --- a/docs/en/tags/extends.md +++ b/docs/en/tags/extends.md @@ -1,4 +1,4 @@ -Tag {extends}] +Tag {extends} ============= `{extends}` tags are used in child templates in template inheritance for extending parent templates. From 1559adf0309fe05f7bd604d49a8e85aa2756a324 Mon Sep 17 00:00:00 2001 From: bzick Date: Thu, 14 Apr 2016 12:58:16 +0300 Subject: [PATCH 07/10] Docs++ --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 565028e..b16905b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,46 @@ Fenom - Template Engine for PHP ### Install -If you use composer in your project then you can to install Fenom to project as package. -However, if you are not using composer you have to configure autoloader to work with Fenom. +If you use composer in your project then you can to install Fenom as package. +However, if you are not using composer you have to configure _autoloader_ to work with Fenom. +Fenom implements the `PSR-0` PHP standard to load classes which are located in the `src/` directory. +Templater already has own autoload-function, to register call method `Fenom::registerAutoload`: +```php +Fenom::registerAutoload(); +``` + +### Usage + +There is two way to create Fenom instance: + +* Long way: use operator `new` +* Shot way: use static factory-method + +**Long way.** Create you own template provider or default provider `Fenom\Provider` (that is provider read [there](./)). +Using provider instance create Fenom instance: + +```php +$fenom = new Fenom(new Fenom\Provider($template_dir)); +``` + +After that, set compile directory: + +```php +$fenom->setCompileDir($template_cache_dir); +``` + +This directory will be used for storing compiled templates, therefore it should be writable for Fenom. +Now Fenom is ready to work and now you can to configure it: + +```php +$fenom->setOptions($options); +``` + +**Short way.** Creating an object via factory method + +```php +$fenom = Fenom::factory($template_dir, $template_cache_dir, $options); +``` + +Now Fenom is ready to work. -### Usage \ No newline at end of file From ba4ba548ff0f13681b500bed4c13627af5cc627a Mon Sep 17 00:00:00 2001 From: bzick Date: Fri, 6 May 2016 23:04:08 +0300 Subject: [PATCH 08/10] Foreach props, range iterator and more --- README.md | 7 +- docs/ru/tags/foreach.md | 20 +++-- sandbox/fenom.php | 2 +- src/Fenom.php | 1 + src/Fenom/Compiler.php | 129 +++++++++++++++++------------ src/Fenom/Modifier.php | 9 +- src/Fenom/RangeIterator.php | 102 +++++++++++++++++++++++ src/Fenom/Tag.php | 6 +- src/Fenom/Template.php | 23 +++-- tests/cases/Fenom/SandboxTest.php | 27 +++--- tests/cases/Fenom/TemplateTest.php | 53 +++++++++--- 11 files changed, 283 insertions(+), 96 deletions(-) create mode 100644 src/Fenom/RangeIterator.php diff --git a/README.md b/README.md index b16905b..b25dda4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Templater already has own autoload-function, to register call method `Fenom::reg Fenom::registerAutoload(); ``` -### Usage +### Setup There is two way to create Fenom instance: @@ -56,7 +56,7 @@ Now Fenom is ready to work and now you can to configure it: $fenom->setOptions($options); ``` -**Short way.** Creating an object via factory method +**Short way.** Creating an object via factory method with arguments from long way. ```php $fenom = Fenom::factory($template_dir, $template_cache_dir, $options); @@ -64,3 +64,6 @@ $fenom = Fenom::factory($template_dir, $template_cache_dir, $options); Now Fenom is ready to work. +### Usage + +### Example \ No newline at end of file diff --git a/docs/ru/tags/foreach.md b/docs/ru/tags/foreach.md index 9dcac01..8ffb247 100644 --- a/docs/ru/tags/foreach.md +++ b/docs/ru/tags/foreach.md @@ -38,9 +38,15 @@ {/foreach} ``` -Получение номера (индекса) итерации +Получение номера (индекса) итерации, начиная с 0 ```smarty +{foreach $list as $value} +
№{$value:index}: {$value}
+{/foreach} + +или + {foreach $list as $value index=$index}
№{$index}: {$value}
{/foreach} @@ -49,21 +55,21 @@ Определение первого элемента ```smarty -{foreach $list as $value first=$first} -
{if $first} first item {/if} {$value}
+{foreach $list as $value} +
{if $value:first} first item {/if} {$value}
{/foreach} ``` -Переменная `$first` будет иметь значение **TRUE**, если текущая итерация является первой. +Переменная `$value:first` будет иметь значение **TRUE**, если текущая итерация является первой. Определение последнего элемента ```smarty -{foreach $list as $value last=$last} -
{if $last} last item {/if} {$value}
+{foreach $list as $value} +
{if $value:last} last item {/if} {$value}
{/foreach} ``` -Переменная `$last` будет иметь значение **TRUE**, если текущая итерация является последней. +Переменная `$value:last` будет иметь значение **TRUE**, если текущая итерация является последней. **Замечание:** Использование `last` требует от `$list` быть **countable**. diff --git a/sandbox/fenom.php b/sandbox/fenom.php index df2c50e..1fe4b8a 100644 --- a/sandbox/fenom.php +++ b/sandbox/fenom.php @@ -10,7 +10,7 @@ $fenom->setOptions(Fenom::AUTO_RELOAD); $fenom->addModifier('firstimg', function ($img) { return $img; }); -var_dump($fenom->compileCode('{block "pb"}- {$a} -{/block} =={paste "pb"}==')->getTemplateCode()); +var_dump($fenom->compileCode('{foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach}')->getTemplateCode()); //var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode()); //var_dump($fenom->display("bug158/main.tpl", [])); // $fenom->getTemplate("problem.tpl"); \ No newline at end of file diff --git a/src/Fenom.php b/src/Fenom.php index 3d21b2f..155acd1 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -166,6 +166,7 @@ class Fenom "esplit" => 'Fenom\Modifier::esplit', "join" => 'Fenom\Modifier::join', "in" => 'Fenom\Modifier::in', + "range" => 'Fenom\Modifier::range', ); /** diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index d221d8b..d057f68 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -10,6 +10,7 @@ namespace Fenom; use Doctrine\Instantiator\Exception\InvalidArgumentException; +use Fenom\Error\CompileException; use Fenom\Error\InvalidUsageException; use Fenom\Error\UnexpectedTokenException; @@ -132,70 +133,53 @@ class Compiler */ public static function foreachOpen(Tokenizer $tokens, Tag $scope) { - $p = array("index" => false, "first" => false, "last" => false); - $key = null; - $before = $body = array(); - $prepend = ""; - if ($tokens->is('[')) { + $scope["else"] = false; + $scope["key"] = null; + $scope["prepend"] = ""; + $scope["before"] = array(); + $scope["after"] = array(); + $scope["body"] = array(); + + if ($tokens->is('[')) { // array $count = 0; - $from = $scope->tpl->parseArray($tokens, $count); - $check = $count; - } else { - $from = $scope->tpl->parseExpr($tokens, $is_var); + $scope['from'] = $scope->tpl->parseArray($tokens, $count); + $scope['check'] = $count; + $scope["var"] = $scope->tpl->tmpVar(); + $scope['prepend'] = $scope["var"].' = '.$scope['from'].';'; + $scope['from'] = $scope["var"]; + } else { // expression + $scope['from'] = $scope->tpl->parseExpr($tokens, $is_var); if($is_var) { - $check = '!empty('.$from.') && (is_array('.$from.') || '.$from.' instanceof \Traversable)'; + $scope['check'] = '!empty('.$scope['from'].') && (is_array('.$scope['from'].') || '.$scope['from'].' instanceof \Traversable)'; } else { $scope["var"] = $scope->tpl->tmpVar(); - $prepend = $scope["var"].' = '.$from.';'; - $from = $scope["var"]; - $check = 'is_array('.$from.') && count('.$from.') || ('.$from.' instanceof \Traversable)'; + $scope['prepend'] = $scope["var"].' = '.$scope['from'].';'; + $scope['from'] = $scope["var"]; + $scope['check'] = 'is_array('.$scope['from'].') && count('.$scope['from'].') || ('.$scope['from'].' instanceof \Traversable)'; } } - $tokens->get(T_AS); - $tokens->next(); - $value = $scope->tpl->parseVariable($tokens); - if ($tokens->is(T_DOUBLE_ARROW)) { + if($tokens->is(T_AS)) { $tokens->next(); - $key = $value; $value = $scope->tpl->parseVariable($tokens); + if ($tokens->is(T_DOUBLE_ARROW)) { + $tokens->next(); + $scope["key"] = $value; + $scope["value"] = $scope->tpl->parseVariable($tokens); + } else { + $scope["value"] = $value; + } + } else { + $scope["value"] = '$_un'; } - - $scope["after"] = array(); - $scope["else"] = false; - while ($token = $tokens->key()) { $param = $tokens->get(T_STRING); - if (!isset($p[$param])) { - throw new InvalidUsageException("Unknown parameter '$param' in {foreach}"); - } + $var_name = self::foreachProp($scope, $param); $tokens->getNext("="); $tokens->next(); - $p[$param] = $scope->tpl->parseVariable($tokens); + $scope['before'][] = $scope->tpl->parseVariable($tokens)." = &". $var_name; } - if ($p["index"]) { - $before[] = $p["index"] . ' = 0'; - $scope["after"][] = $p["index"] . '++'; - } - if ($p["first"]) { - $before[] = $p["first"] . ' = true'; - $scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )'; - } - if ($p["last"]) { - $before[] = $p["last"] . ' = false'; - $scope["uid"] = "v" . $scope->tpl->i++; - $before[] = '$' . $scope["uid"] . " = count($from)"; - $body[] = 'if(!--$' . $scope["uid"] . ') ' . $p["last"] . ' = true'; - } - - $before = $before ? implode("; ", $before) . ";" : ""; - $body = $body ? implode("; ", $body) . ";" : ""; - $scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; - if ($key) { - return "$prepend if($check) {\n $before foreach($from as $key => $value) { $body"; - } else { - return "$prepend if($check) {\n $before foreach($from as $value) { $body"; - } + return ''; } /** @@ -208,7 +192,40 @@ class Compiler public static function foreachElse($tokens, Tag $scope) { $scope["no-break"] = $scope["no-continue"] = $scope["else"] = true; - return " {$scope['after']} } } else {"; + $after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; + return " {$after} } } else {"; + } + + /** + * @param Tag $scope + * @param string $prop + * @return string + * @throws CompileException + */ + public static function foreachProp(Tag $scope, $prop) { + if(empty($scope["props"][$prop])) { + $var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop; + switch($prop) { + case "index": + $scope["before"][] = $var_name . ' = 0'; + $scope["after"][] = $var_name . '++'; + break; + case "first": + $scope["before"][] = $var_name . ' = true'; + $scope["after"][] = $var_name . ' && (' . $var_name . ' = false )'; + break; + case "last": + $scope["before"][] = $var_name . ' = false'; + $scope["uid"] = $scope->tpl->tmpVar(); + $scope["before"][] = $scope["uid"] . " = count({$scope["from"]})"; + $scope["body"][] = 'if(!--' . $scope["uid"] . ') ' . $var_name . ' = true'; + break; + default: + throw new CompileException("Unknown foreach property '$prop'"); + } + } + + return $scope["props"][$prop]; } /** @@ -221,10 +238,20 @@ class Compiler */ public static function foreachClose($tokens, Tag $scope) { + $before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : ""; + $head = $scope["body"] ? implode("; ", $scope["body"]) . ";" : ""; + $body = $scope->getContent(); + if ($scope["key"]) { + $code = " {$scope["value"]}) { $head?>$body"; + } else { + $code = "$body"; + } + $scope->replaceContent($code); if ($scope["else"]) { return '}'; } else { - return " {$scope['after']} } }"; + $after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; + return " {$after} } }"; } } diff --git a/src/Fenom/Modifier.php b/src/Fenom/Modifier.php index 9976800..b51c7d3 100644 --- a/src/Fenom/Modifier.php +++ b/src/Fenom/Modifier.php @@ -284,10 +284,13 @@ class Modifier * @param string|int $from * @param string|int $to * @param int $step - * @return array + * @return RangeIterator */ public static function range($from, $to, $step = 1) { - $v = range($from, $to, $step); - return $v ? $v : array(); + if($from instanceof RangeIterator) { + return $from->setStep($to); + } else { + return new RangeIterator($from, $to, $step); + } } } diff --git a/src/Fenom/RangeIterator.php b/src/Fenom/RangeIterator.php new file mode 100644 index 0000000..8ca0981 --- /dev/null +++ b/src/Fenom/RangeIterator.php @@ -0,0 +1,102 @@ +min = $min; + $this->max = $max; + $this->setStep($step); + } + + /** + * @param int $step + * @return $this + */ + public function setStep($step) { + if($step > 0) { + $this->current = min($this->min, $this->max); + } elseif($step < 0) { + $this->current = max($this->min, $this->max); + } else { + $step = $this->max - $this->min; + $this->current = $this->min; + } + $this->step = $step; + return $this; + } + + /** + * Return the current element + */ + public function current() + { + return $this->current; + } + + /** + * Move forward to next element + */ + public function next() + { + $this->current += $this->step; + $this->index++; + } + + /** + * Return the key of the current element + * @return int + */ + public function key() + { + return $this->index; + } + + /** + * Checks if current position is valid + * @return bool + */ + public function valid() + { + return $this->current >= $this->min && $this->current <= $this->max; + } + + /** + * Rewind the Iterator to the first element + */ + public function rewind() + { + if($this->step > 0) { + $this->current = min($this->min, $this->max); + } else { + $this->current = max($this->min, $this->max); + } + $this->index = 0; + } + + /** + * Count elements of an object + */ + public function count() + { + return intval(($this->max - $this->min) / $this->step); + } + + /** + * @return string + */ + public function __toString() + { + return "[".implode(", ", range($this->min, $this->max, $this->step))."]"; + } +} \ No newline at end of file diff --git a/src/Fenom/Tag.php b/src/Fenom/Tag.php index 1663613..070ff42 100644 --- a/src/Fenom/Tag.php +++ b/src/Fenom/Tag.php @@ -219,7 +219,7 @@ class Tag extends \ArrayObject } /** - * Return content of block + * Returns tag's content * * @throws \LogicException * @return string @@ -230,7 +230,7 @@ class Tag extends \ArrayObject } /** - * Cut scope content + * Cut tag's content * * @return string * @throws \LogicException @@ -243,7 +243,7 @@ class Tag extends \ArrayObject } /** - * Replace scope content + * Replace tag's content * * @param $new_content */ diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php index 9ea3a2e..deaa2fb 100644 --- a/src/Fenom/Template.php +++ b/src/Fenom/Template.php @@ -314,12 +314,12 @@ class Template extends Render } /** - * Generate temporary internal template variable + * Generate name of temporary internal template variable (may be random) * @return string */ public function tmpVar() { - return sprintf('$t%x_%x', $this->_crc, $this->i++); + return sprintf('$t%x_%x', $this->_crc ? $this->_crc : mt_rand(0, 0x7FFFFFFF), $this->i++); } /** @@ -892,7 +892,7 @@ class Template extends Render } if(($allows & self::TERM_RANGE) && $tokens->is('.') && $tokens->isNext('.')) { $tokens->next()->next(); - $code = 'range('.$code.', '.$this->parseTerm($tokens, $var, self::TERM_MODS).')'; + $code = '(new \Fenom\RangeIterator('.$code.', '.$this->parseTerm($tokens, $var, self::TERM_MODS).'))'; $is_var = false; } return $code; @@ -918,7 +918,7 @@ class Template extends Render } /** - * Parse variable name: $a, $a.b, $a.b['c'] + * Parse variable name: $a, $a.b, $a.b['c'], $a:index * @param Tokenizer $tokens * @param $var * @return string @@ -927,8 +927,19 @@ class Template extends Render public function parseVariable(Tokenizer $tokens, $var = null) { if (!$var) { - $var = '$var["' . substr($tokens->get(T_VARIABLE), 1) . '"]'; - $tokens->next(); + if($tokens->isNext('@')) { +// $v = $tokens->get(T_VARIABLE); + $prop = $tokens->next()->next()->get(T_STRING); + if($tag = $this->getParentScope("foreach")) { + $tokens->next(); + return Compiler::foreachProp($tag, $prop); + } else { + throw new UnexpectedTokenException($tokens); + } + } else { + $var = '$var["' . substr($tokens->get(T_VARIABLE), 1) . '"]'; + $tokens->next(); + } } while ($t = $tokens->key()) { if ($t === ".") { diff --git a/tests/cases/Fenom/SandboxTest.php b/tests/cases/Fenom/SandboxTest.php index b09ec27..6e42360 100644 --- a/tests/cases/Fenom/SandboxTest.php +++ b/tests/cases/Fenom/SandboxTest.php @@ -10,25 +10,26 @@ class SandboxTest extends TestCase { */ public function test() { - return; - $this->fenom->setOptions(\Fenom::FORCE_VERIFY); - $this->fenom->addAccessorSmart('q', 'Navi::$q', \Fenom::ACCESSOR_VAR); +// 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->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) { // return 'cutContent(); // }); // $this->tpl('welcome.tpl', '{$a}'); // var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody()); - try { - var_dump($this->fenom->compileCode('{set $.q.ddqd->d() = 333}')->getBody()); - } catch (\Exception $e) { - print_r($e->getMessage() . "\n" . $e->getTraceAsString()); - while ($e->getPrevious()) { - $e = $e->getPrevious(); - print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString()); - } - } - exit; + +// try { +// var_dump($this->fenom->compileCode('{foreach $a as $k}A{$k:first}{foreachelse}B{/foreach}')->getBody()); +// } catch (\Exception $e) { +// print_r($e->getMessage() . "\n" . $e->getTraceAsString()); +// while ($e->getPrevious()) { +// $e = $e->getPrevious(); +// print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString()); +// } +// } +// exit; } } \ No newline at end of file diff --git a/tests/cases/Fenom/TemplateTest.php b/tests/cases/Fenom/TemplateTest.php index 21e897c..7d3cef4 100644 --- a/tests/cases/Fenom/TemplateTest.php +++ b/tests/cases/Fenom/TemplateTest.php @@ -696,11 +696,11 @@ class TemplateTest extends TestCase 'Fenom\Error\CompileException', "Unexpected end of expression" ), - array( - 'Foreach: {foreach $list} {$e}, {/foreach} end', - 'Fenom\Error\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\Error\CompileException', @@ -754,7 +754,7 @@ class TemplateTest extends TestCase array( 'Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end', 'Fenom\Error\CompileException', - "Unknown parameter 'unknown'" + "Unknown foreach property 'unknown'" ), array( 'Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end', @@ -1316,6 +1316,7 @@ class TemplateTest extends TestCase return array( array('Foreach: {foreach $list as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'), array('Foreach: {foreach $list as $e} {$e},{break} break {/foreach} end', $a, 'Foreach: one, end'), + array('Foreach: {foreach $list} 1, {/foreach} end', $a, 'Foreach: 1, 1, 1, end'), array( 'Foreach: {foreach $list as $e} {$e},{continue} continue {/foreach} end', $a, @@ -1350,11 +1351,21 @@ class TemplateTest extends TestCase $a, 'Foreach: 0: one, 1: two, 2: three, end' ), + array( + 'Foreach: {foreach $list as $e} {$e@index}: {$e}, {/foreach} end', + $a, + 'Foreach: 0: one, 1: two, 2: three, end' + ), array( 'Foreach: {foreach $list as $k => $e index=$i} {$i}: {$k} => {$e}, {/foreach} end', $a, 'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end' ), + array( + 'Foreach: {foreach $list as $k => $e} {$e@index}: {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end' + ), array( 'Foreach: {foreach $empty as $k => $e index=$i} {$i}: {$k} => {$e}, {foreachelse} empty {/foreach} end', $a, @@ -1365,11 +1376,21 @@ class TemplateTest extends TestCase $a, 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end' ), + array( + 'Foreach: {foreach $list as $k => $e} {if $e@first}first{/if} {$e@index}: {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end' + ), array( 'Foreach: {foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', $a, 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end' ), + array( + 'Foreach: {foreach $list as $k => $e} {if $e@first}first{/if} {$e@index}: {$k} => {$e}, {if $e@last}last{/if} {/foreach} end', + $a, + 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end' + ), array( 'Foreach: {foreach $empty as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {foreachelse} empty {/foreach} end', $a, @@ -1566,9 +1587,9 @@ class TemplateTest extends TestCase { return array( array('{set $a=1..3}', "1,2,3,"), - array('{set $a="a".."f"}', "a,b,c,d,e,f,"), - array('{set $a=1.."f"}', "1,0,"), - array('{set $a="a"..2}', "0,1,2,"), +// array('{set $a="a".."f"}', "a,b,c,d,e,f,"), +// array('{set $a=1.."f"}', "1,0,"), +// array('{set $a="a"..2}', "0,1,2,"), array('{set $a=$one..$three}', "1,2,3,"), array('{set $a=$one..3}', "1,2,3,"), array('{set $a=1..$three}', "1,2,3,"), @@ -1576,16 +1597,28 @@ class TemplateTest extends TestCase array('{set $a=$one..++$three}', "1,2,3,4,"), array('{set $a=$one--..$three++}', "1,2,3,"), array('{set $a=--$one..++$three}', "0,1,2,3,4,"), - array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"), +// array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"), array('{set $a=$one|min:0..$three|max:4}', "0,1,2,3,4,"), array('{set $a=$one|min:0..4}', "0,1,2,3,4,"), array('{set $a=0..$three|max:4}', "0,1,2,3,4,"), + array('{set $a=0..$three|max:4}', "0,1,2,3,4,"), + + array('{set $a=range(1,3)}', "1,2,3,"), + array('{set $a=range(1,3, 2)}', "1,3,"), + array('{set $a=range(1..3, 2)}', "1,3,"), + array('{set $a=range(1..3, 3)}', "1,"), + + array('{set $a=range(1,3, -1)}', "3,2,1,"), + array('{set $a=range(1,3, -2)}', "3,1,"), + array('{set $a=range(1..3, -2)}', "3,1,"), + array('{set $a=range(1..3, -3)}', "3,"), ); } /** * @dataProvider providerRange * @group testRange + * @group dev * @param string $code * @param string $result */ From e289b9b3b17bfd2b3978e88fce894154c78754ce Mon Sep 17 00:00:00 2001 From: bzick Date: Sun, 8 May 2016 00:33:23 +0300 Subject: [PATCH 09/10] Docs++ --- docs/en/tags/foreach.md | 22 ++++++++++++++++++++-- docs/ru/tags/foreach.md | 25 +++++++++++++++++++------ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/en/tags/foreach.md b/docs/en/tags/foreach.md index 375f974..9781d0b 100644 --- a/docs/en/tags/foreach.md +++ b/docs/en/tags/foreach.md @@ -37,6 +37,12 @@ The next form will additionally assign the current element's key to the `$key` v Gets the current array index, starting with zero. ```smarty +{foreach $list as $value} +
№{$value@index}: {$value}
+{/foreach} + +or + {foreach $list as $value index=$index}
№{$index}: {$value}
{/foreach} @@ -45,22 +51,34 @@ Gets the current array index, starting with zero. Detect first iteration: ```smarty +{foreach $list as $value} +
{if $value@first} first item {/if} {$value}
+{/foreach} + +or + {foreach $list as $value first=$first}
{if $first} first item {/if} {$value}
{/foreach} ``` -`$first` is `TRUE` if the current `{foreach}` iteration is the initial one. +`$first` is `TRUE` if the current `{foreach}` iteration is first iteration. Detect last iteration: ```smarty +{foreach $list as $value} +
{if $value@last} last item {/if} {$value}
+{/foreach} + +or + {foreach $list as $value last=$last}
{if $last} last item {/if} {$value}
{/foreach} ``` -`$last` is set to `TRUE` if the current `{foreach}` iteration is the final one. +`$last` is set to `TRUE` if the current `{foreach}` iteration is last iteration. ### {break} diff --git a/docs/ru/tags/foreach.md b/docs/ru/tags/foreach.md index 8ffb247..1b829d9 100644 --- a/docs/ru/tags/foreach.md +++ b/docs/ru/tags/foreach.md @@ -38,11 +38,12 @@ {/foreach} ``` + Получение номера (индекса) итерации, начиная с 0 ```smarty {foreach $list as $value} -
№{$value:index}: {$value}
+
№{$value@index}: {$value}
{/foreach} или @@ -52,20 +53,32 @@ {/foreach} ``` -Определение первого элемента +Определение первой итерации: ```smarty {foreach $list as $value} -
{if $value:first} first item {/if} {$value}
+
{if $value@first} first item {/if} {$value}
+{/foreach} + +или + +{foreach $list as $value first=$first} +
{if $first} first item {/if} {$value}
{/foreach} ``` -Переменная `$value:first` будет иметь значение **TRUE**, если текущая итерация является первой. -Определение последнего элемента +Переменная `$value@first` будет иметь значение **TRUE**, если текущая итерация является первой. +Определение последней интерации: ```smarty {foreach $list as $value} -
{if $value:last} last item {/if} {$value}
+
{if $value@last} last item {/if} {$value}
+{/foreach} + +или + +{foreach $list as $value last=$last} +
{if $last} last item {/if} {$value}
{/foreach} ``` From f315d937a89b15f02c03bdd7f258ead1e5acae03 Mon Sep 17 00:00:00 2001 From: bzick Date: Sun, 8 May 2016 00:38:17 +0300 Subject: [PATCH 10/10] Docs++ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b25dda4..9e30439 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Fenom - Template Engine for PHP * **Subject:** Template engine * **Syntax:** Smarty-like -* **Documentation:** [english](./docs/en/readme.md), [russian](./docs/ru/readme.md) +* **Documentation:** **[English](./docs/en/readme.md)**, **[Russian](./docs/ru/readme.md)** * **PHP version:** 5.3+ * **State:** [![Build Status](https://travis-ci.org/fenom-template/fenom.svg?branch=master)](https://travis-ci.org/fenom-template/fenom) [![Coverage Status](https://coveralls.io/repos/fenom-template/fenom/badge.svg?branch=master)](https://coveralls.io/r/fenom-template/fenom?branch=master) * **Version:** [![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom)