Update docs
  Add {import ... from ...}
  Improve parsing
This commit is contained in:
bzick
2013-03-14 21:45:00 +04:00
parent 26393ad22d
commit a3c5128aba
15 changed files with 221 additions and 101 deletions

View File

@ -28,7 +28,7 @@ Benchmark::runs("twig", 'inheritance/twig/b100.tpl', __DIR__.'/templates/foreach
Benchmark::runs("aspect", 'inheritance/smarty/b100.tpl', __DIR__.'/templates/foreach/data.json'); Benchmark::runs("aspect", 'inheritance/smarty/b100.tpl', __DIR__.'/templates/foreach/data.json');
echo "\nDone. Cleanup.\n"; echo "\nDone. Cleanup.\n";
passthru("rm -rf ".__DIR__."/compile/*"); //passthru("rm -rf ".__DIR__."/compile/*");
passthru("rm -f ".__DIR__."/templates/inheritance/smarty/*"); passthru("rm -f ".__DIR__."/templates/inheritance/smarty/*");
passthru("rm -f ".__DIR__."/templates/inheritance/twig/*"); passthru("rm -f ".__DIR__."/templates/inheritance/twig/*");

View File

@ -1,5 +1,5 @@
About Aspect About Aspect [RU]
============ =================
Aspect - самый быстрый, гибкий и тонкий шаблонизатор для PHP, унаследовавший синтаксис от Smarty3 и улучшив его. Aspect - самый быстрый, гибкий и тонкий шаблонизатор для PHP, унаследовавший синтаксис от Smarty3 и улучшив его.
Пожалуй это единственный шаблонизатор, который не использет ни регулярные выражения, как Twig, ни лексер от BISON, как Smarty3. Пожалуй это единственный шаблонизатор, который не использет ни регулярные выражения, как Twig, ни лексер от BISON, как Smarty3.

View File

@ -1,11 +1,22 @@
Модификаторы Модификаторы [RU]
============ ============
Добавить модификатор:
```php ```
$aspect->addModifier($modifier, $callback); $aspect->addModifier(string $modifier, callable $callback);
``` ```
* `$modifier` - имя модификатора * `$modifier` - название модификатора, которое будет использоваться в шаблоне
* `$callback` - строка с именем функции * `$callback` - коллбек, который будет вызван для изменения данных
For example:
```smarty
{$variable|my_modifier:$param1:$param2}
```
```php
$aspect->addModifier('my_modifier', function ($variable, $param1, $param2) {
// ...
});
```

View File

@ -1,20 +1,68 @@
Теги Tags [RU]
==== =========
Теги делятся на компилеры и функции. В шаблонизаторе принято различать два типа тегов: компиляторы и функции.
Компилеры формируют синтаксис языка шаблона, добавляя такой функционал как foreach, if, while и т.д. В то время как функции - обычный вызов некоторой именованной функции Компиляторы вызываются во время преобразования кода шаблона в PHP код и возвращяю PHP код который будет вставлен вместо тега.
А функции вызываются непременно в момент выполнения шаблона и возвращают непосредственно данные которые будут отображены.
Среди тегов как и в HTML есть строчные и блоковые теги.
Добавить компилер: ## Inline function
Примитивное добавление функции можно осуществить следующим образом:
```php ```php
$aspect->addCompiler($compiler, $parser); $aspect->addFunction(string $function_name, callable $callback[, callable $parser]);
``` ```
* `$compiler` - имя модификатора В данном случае запускается стандартный парсер, который автоматически разберет аргументы тега, которые должны быть в формате HTML аттрибутов и отдаст их в функцию ассоциативным массивом.
* `$parser` - функция разбора тега в формате function (MF\Tokenizer $tokens, MF\Aspect\Template $tpl) {} В данном случае вы можете переопределить парсер на произвольный в формате `function (Aspect\Tokenizer $tokenizer, Aspect\Template $template)`
Существует более совершенный способ добавления функции:
Добавить блочный компилер:
```php ```php
$aspect->addBlockCompiler($compiler, $parsers, $tags); $aspect->addFunctionSmarty(string $function_name, callable $callback);
``` ```
В данном случае парсер просканирует список аргументов коллбека и попробует сопоставить с аргументами из тега. Таким образом вы успешно можете добавлять Ваши штатные функции.
## Block function
Добавление блоковой функции аналогичен добавлению строковой за исключением того что есть возможность указать парсер для закрывающего тега.
```php
$aspect->addBlockFunction(string $function_name, callable $callback[, callable $parser_open[, callable $parser_close]]);
```
Сам коллбек принимает первым аргументом контент между открывающим и закрывающим тегом, а вторым аргументом - ассоциативный массив из аргуметов тега.
## Inline compiler
Добавление строчного компилятора осуществляеться очень просто:
```php
$aspect->addCompiler(string $compiler, callable $parser);
```
Парсер должен принимать `Aspect\Tokenizer $tokenizer`, `Aspect\Template $template` и возвращать PHP код.
Компилятор так же можно импортировать из класса автоматически
```php
$aspect->addCompilerSmart(string $compiler, $storage);
```
`$storage` может быть как классом так и объектом. В данном случае шаблонизатор будет искать метод `tag{$compiler}`, который будет взят в качестве парсера тега.
## Block compiler
Добавление блочного компилятора осуществяется двум способами. Первый
```php
$aspect->addBlockCompiler(string $compiler, array $parsers, array $tags);
```
где `$parser` ассоциативный массив `["open" => parser, "close" => parser]`, сождержащий парсер на открывающий и на закрывающий тег, а `$tags` содержит список внутренних тегов в формате `["tag_name"] => parser`, которые могут быть использованы только с этим компилятором.
Второй способ добавления парсера через импортирование из класса или объекта методов:
```php
$aspect->addBlockCompilerSmart(string $compiler, $storage, array $tags, array $floats);
```

View File

@ -1,5 +1,5 @@
Installation Installation
============================= ============
For installation use [composer](http://getcompoer.org). Add in your `composer.json` requirements: For installation use [composer](http://getcompoer.org). Add in your `composer.json` requirements:
```json ```json

View File

@ -7,7 +7,7 @@ Math
Bitwize Bitwize
`| & <<` `| & << >> |= &= <<= >>=`
Unary Unary
@ -15,12 +15,8 @@ Unary
Boolean Boolean
`|| && and or < > <= >= == === !== != ≥ ≤ ≠` `|| && and or < > <= >= == === !== !=`
Ternar Ternar
`? :` `? :`
Test
`is in like`

View File

@ -1,5 +1,5 @@
Настройка Settings [RU]
========= =============
### Engine settings ### Engine settings

View File

@ -1,5 +1,5 @@
Syntax Syntax [RU]
====== ===========
### Output variables ### Output variables

View File

@ -2,7 +2,7 @@ Tag {for}
========= =========
```smarty ```smarty
{for $counter=<start..end> [to=<end>] [step=<step>] [index=$index] [first=$first] [last=$last]} {for $counter=<start> to=<end> [step=<step>] [index=$index] [first=$first] [last=$last]}
{* ...code... *} {* ...code... *}
{break} {break}
{* ...code... *} {* ...code... *}

View File

@ -1,7 +1,12 @@
Tag {macro} Tag {macro} [RU]
============ ================
Declare macro Макросы - фрагмент шаблона который можно повторить сколь угодно раз и в каком угодно месте.
Макросы не имеют общего пространства имен с шаблоном и могут оперировать только переданными переменными.
### {macro}
Обявление макроса происходит при помощи блочного тега `{macro}`
```smarty ```smarty
{macro plus(x, y, z=0)} {macro plus(x, y, z=0)}
@ -9,7 +14,7 @@ Declare macro
{/macro} {/macro}
``` ```
Invoke macro Вызов макроса происходит при помощи строкового тега `{macro}`. Аргументы передаются стандартно, как атрибуты в HTML тегах
```smarty ```smarty
{macro.plus x=$num y=100} {macro.plus x=$num y=100}
@ -17,14 +22,23 @@ Invoke macro
### {import} ### {import}
Import [macro](./macro.md) from another template Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}`
```smarty ```smarty
{import 'math.tpl'} {import 'math.tpl'}
``` ```
При импорте можно указать дргое пространство имен что бы можно было использовать одноименные макросы из разных шаблонов
```smarty ```smarty
{import 'math.tpl' as math} {import 'math.tpl' as math}
... ...
{math.plus x=5 y=100} {math.plus x=5 y=100}
``` ```
Пространство имен макросов может совпадать с названием какого-либо тега, в данном случае ничего плохого не произойдет: будет вызван макрос, а тег не исчезнит
При необходимости можно импортировать только необходимые макросы, явно указав в теге `{import}`
```smarty
{import [plus, minus, exp] from 'math.tpl' as math}
```

View File

@ -25,7 +25,7 @@ class Aspect {
const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose'; const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser'; const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser';
const DEFAULT_FUNC_OPEN = 'Aspect\Compiler::stdFuncOpen'; const DEFAULT_FUNC_OPEN = 'Aspect\Compiler::stdFuncOpen';
const DEFAULT_FUNC_CLOSE = 'Aspect\Compiler::stdFuncOpen'; const DEFAULT_FUNC_CLOSE = 'Aspect\Compiler::stdFuncClose';
const SMART_FUNC_PARSER = 'Aspect\Compiler::smartFuncParser'; const SMART_FUNC_PARSER = 'Aspect\Compiler::smartFuncParser';
/** /**
@ -320,7 +320,18 @@ class Aspect {
return $this; return $this;
} }
public function addCompilerSmart($class) { /**
* @param string $compiler
* @param string|object $storage
* @return $this
*/
public function addCompilerSmart($compiler, $storage) {
if(method_exists($storage, "tag".$compiler)) {
$this->_actions[$compiler] = array(
'type' => self::INLINE_COMPILER,
'parser' => array($storage, "tag".$compiler)
);
}
return $this; return $this;
} }
@ -329,7 +340,7 @@ class Aspect {
* *
* @param string $compiler * @param string $compiler
* @param callable $open_parser * @param callable $open_parser
* @param callable $close_parser * @param callable|string $close_parser
* @param array $tags * @param array $tags
* @return Aspect * @return Aspect
*/ */
@ -344,12 +355,40 @@ class Aspect {
} }
/** /**
* @param string $class * @param $compiler
* @param $storage
* @param array $tags * @param array $tags
* @param array $floats * @param array $floats
* @throws LogicException
* @return Aspect * @return Aspect
*/ */
public function addBlockCompilerSmart($class, array $tags, array $floats = array()) { public function addBlockCompilerSmart($compiler, $storage, array $tags, array $floats = array()) {
$c = array(
'type' => self::BLOCK_COMPILER,
"tags" => array(),
"float_tags" => array()
);
if(method_exists($storage, $compiler."Open")) {
$c["open"] = $compiler."Open";
} else {
throw new \LogicException("Open compiler {$compiler}Open not found");
}
if(method_exists($storage, $compiler."Close")) {
$c["close"] = $compiler."Close";
} else {
throw new \LogicException("Close compiler {$compiler}Close not found");
}
foreach($tags as $tag) {
if(method_exists($storage, "tag".$tag)) {
$c["tags"][ $tag ] = "tag".$tag;
if($floats && in_array($tag, $floats)) {
$c['float_tags'][ $tag ] = 1;
}
} else {
throw new \LogicException("Tag compiler $tag (tag{$compiler}) not found");
}
}
$this->_actions[$compiler] = $c;
return $this; return $this;
} }
@ -362,7 +401,7 @@ class Aspect {
public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER) { public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER) {
$this->_actions[$function] = array( $this->_actions[$function] = array(
'type' => self::INLINE_FUNCTION, 'type' => self::INLINE_FUNCTION,
'parser' => $parser ?: self::DEFAULT_FUNC_PARSER, 'parser' => $parser,
'function' => $callback, 'function' => $callback,
); );
return $this; return $this;
@ -385,15 +424,15 @@ class Aspect {
/** /**
* @param string $function * @param string $function
* @param callable $callback * @param callable $callback
* @param callable $parser_open * @param callable|string $parser_open
* @param callable $parser_close * @param callable|string $parser_close
* @return Aspect * @return Aspect
*/ */
public function addBlockFunction($function, $callback, $parser_open = null, $parser_close = null) { public function addBlockFunction($function, $callback, $parser_open = self::DEFAULT_FUNC_OPEN, $parser_close = self::DEFAULT_FUNC_CLOSE) {
$this->_actions[$function] = array( $this->_actions[$function] = array(
'type' => self::BLOCK_FUNCTION, 'type' => self::BLOCK_FUNCTION,
'open' => $parser_open ?: 'Aspect\Compiler::stdFuncOpen', 'open' => $parser_open,
'close' => $parser_close ?: 'Aspect\Compiler::stdFuncClose', 'close' => $parser_close,
'function' => $callback, 'function' => $callback,
); );
return $this; return $this;
@ -564,7 +603,7 @@ class Aspect {
return $this->_storage[ $template ]; return $this->_storage[ $template ];
} }
} elseif($this->_options & self::FORCE_COMPILE) { } elseif($this->_options & self::FORCE_COMPILE) {
return $this->compile($template, false); return $this->compile($template, $this->_options & self::DISABLE_CACHE);
} else { } else {
return $this->_storage[ $template ] = $this->_load($template); return $this->_storage[ $template ] = $this->_load($template);
} }
@ -583,7 +622,7 @@ class Aspect {
* *
* @param string $tpl * @param string $tpl
* @throws \RuntimeException * @throws \RuntimeException
* @return Aspect\Template|mixed * @return Aspect\Render
*/ */
protected function _load($tpl) { protected function _load($tpl) {
$file_name = $this->_getHash($tpl); $file_name = $this->_getHash($tpl);
@ -591,7 +630,6 @@ class Aspect {
return $this->compile($tpl); return $this->compile($tpl);
} else { } else {
$aspect = $this; $aspect = $this;
/** @var Aspect\Render $tpl */
return include($this->_compile_dir."/".$file_name); return include($this->_compile_dir."/".$file_name);
} }
} }
@ -604,7 +642,7 @@ class Aspect {
*/ */
private function _getHash($tpl) { private function _getHash($tpl) {
$hash = $tpl.":".$this->_options; $hash = $tpl.":".$this->_options;
return sprintf("%s.%u.%d.php", basename($tpl), crc32($hash), strlen($hash)); return sprintf("%s.%u.%d.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash));
} }
/** /**

View File

@ -148,7 +148,6 @@ class Compiler {
$before = $before ? implode("; ", $before).";" : ""; $before = $before ? implode("; ", $before).";" : "";
$body = $body ? implode("; ", $body).";" : ""; $body = $body ? implode("; ", $body).";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]).";" : ""; $scope["after"] = $scope["after"] ? implode("; ", $scope["after"]).";" : "";
if($key) { if($key) {
return "$prepend if($from) { $before foreach($from as $key => $value) { $body"; return "$prepend if($from) { $before foreach($from as $key => $value) { $body";
} else { } else {
@ -161,8 +160,6 @@ class Compiler {
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Scope $scope * @param Scope $scope
* @internal param $
* @param Scope $scope
* @return string * @return string
*/ */
public static function foreachElse($tokens, Scope $scope) { public static function foreachElse($tokens, Scope $scope) {
@ -385,7 +382,7 @@ class Compiler {
return ""; return "";
} else { // dynamic extends } else { // dynamic extends
$tpl->_extends = $tpl_name; $tpl->_extends = $tpl_name;
return '$parent = $tpl->getStorage()->getTemplate('.$tpl_name.');'; return '$parent = $tpl->getStorage()->getTemplate("extend:".'.$tpl_name.');';
} }
} }
@ -561,7 +558,8 @@ class Compiler {
*/ */
public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl) { public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl) {
if(strpos($function, "::")) { if(strpos($function, "::")) {
$ref = new \ReflectionMethod($function); list($class, $method) = explode("::", $function, 2);
$ref = new \ReflectionMethod($class, $method);
} else { } else {
$ref = new \ReflectionFunction($function); $ref = new \ReflectionFunction($function);
} }
@ -710,6 +708,24 @@ class Compiler {
* @throws ImproperUseException * @throws ImproperUseException
*/ */
public static function tagImport(Tokenizer $tokens, Template $tpl) { public static function tagImport(Tokenizer $tokens, Template $tpl) {
$import = array();
if($tokens->is('[')) {
$tokens->next();
while($tokens->valid()) {
if($tokens->is(Tokenizer::MACRO_STRING)) {
$import[ $tokens->current() ] = true;
$tokens->next();
} elseif($tokens->is(']')) {
$tokens->next();
break;
}
}
if($tokens->current() != "from") {
throw new UnexpectedException($tokens);
}
$tokens->next();
}
$tpl->parseFirstArg($tokens, $name); $tpl->parseFirstArg($tokens, $name);
if(!$name) { if(!$name) {
throw new ImproperUseException("Invalid usage tag {import}"); throw new ImproperUseException("Invalid usage tag {import}");

View File

@ -163,7 +163,7 @@ class Template extends Render {
$_tag = substr($tag, 1, -1); $_tag = substr($tag, 1, -1);
$_frag = $frag; $_frag = $frag;
} }
if($this->_ignore) { // check ignore scope if($this->_ignore) { // check ignore
if($_tag === '/ignore') { if($_tag === '/ignore') {
$this->_ignore = false; $this->_ignore = false;
$this->_appendText($_frag); $this->_appendText($_frag);
@ -173,7 +173,12 @@ class Template extends Render {
} }
} else { } else {
$this->_appendText($_frag); $this->_appendText($_frag);
$this->_appendCode($this->_tag($_tag)); $tokens = new Tokenizer($_tag);
$this->_appendCode($this->_tag($tokens), $tag);
if($tokens->key()) { // if tokenizer still have tokens
throw new CompileException("Unexpected token '".$tokens->current()."' in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, E_ERROR, $this->_name, $this->_line);
}
} }
$frag = ""; $frag = "";
} }
@ -195,7 +200,6 @@ class Template extends Render {
call_user_func_array($cb, array(&$this->_body, $this)); call_user_func_array($cb, array(&$this->_body, $this));
} }
} }
/*$this->_body = str_replace(array('?>'.PHP_EOL.'<?php ', '?><?php'), array(PHP_EOL, ' '), $this->_body);*/
} }
/** /**
@ -207,7 +211,7 @@ class Template extends Render {
$this->_body .= str_replace("<?", '<?php echo "<?"; ?>'.PHP_EOL, $text); $this->_body .= str_replace("<?", '<?php echo "<?"; ?>'.PHP_EOL, $text);
} }
public static function escapeCode($code) { private function _escapeCode($code) {
$c = ""; $c = "";
foreach(token_get_all($code) as $token) { foreach(token_get_all($code) as $token) {
if(is_string($token)) { if(is_string($token)) {
@ -225,12 +229,16 @@ class Template extends Render {
* Append PHP code to template body * Append PHP code to template body
* *
* @param string $code * @param string $code
* @param $source
*/ */
private function _appendCode($code) { private function _appendCode($code, $source) {
if(!$code) { if(!$code) {
return; return;
} else { } else {
$this->_body .= self::escapeCode($code); if(strpos($code, '?>') !== false) {
$code = $this->_escapeCode($code); // paste PHP_EOL
}
$this->_body .= "<?php\n/* {$this->_name}:{$this->_line}: {$source} */\n $code ?>".PHP_EOL;
} }
} }
@ -259,7 +267,7 @@ class Template extends Render {
return "<?php \n". return "<?php \n".
"/** Aspect template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\n". "/** Aspect template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\n".
"return new Aspect\\Render(\$aspect, ".$this->_getClosureSource().", ".var_export(array( "return new Aspect\\Render(\$aspect, ".$this->_getClosureSource().", ".var_export(array(
//"options" => $this->_options, "options" => $this->_options,
"provider" => $this->_scm, "provider" => $this->_scm,
"name" => $this->_name, "name" => $this->_name,
"base_name" => $this->_base_name, "base_name" => $this->_base_name,
@ -321,43 +329,28 @@ class Template extends Render {
/** /**
* Internal tags router * Internal tags router
* @param string $src * @param Tokenizer $tokens
* @throws UnexpectedException * @throws UnexpectedException
* @throws CompileException * @throws CompileException
* @throws SecurityException * @throws SecurityException
* @return string executable PHP code * @return string executable PHP code
*/ */
private function _tag($src) { private function _tag(Tokenizer $tokens) {
$tokens = new Tokenizer($src);
try { try {
switch($src[0]) { if($tokens->is(Tokenizer::MACRO_STRING)) {
case '"':
case '\'':
case '$':
$code = "echo ".$this->parseExp($tokens).";";
break;
case '#':
$code = "echo ".$this->parseConst($tokens);
break;
case '/':
$code = $this->_end($tokens);
break;
default:
if($tokens->current() === "ignore") { if($tokens->current() === "ignore") {
$this->_ignore = true; $this->_ignore = true;
$tokens->next(); $tokens->next();
$code = ''; return '';
} else { } else {
$code = $this->_parseAct($tokens); return $this->_parseAct($tokens);
} }
} } elseif ($tokens->is('/')) {
if($tokens->key()) { // if tokenizer still have tokens return $this->_end($tokens);
throw new UnexpectedException($tokens); } elseif ($tokens->is('#')) {
} return "echo ".$this->parseConst($tokens).';';
if(!$code) {
return "";
} else { } else {
return "<?php\n/* {$this->_name}:{$this->_line}: {$src} */\n {$code} ?>"; return $code = "echo ".$this->parseExp($tokens).";";
} }
} catch (ImproperUseException $e) { } catch (ImproperUseException $e) {
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e); throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e);
@ -376,6 +369,7 @@ class Template extends Render {
* @throws TokenizeException * @throws TokenizeException
*/ */
private function _end(Tokenizer $tokens) { private function _end(Tokenizer $tokens) {
//return "end";
$name = $tokens->getNext(Tokenizer::MACRO_STRING); $name = $tokens->getNext(Tokenizer::MACRO_STRING);
$tokens->next(); $tokens->next();
if(!$this->_stack) { if(!$this->_stack) {
@ -402,7 +396,7 @@ class Template extends Render {
if($tokens->is(Tokenizer::MACRO_STRING)) { if($tokens->is(Tokenizer::MACRO_STRING)) {
$action = $tokens->getAndNext(); $action = $tokens->getAndNext();
} else { } else {
return 'echo '.$this->parseExp($tokens).';'; // may be math and boolean expression return 'echo '.$this->parseExp($tokens).';'; // may be math and/or boolean expression
} }
if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method

View File

@ -266,7 +266,7 @@ class TemplateTest extends TestCase {
public static function providerIncludeInvalid() { public static function providerIncludeInvalid() {
return array( return array(
array('Include {include} template', 'Aspect\CompileException', "Unexpected end of expression"), array('Include {include} template', 'Aspect\CompileException', "Unexpected end of expression"),
array('Include {include another="welcome.tpl"} template', 'Aspect\CompileException', "Unexpected token '=' in expression"), array('Include {include another="welcome.tpl"} template', 'Aspect\CompileException', "Unexpected token '='"),
); );
} }

View File

@ -68,6 +68,9 @@ class AspectTest extends \Aspect\TestCase {
$this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->aspect->fetch('custom.tpl', array("a" => "Custom"))); $this->assertSame("Custom modifier (myMod)Custom(/myMod)", $this->aspect->fetch('custom.tpl', array("a" => "Custom")));
} }
/**
* @group add_functions
*/
public function testSetFunctions() { public function testSetFunctions() {
$this->aspect->setOptions(Aspect::FORCE_COMPILE); $this->aspect->setOptions(Aspect::FORCE_COMPILE);
$this->aspect->addFunction("myfunc", "myFunc"); $this->aspect->addFunction("myfunc", "myFunc");