Merge branch 'origin/develop'

This commit is contained in:
bzick 2014-05-17 12:16:14 +04:00
commit f7dd59fc82
96 changed files with 3671 additions and 7094 deletions

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ tests/resources/compile/*
!.gitkeep
tests/resources/template/*
!.gitkeep
sandbox/compiled/*
!.gitkeep
benchmark/compile/*
benchmark/templates/inheritance/smarty
benchmark/templates/inheritance/twig

View File

@ -4,6 +4,7 @@ php:
- 5.3
- 5.4
- 5.5
- 5.6
before_script:
- composer install --dev

View File

@ -1,6 +1,20 @@
Changelog
=========
## 2.0.0RC1
- Add tag the {filter}
- Redesign `extends` algorithm:
- Blocks don't support dynamic names
- Blocks can't be nested
- Add tag options support
- Improve Fenom API
- Move benchmark to another project
- Internal improvements
- Add `Fenom::STRIP` option
- Add tags {escape} and {strip}
- Method addProvider accept compile path which will saved the template's PHP cache. If compile path is not specified, will be taken global compile path.
### 1.4.9 (2013-04-09)
- Fix #75
@ -11,7 +25,7 @@ Changelog
- Fix #52
- Tests++
### 1.4.7 (2013-09-19)
### 1.4.7 (2013-09-21)
- Bug fixes
- Tests++

View File

@ -1,68 +1,29 @@
Fenom - Template Engine for PHP
===============================
> Composer package: `{"fenom/fenom": "1.*"}`. See on [Packagist.org](https://packagist.org/packages/fenom/fenom)
> Composer [package](https://packagist.org/packages/fenom/fenom): `{"fenom/fenom": "2.*"}`. <br />
> For old version: `{"fenom/fenom": "1.*"}`. [List](https://github.com/bzick/fenom/wiki/Migrate-from-1.*-to-2.*) of incompatibilities between 1.* and 2.* versions.
[![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom)
[![Build Status](https://travis-ci.org/bzick/fenom.png?branch=master)](https://travis-ci.org/bzick/fenom)
[![Coverage Status](https://coveralls.io/repos/bzick/fenom/badge.png?branch=master)](https://coveralls.io/r/bzick/fenom?branch=master)
[![Build Status](https://travis-ci.org/bzick/fenom.svg?branch=develop)](https://travis-ci.org/bzick/fenom)
[![Coverage Status](https://coveralls.io/repos/bzick/fenom/badge.png?branch=develop)](https://coveralls.io/r/bzick/fenom?branch=master)
[![Total Downloads](https://poser.pugx.org/fenom/fenom/downloads.png)](https://packagist.org/packages/fenom/fenom)
## [Usage](./docs/usage.md) :: [Documentation](./docs/readme.md) :: [Benchmark](./docs/benchmark.md) :: [Articles](./docs/articles.md)
## [Quick start](./docs/start.md) :: [Documentation](./docs/readme.md) :: [Benchmark](./docs/benchmark.md)
<!-- :: [Articles](./docs/articles.md) -->
* Simple [syntax](./docs/syntax.md)
* [Fast](./docs/benchmark.md)
* [Secure](./docs/settings.md)
* Simple
* [Flexible](./docs/ext/extensions.md)
* [Lightweight](./docs/benchmark.md#stats)
* [Powerful](./docs/readme.md)
* Easy to use:
### What is it
Simple template
**Fenom** *(from "fenomenal")* — lightweight template engine for PHP.
```smarty
<html>
<head>
<title>Fenom</title>
</head>
<body>
{if $templaters.fenom?}
{var $tpl = $templaters.fenom}
<div>Name: {$tpl.name}</div>
<div>Description: {$tpl.name|truncate:80}</div>
<ul>
{foreach $tpl.features as $feature}
<li>{$feature.name} (from {$feature.timestamp|gmdate:"Y-m-d H:i:s"})</li>
{/foreach}
</ul>
{/if}
</body>
</html>
```
It mean:
Display template
* Known Smarty-like [syntax](./docs/syntax.md) with improvements.
* Very [fast](./docs/benchmark.md).
* [Lightweight](./docs/benchmark.md).
* Very [flexible](./docs/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/dev.md).
* Easy to [use](./docs/start.md).
* Maximum [protection](./docs/configuration.md#configure).
```php
<?php
$fenom = Fenom::factory('./templates', './compiled', Fenom::AUTO_RELOAD);
$fenom->display("pages/about.tpl", $data);
```
Get content
```php
<?php
$fenom = Fenom::factory('./templates', './compiled', Fenom::AUTO_RELOAD);
$content = $fenom->fetch("pages/about.tpl", $data);
```
Runtime compilation
```php
<?php
$fenom = new Fenom();
$template = $fenom->compileCode('Hello {$user.name}! {if $user.email?} Your email: {$user.email} {/if}');
$template->display($data);
// or
$content = $template->fetch($data);
```

View File

@ -1,74 +0,0 @@
<?php
require_once __DIR__.'/scripts/bootstrap.php';
$opt = getopt("h", array(
/** @var string $message */
"cleanup",
/** @var boolean $stress */
"stress:",
/** @var boolean $auto_reload */
"auto_reload",
/** @vat boolean $help */
/** @vat boolean $h */
"help"
));
$opt += array(
"stress" => 0
);
extract($opt);
if(isset($h) || isset($help)) {
echo "
Start: ".basename(__FILE__)." [--stress COUNT] [--auto_reload] [--cleanup]
Usage: ".basename(__FILE__)." [--help | -h]
";
exit;
}
Benchmark::$stress = intval($stress);
Benchmark::$auto_reload = isset($auto_reload);
exec("rm -rf ".__DIR__."/compile/*");
echo "Smarty3 vs Twig vs Fenom\n\n";
echo "Generate templates... ";
passthru("php ".__DIR__."/templates/inheritance/smarty.gen.php");
passthru("php ".__DIR__."/templates/inheritance/twig.gen.php");
echo "Done\n";
echo "Testing a lot output...\n";
Benchmark::runs("smarty3", 'echo/smarty.tpl', __DIR__.'/templates/echo/data.json');
Benchmark::runs("twig", 'echo/twig.tpl', __DIR__.'/templates/echo/data.json');
Benchmark::runs("fenom", 'echo/smarty.tpl', __DIR__.'/templates/echo/data.json');
//if(extension_loaded("phalcon")) {
// Benchmark::runs("volt", 'echo/twig.tpl', __DIR__.'/templates/echo/data.json');
//}
echo "\nTesting 'foreach' of big array...\n";
Benchmark::runs("smarty3", 'foreach/smarty.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("twig", 'foreach/twig.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("fenom", 'foreach/smarty.tpl', __DIR__.'/templates/foreach/data.json');
//if(extension_loaded("phalcon")) {
// Benchmark::runs("volt", 'foreach/twig.tpl', __DIR__.'/templates/foreach/data.json');
//}
echo "\nTesting deep 'inheritance'...\n";
Benchmark::runs("smarty3", 'inheritance/smarty/b100.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("twig", 'inheritance/twig/b100.tpl', __DIR__.'/templates/foreach/data.json');
Benchmark::runs("fenom", 'inheritance/smarty/b100.tpl', __DIR__.'/templates/foreach/data.json');
//if(extension_loaded("phalcon")) {
// Benchmark::runs("volt", 'inheritance/twig/b100.tpl', __DIR__.'/templates/foreach/data.json');
//}
echo "\nDone\n";
if(isset($cleanup)) {
echo "Cleanup.\n";
passthru("rm -rf ".__DIR__."/compile/*");
passthru("rm -f ".__DIR__."/templates/inheritance/smarty/*");
passthru("rm -f ".__DIR__."/templates/inheritance/twig/*");
}

View File

@ -1,13 +0,0 @@
<?php
require_once __DIR__.'/../../vendor/autoload.php';
$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled', 0);
$fenom->display("greeting.tpl", array(
"user" => array(
"name" => "Ivka",
'type' => 'new'
),
'type' => 'new'
));

View File

@ -1,5 +0,0 @@
{import 'macros.tpl' as mc}
A1
{mc.factorial num=10}
A2

View File

@ -1,117 +0,0 @@
<?php
require(__DIR__.'/../../vendor/autoload.php');
class Benchmark {
const OUTPUT = "%8s: %-22s %10.4f sec, %10.1f MiB\n";
public static $stress = 0;
public static $auto_reload = false;
public static function smarty3($tpl, $data, $double, $stress = false, $auto_reload = false) {
$smarty = new Smarty();
$smarty->compile_check = $auto_reload;
$smarty->setTemplateDir(__DIR__.'/../templates');
$smarty->setCompileDir(__DIR__."/../compile/");
if($double) {
$smarty->assign($data);
$smarty->fetch($tpl);
}
$start = microtime(true);
if($stress) {
for($i=0; $i<$stress; $i++) {
$smarty->assign($data);
$smarty->fetch($tpl);
}
} else {
$smarty->assign($data);
$smarty->fetch($tpl);
}
return microtime(true) - $start;
// printf(self::$t, __FUNCTION__, $message, round(microtime(true)-$start, 4), round(memory_get_peak_usage()/1024/1024, 2));
}
public static function twig($tpl, $data, $double, $stress = false, $auto_reload = false) {
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem(__DIR__.'/../templates');
$twig = new Twig_Environment($loader, array(
'cache' => __DIR__."/../compile/",
'autoescape' => false,
'auto_reload' => $auto_reload,
));
if($double) {
$twig->loadTemplate($tpl)->render($data);
}
$start = microtime(true);
if($stress) {
for($i=0; $i<$stress; $i++) {
$twig->loadTemplate($tpl)->render($data);
}
} else {
$twig->loadTemplate($tpl)->render($data);
}
return microtime(true) - $start;
}
public static function fenom($tpl, $data, $double, $stress = false, $auto_reload = false) {
$fenom = Fenom::factory(__DIR__.'/../templates', __DIR__."/../compile");
if($auto_reload) {
$fenom->setOptions(Fenom::AUTO_RELOAD);
}
if($double) {
$fenom->fetch($tpl, $data);
}
$start = microtime(true);
if($stress) {
for($i=0; $i<$stress; $i++) {
$fenom->fetch($tpl, $data);
}
} else {
$fenom->fetch($tpl, $data);
}
return microtime(true) - $start;
}
// public static function volt($tpl, $data, $double, $message) {
// $view = new \Phalcon\Mvc\View();
// //$view->setViewsDir(__DIR__.'/../templates');
// $volt = new \Phalcon\Mvc\View\Engine\Volt($view);
//
//
// $volt->setOptions(array(
// "compiledPath" => __DIR__.'/../compile'
// ));
// $tpl = __DIR__.'/../templates/'.$tpl;
// if($double) {
// $volt->render($tpl, $data);
// }
//
// $start = microtime(true);
// $volt->render($tpl, $data);
// printf(self::$t, __FUNCTION__, $message, round(microtime(true)-$start, 4), round(memory_get_peak_usage()/1024/1024, 2));
// }
public static function run($engine, $template, $data, $double, $message) {
passthru(sprintf(PHP_BINARY." -n -dextension=phalcon.so -ddate.timezone=Europe/Moscow -dmemory_limit=512M %s/run.php --engine '%s' --template '%s' --data '%s' --message '%s' %s --stress %d %s", __DIR__, $engine, $template, $data, $message, $double ? '--double' : '', self::$stress, self::$auto_reload ? '--auto_reload' : ''));
}
/**
* @param $engine
* @param $template
* @param $data
*/
public static function runs($engine, $template, $data) {
self::run($engine, $template, $data, false, '!compiled and !loaded');
self::run($engine, $template, $data, false, ' compiled and !loaded');
self::run($engine, $template, $data, true, ' compiled and loaded');
echo "\n";
}
}

View File

@ -1,32 +0,0 @@
<?php
$opt = getopt("", array(
/** @var string $engine */
"engine:",
/** @var string $template */
"template:",
/** @var string $data */
"data:",
/** @var boolean $double */
"double",
/** @var string $message */
"message:",
/** @var boolean $stress */
"stress:",
/** @var boolean $auto_reload */
"auto_reload"
));
require_once __DIR__.'/bootstrap.php';
$opt += array(
"message" => "plain",
"stress" => 0,
);
extract($opt);
$time = Benchmark::$engine($template, json_decode(file_get_contents($data), true), isset($double), $stress, isset($auto_reload));
printf(Benchmark::OUTPUT, $engine, $message, round($time, 4), round(memory_get_peak_usage()/1024/1024, 2));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
<h1>Вывод 10 полей из 1000 элементов в цикле</h1>
{foreach $array as $item}
{$item.id} {$item.title} {$item.var1} {$item.var2} {$item.var3} {$item.var4} {$item.var5} {$item.var6} {$item.var5} {$item.var6}
{/foreach}

View File

@ -1,4 +0,0 @@
<h1>Вывод 10 полей из 1000 элементов в цикле<h1>
{% for item in array %}
{{ item.id }} {{ item.title }} {{ item.var1 }} {{ item.var2 }} {{ item.var3 }} {{ item.var4 }} {{ item.var5 }} {{ item.var6 }} {{ item.var5 }} {{ item.var6 }}
{% endfor %}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
<?php
$b0 = '<h1>Вывод статических данных в 500 наследуемых блоков</h1>' . "\r\n";
for($i = 1; $i < 501; $i++)
{
$b0 .= '{block b'.$i.'}{/block}'."\r\n";
$data = '{extends "inheritance/smarty/b'.($i-1).'.tpl"}' . "\r\n";
$data .= '{block b'.$i.'}data'.$i.'{/block}' . "\r\n";
file_put_contents(__DIR__.'/smarty/b'.$i.'.tpl', $data);
}
file_put_contents(__DIR__.'/smarty/b0.tpl', $b0);

View File

@ -1,10 +0,0 @@
<?php
$b0 = '<h1>Вывод статических данных в 500 наследуемых блоков</h1>' . "\r\n";
for($i = 1; $i < 501; $i++)
{
$b0 .= '{% block b'.$i.' %}{% endblock %}'."\r\n";
$data = '{% extends "inheritance/twig/b'.($i-1).'.tpl" %}' . "\r\n";
$data .= '{% block b'.$i.' %}data'.$i.'{% endblock %}' . "\r\n";
file_put_contents(__DIR__.'/twig/b'.$i.'.tpl', $data);
}
file_put_contents(__DIR__.'/twig/b0.tpl', $b0);

View File

@ -16,8 +16,6 @@
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"smarty/smarty": "3.*",
"twig/twig": "1.*",
"satooshi/php-coveralls": "dev-master"
},
"autoload": {

View File

@ -1,4 +1,8 @@
Adapters
========
* [Fenom + Yii](https://bitbucket.org/RSol/rfenomviewrender)
* [Fenom + Yii](https://bitbucket.org/RSol/rfenomviewrender)
* [Fenom + Kohana](https://github.com/2bj/kofenom) — Kofenom
* Fenom + Symphony
* Fenom + Symphony2
* Fenom + Zend Framework

107
docs/configuration.md Normal file
View File

@ -0,0 +1,107 @@
Setup
=====
## Configure
### Template cache
```php
$fenom->setCompileDir($dir);
```
This method set the name of the directory where template caches are stored. By default this is `/tmp`. This directory must be writeable.
### Template settings
```php
// set options using factory
$fenom = Fenom::factory($tpl_dir, $compile_dir, $options);
// or inline using method setOptions
$fenom->setOptions($options);
```
Options may by associative array like `'option_name' => true` or bitwise mask.
| Option name | Constant | Description | Affect |
| ---------------------- | ------------------------- | ------------ | ------- |
| *disable_methods* | `Fenom::DENY_METHODS` | disable calling methods of objects in templates. | |
| *disable_native_funcs* | `Fenom::DENY_NATIVE_FUNCS`| disable calling native function in templates, except allowed. | |
| *auto_reload* | `Fenom::AUTO_RELOAD` | reload template if source will be changed | decreases performance |
| *force_compile* | `Fenom::FORCE_COMPILE` | recompile template every time when the template renders | very decreases performance |
| *disable_cache* | `Fenom::DISABLE_CACHE` | disable compile cache | greatly decreases performance |
| *force_include* | `Fenom::FORCE_INCLUDE` | paste template body instead of include-tag | increases performance, increases cache size |
| *auto_escape* | `Fenom::AUTO_ESCAPE` | html-escape each variables outputs | decreases performance |
| *force_verify* | `Fenom::FORCE_VERIFY` | check existence every used variable | decreases performance |
<!-- | *auto_trim* | `Fenom::AUTO_TRIM` | remove space-characters before and after tags | | -->
| *disable_statics* | `Fenom::DENY_STATICS` | disable calling static methods in templates. | |
| *strip* | `Fenom::STRIP` | strip all whitespaces in templates. | decrease cache size |
```php
$fenom->setOptions(array(
"compile_check" => true,
"force_include" => true
));
// same
$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_INCLUDE);
```
**Note**
By default all options disabled
## Extends
### Template providers [TRANSLATE]
Бывает так что шаблны не хранятся на файловой сиситеме, а хранятся в некотором хранилище, например, в базе данных MySQL.
В этом случае шаблонизатору нужно описать как забирать шаблоны из хранилища, как проверять дату изменения шаблона и где хранить кеш шаблонов (опционально).
Эту задачу берут на себя Providers, это объекты реальзующие интерфейс `Fenom\ProviderInterface`.
### Cache providers [TRANSLATE]
Изначально Fenom не расчитывался на то что кеш скомпиленых шаблонов может располагаться не на файловой системе.
Однако, в теории, есть возможность реализовать свое кеширование для скомпиленых шаблонов без переопределения шаблонизатора.
Речь идет о своем протоколе, отличным от `file://`, который [можно определить](http://php.net/manual/en/class.streamwrapper.php) в PHP.
Ваш протол должени иметь класс реализации протокола как указан в документации [Stream Wrapper](http://www.php.net/manual/en/class.streamwrapper.php).
Класс протокола может иметь не все указанные в документации методы. Вот список методов, необходимых шаблонизатору:
* [CacheStreamWrapper::stream_open](http://www.php.net/manual/en/streamwrapper.stream-open.php)
* [CacheStreamWrapper::stream_write](http://www.php.net/manual/en/streamwrapper.stream-write.php)
* [CacheStreamWrapper::stream_close](http://www.php.net/manual/en/streamwrapper.stream-close.php)
* [CacheStreamWrapper::rename](http://www.php.net/manual/en/streamwrapper.rename.php)
For `include`:
* [CacheStreamWrapper::stream_stat](http://www.php.net/manual/en/streamwrapper.stream-stat.php)
* [CacheStreamWrapper::stream_read](http://www.php.net/manual/en/streamwrapper.stream-read.php)
* [CacheStreamWrapper::stream_eof](http://www.php.net/manual/en/streamwrapper.stream-eof.php)
**Note**
(On 2014-05-13) Zend OpCacher doesn't support custom protocols except `file://` and `phar://`.
For example,
```php
$this->setCacheDir("redis://hash/compiled/");
```
* `$cache = fopen("redis://hash/compiled/XnsbfeDnrd.php", "w");`
* `fwrite($cache, "... <template content> ...");`
* `fclose($cache);`
* `rename("redis://hash/compiled/XnsbfeDnrd.php", "redis://hash/compiled/main.php");`
### Callbacks and filters
#### Before compile callback
```php
$fenom->addPreFilter(function () { /* ... */ });
```
#### Tag filter callback
#### Filter callback
#### After compile callback

120
docs/dev/schema.md Normal file
View File

@ -0,0 +1,120 @@
How Fenom works
===============
```
use Fenom;
use Fenom\Render;
use Fenom\Template;
use Fenom\Tokenizer;
______________________________
| |
| Fenom::display($tpl, $var) |
|____________________________|
|
| search the template
______________|___________________________
| Template loaded into Fenom::$_storage? |
| Fenom::getTemplate($tpl) |
|________________________________________|
| |
| yes | no
______________|__________ |
| Render the template | |
| Render::display($tpl) | |
|_______________________| |
| |
| (hot start) |
| ______________________________|__________________
| | Template already compiled and stored in cache |
| | Fenom::getTemplate($template) |
| |_______________________________________________|
| | |
| | yes | no
| ____________|_______________ |
| | Load template from cache | not found |
| | Fenom::_load(...) |-------------->|
| |__________________________| |
| | |
| | found |
| ____________|___________ |
| | Validate template | invalid |
| | Render::isValid(...) |------------------>|
| |______________________| |
| | |
| | valid |
| ____________|____________ |
| | Render the template | |
|<----| Render::display(...) | |
| |_______________________| |
| |
| _____________________________ ________|___________________
| | Initialize compiler | | Compile the template |
| | Template::load($tpl) |<-----| Fenom::compile($tpl) |
| |___________________________| |__________________________|
| |
| ____________|________________
| | Load template source |
| | Provider::getSource($tpl) |
| |___________________________|
| |
| ____________|______________
| | Start compilation |
| | Template::compile($tpl) |
| |_________________________|
| |
| ____________|______________
| | Search template tag |
| | Template::compile($tpl) |<------------------------------------------------------|
| |_________________________| |
| | | |
| | not found | found |
| | _____________|_______________ _______________________________ |
| | | Tokenize the tag's code | | Parse the tag | |
| | | new Tokenizer($tag) |--->| Template::parseTag($tokens) | |
| | |___________________________| |_____________________________| |
| | | | |
| | is tag | | is expression |
| | _______________________________ | _______________|________________ |
| | | Detect tag name | | | Detect expression | |
| | | Template::parseAct($tokens) |<--- | Template::parseAct($tokens) | |
| | | Get callback by tag name | | Parse expression | |
| | | Fenom::getTag($tag_name) | | Template::parseExpr($tokens) | |
| | |_____________________________| |______________________________| |
| | | | |
| | | found | |
| | _______________|_______________ | |
| | | Invoke callback | | |
| | | Template::parseAct($tokens) | | |
| | |_____________________________| | |
| | | | |
| | _______________|________________ | |
| | | Append code to template | | |
| | | Template::_appendCode($code) |<----------------------- |
| | |______________________________| |
| | | |
| | _______________|___________ |
| | | Finalize the tag | starts search next tag |
| | | Template::compile($tpl) |>------------------------------------------------
| | |_________________________|
| |
| __|___________________________________
| | Store template to cache |
| | Fenom::compile($tpl) |
| | Store template to Fenom::$_storage |
| | Fenom::getTemplate($tpl) |
| |____________________________________|
| |
| ____________|_____________
| | Render the template |
| | Template::display(...) |
| |________________________|
| |
| | (cold start)
__|_________|________
| |
| DONE |
|___________________|
```

View File

@ -1,15 +0,0 @@
Installation
============
For installation use [composer](http://getcomposer.org). Add in your `composer.json` requirements:
```json
{
"require": {
"fenom/fenom": "1.*"
}
}
```
or use shell
`composer require fenom/fenom`
If you do not use composer - use `psr-0` format for loading Fenom's classes.

View File

@ -1,21 +1,19 @@
Modifier date_format [RU]
=========================
Модификатор позволят вывести дату в произвольном формате, согласно форматированию [strftime()](http://docs.php.net/strftime).
Модификатор принимает timestamp или строку, которую можно преобразовать через [strtotime()](http://docs.php.net/strtotime).
Формат по умолчанию: `%b %e, %Y`.
**[Допустимые квантификаторы](http://docs.php.net/strftime#refsect1-function.strftime-parameters) в формате даты**
Modifier date_format
====================
This formats a date and time into the given [strftime()](http://docs.php.net/strftime) format.
Dates can be passed to Fenom as unix timestamps, DateTime objects or any string made up of month day year, parsable by [strftime()](http://docs.php.net/strftime).
By default format is: `%b %e, %Y`.
```smarty
{var $ts = time()}
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} output like 2013/02/08 21:01:43
{$ts|date_format:"-1 day"} output like 2013/02/07 21:01:43
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} outputs 2013/02/08 21:01:43
{$ts|date_format:"-1 day"} outputs 2013/02/07 21:01:43
{var $date = "2008-12-08"}
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} output like 2008/12/08 00:00:00
```
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} outputs 2008/12/08 00:00:00
```
[Allowed quantificators](http://docs.php.net/strftime#refsect1-function.strftime-parameters) in **date_format**

View File

@ -1,15 +1,20 @@
Modifier escape [RU]
====================
Modifier escape
===============
Используется для кодирования / экранирования спецсимволов по алгоритмам экранирования HTML, URL'ов. По умолчанию активирован режим экранирования HTML.
The modifier escapes a string for safe insertion into the output.
It supports different escaping strategies depending on the template context.
By default, it uses the HTML escaping strategy:
```smarty
{$html_data|escape:'HTML'}
{$post.title|escape:'html'}
```
Возможные режимы экранирования
The modifier supports the following escaping strategies:
* HTML
* URL
* html: escapes a string for the HTML body context.
* url: escapes a string for the URI or parameter contexts.
* js: escapes a string for the JavaScript context.
для простоты, модификатор обладает коротким псевдонимом `|e`
For convenience, the `e` modifier is defined as an alias of `escape` modifier.
Second parameter is charset.

View File

@ -1,10 +1,10 @@
Modifier in [RU]
================
Modifier in
===========
Булевый модификатор, позволяющий проверить наличие значения переменной в массиве:
The modifier is implementation of [in](../operators.md#containment-operator) operator (performs containment test).
```smarty
{if $number|in:[1, 3, 55]}
{if $number|in:[1, 3, 42]}
...
{/if}
```
```

View File

@ -1,9 +1,10 @@
Modifier length [RU]
====================
Modifier length
===============
Модификатор возвращает длину значения переменной
The modifier returns the number of items of a sequence or mapping, or the length of a string (works with UTF8 without `mbstring`)
* если массив - длина массива
* если итератор - длина итератора
* если строка - длина строки (поддерживается UTF8 и не требует `mbstring`)
* для других значений возвращается 0
```smarty
{if $images|length > 5}
to many images
{/if}
```

View File

@ -1,5 +1,5 @@
Modifier |lower
===============
Modifier lower
==============
Modifier is used to lowercase a variable or string. Have short alias `low`
This is equivalent to the PHP [strtolower()](http://docs.php.net/lower) function.

View File

@ -1,13 +1,13 @@
Modifier strip [RU]
===================
Modifier strip
==============
Для удаления символы пробелов при использовании переменной используйте можификатор `|strip`
This replaces all repeated spaces and tabs with a single space, or with the supplied string.
```smarty
{" one two "|strip} => 'one two'
```
Что бы убрать переносы строк укажите **TRUE** первым аргументом модификатора
Optional boolean parameter tell to the modifier strip also newline
```smarty
{" multi

View File

@ -1,16 +1,16 @@
Modifier truncate [RU]
=======================
Modifier truncate
=================
Обрезает строку до указанной длины. Может обрезать как ровно по символу так и завершивемогуся слову, где итоговоя строка не привыет указанной длины.
Modifier truncates a variable to a character length.
```smarty
{$long_string|truncate:$length:$etc:$by_words:$middle}
```
* `$length` обязательный параметр, указывающий максимальную длину выводимой сроки
* `$etc`, по умолчанию `...`, содержащий строку которой будет заменены "лишние" символы.
* `$by_word`, по умолчанию **FALSE**. Флаг указывает модификатору не разбивать слово, а найти ближайший (в меньшую строну) пробельный символ, после которого строка буде обрезана
* `$middle`, по умочанию **FALSE**. Включенный флаг, который указывает, что "лишние" данные нужно вырезать из середины строки, а не из конца.
* `$length`, required. Parameter determines how many characters to truncate to.
* `$etc`, by default `...`. This is a text string that replaces the truncated text.
* `$by_word`, by default **FALSE**. This determines whether or not to truncate at a word boundary with TRUE, or at the exact character with FALSE.
* `$middle`, by default **FALSE**. This determines whether the truncation happens at the end of the string with FALSE, or in the middle of the string with TRUE.
```smarty
{var $str = "very very long string"}
@ -19,4 +19,4 @@ Modifier truncate [RU]
{$str|truncate:5:" ... ":true:true} output: very ... string
```
Модификатор работает отлично с UTF8 и не требует расширения `mbstring`
Modifier do not use `mbstring` when works with UTF8.

View File

@ -1,5 +1,5 @@
Modifier unescape [RU]
======================
Modifier unescape
=================
Модификатор, обратный модификатору [escape](./escape.md)
`Unescape` is used to decode entity, html, js and URI. See [escape](./escape.md)

View File

@ -1,13 +1,13 @@
Modifier |upper
===============
Modifier upper
==============
Modifier is used to uppercase a variable or string. Have short alias `up`
Modifier is used to uppercase a variable or string. Have short alias `up`.
This is equivalent to the PHP [strtoupper()](http://docs.php.net/strtoupper) function.
```smarty
{var $name = "Bzick"}
{$name} output Bzick
{$name|upper} output BZICK
{$name|up} output BZICK too
{$name} outputs Bzick
{$name|upper} outputs BZICK
{$name|up} outputs BZICK too
```

View File

@ -1,14 +1,15 @@
Documentation
=============
**Please, help translate documentation to english or fix typos. [Read more](./helpme.md).**
### Fenom
* [Install](./install.md)
* [Usage](./usage.md)
* [Fenom adapters](./adapters.md)
* [Develop](./dev/readme.md)
* [Settings](./settings.md)
* [Callbacks and filters](./callbacks.md)
* [Quick start](./start.md)
* [Usage](./start.md#install-fenom)
* [Framework adapters](./adapters.md)
* [For developers](./dev/readme.md)
* [Configuration](./settings.md)
* [Syntax](./syntax.md)
* [Operators](./operators.md)
@ -18,16 +19,16 @@ Documentation
[Usage](./syntax.md#modifiers)
* [upper](./mods/upper.md) aka `up`
* [lower](./mods/lower.md) aka `low`
* [date_format](./mods/date_format.md)
* [date](./mods/date.md)
* [truncate](./mods/truncate.md)
* [escape](./mods/escape.md) aka `e`
* [unescape](./mods/unescape.md)
* [strip](./mods/strip.md)
* [length](./mods/length.md)
* [in](./mods/in.md)
* [upper](./mods/upper.md) aka `up` — convert to uppercase a string
* [lower](./mods/lower.md) aka `low` — convert to lowercase a string
* [date_format](./mods/date_format.md) - format date, timestamp via strftime() function
* [date](./mods/date.md) - format date, timestamp via date() function
* [truncate](./mods/truncate.md) — truncate thee string to specified length
* [escape](./mods/escape.md) aka `e` — escape the string
* [unescape](./mods/unescape.md) — unescape the string
* [strip](./mods/strip.md) — remove extra whitespaces
* [length](./mods/length.md) — calculate length of string, array, object
* [in](./mods/in.md) — find value in string or array
* allowed functions: `json_encode`, `json_decode`, `count`, `is_string`, `is_array`, `is_numeric`, `is_int`, `is_object`,
`strtotime`, `gettype`, `is_double`, `ip2long`, `long2ip`, `strip_tags`, `nl2br`
* or [add](./ext/mods.md) yours
@ -38,19 +39,19 @@ Documentation
[Usage](./syntax.md#tags)
* [var](./tags/var.md)
* [if](./tags/if.md), `elseif` and `else`
* [foreach](./tags/foreach.md), `foreaelse`, `break` and `continue`
* [for](./tags/for.md), `forelse`, `break` and `continue`
* [switch](./tags/switch.md), `case`, `default`
* [cycle](./tags/cycle.md)
* [include](./tags/include.md), `insert`
* [extends](./tags/extends.md), `use`, `block` and `parent`
* [filter](./tags/filter.md)
* [ignore](./tags/ignore.md)
* [macro](./tags/macro.md) and `import`
* [autoescape](./tags/autoescape.md)
* [raw](./tags/raw.md)
* [var](./tags/var.md) — define variable
* [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`
* [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
* [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
* [autoescape](./tags/autoescape.md) — escape template fragment
* [raw](./tags/raw.md) — unescape template fragment
* or [add](./ext/tags.md) yours
***
@ -59,6 +60,5 @@ Documentation
* [Extensions](./ext/extensions.md)
* [Add tags](./ext/tags.md)
* [Add modificators](./ext/mods.md)
* [Add template provider](./ext/provider.md)
* [Add modifiers](./ext/mods.md)
* [Parsing](./ext/parsing.md)

View File

@ -1,47 +0,0 @@
Settings
========
### Template cache
```php
$fenom->setCompileDir($dir);
```
This method set the name of the directory where template caches are stored. By default this is `/tmp`. This directory must be writeable.
### Template settings
```php
// set options using factory
$fenom = Fenom::factory($tpl_dir, $compile_dir, $options);
// or inline using method setOptions
$fenom->setOptions($options);
```
Параметры могут быть массивом `'option_name' => true` (если ключ не указан автоматически задаётся false) или битовой маской.
| Code | Constant | Description | Affect |
| -------------------- | ------------------------- | ------------ | ------- |
| disable_methods | `Fenom::DENY_METHODS` | disable calling methods of objects in templates. | |
| disable_native_funcs | `Fenom::DENY_INLINE_FUNCS`| disable calling native function in templates, except allowed. | |
| auto_reload | `Fenom::AUTO_RELOAD` | reload template if source will be changed | decreases the performance |
| force_compile | `Fenom::FORCE_COMPILE` | recompile template every time when the template renders | greatly decreases performance |
| disable_cache | `Fenom::DISABLE_CACHE` | disable compile cache | greatly decreases performance |
| force_include | `Fenom::FORCE_INCLUDE` | paste template body instead of include-tag | increases performance, increases cache size |
| auto_escape | `Fenom::AUTO_ESCAPE` | html-escape each variables outputs | decreases performance |
| force_verify | `Fenom::FORCE_VERIFY` | check existence every used variable | decreases performance |
| auto_trim | `Fenom::AUTO_TRIM` | remove space-characters before and after tags | |
```php
$fenom->setOptions(array(
"compile_check" => true,
"force_include" => true
));
// same
$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_INCLUDE);
```
**By default all options disabled**
### Tag options

68
docs/start.md Normal file
View File

@ -0,0 +1,68 @@
Basic usage
===========
## Install Fenom
### Composer
Add package Fenom in your require-list in `composer.json`:
```json
{
"require": {
"fenom/fenom": "2.*"
}
}
```
and update project's dependencies: `composer update`.
### Custom loader
Clone Fenom to any directory: `git clone https://github.com/bzick/fenom.git`. Recommended use latest tag.
Fenom use [psr-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md#autoloading-standard) autoloading standard. Therefore you can
* use `psr-0` format in your project loader for loading Fenom's classes
* or register Fenom's autoloader: `Fenom::registerAutoload();` for loading itself.
Also you can use this autoloader for loading any library with `psr-0` file naming:
```php
Fenom::registerAutoload(PROJECT_DIR."/src");
```
## Setup Fenom
Create an object via factory method
```php
$fenom = Fenom::factory('/path/to/templates', '/path/to/compiled/template', $options);
```
Create an object via `new` operator
```php
$fenom = new Fenom(new Provider('/path/to/templates'));
$fenom->setCompileDir('/path/to/template/cache');
$fenom->setOptions($options);
```
* `/path/to/templates` — directory, where stores your templates.
* `/path/to/template/cache` — directory, where stores compiled templates in PHP files.
* `$options` - bit-mask or array of [Fenom settings](./docs/settings.md).
### Use Fenom
Output template
```php
$fenom->display("template/name.tpl", $vars);
```
Get the result of rendering the template
```php
$result = $fenom->fetch("template/name.tpl", $vars);
```
Create the pipeline of rendering into callback
```php
$fenom->pipe(
"template/sitemap.tpl",
$vars,
$callback = [new SplFileObject("/tmp/sitemap.xml", "w"), "fwrite"], // pipe to file /tmp/sitemap.xml
$chunk_size = 1e6 // chunk size for callback
);
```

View File

@ -1,17 +1,66 @@
Syntax [RU]
===========
Syntax
======
Fenom implement [Smarty](http://www.smarty.net/) syntax with some improvements
Fenom implements [Smarty](http://www.smarty.net/) syntax with some improvements.
All Fenom tags enclosed in the delimiters `{` and `}`, for example `{var $five = 5}`.
If you wanna leave delimiters as is in the template use [special statements or tags](#ignoring-delimiters).
**Note**
Fenom implements [Smarty](http://www.smarty.net/) syntax but not implements Smarty tags, however, some tags very similar.
But not so bad, Fenom has the [extras](https://github.com/bzick/fenom-extra) that make Fenom like Smarty.
## Variable
Variables in Fenom can be either displayed directly or used as arguments for functions, attributes and modifiers,
inside conditional expressions, etc.
### Use variables
Next example uses simple variables `$user_id` ans `$user_name`
```smarty
{$foo}
{$bar}
{$foo[4]}
{$foo.4}
<div class="user">Hello, <a href="/users/{$user_id}">{$user_name}</a>.</div>
```
Example outputs next HTML code:
```html
<div class="user">Hello, <a href="/users/17">Bzick</a>.</div>
```
Переменные могут быть массивом. В этом случае обращение по ключу происходит через опертор `.` или, как в PHP, через операторы `[` и `]`
```smarty
<div class="user">Hello, <a href="/users/{$user.id}">{$user.name}</a>.</div>
```
`{$user.id}` and `{$user['id']}` are same:
```smarty
<div class="user">Hello, <a href="/users/{$user['id']}">{$user.['name']}</a>.</div>
```
В случае объекта, доступ к его свойствам осущесвляется так как и в PHP — через оператор `->`:
```smarty
<div class="user">Hello, <a href="/users/{$user->id}">{$user->name}</a>.</div>
```
Методы, как и свойства можно вызвать через оператор `->`, передав в метод любые рагументы:
```smarty
<div class="user">Hello, <a href="/users/{$user->getId()}">{$user->getName()}</a>.</div>
```
*Note*
Be careful, Fenom do not checks existence of the method before invoke.
To avoid the problem class of the object have to define method `__call`, which throws an exception, etc.
Also you can prohibit method call in [settings](./docs/configuration.md).
Можно комбинировать различные варианты вызовов:
```smarty
{$foo.bar.baz}
{$foo.$bar.$baz}
{$foo[5].baz}
{$foo[5].$baz}
{$foo.bar.baz[4]}
{$foo[ $bar.baz ]}
{$foo[5]}
{$foo.5}
{$foo.bar}
{$foo.'bar'}
{$foo."bar"}
@ -21,11 +70,13 @@ Fenom implement [Smarty](http://www.smarty.net/) syntax with some improvements
{$foo[$bar]}
{$foo->bar}
{$foo->bar.buz}
{$foo->bar.buz[ $bar->getId("user") ]}
{$foo->bar(5)->buz(5.5)}
```
### System variable
Unnamed system variable starts with `$.` and allow access to global variables and system info:
Unnamed system variable starts with `$.` and allows access to global variables and system info (fix doc):
* `$.get` is `$_GET`.
* `$.post` is `$_POST`.
@ -47,17 +98,6 @@ Unnamed system variable starts with `$.` and allow access to global variables an
{/if}
```
### Multidimensional value support
```smarty
{$foo.bar.baz}
{$foo.$bar.$baz}
{$foo[4].baz}
{$foo[4].$baz}
{$foo.bar.baz[4]}
{$foo[ $bar.baz ]}
```
### Math operations
```smarty
@ -68,17 +108,17 @@ Unnamed system variable starts with `$.` and allow access to global variables an
See all [operators](./operators.md)
### Object support
### Static method support
```smarty
{$object->item}
{$object->item|upper} {* apply modifier *}
{$object->item->method($y, 'named')}
{$object->item->method($y->name, 'named')|upper} {* apply modifier to method result*}
{Lib\Math::multiple x=3 y=4} static method as tag
{Lib\Math::multiple(3,4)} inline static method
{12 + Lib\Math::multiple(3,4)}
{12 + 3|Lib\Math::multiple:4} static method as modifier
```
You may disable call methods in template, see [security options](./settings.md)
You may disable call static methods in template, see in [security options](./settings.md) option `deny_static`
### Set variable
@ -125,7 +165,7 @@ See also [{var}](./tags/var.md) documentation.
### Strings
When the string in double quotation marks, all the expressions in the string will be run.
The result of the expression will be inserted into the string instead it.
The result of expressions will be inserted into the string instead it.
```smarty
{var $foo="Username"}
@ -147,7 +187,7 @@ but if use single quote any template expressions will be on display as it is
{'Hi, {$user.name|up}'} outputs "Hi, {$user.name|up}"
```
## Numbers
### Numbers
```smarty
{2|pow:10}
@ -156,11 +196,11 @@ but if use single quote any template expressions will be on display as it is
{1e-6|round}
```
### Modifiers
## Modifiers
* Модификаторы позволяют изменить значение переменной перед выводом или использованием в выражении
* To apply a modifier, specify the value followed by a | (pipe) and the modifier name.
* A modifier may accept additional parameters that affect its behavior. These parameters follow the modifier name and are separated by a : (colon).
* Modifiers allows change some value before output or using.
* To apply a modifier, specify the value followed by a `|` (pipe) and the modifier name.
* A modifier may accept additional parameters that affect its behavior. These parameters follow the modifier name and are separated by a `:` (colon).
```smarty
{var $foo="User"}
@ -175,15 +215,16 @@ but if use single quote any template expressions will be on display as it is
[List of modifiers](./main.md#modifiers)
### Tags
## Tags
Каждый тэг шаблонизатора либо выводит переменную, либо вызывает какую-либо функцию. (переписать)
Тег вызова функции начинается с названия функции и содержит список аргументов:
Basically, tag seems like
```smarty
{FUNCNAME attr1 = "val1" attr2 = $val2}
```
Tags starts with name and may have attributes
Это общий формат функций, но могут быть исключения, например функция [{var}](./tags/var.md), использованная выше.
```smarty
@ -223,12 +264,13 @@ but if use single quote any template expressions will be on display as it is
### Ignoring template code
В шаблонизаторе Fenom используются фигурные скобки для отделения HTML от кода Fenom.
Если требуется вывести текст, содержащий фигурные скобки, помните о следующих возможностях:
Если требуется вывести текст, содержащий фигурные скобки, то есть следующие варианты это сделать:
1. Использование блочного тега `{ignore}{/ignore}`. Текст внутри этого тега текст не компилируется шаблонизатором и выводится как есть.
2. Если после открывающей фигурной скобки есть пробельный символ (пробел или `\t`) или перенос строки (`\r` или `\n`), то она не воспринимается как разделитель кода Fenom и код после неё выводится как есть.
3. Установить опцию `:ignore` у блочного тега. Все Fenom теги внутри блока будут проигнорированны
Пример:
Example:
```smarty
{ignore}
@ -242,10 +284,13 @@ but if use single quote any template expressions will be on display as it is
e.innerHTML = text;
document.body.appendChild(e);
})('test');
</ignore>
{if:ignore $cdn.yandex}
var item = {cdn: "//yandex.st/"};
{/if}
</script>
```
Выведет
Outputs
```html
<style>
@ -257,6 +302,7 @@ but if use single quote any template expressions will be on display as it is
e.innerHTML = text;
document.body.appendChild(e);
})('test');
var item = {cdn: "//yandex.st/"};
</script>
```
@ -266,9 +312,11 @@ but if use single quote any template expressions will be on display as it is
```smarty
{include 'control.tpl'
options = $list
name = $cp.name
type = 'select'
$options = $list
$name = $cp.name
$type = 'select'
isolate = true
disable_static = true
}
{foreach [
@ -280,4 +328,18 @@ but if use single quote any template expressions will be on display as it is
{$key}: {$val}
{/foreach}
```
```
### Tag options
| name | code | type | description |
| ------- | ---- | ----- | ------------ |
| strip | s | block | enable `strip` option for a block of the template |
| raw | a | any | ignore escape option |
| escape | e | any | force escape |
| ignore | i | block | ignore Fenom syntax |
```smarty
{script:ignore} ... {/script}
{foreach:ignore:strip ...} ... {/foreach}
```

View File

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

View File

@ -1,7 +1,8 @@
Tag {raw} [RU]
==================
Tag {raw}
=========
Тег `{raw <expression>}` позволяет выводить результат выражения или функций без экранирования, игнорируя глобальную настройку `auto_escape`.
Tag `{raw <expression>}` allow outputs render results without escaping.
This tag rewrite global option `auto_escape` for specified code.
```smarty
{autoescape true}
@ -15,15 +16,15 @@ Tag {raw} [RU]
{/autoescate}
```
Для результатов функций то же может быть отключено экранирование:
For functions use tag with prefix `raw:`:
```smarty
{autoescape true}
...
{my_func page=5} {* escape *}
{raw:my_func page=5} {* unescape *}
{my_func:raw page=5} {* unescape *}
...
{/autoescate}
```
На компиляторы свойство raw не распространяется.
Tag can not be applied to compilers as `foreach`, `if` and other.

View File

@ -15,7 +15,7 @@ Tag `{switch}` accepts any expression. But `{case}` accepts only static scalar v
...
{case <value3>}
...
{default case <value1>}
{case default, <value1>}
...
{/switch}
```
@ -30,9 +30,9 @@ For example,
It is new or current item
{case 'current'}
It is current item
{case 'new', $.const.NEW_STATUS}
{case 'new', 'newer'}
It is new item, again
{default}
{case default}
I don't know the type {$type}
{/switch}
```

View File

@ -1,38 +0,0 @@
Basic usage
===========
### Initialize Fenom
Creating an object via factory method
```php
$fenom = Fenom::factory('/path/to/templates', '/path/to/compiled/template', $options);
```
Creating an object via `new` operator
```php
$fenom = new Fenom(new Provider('/path/to/templates'));
$fenom->setCompileDir('/path/to/template/cache');
$fenom->setOptions($options);
```
### Rendering template
Output template
```php
$fenom->display("template/name.tpl", $vars);
```
Get the result of rendering the template
```php
$result = $fenom->fetch("template/name.tpl", $vars);
```
Create the pipeline of rendering into callback
```php
$fenom->pipe(
"template/sitemap.tpl",
$vars,
$callback = [new SplFileObject("/tmp/sitemap.xml", "w"), "fwrite"], // pipe to file /tmp/sitemap.xml
$chunk_size = 1e6 // chunk size for callback
);
```

View File

@ -0,0 +1,61 @@
<?php
/** Fenom template 'greeting.tpl' compiled at 2013-09-02 17:37:18 */
return new Fenom\Render($fenom, function ($tpl) {
?>
A1
<?php
/* greeting.tpl:4: {mc.factorial num=10} */
$_tpl4154309674_1 = $tpl->exchangeArray(array("num" => 10));
?><?php
/* macros.tpl:2: {if $num} */
if($tpl["num"]) { ?>
<?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?> <?php
/* macros.tpl:3: {macro.factorial num=$num-1} */
$_tpl2531688351_1 = $tpl->exchangeArray(array("num" => $tpl["num"] - 1));
$tpl->getMacro("factorial")->__invoke($tpl);
$tpl->exchangeArray($_tpl2531688351_1); /* X */ unset($_tpl2531688351_1); ?> <?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?>
<?php
/* macros.tpl:4: {/if} */
} ?>
<?php
$tpl->exchangeArray($_tpl4154309674_1); /* X */ unset($_tpl4154309674_1); ?>
A2<?php
}, array(
'options' => 128,
'provider' => false,
'name' => 'greeting.tpl',
'base_name' => 'greeting.tpl',
'time' => 1378125225,
'depends' => array (
0 =>
array (
'macros.tpl' => 1378129033,
'greeting.tpl' => 1378125225,
),
),
'macros' => array(
'factorial' => function ($tpl) {
?><?php
/* macros.tpl:2: {if $num} */
if($tpl["num"]) { ?>
<?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?> <?php
/* macros.tpl:3: {macro.factorial num=$num-1} */
$_tpl2531688351_1 = $tpl->exchangeArray(array("num" => $tpl["num"] - 1));
$tpl->getMacro("factorial")->__invoke($tpl);
$tpl->exchangeArray($_tpl2531688351_1); /* X */ unset($_tpl2531688351_1); ?> <?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?>
<?php
/* macros.tpl:4: {/if} */
} ?>
<?php
}),
));

View File

@ -0,0 +1,61 @@
<?php
/** Fenom template 'greeting.tpl' compiled at 2013-09-02 17:37:39 */
return new Fenom\Render($fenom, function ($tpl) {
?>
A1
<?php
/* greeting.tpl:4: {mc.factorial num=10} */
$_tpl4154309674_1 = $tpl->exchangeArray(array("num" => 10));
?><?php
/* macros.tpl:2: {if $num} */
if($tpl["num"]) { ?>
<?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?> <?php
/* macros.tpl:3: {macro.factorial num=$num-1} */
$_tpl2531688351_1 = $tpl->exchangeArray(array("num" => $tpl["num"] - 1));
$tpl->getMacro("factorial")->__invoke($tpl);
$tpl->exchangeArray($_tpl2531688351_1); /* X */ unset($_tpl2531688351_1); ?> <?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?>
<?php
/* macros.tpl:4: {/if} */
} ?>
<?php
$tpl->exchangeArray($_tpl4154309674_1); /* X */ unset($_tpl4154309674_1); ?>
A2<?php
}, array(
'options' => 0,
'provider' => false,
'name' => 'greeting.tpl',
'base_name' => 'greeting.tpl',
'time' => 1378125225,
'depends' => array (
0 =>
array (
'macros.tpl' => 1378129033,
'greeting.tpl' => 1378125225,
),
),
'macros' => array(
'factorial' => function ($tpl) {
?><?php
/* macros.tpl:2: {if $num} */
if($tpl["num"]) { ?>
<?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?> <?php
/* macros.tpl:3: {macro.factorial num=$num-1} */
$_tpl2531688351_1 = $tpl->exchangeArray(array("num" => $tpl["num"] - 1));
$tpl->getMacro("factorial")->__invoke($tpl);
$tpl->exchangeArray($_tpl2531688351_1); /* X */ unset($_tpl2531688351_1); ?> <?php
/* macros.tpl:3: {$num} */
echo $tpl["num"]; ?>
<?php
/* macros.tpl:4: {/if} */
} ?>
<?php
}),
));

25
sandbox/fenom.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace Ts {
class Math {
public static function multi($x, $y) {
return $x * $y;
}
}
}
namespace {
require_once __DIR__.'/../src/Fenom.php';
\Fenom::registerAutoload();
$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled', Fenom::FORCE_COMPILE);
$fenom->display("extends/75-child.tpl", array(
"user" => array(
"name" => "Ivka",
'type' => 'new'
),
'type' => 'new'
));
}

View File

@ -0,0 +1,13 @@
{extends 'extends/75-parent.tpl'}
{block 'child'}
{macro child_test(v, i)}
child test - {$v}, i = {$i};<br/>
{var $i = $i -1}
{if $i > 0}
{macro.child_test v=$v i=$i}
{/if}
{/macro}
child call: <br/>
{macro.child_test v = 'ok' i = 5}
{/block}

View File

@ -0,0 +1,12 @@
{macro parent_test(v, i)}
parent test - {$v}, i = {$i};<br/>
{var $i = $i -1}
{if $i > 0}
{macro.parent_test v=$v i=$i}
{/if}
{/macro}
{block 'child'}{/block}
parent call:<br/>
{macro.parent_test v = 'ok' i = 5} <br/>

View File

View File

@ -0,0 +1,7 @@
{var:escape $a}
asdasd
{/var}
{*{Ts\Math::multi x=34 y=44}*}
{*{$a + Ts\Math::multi(34, 44)}*}
{*{34|Ts\Math::multi:44}*}

View File

@ -17,37 +17,33 @@ use Fenom\Template;
*/
class Fenom
{
const VERSION = '1.4';
const VERSION = '2.0';
/* Actions */
const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 2;
const INLINE_FUNCTION = 3;
const BLOCK_FUNCTION = 4;
const MODIFIER = 5;
const BLOCK_COMPILER = 5;
const INLINE_FUNCTION = 2;
const BLOCK_FUNCTION = 7;
/* Options */
const DENY_ACCESSOR = 0x8;
const DENY_METHODS = 0x10;
const DENY_ACCESSOR = 0x8;
const DENY_METHODS = 0x10;
const DENY_NATIVE_FUNCS = 0x20;
const FORCE_INCLUDE = 0x40;
const AUTO_RELOAD = 0x80;
const FORCE_COMPILE = 0x100;
const AUTO_ESCAPE = 0x200;
const DISABLE_CACHE = 0x400;
const FORCE_VERIFY = 0x800;
const AUTO_TRIM = 0x1000; // reserved
const DENY_STATICS = 0x2000; // reserved
/* @deprecated */
const DENY_INLINE_FUNCS = 0x20;
const FORCE_INCLUDE = 0x40;
const AUTO_RELOAD = 0x80;
const FORCE_COMPILE = 0x100;
const AUTO_ESCAPE = 0x200;
const DISABLE_CACHE = 0x400;
const FORCE_VERIFY = 0x800;
const AUTO_TRIM = 0x1000; // reserved
const DENY_STATICS = 0x2000;
const AUTO_STRIP = 0x4000;
/* Default parsers */
const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Fenom\Compiler::stdFuncParser';
const DEFAULT_FUNC_OPEN = 'Fenom\Compiler::stdFuncOpen';
const DEFAULT_FUNC_CLOSE = 'Fenom\Compiler::stdFuncClose';
const SMART_FUNC_PARSER = 'Fenom\Compiler::smartFuncParser';
const DEFAULT_FUNC_PARSER = 'Fenom\Compiler::stdFuncParser';
const DEFAULT_FUNC_OPEN = 'Fenom\Compiler::stdFuncOpen';
const DEFAULT_FUNC_CLOSE = 'Fenom\Compiler::stdFuncClose';
const SMART_FUNC_PARSER = 'Fenom\Compiler::smartFuncParser';
const MAX_MACRO_RECURSIVE = 32;
@ -56,17 +52,18 @@ class Fenom
* @see setOptions
*/
private static $_options_list = array(
"disable_accessor" => self::DENY_ACCESSOR,
"disable_methods" => self::DENY_METHODS,
"disable_accessor" => self::DENY_ACCESSOR,
"disable_methods" => self::DENY_METHODS,
"disable_native_funcs" => self::DENY_NATIVE_FUNCS,
"disable_cache" => self::DISABLE_CACHE,
"force_compile" => self::FORCE_COMPILE,
"auto_reload" => self::AUTO_RELOAD,
"force_include" => self::FORCE_INCLUDE,
"auto_escape" => self::AUTO_ESCAPE,
"force_verify" => self::FORCE_VERIFY,
"auto_trim" => self::AUTO_TRIM,
"disable_statics" => self::DENY_STATICS,
"disable_cache" => self::DISABLE_CACHE,
"force_compile" => self::FORCE_COMPILE,
"auto_reload" => self::AUTO_RELOAD,
"force_include" => self::FORCE_INCLUDE,
"auto_escape" => self::AUTO_ESCAPE,
"force_verify" => self::FORCE_VERIFY,
"auto_trim" => self::AUTO_TRIM,
"disable_statics" => self::DENY_STATICS,
"strip" => self::AUTO_STRIP,
);
/**
@ -79,6 +76,11 @@ class Fenom
*/
public $filters = array();
/**
* @var callable[]
*/
public $tag_filters = array();
/**
* @var callable[]
*/
@ -94,6 +96,11 @@ class Fenom
*/
protected $_compile_dir = "/tmp";
/**
* @var string[] compile directory for custom provider
*/
protected $_compiles = array();
/**
* @var int masked options
*/
@ -112,143 +119,205 @@ class Fenom
* @var string[] list of modifiers [modifier_name => callable]
*/
protected $_modifiers = array(
"upper" => 'strtoupper',
"up" => 'strtoupper',
"lower" => 'strtolower',
"low" => 'strtolower',
"upper" => 'strtoupper',
"up" => 'strtoupper',
"lower" => 'strtolower',
"low" => 'strtolower',
"date_format" => 'Fenom\Modifier::dateFormat',
"date" => 'Fenom\Modifier::date',
"truncate" => 'Fenom\Modifier::truncate',
"escape" => 'Fenom\Modifier::escape',
"e" => 'Fenom\Modifier::escape', // alias of escape
"unescape" => 'Fenom\Modifier::unescape',
"strip" => 'Fenom\Modifier::strip',
"length" => 'Fenom\Modifier::length',
"iterable" => 'Fenom\Modifier::isIterable'
"date" => 'Fenom\Modifier::date',
"truncate" => 'Fenom\Modifier::truncate',
"escape" => 'Fenom\Modifier::escape',
"e" => 'Fenom\Modifier::escape', // alias of escape
"unescape" => 'Fenom\Modifier::unescape',
"strip" => 'Fenom\Modifier::strip',
"length" => 'Fenom\Modifier::length',
"iterable" => 'Fenom\Modifier::isIterable'
);
/**
* @var array of allowed PHP functions
*/
protected $_allowed_funcs = array(
"count" => 1, "is_string" => 1, "is_array" => 1, "is_numeric" => 1, "is_int" => 1, 'constant' => 1,
"is_object" => 1, "strtotime" => 1, "gettype" => 1, "is_double" => 1, "json_encode" => 1, "json_decode" => 1,
"ip2long" => 1, "long2ip" => 1, "strip_tags" => 1, "nl2br" => 1, "explode" => 1, "implode" => 1
"count" => 1,
"is_string" => 1,
"is_array" => 1,
"is_numeric" => 1,
"is_int" => 1,
'constant' => 1,
"is_object" => 1,
"strtotime" => 1,
"gettype" => 1,
"is_double" => 1,
"json_encode" => 1,
"json_decode" => 1,
"ip2long" => 1,
"long2ip" => 1,
"strip_tags" => 1,
"nl2br" => 1,
"explode" => 1,
"implode" => 1
);
/**
* @var array[] of compilers and functions
*/
protected $_actions = array(
'foreach' => array( // {foreach ...} {break} {continue} {foreachelse} {/foreach}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::foreachOpen',
'close' => 'Fenom\Compiler::foreachClose',
'tags' => array(
'foreach' => array( // {foreach ...} {break} {continue} {foreachelse} {/foreach}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::foreachOpen',
'close' => 'Fenom\Compiler::foreachClose',
'tags' => array(
'foreachelse' => 'Fenom\Compiler::foreachElse',
'break' => 'Fenom\Compiler::tagBreak',
'continue' => 'Fenom\Compiler::tagContinue',
'break' => 'Fenom\Compiler::tagBreak',
'continue' => 'Fenom\Compiler::tagContinue',
),
'float_tags' => array('break' => 1, 'continue' => 1)
),
'if' => array( // {if ...} {elseif ...} {else} {/if}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::ifOpen',
'if' => array( // {if ...} {elseif ...} {else} {/if}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::ifOpen',
'close' => 'Fenom\Compiler::stdClose',
'tags' => array(
'tags' => array(
'elseif' => 'Fenom\Compiler::tagElseIf',
'else' => 'Fenom\Compiler::tagElse'
'else' => 'Fenom\Compiler::tagElse'
)
),
'switch' => array( // {switch ...} {case ..., ...} {default} {/switch}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::switchOpen',
'close' => 'Fenom\Compiler::switchClose',
'tags' => array(
'case' => 'Fenom\Compiler::tagCase',
'switch' => array( // {switch ...} {case ..., ...} {default} {/switch}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::switchOpen',
'close' => 'Fenom\Compiler::switchClose',
'tags' => array(
'case' => 'Fenom\Compiler::tagCase',
'default' => 'Fenom\Compiler::tagDefault'
),
'float_tags' => array('break' => 1)
),
'for' => array( // {for ...} {break} {continue} {/for}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::forOpen',
'close' => 'Fenom\Compiler::forClose',
'tags' => array(
'forelse' => 'Fenom\Compiler::forElse',
'break' => 'Fenom\Compiler::tagBreak',
'for' => array( // {for ...} {break} {continue} {/for}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::forOpen',
'close' => 'Fenom\Compiler::forClose',
'tags' => array(
'forelse' => 'Fenom\Compiler::forElse',
'break' => 'Fenom\Compiler::tagBreak',
'continue' => 'Fenom\Compiler::tagContinue',
),
'float_tags' => array('break' => 1, 'continue' => 1)
),
'while' => array( // {while ...} {break} {continue} {/while}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::whileOpen',
'close' => 'Fenom\Compiler::stdClose',
'tags' => array(
'break' => 'Fenom\Compiler::tagBreak',
'while' => array( // {while ...} {break} {continue} {/while}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::whileOpen',
'close' => 'Fenom\Compiler::stdClose',
'tags' => array(
'break' => 'Fenom\Compiler::tagBreak',
'continue' => 'Fenom\Compiler::tagContinue',
),
'float_tags' => array('break' => 1, 'continue' => 1)
),
'include' => array( // {include ...}
'type' => self::INLINE_COMPILER,
'include' => array( // {include ...}
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagInclude'
),
'insert' => array( // {include ...}
'type' => self::INLINE_COMPILER,
'insert' => array( // {include ...}
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagInsert'
),
'var' => array( // {var ...}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::varOpen',
'var' => array( // {var ...}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::varOpen',
'close' => 'Fenom\Compiler::varClose'
),
'block' => array( // {block ...} {parent} {/block}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::tagBlockOpen',
'close' => 'Fenom\Compiler::tagBlockClose',
'tags' => array(// 'parent' => 'Fenom\Compiler::tagParent' // not implemented yet
),
'block' => array( // {block ...} {parent} {/block}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::tagBlockOpen',
'close' => 'Fenom\Compiler::tagBlockClose',
'tags' => array('parent' => 'Fenom\Compiler::tagParent'),
'float_tags' => array('parent' => 1)
),
'extends' => array( // {extends ...}
'type' => self::INLINE_COMPILER,
'extends' => array( // {extends ...}
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagExtends'
),
'use' => array( // {use}
'type' => self::INLINE_COMPILER,
'use' => array( // {use}
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagUse'
),
'filter' => array( // {filter} ... {/filter}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::filterOpen',
'filter' => array( // {filter} ... {/filter}
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::filterOpen',
'close' => 'Fenom\Compiler::filterClose'
),
'macro' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::macroOpen',
'macro' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::macroOpen',
'close' => 'Fenom\Compiler::macroClose'
),
'import' => array(
'type' => self::INLINE_COMPILER,
'import' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagImport'
),
'cycle' => array(
'type' => self::INLINE_COMPILER,
'cycle' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagCycle'
),
'raw' => array(
'type' => self::INLINE_COMPILER,
'raw' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Fenom\Compiler::tagRaw'
),
'autoescape' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::autoescapeOpen',
'close' => 'Fenom\Compiler::autoescapeClose'
'autoescape' => array( // deprecated
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::escapeOpen',
'close' => 'Fenom\Compiler::nope'
),
'escape' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::escapeOpen',
'close' => 'Fenom\Compiler::nope'
),
'strip' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::stripOpen',
'close' => 'Fenom\Compiler::nope'
),
'ignore' => array(
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::ignoreOpen',
'close' => 'Fenom\Compiler::nope'
)
);
/**
* List of tests
* @see https://github.com/bzick/fenom/blob/develop/docs/operators.md#test-operator
* @var array
*/
protected $_tests = array(
'integer' => 'is_int(%s)',
'int' => 'is_int(%s)',
'float' => 'is_float(%s)',
'double' => 'is_float(%s)',
'decimal' => 'is_float(%s)',
'string' => 'is_string(%s)',
'bool' => 'is_bool(%s)',
'boolean' => 'is_bool(%s)',
'number' => 'is_numeric(%s)',
'numeric' => 'is_numeric(%s)',
'scalar' => 'is_scalar(%s)',
'object' => 'is_object(%s)',
'callable' => 'is_callable(%s)',
'callback' => 'is_callable(%s)',
'array' => 'is_array(%s)',
'iterable' => '\Fenom\Modifier::isIterable(%s)',
'const' => 'defined(%s)',
'template' => '$tpl->getStorage()->templateExists(%s)',
'empty' => 'empty(%s)',
'set' => 'isset(%s)',
'_empty' => '!%s', // for none variable
'_set' => '(%s !== null)', // for none variable
'odd' => '(%s & 1)',
'even' => '!(%s %% 2)',
'third' => '!(%s %% 3)'
);
/**
* Just factory
*
@ -293,7 +362,7 @@ class Fenom
*/
public function setCompileDir($dir)
{
if(!is_writable($dir)) {
if (!is_writable($dir)) {
throw new LogicException("Cache directory $dir is not writable");
}
$this->_compile_dir = $dir;
@ -349,6 +418,22 @@ class Fenom
return $this->filters;
}
/**
* @param callable $cb
* @return self
*/
public function addTagFilter($cb)
{
$this->tag_filters[] = $cb;
return $this;
}
public function getTagFilters()
{
return $this->tag_filters;
}
/**
* Add modifier
*
@ -372,7 +457,7 @@ class Fenom
public function addCompiler($compiler, $parser)
{
$this->_actions[$compiler] = array(
'type' => self::INLINE_COMPILER,
'type' => self::INLINE_COMPILER,
'parser' => $parser
);
return $this;
@ -387,7 +472,7 @@ class Fenom
{
if (method_exists($storage, "tag" . $compiler)) {
$this->_actions[$compiler] = array(
'type' => self::INLINE_COMPILER,
'type' => self::INLINE_COMPILER,
'parser' => array($storage, "tag" . $compiler)
);
}
@ -403,13 +488,17 @@ class Fenom
* @param array $tags
* @return Fenom
*/
public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array())
{
public function addBlockCompiler(
$compiler,
$open_parser,
$close_parser = self::DEFAULT_CLOSE_COMPILER,
array $tags = array()
) {
$this->_actions[$compiler] = array(
'type' => self::BLOCK_COMPILER,
'open' => $open_parser,
'type' => self::BLOCK_COMPILER,
'open' => $open_parser,
'close' => $close_parser ? : self::DEFAULT_CLOSE_COMPILER,
'tags' => $tags,
'tags' => $tags,
);
return $this;
}
@ -425,8 +514,8 @@ class Fenom
public function addBlockCompilerSmart($compiler, $storage, array $tags, array $floats = array())
{
$c = array(
'type' => self::BLOCK_COMPILER,
"tags" => array(),
'type' => self::BLOCK_COMPILER,
"tags" => array(),
"float_tags" => array()
);
if (method_exists($storage, $compiler . "Open")) {
@ -462,8 +551,8 @@ class Fenom
public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER)
{
$this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION,
'parser' => $parser,
'type' => self::INLINE_FUNCTION,
'parser' => $parser,
'function' => $callback,
);
return $this;
@ -477,8 +566,8 @@ class Fenom
public function addFunctionSmart($function, $callback)
{
$this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION,
'parser' => self::SMART_FUNC_PARSER,
'type' => self::INLINE_FUNCTION,
'parser' => self::SMART_FUNC_PARSER,
'function' => $callback,
);
return $this;
@ -491,12 +580,16 @@ class Fenom
* @param callable|string $parser_close
* @return Fenom
*/
public function addBlockFunction($function, $callback, $parser_open = self::DEFAULT_FUNC_OPEN, $parser_close = self::DEFAULT_FUNC_CLOSE)
{
public function addBlockFunction(
$function,
$callback,
$parser_open = self::DEFAULT_FUNC_OPEN,
$parser_close = self::DEFAULT_FUNC_CLOSE
) {
$this->_actions[$function] = array(
'type' => self::BLOCK_FUNCTION,
'open' => $parser_open,
'close' => $parser_close,
'type' => self::BLOCK_FUNCTION,
'open' => $parser_open,
'close' => $parser_close,
'function' => $callback,
);
return $this;
@ -512,6 +605,26 @@ class Fenom
return $this;
}
/**
* Add custom test
* @param string $name test name
* @param string $code test PHP code. Code may contains placeholder %s, which will be replaced by test-value. For example: is_callable(%s)
*/
public function addTest($name, $code)
{
$this->_tests[$name] = $code;
}
/**
* Get test code by name
* @param string $name
* @return string|bool
*/
public function getTest($name)
{
return isset($this->_tests[$name]) ? $this->_tests[$name] : false;
}
/**
* Return modifier function
*
@ -531,6 +644,7 @@ class Fenom
}
/**
* Modifier autoloader
* @param string $modifier
* @param Fenom\Template $template
* @return bool
@ -557,7 +671,8 @@ class Fenom
}
/**
* @param $tag
* Tags autoloader
* @param string $tag
* @param Fenom\Template $template
* @return bool
*/
@ -599,11 +714,15 @@ class Fenom
*
* @param string $scm scheme name
* @param Fenom\ProviderInterface $provider provider object
* @param string $compile_path
* @return $this
*/
public function addProvider($scm, \Fenom\ProviderInterface $provider)
public function addProvider($scm, \Fenom\ProviderInterface $provider, $compile_path = null)
{
$this->_providers[$scm] = $provider;
if ($compile_path) {
$this->_compiles[$scm] = $compile_path;
}
return $this;
}
@ -683,17 +802,17 @@ class Fenom
}
/**
*
*
* @param string $template name of template
* Creates pipe-line of template's data to callback
* @note Method not works correctly in old PHP 5.3.*
* @param string $template name of the template
* @param callable $callback template's data handler
* @param array $vars
* @param callable $callback
* @param float $chunk
* @param float $chunk amount of bytes of chunk
* @return array
*/
public function pipe($template, $callback, array $vars = array(), $chunk = 1e6)
{
ob_start($callback, $chunk, true);
ob_start($callback, $chunk, PHP_OUTPUT_HANDLER_STDFLAGS);
$data = $this->getTemplate($template)->display($vars);
ob_end_flush();
return $data;
@ -709,7 +828,11 @@ class Fenom
public function getTemplate($template, $options = 0)
{
$options |= $this->_options;
$key = dechex($options) . "@" . $template;
if (is_array($template)) {
$key = dechex($options) . "@" . implode(",", $template);
} else {
$key = dechex($options) . "@" . $template;
}
if (isset($this->_storage[$key])) {
/** @var Fenom\Template $tpl */
$tpl = $this->_storage[$key];
@ -719,6 +842,7 @@ class Fenom
return $tpl;
}
} elseif ($this->_options & self::FORCE_COMPILE) {
return $this->compile($template, $this->_options & self::DISABLE_CACHE & ~self::FORCE_COMPILE, $options);
} else {
return $this->_storage[$key] = $this->_load($template, $options);
@ -745,22 +869,22 @@ class Fenom
/**
* Load template from cache or create cache if it doesn't exists.
*
* @param string $tpl
* @param string $template
* @param int $opts
* @return Fenom\Render
*/
protected function _load($tpl, $opts)
protected function _load($template, $opts)
{
$file_name = $this->_getCacheName($tpl, $opts);
$file_name = $this->_getCacheName($template, $opts);
if (is_file($this->_compile_dir . "/" . $file_name)) {
$fenom = $this; // used in template
$_tpl = include($this->_compile_dir . "/" . $file_name);
$_tpl = include($this->_compile_dir . "/" . $file_name);
/* @var Fenom\Render $_tpl */
if (!($this->_options & self::AUTO_RELOAD) || ($this->_options & self::AUTO_RELOAD) && $_tpl->isValid()) {
return $_tpl;
}
}
return $this->compile($tpl, true, $opts);
return $this->compile($template, true, $opts);
}
/**
@ -772,14 +896,22 @@ class Fenom
*/
private function _getCacheName($tpl, $options)
{
$hash = $tpl . ":" . $options;
return sprintf("%s.%x.%x.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash));
if (is_array($tpl)) {
$hash = implode(".", $tpl) . ":" . $options;
foreach ($tpl as &$t) {
$t = str_replace(":", "_", basename($t));
}
return implode("~", $tpl) . "." . sprintf("%x.%x.php", crc32($hash), strlen($hash));
} else {
$hash = $tpl . ":" . $options;
return sprintf("%s.%x.%x.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash));
}
}
/**
* Compile and save template
*
* @param string $tpl
* @param string|array $tpl
* @param bool $store store template on disk
* @param int $options
* @throws RuntimeException
@ -788,11 +920,19 @@ class Fenom
public function compile($tpl, $store = true, $options = 0)
{
$options = $this->_options | $options;
$template = $this->getRawTemplate()->load($tpl);
if (is_string($tpl)) {
$template = $this->getRawTemplate()->load($tpl);
} else {
$template = $this->getRawTemplate()->load($tpl[0], false);
unset($tpl[0]);
foreach ($tpl as $t) {
$template->extend($t);
}
}
if ($store) {
$cache = $this->_getCacheName($tpl, $options);
$cache = $this->_getCacheName($tpl, $options);
$tpl_tmp = tempnam($this->_compile_dir, $cache);
$tpl_fp = fopen($tpl_tmp, "w");
$tpl_fp = fopen($tpl_tmp, "w");
if (!$tpl_fp) {
throw new \RuntimeException("Can't to open temporary file $tpl_tmp. Directory " . $this->_compile_dir . " is writable?");
}
@ -859,4 +999,24 @@ class Fenom
}
return $mask;
}
/**
* Register PSR-0 autoload
* @param string $dir custom directory for autoloading, if NULL autoload itself
* @return bool
*/
public static function registerAutoload($dir = null)
{
if (!$dir) {
$dir = __DIR__;
}
return spl_autoload_register(
function ($classname) use ($dir) {
$file = $dir . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $classname) . '.php';
if (is_file($file)) {
require_once $file;
}
}
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
*/
namespace Fenom\Error;
use Fenom\Tokenizer;
/**

View File

@ -27,7 +27,9 @@ class Modifier
{
if (!is_numeric($date)) {
$date = strtotime($date);
if (!$date) $date = time();
if (!$date) {
$date = time();
}
}
return strftime($format, $date);
}
@ -41,7 +43,9 @@ class Modifier
{
if (!is_numeric($date)) {
$date = strtotime($date);
if (!$date) $date = time();
if (!$date) {
$date = time();
}
}
return date($format, $date);
}
@ -51,15 +55,18 @@ class Modifier
*
* @param string $text
* @param string $type
* @param string $charset
* @return string
*/
public static function escape($text, $type = 'html')
public static function escape($text, $type = 'html', $charset = 'UTF-8')
{
switch (strtolower($type)) {
case "url":
return urlencode($text);
case "html";
return htmlspecialchars($text, ENT_COMPAT, 'UTF-8');
return htmlspecialchars($text, ENT_COMPAT, $charset);
case "js":
return json_encode($text, 64 | 256); // JSON_UNESCAPED_SLASHES = 64, JSON_UNESCAPED_UNICODE = 256
default:
return $text;
}
@ -100,7 +107,11 @@ class Modifier
if (preg_match('#^(.{' . $length . '}).*?(.{' . $length . '})?$#usS', $string, $match)) {
if (count($match) == 3) {
if ($by_words) {
return preg_replace('#\s.*$#usS', "", $match[1]) . $etc . preg_replace('#.*\s#usS', "", $match[2]);
return preg_replace('#\s.*$#usS', "", $match[1]) . $etc . preg_replace(
'#.*\s#usS',
"",
$match[2]
);
} else {
return $match[1] . $etc . $match[2];
}
@ -129,7 +140,7 @@ class Modifier
{
$str = trim($str);
if ($to_line) {
return preg_replace('#[\s]+#ms', ' ', $str);
return preg_replace('#\s+#ms', ' ', $str);
} else {
return preg_replace('#[ \t]{2,}#', ' ', $str);
}
@ -147,7 +158,7 @@ class Modifier
} elseif (is_array($item)) {
return count($item);
} elseif ($item instanceof \Countable) {
return count($item);
return $item->count();
} else {
return 0;
}

View File

@ -84,7 +84,7 @@ class Provider implements ProviderInterface
public function getSource($tpl, &$time)
{
$tpl = $this->_getTemplatePath($tpl);
clearstatcache(null, $tpl);
clearstatcache(true, $tpl);
$time = filemtime($tpl);
return file_get_contents($tpl);
}
@ -96,7 +96,7 @@ class Provider implements ProviderInterface
*/
public function getLastModified($tpl)
{
clearstatcache(null, $tpl = $this->_getTemplatePath($tpl));
clearstatcache(true, $tpl = $this->_getTemplatePath($tpl));
return filemtime($tpl);
}
@ -108,7 +108,7 @@ class Provider implements ProviderInterface
*/
public function getList($extension = "tpl")
{
$list = array();
$list = array();
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->_path,
\FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS),
@ -132,6 +132,7 @@ class Provider implements ProviderInterface
*/
protected function _getTemplatePath($tpl)
{
if (($path = realpath($this->_path . "/" . $tpl)) && strpos($path, $this->_path) === 0) {
return $path;
} else {
@ -145,7 +146,8 @@ class Provider implements ProviderInterface
*/
public function templateExists($tpl)
{
return file_exists($this->_path . "/" . $tpl);
return ($path = realpath($this->_path . "/" . $tpl)) && strpos($path, $this->_path) === 0;
// return file_exists($this->_path . "/" . $tpl);
}
/**
@ -157,7 +159,7 @@ class Provider implements ProviderInterface
public function verify(array $templates)
{
foreach ($templates as $template => $mtime) {
clearstatcache(null, $template = $this->_path . '/' . $template);
clearstatcache(true, $template = $this->_path . '/' . $template);
if (@filemtime($template) !== $mtime) {
return false;
}

View File

@ -8,6 +8,7 @@
* file that was distributed with this source code.
*/
namespace Fenom;
use Fenom;
/**
@ -17,12 +18,12 @@ use Fenom;
class Render extends \ArrayObject
{
private static $_props = array(
"name" => "runtime",
"name" => "runtime",
"base_name" => "",
"scm" => false,
"time" => 0,
"depends" => array(),
"macros" => array()
"scm" => false,
"time" => 0,
"depends" => array(),
"macros" => array()
);
/**
* @var \Closure
@ -83,12 +84,13 @@ class Render extends \ArrayObject
{
$this->_fenom = $fenom;
$props += self::$_props;
$this->_name = $props["name"];
$this->_name = $props["name"];
$this->_base_name = $props["base_name"];
$this->_scm = $props["scm"];
$this->_time = $props["time"];
$this->_depends = $props["depends"];
$this->_macros = $props["macros"];
$this->_scm = $props["scm"];
$this->_time = $props["time"];
$this->_depends = $props["depends"];
$this->_macros = $props["macros"];
// $this->_blocks = $props["blocks"];
$this->_code = $code;
}
@ -193,14 +195,14 @@ class Render extends \ArrayObject
/**
* Get internal macro
* @param string $name
* @param $name
* @throws \RuntimeException
* @return array
* @return mixed
*/
public function getMacro($name)
{
if (empty($this->_macros[$name])) {
throw new \RuntimeException('macro '.$name.' not found');
throw new \RuntimeException('macro ' . $name . ' not found');
}
return $this->_macros[$name];
}
@ -212,9 +214,8 @@ class Render extends \ArrayObject
*/
public function display(array $values)
{
$this->exchangeArray($values);
$this->_code->__invoke($this);
return $this->exchangeArray(array());
$this->_code->__invoke($values, $this);
return $values;
}
/**
@ -250,9 +251,9 @@ class Render extends \ArrayObject
{
if ($name == 'info') {
return array(
'name' => $this->_name,
'name' => $this->_name,
'schema' => $this->_scm,
'time' => $this->_time
'time' => $this->_time
);
} else {
return null;

View File

@ -1,158 +0,0 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom;
/**
* Scope for blocks tags
*
* @author Ivan Shalganov <a.cobest@gmail.com>
*/
class Scope extends \ArrayObject
{
public $line = 0;
public $name;
public $level = 0;
/**
* @var Template
*/
public $tpl;
public $is_compiler = true;
public $is_closed = false;
public $escape = false;
private $_action;
private $_body;
private $_offset;
/**
* Creating cope
*
* @param string $name
* @param Template $tpl
* @param int $line
* @param array $action
* @param int $level
* @param $body
*/
public function __construct($name, $tpl, $line, $action, $level, &$body)
{
$this->line = $line;
$this->name = $name;
$this->tpl = $tpl;
$this->_action = $action;
$this->level = $level;
$this->_body = & $body;
$this->_offset = strlen($body);
}
/**
*
* @param string $function
*/
public function setFuncName($function)
{
$this["function"] = $function;
$this->is_compiler = false;
$this->escape = $this->tpl->escape;
}
/**
* Open callback
*
* @param Tokenizer $tokenizer
* @return mixed
*/
public function open($tokenizer)
{
return call_user_func($this->_action["open"], $tokenizer, $this);
}
/**
* Check, has the block this tag
*
* @param string $tag
* @param int $level
* @return bool
*/
public function hasTag($tag, $level)
{
if (isset($this->_action["tags"][$tag])) {
if ($level) {
return isset($this->_action["float_tags"][$tag]);
} else {
return true;
}
}
return false;
}
/**
* Call tag callback
*
* @param string $tag
* @param Tokenizer $tokenizer
* @return string
*/
public function tag($tag, $tokenizer)
{
return call_user_func($this->_action["tags"][$tag], $tokenizer, $this);
}
/**
* Close callback
*
* @param Tokenizer $tokenizer
* @return string
*/
public function close($tokenizer)
{
return call_user_func($this->_action["close"], $tokenizer, $this);
}
/**
* Return content of block
*
* @throws \LogicException
* @return string
*/
public function getContent()
{
return substr($this->_body, $this->_offset);
}
/**
* Cut scope content
*
* @return string
* @throws \LogicException
*/
public function cutContent()
{
$content = substr($this->_body, $this->_offset + 1);
$this->_body = substr($this->_body, 0, $this->_offset);
return $content;
}
/**
* Replace scope content
*
* @param $new_content
*/
public function replaceContent($new_content)
{
$this->cutContent();
$this->_body .= $new_content;
}
public function unEscapeContent()
{
}
}

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

@ -0,0 +1,299 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom;
class Tag extends \ArrayObject
{
const COMPILER = 1;
const FUNC = 2;
const BLOCK = 4;
const LTRIM = 1;
const RTRIM = 2;
/**
* @var Template
*/
public $tpl;
public $name;
public $options = array();
public $line = 0;
public $level = 0;
public $callback;
public $escape;
private $_offset = 0;
private $_closed = true;
private $_body;
private $_type = 0;
private $_open;
private $_close;
private $_tags = array();
private $_floats = array();
private $_changed = array();
/**
* Create tag entity
* @param string $name the tag name
* @param Template $tpl current template
* @param string $info tag's information
* @param string $body template's code
*/
public function __construct($name, Template $tpl, $info, &$body)
{
$this->tpl = $tpl;
$this->name = $name;
$this->line = $tpl->getLine();
$this->level = $tpl->getStackSize();
$this->_body = & $body;
$this->_offset = strlen($body);
$this->_type = $info["type"];
$this->escape = $tpl->getOptions() & \Fenom::AUTO_ESCAPE;
if ($this->_type & self::BLOCK) {
$this->_open = $info["open"];
$this->_close = $info["close"];
$this->_tags = isset($info["tags"]) ? $info["tags"] : array();
$this->_floats = isset($info["float_tags"]) ? $info["float_tags"] : array();
$this->_closed = false;
} else {
$this->_open = $info["parser"];
}
if ($this->_type & self::FUNC) {
$this->callback = $info["function"];
}
}
/**
* Set tag option
* @param string $option
* @throws \RuntimeException
*/
public function tagOption($option)
{
if (method_exists($this, 'opt' . $option)) {
$this->options[] = $option;
} else {
throw new \RuntimeException("Unknown tag option $option");
}
}
/**
* Rewrite template option for tag. When tag will be closed option will be reverted.
* @param int $option option constant
* @param bool $value true add option, false remove option
*/
public function setOption($option, $value)
{
$actual = (bool)($this->tpl->getOptions() & $option);
if ($actual != $value) {
$this->_changed[$option] = $actual;
$this->tpl->setOption(\Fenom::AUTO_ESCAPE, $value);
}
}
/**
* Restore the option
* @param int $option
*/
public function restore($option)
{
if (isset($this->_changed[$option])) {
$this->tpl->setOption($option, $this->_changed[$option]);
unset($this->_changed[$option]);
}
}
public function restoreAll()
{
foreach ($this->_changed as $option => $value) {
$this->tpl->setOption($option, $this->_changed[$option]);
unset($this->_changed[$option]);
}
}
/**
* Check, if the tag closed
* @return bool
*/
public function isClosed()
{
return $this->_closed;
}
/**
* Open callback
*
* @param Tokenizer $tokenizer
* @return mixed
*/
public function start($tokenizer)
{
foreach ($this->options as $option) {
$option = 'opt' . $option;
$this->$option();
}
return call_user_func($this->_open, $tokenizer, $this);
}
/**
* Check, has the block this tag
*
* @param string $tag
* @param int $level
* @return bool
*/
public function hasTag($tag, $level)
{
if (isset($this->_tags[$tag])) {
if ($level) {
return isset($this->_floats[$tag]);
} else {
return true;
}
}
return false;
}
/**
* Call tag callback
*
* @param string $tag
* @param Tokenizer $tokenizer
* @throws \LogicException
* @return string
*/
public function tag($tag, $tokenizer)
{
if (isset($this->_tags[$tag])) {
return call_user_func($this->_tags[$tag], $tokenizer, $this);
} else {
throw new \LogicException("The block tag {$this->name} no have tag {$tag}");
}
}
/**
* Close callback
*
* @param Tokenizer $tokenizer
* @throws \LogicException
* @return string
*/
public function end($tokenizer)
{
if ($this->_closed) {
throw new \LogicException("Tag {$this->name} already closed");
}
if ($this->_close) {
foreach ($this->options as $option) {
$option = 'opt' . $option . 'end';
if (method_exists($this, $option)) {
$this->$option();
}
}
$code = call_user_func($this->_close, $tokenizer, $this);
$this->restoreAll();
return $code;
} else {
throw new \LogicException("Can not use a inline tag {$this->name} as a block");
}
}
/**
* Forcefully close the tag
*/
public function close()
{
$this->_closed = true;
}
/**
* Return content of block
*
* @throws \LogicException
* @return string
*/
public function getContent()
{
return substr($this->_body, $this->_offset);
}
/**
* Cut scope content
*
* @return string
* @throws \LogicException
*/
public function cutContent()
{
$content = substr($this->_body, $this->_offset + 1);
$this->_body = substr($this->_body, 0, $this->_offset);
return $content;
}
/**
* Replace scope content
*
* @param $new_content
*/
public function replaceContent($new_content)
{
$this->cutContent();
$this->_body .= $new_content;
}
/**
* Generate output code
* @param string $code
* @return string
*/
public function out($code)
{
return $this->tpl->out($code, $this->escape);
}
/**
* Enable escape option for the tag
*/
public function optEscape()
{
$this->escape = true;
}
/**
* Disable escape option for the tag
*/
public function optRaw()
{
$this->escape = false;
}
/**
* Enable strip spaces option for the tag
*/
public function optStrip()
{
$this->setOption(\Fenom::AUTO_STRIP, true);
}
/**
* Enable ignore for body of the tag
*/
public function optIgnore()
{
if(!$this->isClosed()) {
$this->tpl->ignore($this->name);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -39,10 +39,10 @@ defined('T_YIELD') || define('T_YIELD', 267);
*/
class Tokenizer
{
const TOKEN = 0;
const TEXT = 1;
const TOKEN = 0;
const TEXT = 1;
const WHITESPACE = 2;
const LINE = 3;
const LINE = 3;
/**
* Some text value: foo, bar, new, class ...
@ -92,64 +92,174 @@ class Tokenizer
* @var array groups of tokens
*/
public static $macros = array(
self::MACRO_STRING => array(
\T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1, \T_BREAK => 1, \T_CASE => 1,
\T_CATCH => 1, \T_CLASS => 1, \T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
\T_DECLARE => 1, \T_DEFAULT => 1, \T_DIR => 1, \T_DO => 1, \T_ECHO => 1, \T_ELSE => 1,
\T_ELSEIF => 1, \T_EMPTY => 1, \T_ENDDECLARE => 1, \T_ENDFOR => 1, \T_ENDFOREACH => 1, \T_ENDIF => 1,
\T_ENDSWITCH => 1, \T_ENDWHILE => 1, \T_EVAL => 1, \T_EXIT => 1, \T_EXTENDS => 1, \T_FILE => 1,
\T_FINAL => 1, \T_FOR => 1, \T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
\T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1, \T_INCLUDE => 1, \T_INCLUDE_ONCE => 1,
\T_INSTANCEOF => 1, \T_INSTEADOF => 1, \T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
\T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1, \T_NAMESPACE => 1, \T_NS_C => 1,
\T_NEW => 1, \T_PRINT => 1, \T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
\T_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1, \T_SWITCH => 1, \T_THROW => 1,
\T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_USE => 1, \T_VAR => 1,
\T_WHILE => 1, \T_YIELD => 1
self::MACRO_STRING => array(
\T_ABSTRACT => 1,
\T_ARRAY => 1,
\T_AS => 1,
\T_BREAK => 1,
\T_BREAK => 1,
\T_CASE => 1,
\T_CATCH => 1,
\T_CLASS => 1,
\T_CLASS_C => 1,
\T_CLONE => 1,
\T_CONST => 1,
\T_CONTINUE => 1,
\T_DECLARE => 1,
\T_DEFAULT => 1,
\T_DIR => 1,
\T_DO => 1,
\T_ECHO => 1,
\T_ELSE => 1,
\T_ELSEIF => 1,
\T_EMPTY => 1,
\T_ENDDECLARE => 1,
\T_ENDFOR => 1,
\T_ENDFOREACH => 1,
\T_ENDIF => 1,
\T_ENDSWITCH => 1,
\T_ENDWHILE => 1,
\T_EVAL => 1,
\T_EXIT => 1,
\T_EXTENDS => 1,
\T_FILE => 1,
\T_FINAL => 1,
\T_FOR => 1,
\T_FOREACH => 1,
\T_FUNCTION => 1,
\T_FUNC_C => 1,
\T_GLOBAL => 1,
\T_GOTO => 1,
\T_HALT_COMPILER => 1,
\T_IF => 1,
\T_IMPLEMENTS => 1,
\T_INCLUDE => 1,
\T_INCLUDE_ONCE => 1,
\T_INSTANCEOF => 1,
\T_INSTEADOF => 1,
\T_INTERFACE => 1,
\T_ISSET => 1,
\T_LINE => 1,
\T_LIST => 1,
\T_LOGICAL_AND => 1,
\T_LOGICAL_OR => 1,
\T_LOGICAL_XOR => 1,
\T_METHOD_C => 1,
\T_NAMESPACE => 1,
\T_NS_C => 1,
\T_NEW => 1,
\T_PRINT => 1,
\T_PRIVATE => 1,
\T_PUBLIC => 1,
\T_PROTECTED => 1,
\T_REQUIRE => 1,
\T_REQUIRE_ONCE => 1,
\T_RETURN => 1,
\T_RETURN => 1,
\T_STRING => 1,
\T_SWITCH => 1,
\T_THROW => 1,
\T_TRAIT => 1,
\T_TRAIT_C => 1,
\T_TRY => 1,
\T_UNSET => 1,
\T_USE => 1,
\T_VAR => 1,
\T_WHILE => 1,
\T_YIELD => 1
),
self::MACRO_INCDEC => array(
\T_INC => 1, \T_DEC => 1
self::MACRO_INCDEC => array(
\T_INC => 1,
\T_DEC => 1
),
self::MACRO_UNARY => array(
"!" => 1, "~" => 1, "-" => 1
self::MACRO_UNARY => array(
"!" => 1,
"~" => 1,
"-" => 1
),
self::MACRO_BINARY => array(
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_IS_GREATER_OR_EQUAL => 1, \T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1,
\T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1, \T_SR => 1,
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
self::MACRO_BINARY => array(
\T_BOOLEAN_AND => 1,
\T_BOOLEAN_OR => 1,
\T_IS_GREATER_OR_EQUAL => 1,
\T_IS_EQUAL => 1,
\T_IS_IDENTICAL => 1,
\T_IS_NOT_EQUAL => 1,
\T_IS_NOT_IDENTICAL => 1,
\T_IS_SMALLER_OR_EQUAL => 1,
\T_LOGICAL_AND => 1,
\T_LOGICAL_OR => 1,
\T_LOGICAL_XOR => 1,
\T_SL => 1,
\T_SR => 1,
"+" => 1,
"-" => 1,
"*" => 1,
"/" => 1,
">" => 1,
"<" => 1,
"^" => 1,
"%" => 1,
"&" => 1
),
self::MACRO_BOOLEAN => array(
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_LOGICAL_AND => 1
\T_LOGICAL_OR => 1,
\T_LOGICAL_XOR => 1,
\T_BOOLEAN_AND => 1,
\T_BOOLEAN_OR => 1,
\T_LOGICAL_AND => 1
),
self::MACRO_MATH => array(
"+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1
self::MACRO_MATH => array(
"+" => 1,
"-" => 1,
"*" => 1,
"/" => 1,
"^" => 1,
"%" => 1,
"&" => 1,
"|" => 1
),
self::MACRO_COND => array(
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1, "<" => 1, \T_SL => 1, \T_SR => 1,
\T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
self::MACRO_COND => array(
\T_IS_EQUAL => 1,
\T_IS_IDENTICAL => 1,
">" => 1,
"<" => 1,
\T_SL => 1,
\T_SR => 1,
\T_IS_NOT_EQUAL => 1,
\T_IS_NOT_IDENTICAL => 1,
\T_IS_SMALLER_OR_EQUAL => 1,
),
self::MACRO_EQUALS => array(
\T_AND_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1, \T_MOD_EQUAL => 1,
\T_MUL_EQUAL => 1, \T_OR_EQUAL => 1, \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1, '=' => 1,
self::MACRO_EQUALS => array(
\T_AND_EQUAL => 1,
\T_DIV_EQUAL => 1,
\T_MINUS_EQUAL => 1,
\T_MOD_EQUAL => 1,
\T_MUL_EQUAL => 1,
\T_OR_EQUAL => 1,
\T_PLUS_EQUAL => 1,
\T_SL_EQUAL => 1,
\T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1,
'=' => 1,
// \T_CONCAT_EQUAL => 1,
),
self::MACRO_SCALAR => array(
\T_LNUMBER => 1, \T_DNUMBER => 1, \T_CONSTANT_ENCAPSED_STRING => 1
self::MACRO_SCALAR => array(
\T_LNUMBER => 1,
\T_DNUMBER => 1,
\T_CONSTANT_ENCAPSED_STRING => 1
)
);
public static $description = array(
self::MACRO_STRING => 'string',
self::MACRO_INCDEC => 'increment/decrement operator',
self::MACRO_UNARY => 'unary operator',
self::MACRO_BINARY => 'binary operator',
self::MACRO_STRING => 'string',
self::MACRO_INCDEC => 'increment/decrement operator',
self::MACRO_UNARY => 'unary operator',
self::MACRO_BINARY => 'binary operator',
self::MACRO_BOOLEAN => 'boolean operator',
self::MACRO_MATH => 'math operator',
self::MACRO_COND => 'conditional operator',
self::MACRO_EQUALS => 'equal operator',
self::MACRO_SCALAR => 'scalar value'
self::MACRO_MATH => 'math operator',
self::MACRO_COND => 'conditional operator',
self::MACRO_EQUALS => 'equal operator',
self::MACRO_SCALAR => 'scalar value'
);
/**
@ -157,7 +267,12 @@ class Tokenizer
* @var array
*/
private static $spec = array(
'true' => 1, 'false' => 1, 'null' => 1, 'TRUE' => 1, 'FALSE' => 1, 'NULL' => 1
'true' => 1,
'false' => 1,
'null' => 1,
'TRUE' => 1,
'FALSE' => 1,
'NULL' => 1
);
/**
@ -165,9 +280,9 @@ class Tokenizer
*/
public function __construct($query)
{
$tokens = array(-1 => array(\T_WHITESPACE, '', '', 1));
$tokens = array(-1 => array(\T_WHITESPACE, '', '', 1));
$_tokens = token_get_all("<?php " . $query);
$line = 1;
$line = 1;
array_shift($_tokens);
$i = 0;
foreach ($_tokens as $token) {
@ -197,8 +312,8 @@ class Tokenizer
}
unset($tokens[-1]);
$this->tokens = $tokens;
$this->_max = count($this->tokens) - 1;
$this->tokens = $tokens;
$this->_max = count($this->tokens) - 1;
$this->_last_no = $this->tokens[$this->_max][3];
}
@ -387,7 +502,7 @@ class Tokenizer
public function hasBackList($token1 /*, $token2 ...*/)
{
$tokens = func_get_args();
$c = $this->p;
$c = $this->p;
foreach ($tokens as $token) {
$c--;
if ($c < 0 || $this->tokens[$c][0] !== $token) {
@ -520,7 +635,7 @@ class Tokenizer
public function getSnippet($before = 0, $after = 0)
{
$from = 0;
$to = $this->p;
$to = $this->p;
if ($before > 0) {
if ($before > $this->p) {
$from = $this->p;
@ -593,6 +708,8 @@ class Tokenizer
public function end()
{
$this->p = $this->_max;
unset($this->prev, $this->curr, $this->next);
return $this;
}
/**
@ -617,4 +734,16 @@ class Tokenizer
{
return $this->curr ? $this->curr[2] : false;
}
/**
* Seek to custom element
* @param int $p
* @return $this
*/
public function seek($p)
{
$this->p = $p;
unset($this->prev, $this->curr, $this->next);
return $this;
}
}

View File

@ -1,47 +1,44 @@
<?php
namespace Fenom;
use Fenom, Fenom\Provider as FS;
class TestCase extends \PHPUnit_Framework_TestCase
{
public $template_path = 'template';
/**
* @var Fenom
*/
public $fenom;
public $values = array(
"zero" => 0,
"one" => 1,
"two" => 2,
"three" => 3,
"float" => 4.5,
"bool" => true,
0 => "empty value",
1 => "one value",
2 => "two value",
3 => "three value",
);
public $values;
public static function getVars()
{
return array(
"zero" => 0,
"one" => 1,
"two" => 2,
"zero" => 0,
"one" => 1,
"two" => 2,
"three" => 3,
"float" => 4.5,
"bool" => true,
"obj" => new \StdClass,
"list" => array(
"a" => 1,
"bool" => true,
"obj" => new \StdClass,
"list" => array(
"a" => 1,
"one" => 1,
"b" => 2,
"b" => 2,
"two" => 2
),
0 => "empty value",
1 => "one value",
2 => "two value",
3 => "three value",
"num" => array(
1 => "one",
2 => "two",
3 => "three",
4 => "four"
),
0 => "empty value",
1 => "one value",
2 => "two value",
3 => "three value",
);
}
@ -53,12 +50,14 @@ class TestCase extends \PHPUnit_Framework_TestCase
FS::clean(FENOM_RESOURCES . '/compile/');
}
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/template', FENOM_RESOURCES . '/compile');
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/' . $this->template_path, FENOM_RESOURCES . '/compile');
$this->fenom->addProvider('persist', new Provider(FENOM_RESOURCES . '/provider'));
$this->fenom->addModifier('dots', __CLASS__ . '::dots');
$this->fenom->addModifier('concat', __CLASS__ . '::concat');
$this->fenom->addModifier('append', __CLASS__ . '::append');
$this->fenom->addFunction('test_function', __CLASS__ . '::inlineFunction');
$this->fenom->addBlockFunction('test_block_function', __CLASS__ . '::blockFunction');
$this->values = $this->getVars();
}
public static function dots($value)
@ -121,9 +120,10 @@ class TestCase extends \PHPUnit_Framework_TestCase
$this->fenom->setOptions($options);
$tpl = $this->fenom->compileCode($code, "runtime.tpl");
if ($dump) {
echo "\n========= DUMP BEGIN ===========\n" . $code . "\n--- to ---\n" . $tpl->getBody() . "\n========= DUMP END =============\n";
echo "\n========= DUMP BEGIN ===========\n" . $code . "\n--- to ---\n" . $tpl->getBody(
) . "\n========= DUMP END =============\n";
}
$this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code");
$this->assertSame(Modifier::strip($result, true), Modifier::strip($tpl->fetch($vars), true), "Test $code");
return $tpl;
}
@ -132,7 +132,8 @@ class TestCase extends \PHPUnit_Framework_TestCase
$this->tpl($name, $code);
$tpl = $this->fenom->getTemplate($name);
if ($dump) {
echo "\n========= DUMP BEGIN ===========\n" . $code . "\n--- to ---\n" . $tpl->getBody() . "\n========= DUMP END =============\n";
echo "\n========= DUMP BEGIN ===========\n" . $code . "\n--- to ---\n" . $tpl->getBody(
) . "\n========= DUMP END =============\n";
}
$this->assertSame(Modifier::strip($result, true), Modifier::strip($tpl->fetch($vars), true), "Test tpl $name");
}
@ -232,7 +233,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
public static function providerArrays()
{
$scalars = array();
$data = array(
$data = array(
array('[]', array()),
array('[[],[]]', array(array(), array())),
);
@ -275,20 +276,24 @@ class TestCase extends \PHPUnit_Framework_TestCase
}
}
class Helper {
class Helper
{
public $word = 'helper';
public function __construct($word) {
public function __construct($word)
{
$this->word = $word;
$this->self = $this;
}
public function chunk() {
public function chunk()
{
return $this;
}
public function __toString() {
public function __toString()
{
return $this->word;
}
}

View File

@ -1,9 +1,7 @@
<?php
$loader = include(__DIR__ . "/../vendor/autoload.php");
/* @var Composer\Autoload\ClassLoader $loader */
$loader->add('Fenom', __DIR__.'/cases');
require(__DIR__ . "/../src/Fenom.php");
Fenom::registerAutoload();
define('FENOM_RESOURCES', __DIR__ . "/resources");
@ -26,4 +24,18 @@ function dump()
fwrite(STDERR, "DUMP: " . call_user_func("print_r", $arg, true) . "\n");
}
}
function dumpt()
{
foreach (func_get_args() as $arg) {
fwrite(STDERR, "DUMP: " . call_user_func("print_r", $arg, true) . "\n");
}
$e = new Exception();
echo "-------\nDump trace: \n" . $e->getTraceAsString() . "\n";
}
if(PHP_VERSION_ID > 50400) {
function php_gte_54() {}
}

View File

@ -6,6 +6,7 @@ namespace Fenom;
class AutoEscapeTest extends TestCase
{
public static function providerHTML()
{
$html = "<script>alert('injection');</script>";
@ -18,7 +19,7 @@ class AutoEscapeTest extends TestCase
array('{$html}, {$html}', "$html, $html", $vars, 0),
array('{$html}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw $html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw $html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw $html}, {$html}', "$html, $html", $vars, 0),
array('{raw "{$html|up}"}, {$html}', strtoupper($html) . ", $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{$html}{/autoescape}, {$html}', "$escaped, $html", $vars, 0),
array('{autoescape false}{$html}{/autoescape}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
@ -28,30 +29,122 @@ class AutoEscapeTest extends TestCase
array('{autoescape false}{raw $html}{/autoescape}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{raw $html}{/autoescape}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape false}{raw $html}{/autoescape}, {$html}', "$html, $html", $vars, 0),
// inline function
array('{test_function text=$html}, {$html}', "$html, $html", $vars, 0),
array('{test_function text=$html}, {$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw:test_function text=$html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{raw:test_function text="{$html|up}"}, {$html}', strtoupper($html) . ", $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$escaped, $html", $vars, 0),
array('{autoescape false}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape false}{test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $html", $vars, 0),
array('{autoescape true}{raw:test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $html", $vars, 0),
array('{autoescape false}{raw:test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape true}{raw:test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array('{autoescape false}{raw:test_function text=$html}{/autoescape}, {test_function text=$html}', "$html, $html", $vars, 0),
// block function. Have bugs
// array('{test_block_function}{$html}{/test_block_function}', $html, $vars, 0),
// array('{test_block_function}{$html}{/test_block_function}', $escaped, $vars, \Fenom::AUTO_ESCAPE),
// array('{raw:test_block_function}{$html}{/test_block_function}', $html, $vars, \Fenom::AUTO_ESCAPE),
// array('{raw:test_block_function}{"{$html|up}"}{/test_block_function}', strtoupper($html), $vars, \Fenom::AUTO_ESCAPE),
// array('{autoescape true}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}', "$escaped, $html", $vars, 0),
// array('{autoescape false}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
// array('{autoescape true}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}', "$escaped, $escaped", $vars, \Fenom::AUTO_ESCAPE),
// array('{autoescape false}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}', "$html, $html", $vars, 0),
array('{test_function:raw text=$html}, {$html}', "$html, $escaped", $vars, \Fenom::AUTO_ESCAPE),
array(
'{test_function:raw text="{$html|up}"}, {$html}',
strtoupper($html) . ", $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}',
"$escaped, $html",
$vars,
0
),
array(
'{autoescape false}{test_function text=$html}{/autoescape}, {test_function text=$html}',
"$html, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_function text=$html}{/autoescape}, {test_function text=$html}',
"$escaped, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape false}{test_function text=$html}{/autoescape}, {test_function text=$html}',
"$html, $html",
$vars,
0
),
array(
'{autoescape true}{test_function:raw text=$html}{/autoescape}, {test_function text=$html}',
"$html, $html",
$vars,
0
),
array(
'{autoescape false}{test_function:raw text=$html}{/autoescape}, {test_function text=$html}',
"$html, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_function:raw text=$html}{/autoescape}, {test_function text=$html}',
"$html, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape false}{test_function:raw text=$html}{/autoescape}, {test_function text=$html}',
"$html, $html",
$vars,
0
),
// block function
array('{test_block_function}{$html}{/test_block_function}', $html, $vars, 0),
array('{test_block_function}{$html}{/test_block_function}', $escaped, $vars, \Fenom::AUTO_ESCAPE),
array('{test_block_function:raw}{$html}{/test_block_function}', $html, $vars, \Fenom::AUTO_ESCAPE),
array(
'{test_block_function:raw}{"{$html|up}"}{/test_block_function}',
strtoupper($html),
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}',
"$escaped, $html",
$vars,
0
),
array(
'{autoescape false}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}',
"$html, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}',
"$escaped, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape false}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}',
"$html, $html",
$vars,
0
),
array(
'{autoescape true}{test_block_function:raw}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}',
"$html, $html",
$vars,
0
),
array(
'{autoescape false}{test_block_function:raw}{$html}{/test_block_function}{/autoescape}, {test_block_function}{$html}{/test_block_function}',
"$html, $escaped",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_block_function}{$html}{/test_block_function}{/autoescape}, {test_block_function:raw}{$html}{/test_block_function}',
"$escaped, $html",
$vars,
\Fenom::AUTO_ESCAPE
),
array(
'{autoescape true}{test_block_function:raw}{$html}{/test_block_function}{/autoescape}, {test_block_function:raw}{$html}{/test_block_function}',
"$html, $html",
$vars,
0
),
);
}

View File

@ -1,157 +0,0 @@
<?php
namespace Fenom;
use Fenom, Fenom\TestCase;
class ExtendsTemplateTest extends TestCase
{
public function _testSandbox()
{
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/provider', FENOM_RESOURCES . '/compile');
try {
print_r($this->fenom->getTemplate('use/child.tpl')->getBody());
} catch (\Exception $e) {
echo "$e";
}
exit;
}
/**
* Templates skeletons
* @param array $vars
* @return array
*/
public static function templates(array $vars)
{
return array(
array(
"name" => "level.0.tpl",
"level" => 0,
"blocks" => array(
"b1" => "default {\$default}",
"b2" => "empty 0"
),
"result" => array(
"b1" => "default " . $vars["default"],
"b2" => "empty 0"
),
),
array(
"name" => "level.1.tpl",
"level" => 1,
"use" => false,
"blocks" => array(
"b1" => "from level 1"
),
"result" => array(
"b1" => "from level 1",
"b2" => "empty 0"
),
),
array(
"name" => "level.2.tpl",
"level" => 2,
"use" => false,
"blocks" => array(
"b2" => "from level 2",
"b4" => "unused block"
),
"result" => array(
"b1" => "from level 1",
"b2" => "from level 2"
),
),
array(
"name" => "level.3.tpl",
"level" => 3,
"use" => false,
"blocks" => array(
"b1" => "from level 3",
"b2" => "also from level 3"
),
"result" => array(
"b1" => "from level 3",
"b2" => "also from level 3"
),
)
);
}
/**
* Generate templates by skeletons
*
* @param $block_mask
* @param $extend_mask
* @param array $skels
* @return array
*/
public static function generate($block_mask, $extend_mask, $skels)
{
$t = array();
foreach ($skels as $level => $tpl) {
$src = 'level#' . $level . ' ';
foreach ($tpl["blocks"] as $bname => &$bcode) {
$src .= sprintf($block_mask, $bname, $bname . ': ' . $bcode) . " level#$level ";
}
$dst = "level#0 ";
foreach ($tpl["result"] as $bname => &$bcode) {
$dst .= $bname . ': ' . $bcode . ' level#0 ';
}
if ($level) {
$src = sprintf($extend_mask, $level - 1) . ' ' . $src;
}
$t[$tpl["name"]] = array("src" => $src, "dst" => $dst);
}
return $t;
}
public function _testTemplateExtends()
{
$vars = array(
"b1" => "b1",
"b2" => "b2",
"b3" => "b3",
"b4" => "b4",
"level" => "level",
"default" => 5
);
$tpls = self::generate('{block "%s"}%s{/block}', '{extends "level.%d.tpl"}', self::templates($vars));
foreach ($tpls as $name => $tpl) {
$this->tpl($name, $tpl["src"]);
$this->assertSame($this->fenom->fetch($name, $vars), $tpl["dst"]);
}
return;
$vars["default"]++;
$this->fenom->flush();
$tpls = self::generate('{block "{$%s}"}%s{/block}', '{extends "level.%d.tpl"}', self::templates($vars));
arsort($tpls);
foreach ($tpls as $name => $tpl) {
$this->tpl("d." . $name, $tpl["src"]);
$this->assertSame($this->fenom->fetch("d." . $name, $vars), $tpl["dst"]);
}
$vars["default"]++;
$this->fenom->flush();
$tpls = self::generate('{block "%s"}%s{/block}', '{extends "$level.%d.tpl"}', self::templates($vars));
arsort($tpls);
foreach ($tpls as $name => $tpl) {
$this->tpl("x." . $name, $tpl["src"]);
$this->assertSame($this->fenom->fetch("x." . $name, $vars), $tpl["dst"]);
}
}
/**
* @group use
*/
public function testUse()
{
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/provider', FENOM_RESOURCES . '/compile');
$this->assertSame("<html>\n block 1 blocks \n block 2 child \n</html>", $this->fenom->fetch('use/child.tpl'));
}
public function _testParent()
{
}
}

View File

@ -0,0 +1,180 @@
<?php
namespace Fenom;
use Fenom, Fenom\TestCase;
class ExtendsTest extends TestCase
{
public $template_path = 'provider';
public function _testSandbox()
{
try {
var_dump($this->fenom->getTemplate('extends/dynamic/child.3.tpl')->getBody());
} catch (\Exception $e) {
echo "$e";
}
exit;
}
public static function providerExtendsInvalid()
{
return array(
array(
'{extends "extends/dynamic/child.3.tpl"} {extends "extends/dynamic/child.3.tpl"}',
'Fenom\Error\CompileException',
"Only one {extends} allowed"
),
array(
'{if true}{extends "extends/dynamic/child.3.tpl"}{/if}',
'Fenom\Error\CompileException',
"Tag {extends} can not be nested"
),
array(
'{if true}{use "extends/dynamic/use.tpl"}{/if}',
'Fenom\Error\CompileException',
"Tag {use} can not be nested"
),
array('{use $use_this}', 'Fenom\Error\CompileException', "Invalid template name for tag {use}"),
array('{block $use_this}{/block}', 'Fenom\Error\CompileException', "Invalid block name"),
);
}
public function testAutoExtendsManual()
{
$child = $this->fenom->getRawTemplate()->load('extends/auto/child.1.tpl', false);
$child->extend('extends/auto/parent.tpl');
$child->compile();
$result = "Before header
Content of the header
Before body
Child 1 Body
Before footer
Content of the footer";
$this->assertSame($result, $child->fetch(array()));
}
/**
* @group testAutoExtends
*/
public function testAutoExtends()
{
$result = "Before header
Child 2 header
Before body
Child 3 content
Before footer
Footer from use";
$this->assertSame(
$result,
$this->fenom->fetch(
array(
'extends/auto/child.3.tpl',
'extends/auto/child.2.tpl',
'extends/auto/child.1.tpl',
'extends/auto/parent.tpl',
),
array()
)
);
}
public function testStaticExtendLevel1()
{
$result = "Before header
Content of the header
Before body
Child 1 Body
Before footer
Content of the footer";
$this->assertSame($result, $this->fenom->fetch('extends/static/child.1.tpl', array()));
}
public function testStaticExtendLevel3()
{
$result = "Before header
Child 2 header
Before body
Child 3 content
Before footer
Footer from use";
$this->assertSame($result, $this->fenom->fetch('extends/static/child.3.tpl', array()));
}
public function testAutoAndStaticExtend()
{
$result = "Before header
Child 2 header
Before body
Child 3 content
Before footer
Footer from use";
$this->assertSame(
$result,
$this->fenom->fetch(
array(
'extends/auto/child.3.tpl',
'extends/auto/child.2.tpl',
'extends/auto/static/child.1.tpl'
),
array()
)
);
}
public function testStaticExtendNested()
{
$result = "Before body
Before header
Child 1: Content of the header
Before footer
Content of the footer
";
$this->assertSame($result, $this->fenom->fetch('extends/static/nested/child.1.tpl', array()));
}
public function testDynamicExtendLevel2()
{
$result = "Before header
Child 2 header
Before body
Child 1 Body
Before footer
Footer from use";
$this->assertSame($result, $this->fenom->fetch('extends/dynamic/child.2.tpl', array()));
}
public function testDynamicExtendLevel3()
{
$result = "Before header
Child 2 header
Before body
Child 3 content
Before footer
Footer from use";
$this->assertSame($result, $this->fenom->fetch('extends/dynamic/child.3.tpl', array()));
}
public function testDynamicExtendLevel4()
{
$result = "Before header
Child 2 header
Before body
Child 3 content
Before footer
Footer from use";
$this->assertSame($result, $this->fenom->fetch('extends/dynamic/child.4.tpl', array()));
}
/**
* @group static-invalid
* @dataProvider providerExtendsInvalid
*/
public function testExtendsInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options);
}
}

View File

@ -37,6 +37,9 @@ class FunctionsTest extends TestCase
$this->tpl('function_array_param_pos.tpl', '{sum [1, 2, 3, 4, 5]}');
}
/**
* @group sb
*/
public function testFunctionWithParams()
{
$output = $this->fenom->fetch('function_params_scalar.tpl');

View File

@ -7,56 +7,74 @@ class MacrosTest extends TestCase
public function setUp()
{
parent::setUp();
$this->tpl("math.tpl", '
{macro plus(x, y)}
x + y = {$x + $y}
{/macro}
$this->tpl(
"math.tpl",
'
{macro plus(x, y)}
x + y = {$x + $y}
{/macro}
{macro minus(x, y, z=0)}
x - y - z = {$x - $y - $z}
{/macro}
{macro minus(x, y, z=0)}
x - y - z = {$x - $y - $z}
{/macro}
{macro multi(x, y)}
x * y = {$x * $y}
{/macro}
{macro multi(x, y)}
x * y = {$x * $y}
{/macro}
Math: {macro.plus x=2 y=3}, {macro.minus x=10 y=4}
');
Math: {macro.plus x=2 y=3}, {macro.minus x=10 y=4}
'
);
$this->tpl("import.tpl", '
{import "math.tpl"}
{import "math.tpl" as math}
$this->tpl(
"import.tpl",
'
{import "math.tpl"}
{import "math.tpl" as math}
Imp: {macro.plus x=1 y=2}, {math.minus x=6 y=2 z=1}
');
Imp: {macro.plus x=1 y=2}, {math.minus x=6 y=2 z=1}
'
);
$this->tpl("import_custom.tpl", '
{macro minus(x, y)}
new minus macros
{/macro}
{import [plus, minus] from "math.tpl" as math}
$this->tpl(
"import_custom.tpl",
'
{macro minus($x, $y)}
new minus macros
{/macro}
{import [plus, minus] from "math.tpl" as math}
a: {math.plus x=1 y=2}, {math.minus x=6 y=2 z=1}, {macro.minus x=5 y=3}.
');
a: {math.plus x=1 y=2}, {math.minus x=6 y=2 z=1}, {macro.minus x=5 y=3}.
'
);
$this->tpl("import_miss.tpl", '
{import [minus] from "math.tpl" as math}
$this->tpl(
"import_miss.tpl",
'
{import [minus] from "math.tpl" as math}
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} {$num}
{/if}
{/macro}
$this->tpl(
"macro_recursive.tpl",
'{macro factorial(num)}
{if $num}
{$num} {macro.factorial num=$num-1} {$num}
{/if}
{/macro}
{macro.factorial num=10}');
{macro.factorial num=10}'
);
$this->tpl("macro_recursive_import.tpl", '
{import "macro_recursive.tpl" as math}
$this->tpl(
"macro_recursive_import.tpl",
'
{import "macro_recursive.tpl" as math}
{math.factorial num=10}');
{math.factorial num=10}'
);
}
public function _testSandbox()
@ -65,8 +83,16 @@ class MacrosTest extends TestCase
// $this->fenom->compile("macro_recursive.tpl")->display([]);
// $this->fenom->flush();
// var_dump($this->fenom->fetch("macro_recursive.tpl", []));
var_dump( $this->fenom->compile("macro_recursive_import.tpl")->display(array()));
var_dump( $this->fenom->display("macro_recursive_import.tpl", array()));
var_dump(
$this->fenom->compileCode(
'{macro factorial(num)}
{if $num}
{$num} {macro.factorial num=$num-1} {$num}
{/if}
{/macro}'
)->getBody()
);
// var_dump($this->fenom->display("macro_recursive_import.tpl", array()));
} catch (\Exception $e) {
var_dump($e->getMessage() . ": " . $e->getTraceAsString());
}
@ -88,11 +114,17 @@ class MacrosTest extends TestCase
$this->assertSame('Imp: x + y = 3 , x - y - z = 3', Modifier::strip($tpl->fetch(array()), true));
}
/**
* @group importCustom
*/
public function testImportCustom()
{
$tpl = $this->fenom->compile('import_custom.tpl');
$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)
);
}
/**
@ -103,9 +135,15 @@ class MacrosTest extends TestCase
{
$tpl = $this->fenom->compile('import_miss.tpl');
$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)
);
}
/**
* @group macro-recursive
*/
public function testRecursive()
{
$this->fenom->compile('macro_recursive.tpl');

View File

@ -8,7 +8,7 @@ class ModifiersTest extends TestCase
public static function providerTruncate()
{
$lorem = 'Lorem ipsum dolor sit amet'; // en
$uni = 'Лорем ипсум долор сит амет'; // ru
$uni = 'Лорем ипсум долор сит амет'; // ru
return array(
// ascii chars
array($lorem, 'Lorem ip...', 8),
@ -38,13 +38,18 @@ class ModifiersTest extends TestCase
public function testTruncate($in, $out, $count, $delim = '...', $by_words = false, $middle = false)
{
$tpl = $this->fenom->compileCode('{$text|truncate:$count:$delim:$by_words:$middle}');
$this->assertEquals($out, $tpl->fetch(array(
"text" => $in,
"count" => $count,
"delim" => $delim,
"by_words" => $by_words,
"middle" => $middle
)));
$this->assertEquals(
$out,
$tpl->fetch(
array(
"text" => $in,
"count" => $count,
"delim" => $delim,
"by_words" => $by_words,
"middle" => $middle
)
)
);
}
public static function providerUpLow()
@ -71,9 +76,14 @@ class ModifiersTest extends TestCase
public function testUpLow($modifier, $in, $out)
{
$tpl = $this->fenom->compileCode('{$text|' . $modifier . '}');
$this->assertEquals($out, $tpl->fetch(array(
"text" => $in,
)));
$this->assertEquals(
$out,
$tpl->fetch(
array(
"text" => $in,
)
)
);
}
public static function providerLength()
@ -99,9 +109,14 @@ class ModifiersTest extends TestCase
public function testLength($in, $out)
{
$tpl = $this->fenom->compileCode('{$data|length}');
$this->assertEquals($out, $tpl->fetch(array(
"data" => $in,
)));
$this->assertEquals(
$out,
$tpl->fetch(
array(
"data" => $in,
)
)
);
}
}

View File

@ -1,5 +1,6 @@
<?php
namespace Fenom;
use Fenom;
use Fenom\TestCase;
@ -87,7 +88,7 @@ class ProviderTest extends TestCase
clearstatcache();
$templates = array(
"template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'),
"unexists.tpl" => 1234567890
"unexists.tpl" => 1234567890
);
$this->assertFalse($this->provider->verify($templates));
}
@ -96,14 +97,18 @@ class ProviderTest extends TestCase
{
$list = $this->provider->getList();
sort($list);
$this->assertSame(array(
"sub/template3.tpl",
"template1.tpl",
"template2.tpl"
), $list);
$this->assertSame(
array(
"sub/template3.tpl",
"template1.tpl",
"template2.tpl"
),
$list
);
}
public function testRm() {
public function testRm()
{
$this->assertTrue(is_dir(FENOM_RESOURCES . '/template/sub'));
Provider::rm(FENOM_RESOURCES . '/template/sub');
$this->assertFalse(is_dir(FENOM_RESOURCES . '/template/sub'));

View File

@ -1,5 +1,6 @@
<?php
namespace Fenom;
use Fenom,
Fenom\Render;

View File

@ -0,0 +1,14 @@
<?php
namespace Fenom;
class TagOptionTest extends TestCase
{
public function testTrim()
{
}
}

View File

@ -37,7 +37,10 @@ class TagsTest extends TestCase
*/
public function testVarBlockModified($tpl_val, $val)
{
$this->assertRender("{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}", "Var: " . strtolower("before " . $val . " after") . "...");
$this->assertRender(
"{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}",
"Var: " . strtolower("before " . $val . " after") . "..."
);
}
public function testCycle()
@ -50,7 +53,10 @@ class TagsTest extends TestCase
*/
public function testCycleIndex()
{
$this->assertRender('{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}', "two, one, two, one, two, ");
$this->assertRender(
'{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}',
"two, one, two, one, two, "
);
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,23 @@
<?php
namespace Fenom;
use Fenom\Error\UnexpectedTokenException;
use Fenom\Tokenizer;
class TokenizerTest extends \PHPUnit_Framework_TestCase
{
public function testGetName()
{
$this->assertSame('T_DOUBLE_COLON', Tokenizer::getName(T_DOUBLE_COLON));
$this->assertSame('++', Tokenizer::getName('++'));
$this->assertSame('T_STRING', Tokenizer::getName(array(T_STRING, 'all', "", 1, "T_STRING")));
$this->assertNull(Tokenizer::getName(false));
}
public function testTokens()
{
$code = 'hello, please resolve this example: sin($x)+tan($x*$t) = {U|[0,1]}';
$code = 'hello, please resolve this example: sin($x)+tan($x*$t) = {U|[0,1]}';
$tokens = new Tokenizer($code);
$this->assertSame(27, $tokens->count());
$this->assertSame($tokens, $tokens->back());
@ -29,13 +38,16 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase
$this->assertSame(",", $tokens->getNext());
$this->assertSame(",", $tokens->key());
$this->assertSame("please", $tokens->getNext(T_STRING));
$this->assertSame(array(
T_STRING,
'please',
' ',
1,
'T_STRING'
), $tokens->curr);
$this->assertSame(
array(
T_STRING,
'please',
' ',
1,
'T_STRING'
),
$tokens->curr
);
$this->assertSame("resolve", $tokens->getNext($tokens::MACRO_UNARY, T_STRING));
$tokens->next();
@ -66,11 +78,12 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase
$this->assertSame($code, $tokens->getSnippetAsString(-100, 100));
$this->assertSame('+', $tokens->getSnippetAsString(100, -100));
$this->assertSame('sin($x)+tan($x*$t)', $tokens->getSnippetAsString(-4, 6));
$this->assertSame('}', $tokens->end()->current());
}
public function testSkip()
{
$text = "1 foo: bar ( 3 + double ) ";
$text = "1 foo: bar ( 3 + double ) ";
$tokens = new Tokenizer($text);
$tokens->skip()->skip(T_STRING)->skip(':');

View File

@ -1,8 +1,5 @@
<?php
use Fenom\Render,
Fenom\Provider as FS;
class FenomTest extends \Fenom\TestCase
{
@ -16,12 +13,15 @@ class FenomTest extends \Fenom\TestCase
array("auto_reload", Fenom::AUTO_RELOAD),
array("force_include", Fenom::FORCE_INCLUDE),
array("auto_escape", Fenom::AUTO_ESCAPE),
array("force_verify", Fenom::FORCE_VERIFY)
array("force_verify", Fenom::FORCE_VERIFY),
array("strip", Fenom::AUTO_STRIP),
);
}
public function testCreating() {
$time = $this->tpl('temp.tpl', 'Template 1 a');
public function testCreating()
{
$time = $this->tpl('temp.tpl', 'Template 1 a');
$fenom = new Fenom($provider = new \Fenom\Provider(FENOM_RESOURCES . '/template'));
$fenom->setCompileDir(FENOM_RESOURCES . '/compile');
$this->assertInstanceOf('Fenom\Template', $tpl = $fenom->getTemplate('temp.tpl'));
@ -32,9 +32,14 @@ class FenomTest extends \Fenom\TestCase
$fenom->clearAllCompiles();
}
public function testFactory() {
$time = $this->tpl('temp.tpl', 'Template 1 a');
$fenom = Fenom::factory($provider = new \Fenom\Provider(FENOM_RESOURCES . '/template'), FENOM_RESOURCES . '/compile', Fenom::AUTO_ESCAPE);
public function testFactory()
{
$time = $this->tpl('temp.tpl', 'Template 1 a');
$fenom = Fenom::factory(
$provider = new \Fenom\Provider(FENOM_RESOURCES . '/template'),
FENOM_RESOURCES . '/compile',
Fenom::AUTO_ESCAPE
);
$this->assertInstanceOf('Fenom\Template', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertSame($provider, $tpl->getProvider());
$this->assertSame('temp.tpl', $tpl->getBaseName());
@ -43,6 +48,25 @@ class FenomTest extends \Fenom\TestCase
$fenom->clearAllCompiles();
}
/**
* @expectedException LogicException
* @expectedExceptionMessage Cache directory /invalid/path is not writable
*/
public function testFactoryInvalid()
{
Fenom::factory(FENOM_RESOURCES . '/template', '/invalid/path');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Source must be a valid path or provider object
*/
public function testFactoryInvalid2()
{
Fenom::factory(new StdClass);
}
public function testCompileFile()
{
$a = array(
@ -95,7 +119,10 @@ class FenomTest extends \Fenom\TestCase
{
$this->fenom->addModifier("mymod", "myMod");
$this->tpl('custom.tpl', 'Custom modifier {$a|mymod}');
$this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->fenom->fetch('custom.tpl', array("a" => "Custom")));
$this->assertSame(
"Custom modifier (myMod)Custom(/myMod)",
$this->fenom->fetch('custom.tpl', array("a" => "Custom"))
);
}
/**
@ -116,13 +143,27 @@ class FenomTest extends \Fenom\TestCase
{
$this->fenom->setOptions(Fenom::FORCE_COMPILE);
$this->fenom->addCompiler("mycompiler", 'myCompiler');
$this->fenom->addBlockCompiler("myblockcompiler", 'myBlockCompilerOpen', 'myBlockCompilerClose', array(
'tag' => 'myBlockCompilerTag'
));
$this->fenom->addBlockCompiler(
"myblockcompiler",
'myBlockCompilerOpen',
'myBlockCompilerClose',
array(
'tag' => 'myBlockCompilerTag'
)
);
$this->tpl('custom.tpl', 'Custom compiler {mycompiler name="bar"}');
$this->assertSame("Custom compiler PHP_VERSION: " . PHP_VERSION . " (for bar)", $this->fenom->fetch('custom.tpl', array()));
$this->tpl('custom.tpl', 'Custom compiler {myblockcompiler name="bar"} block1 {tag name="baz"} block2 {/myblockcompiler}');
$this->assertSame("Custom compiler PHP_VERSION: " . PHP_VERSION . " (for bar) block1 Tag baz of compiler block2 End of compiler", $this->fenom->fetch('custom.tpl', array()));
$this->assertSame(
"Custom compiler PHP_VERSION: " . PHP_VERSION . " (for bar)",
$this->fenom->fetch('custom.tpl', array())
);
$this->tpl(
'custom.tpl',
'Custom compiler {myblockcompiler name="bar"} block1 {tag name="baz"} block2 {/myblockcompiler}'
);
$this->assertSame(
"Custom compiler PHP_VERSION: " . PHP_VERSION . " (for bar) block1 Tag baz of compiler block2 End of compiler",
$this->fenom->fetch('custom.tpl', array())
);
}
/**
@ -144,63 +185,147 @@ class FenomTest extends \Fenom\TestCase
public function testFilter()
{
$punit = $this;
$this->fenom->addPreFilter(function ($src, $tpl) use ($punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
return "== $src ==";
});
$this->fenom->addPreFilter(
function ($tpl, $src) use ($punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
return "== $src ==";
}
);
$this->fenom->addPostFilter(function ($code, $tpl) use ($punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
return "+++ $code +++";
});
$this->fenom->addPostFilter(
function ($tpl, $code) use ($punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
return "+++ $code +++";
}
);
$this->fenom->addFilter(function ($text, $tpl) use ($punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
return "|--- $text ---|";
});
$this->fenom->addFilter(
function ($tpl, $text) use ($punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
return "|--- $text ---|";
}
);
$this->assertSame('+++ |--- == hello ---||--- world == ---| +++', $this->fenom->compileCode('hello {var $user} misterio {/var} world')->fetch(array()));
$this->assertSame('+++ |--- == hello ---||--- world == ---| +++', $this->fenom->compileCode('hello {var $user} <?php misterio ?> {/var} world')->fetch(array()));
$this->assertSame(
'+++ |--- == hello ---||--- world == ---| +++',
$this->fenom->compileCode('hello {var $user} misterio {/var} world')->fetch(array())
);
$this->assertSame(
'+++ |--- == hello ---||--- world == ---| +++',
$this->fenom->compileCode('hello {var $user} <?php misterio ?> {/var} world')->fetch(array())
);
}
public function testAddInlineCompilerSmart() {
$this->fenom->addCompilerSmart('SayA','TestTags');
/**
* @group tag-filter
*/
public function testTagFilter()
{
$tags = array();
$punit = $this;
$this->fenom->addTagFilter(
function ($text, $tpl) use (&$tags, $punit) {
$punit->assertInstanceOf('Fenom\Template', $tpl);
$tags[] = $text;
return $text;
}
);
$this->fenom->compileCode('hello {var $a} misterio {/var} world, {$b}!');
$this->assertSame(array('var $a', '/var', '$b'), $tags);
}
public function testAddInlineCompilerSmart()
{
$this->fenom->addCompilerSmart('SayA', 'TestTags');
$this->tpl('inline_compiler.tpl', 'I just {SayA}.');
$this->assertSame('I just Say A.', $this->fenom->fetch('inline_compiler.tpl', array()));
}
public function testAddBlockCompilerSmart() {
public function testAddBlockCompilerSmart()
{
$this->fenom->addBlockCompilerSmart('SayBlock', 'TestTags', array('SaySomething'), array('SaySomething'));
$this->tpl('block_compiler.tpl', '{SayBlock} and {SaySomething}. It is all, {/SayBlock}');
$this->assertSame('Start saying and say blah-blah-blah. It is all, Stop saying',
$this->fenom->fetch('block_compiler.tpl', array()));
$this->assertSame(
'Start saying and say blah-blah-blah. It is all, Stop saying',
$this->fenom->fetch('block_compiler.tpl', array())
);
}
public function testAddFunctions() {
public function testAddFunctions()
{
$this->fenom->setOptions(Fenom::DENY_NATIVE_FUNCS);
$this->assertFalse($this->fenom->isAllowedFunction('substr'));
$this->fenom->addAllowedFunctions(array('substr'));
$this->assertTrue($this->fenom->isAllowedFunction('substr'));
}
/**
* @requires function php_gte_54
* @group pipe
*/
public function testPipe()
{
$iteration = 0;
$test = $this; // for PHP 5.3
$this->fenom->pipe(
"persist:pipe.tpl",
function ($chunk) use (&$iteration, $test) {
if (!$chunk) {
return;
}
$iteration++;
// error_log(var_export($chunk, 1));
$test->assertSame("key$iteration:value$iteration", $chunk);
},
array(
"items" => array(
"key1" => "value1",
"key2" => "value2",
"key3" => "value3",
)
),
11 // strlen(key1) + strlen(:) + strlen(value1)
);
$this->assertSame(3, $iteration);
}
/**
* @group strip
*/
public function testStrip() {
$this->fenom->setOptions(Fenom::AUTO_STRIP);
$tpl = <<<TPL
<div class="item item-one">
<a href="/item/{\$one}">number {\$num.1}</a>
</div>
TPL;
$this->assertRender($tpl, '<div class="item item-one"><a href="/item/1">number one</a></div>');
}
}
class TestTags
{
class TestTags {
public static function tagSayA() {
public static function tagSayA()
{
return 'echo "Say A"';
}
public static function SayBlockOpen() {
public static function SayBlockOpen()
{
return 'echo "Start saying"';
}
public static function tagSaySomething() {
public static function tagSaySomething()
{
return 'echo "say blah-blah-blah"';
}
public static function SayBlockClose() {
public static function SayBlockClose()
{
return 'echo "Stop saying"';
}
}

View File

@ -15,23 +15,24 @@ function myBlockFunc($params, $content)
return "Block:" . $params["name"] . ':' . trim($content) . ':Block';
}
function myCompiler(Fenom\Tokenizer $tokenizer, Fenom\Template $tpl)
function myCompiler(Fenom\Tokenizer $tokenizer, Fenom\Tag $tag)
{
$p = $tpl->parseParams($tokenizer);
$p = $tag->tpl->parseParams($tokenizer);
return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".' . $p["name"] . '.")";';
}
function myBlockCompilerOpen(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)
function myBlockCompilerOpen(Fenom\Tokenizer $tokenizer, Fenom\Tag $scope)
{
return myCompiler($tokenizer, $scope->tpl);
$p = $scope->tpl->parseParams($tokenizer);
return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".' . $p["name"] . '.")";';
}
function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)
function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Tag $scope)
{
return 'echo "End of compiler";';
}
function myBlockCompilerTag(Fenom\Tokenizer $tokenizer, Fenom\Scope $scope)
function myBlockCompilerTag(Fenom\Tokenizer $tokenizer, Fenom\Tag $scope)
{
$p = $scope->tpl->parseParams($tokenizer);
return 'echo "Tag ".' . $p["name"] . '." of compiler";';

View File

@ -0,0 +1 @@
{block 'body'}Child 1 {parent}{/block}

View File

@ -0,0 +1,2 @@
{use 'extends/auto/use.tpl'}
{block 'header'}Child 2 header{/block}

View File

@ -0,0 +1 @@
{block 'body'}Child 3 content{/block}

View File

@ -0,0 +1,6 @@
Before header
{block 'header'}Content of the header{/block}
Before body
{block 'body'}Body{/block}
Before footer
{block 'footer'}Content of the footer{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/auto/parent.tpl'}
{block 'body'}Child 1 {parent}{/block}

View File

@ -0,0 +1,2 @@
{block 'footer'}Footer from use{/block}
{block 'header'}Header from use{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/dynamic/parent.tpl'}
{block 'body'}Child 1 {parent}{/block}

View File

@ -0,0 +1,4 @@
{var $no = 1}
{extends "extends/dynamic/child.{$no = 1}.tpl"}
{use 'extends/dynamic/use.tpl'}
{block 'header'}Child 2 header{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/dynamic/child.2.tpl'}
{block 'body'}Child 3 content{/block}

View File

@ -0,0 +1 @@
{extends "extends/dynamic/child.{$a = 3}.tpl"}

View File

@ -0,0 +1,6 @@
Before header
{block 'header'}Content of the header{/block}
Before body
{block 'body'}Body{/block}
Before footer
{block 'footer'}Content of the footer{/block}

View File

@ -0,0 +1,2 @@
{block 'footer'}Footer from use{/block}
{block 'header'}Header from use{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/auto/child.2.tpl'}
{block 'body'}Child 3 content{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/static/parent.tpl'}
{block 'body'}Child 1 {parent}{/block}

View File

@ -0,0 +1,3 @@
{extends 'extends/static/child.1.tpl'}
{use 'extends/static/use.tpl'}
{block 'header'}Child 2 header{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/static/child.2.tpl'}
{block 'body'}Child 3 content{/block}

View File

@ -0,0 +1,2 @@
{extends 'extends/static/nested/parent.tpl'}
{block 'header'}Child 1: {parent}{/block}

View File

@ -0,0 +1,7 @@
Before body
{block 'body'}
Before header
{block 'header'}Content of the header{/block}
Before footer
{block 'footer'}Content of the footer{/block}
{/block}

View File

@ -0,0 +1,6 @@
Before header
{block 'header'}Content of the header{/block}
Before body
{block 'body'}Body{/block}
Before footer
{block 'footer'}Content of the footer{/block}

View File

@ -0,0 +1,2 @@
{block 'footer'}Footer from use{/block}
{block 'header'}Header from use{/block}

View File

@ -0,0 +1 @@
{foreach $items as $key => $value}{$key}:{$value}{/foreach}