mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
Fix: cycle tag. Update comments. Add tests.
This commit is contained in:
parent
7453dbcf2b
commit
f42d0cff5d
@ -14,22 +14,24 @@ use Cytro\Template,
|
||||
* Cytro Template Engine
|
||||
*/
|
||||
class Cytro {
|
||||
const VERSION = '1.0.1';
|
||||
const VERSION = '1.0';
|
||||
|
||||
/* Compiler types */
|
||||
const INLINE_COMPILER = 1;
|
||||
const BLOCK_COMPILER = 2;
|
||||
const INLINE_FUNCTION = 3;
|
||||
const BLOCK_FUNCTION = 4;
|
||||
const MODIFIER = 5;
|
||||
|
||||
/* Options */
|
||||
const DENY_METHODS = 0x10;
|
||||
const DENY_INLINE_FUNCS = 0x20;
|
||||
const FORCE_INCLUDE = 0x40;
|
||||
|
||||
const AUTO_RELOAD = 0x80;
|
||||
const FORCE_COMPILE = 0xF0;
|
||||
const DISABLE_CACHE = 0x1F0;
|
||||
|
||||
/* Default parsers */
|
||||
const DEFAULT_CLOSE_COMPILER = 'Cytro\Compiler::stdClose';
|
||||
const DEFAULT_FUNC_PARSER = 'Cytro\Compiler::stdFuncParser';
|
||||
const DEFAULT_FUNC_OPEN = 'Cytro\Compiler::stdFuncOpen';
|
||||
@ -204,7 +206,11 @@ class Cytro {
|
||||
'import' => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Cytro\Compiler::tagImport'
|
||||
)
|
||||
),
|
||||
'cycle' => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Cytro\Compiler::tagCycle'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -696,21 +696,31 @@ class Compiler {
|
||||
* @throws ImproperUseException
|
||||
*/
|
||||
public static function tagCycle(Tokenizer $tokens, Template $tpl) {
|
||||
$exp = $tpl->parseExp($tokens, true);
|
||||
if($tokens->is("[")) {
|
||||
$exp = $tpl->parseArray($tokens);
|
||||
} else {
|
||||
$exp = $tpl->parseExp($tokens, true);
|
||||
}
|
||||
if($tokens->valid()) {
|
||||
$p = $tpl->parseParams($tokens);
|
||||
if(empty($p["index"])) {
|
||||
throw new ImproperUseException("Cycle may contain only index attribute");
|
||||
} else {
|
||||
return __CLASS__.'::cycle((array)'.$exp.', '.$p["index"].');';
|
||||
return 'echo '.__CLASS__.'::cycle('.$exp.', '.$p["index"].')';
|
||||
}
|
||||
} else {
|
||||
$var = $tpl->tmpVar();
|
||||
return "is_array($exp) ? ".__CLASS__.'::cycle('.$exp.", isset($var) ? $var++ : ($var = 0) ) : $exp";
|
||||
return 'echo '.__CLASS__.'::cycle('.$exp.", isset($var) ? ++$var : ($var = 0) )";
|
||||
}
|
||||
}
|
||||
|
||||
public static function cycle($vals, $index) {
|
||||
/**
|
||||
* Runtime cycle callback
|
||||
* @param mixed $vals
|
||||
* @param $index
|
||||
* @return mixed
|
||||
*/
|
||||
public static function cycle($vals, $index) {
|
||||
return $vals[$index % count($vals)];
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,19 @@ use Cytro;
|
||||
*/
|
||||
class Template extends Render {
|
||||
|
||||
const DENY_ARRAY = 1;
|
||||
const DENY_MODS = 2;
|
||||
/**
|
||||
* Disable array parser.
|
||||
*/
|
||||
const DENY_ARRAY = 1;
|
||||
/**
|
||||
* Disable modifier parser.
|
||||
*/
|
||||
const DENY_MODS = 2;
|
||||
|
||||
const EXTENDED = 0x1000;
|
||||
/**
|
||||
* Template was extended
|
||||
*/
|
||||
const EXTENDED = 0x1000;
|
||||
|
||||
/**
|
||||
* @var int shared counter
|
||||
@ -56,7 +65,6 @@ class Template extends Render {
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $_pos = 0;
|
||||
private $_line = 1;
|
||||
private $_post = array();
|
||||
/**
|
||||
@ -131,25 +139,22 @@ class Template extends Render {
|
||||
* @throws CompileException
|
||||
*/
|
||||
public function compile() {
|
||||
if(!isset($this->_src)) { // already compiled
|
||||
return;
|
||||
}
|
||||
$end = $pos = 0;
|
||||
while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-symbol of tags
|
||||
switch($this->_src[$start + 1]) { // check next character
|
||||
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
|
||||
$pos = $start + 1; // try find tags after the current char
|
||||
$pos = $start + 1;
|
||||
continue 2;
|
||||
case "*": // if comment block
|
||||
$end = strpos($this->_src, '*}', $start) + 1; // finding end of the comment block
|
||||
case "*": // if comments
|
||||
$end = strpos($this->_src, '*}', $start) + 1; // find end of the comment block
|
||||
if($end === false) {
|
||||
throw new CompileException("Unclosed comment block in line {$this->_line}", 0, 1, $this->_name, $this->_line);
|
||||
}
|
||||
$this->_appendText(substr($this->_src, $pos, $start - $pos));
|
||||
$comment = substr($this->_src, $start, $end - $start); // read the comment block for processing
|
||||
$this->_line += substr_count($comment, "\n"); // count skipped lines in comment block
|
||||
unset($comment);
|
||||
$pos = $end + 1; // seek pointer
|
||||
$this->_line += substr_count($comment, "\n"); // count lines in comments
|
||||
unset($comment); // cleanup
|
||||
$pos = $end + 1;
|
||||
continue 2;
|
||||
}
|
||||
$frag = substr($this->_src, $pos, $start - $pos); // variable $frag contains chars after previous '}' and current '{'
|
||||
@ -177,7 +182,7 @@ class Template extends Render {
|
||||
$tokens = new Tokenizer($_tag); // tokenize the tag
|
||||
if($tokens->isIncomplete()) { // all strings finished?
|
||||
$from = $end + 1;
|
||||
goto reparse; // find another close-symbol
|
||||
goto reparse; // need next close-symbol
|
||||
}
|
||||
$this->_appendCode( $this->_tag($tokens) , $tag); // start the tag lexer
|
||||
$pos = $end + 1; // move search-pointer to end of the tag
|
||||
@ -187,7 +192,7 @@ class Template extends Render {
|
||||
|
||||
}
|
||||
}
|
||||
unset($frag);
|
||||
unset($frag, $_tag, $tag); // cleanup
|
||||
}
|
||||
gc_collect_cycles();
|
||||
$this->_appendText(substr($this->_src, $end ? $end + 1 : 0));
|
||||
@ -202,7 +207,7 @@ class Template extends Render {
|
||||
}
|
||||
throw new CompileException("Unclosed tag".(count($_names) == 1 ? "" : "s").": ".implode(", ", $_names), 0, 1, $this->_name, $_line);
|
||||
}
|
||||
unset($this->_src);
|
||||
$this->_src = ""; // cleanup
|
||||
if($this->_post) {
|
||||
foreach($this->_post as $cb) {
|
||||
call_user_func_array($cb, array(&$this->_body, $this));
|
||||
@ -1065,5 +1070,4 @@ class Template extends Render {
|
||||
|
||||
class CompileException extends \ErrorException {}
|
||||
class SecurityException extends CompileException {}
|
||||
class ImproperUseException extends \LogicException {}
|
||||
class ReparseTagException extends \Exception {}
|
||||
class ImproperUseException extends \LogicException {}
|
@ -5,14 +5,14 @@ namespace Cytro;
|
||||
|
||||
class TagsTest extends TestCase {
|
||||
|
||||
// public function _testSandbox() {
|
||||
// try {
|
||||
// var_dump($this->cytro->compileCode(" literal: { \$a} end")->getBody());
|
||||
// } catch(\Exception $e) {
|
||||
// echo "$e";
|
||||
// }
|
||||
// exit;
|
||||
// }
|
||||
public function _testSandbox() {
|
||||
try {
|
||||
var_dump($this->cytro->compileCode('{for $i=0 to=5}{cycle ["one", "two"]}, {/for}')->getBody());
|
||||
} catch(\Exception $e) {
|
||||
echo "$e";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerScalars
|
||||
@ -35,13 +35,22 @@ class TagsTest extends TestCase {
|
||||
$this->assertRender("{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}", "\nVar: ".strtolower("before ".$val." after")."...");
|
||||
}
|
||||
|
||||
public function testCycle() {
|
||||
|
||||
public function testCycle() {
|
||||
$this->assertRender('{for $i=0 to=4}{cycle ["one", "two"]}, {/for}', "one, two, one, two, one, ");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function testCycleIndex() {
|
||||
$this->assertRender('{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}', "two, one, two, one, two, ");
|
||||
}
|
||||
|
||||
public function testFilter() {
|
||||
|
||||
/**
|
||||
* @dataProvider providerScalars
|
||||
*/
|
||||
public function testFilter($tpl_val, $val) {
|
||||
$this->assertRender("{filter|up} before {{$tpl_val}} after {/filter}", strtoupper(" before {$val} after "));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user