Skip to content

Commit

Permalink
Merge pull request #170 from fenom-template/develop
Browse files Browse the repository at this point in the history
2.7.0
  • Loading branch information
bzick committed Jun 3, 2015
2 parents 0511a43 + bf44511 commit 56dfcfc
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 121 deletions.
2 changes: 1 addition & 1 deletion docs/ru/tags/ignore.md
Expand Up @@ -18,7 +18,7 @@
var data = { "time": obj.ts };
```

Так же для игнорирования синтаксиса Fenom можно использовать модификатор `:ignore` для любого блочного тега.
Так же для игнорирования синтаксиса Fenom можно использовать опцию `:ignore` для любого блочного тега.
```smarty
{if:ignore $cdn.yandex}
var item = {cdn: "//yandex.st/"};
Expand Down
8 changes: 5 additions & 3 deletions sandbox/fenom.php
Expand Up @@ -6,7 +6,9 @@
\Fenom::registerAutoload();

$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled');
$fenom->setOptions(Fenom::AUTO_RELOAD);
var_dump($fenom->compileCode('{set $z = "A"~~"B"}')->getBody());
//$fenom->display("blocks/second.tpl", []);
$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_COMPILE);
$fenom->addAccessorSmart('g', 'App::$q->get', Fenom::ACCESSOR_CALL);
var_dump($fenom->compileCode('{$.g("env")}')->getBody());
//var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode());
//var_dump($fenom->display("bug158/main.tpl", []));
// $fenom->getTemplate("problem.tpl");
3 changes: 3 additions & 0 deletions sandbox/templates/bug158/main.tpl
@@ -0,0 +1,3 @@
{* Отображаемый шаблон *}
{import [test] from "bug158/test.tpl" as test}
{test.test}
7 changes: 7 additions & 0 deletions sandbox/templates/bug158/test.tpl
@@ -0,0 +1,7 @@
{* template:test.tpl *}
{macro test($break = false)}
Test macro recursive
{if $break?}
{macro.test break = true}
{/if}
{/macro}
24 changes: 23 additions & 1 deletion src/Fenom.php
Expand Up @@ -18,7 +18,8 @@
*/
class Fenom
{
const VERSION = '2.6';
const VERSION = '2.7';
const REV = 1;
/* Actions */
const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 5;
Expand Down Expand Up @@ -53,6 +54,12 @@ class Fenom

const MAX_MACRO_RECURSIVE = 32;

const ACCESSOR_CUSTOM = null;
const ACCESSOR_VAR = 'Fenom\Accessor::parserVar';
const ACCESSOR_CALL = 'Fenom\Accessor::parserCall';

public static $charset = "UTF-8";

/**
* @var int[] of possible options, as associative array
* @see setOptions
Expand Down Expand Up @@ -805,6 +812,21 @@ public function addAccessor($name, $parser)
return $this;
}

/**
* Add global accessor ($.)
* @param string $name
* @param callable|string $accessor
* @param string $parser
* @return Fenom
*/
public function addAccessorSmart($name, $accessor, $parser) {
$this->_accessors[$name] = array(
"accessor" => $accessor,
"parser" => $parser
);
return $this;
}

/**
* Remove accessor
* @param string $name
Expand Down
12 changes: 12 additions & 0 deletions src/Fenom/Accessor.php
Expand Up @@ -28,10 +28,21 @@ class Accessor {
'env' => '$_ENV'
);

public static function parserVar($var, Tokenizer $tokens, Template $tpl, &$is_var) {
$is_var = true;
return $tpl->parseVariable($tokens, $var);
}


public static function parserCall($call, Tokenizer $tokens, Template $tpl) {
return $call.$tpl->parseArgs($tokens);
}

/**
* Accessor for global variables
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
*/
public static function getVar(Tokenizer $tokens, Template $tpl)
{
Expand All @@ -47,6 +58,7 @@ public static function getVar(Tokenizer $tokens, Template $tpl)
/**
* Accessor for template information
* @param Tokenizer $tokens
* @return string
*/
public static function tpl(Tokenizer $tokens)
{
Expand Down
11 changes: 10 additions & 1 deletion src/Fenom/Compiler.php
Expand Up @@ -741,7 +741,16 @@ public static function toArray($params)
*/
public static function setOpen(Tokenizer $tokens, Tag $scope)
{
$var = $scope->tpl->parseVariable($tokens);
if($tokens->is(T_VARIABLE)) {
$var = $scope->tpl->parseVariable($tokens);
} elseif($tokens->is('$')) {
$var = $scope->tpl->parseAccessor($tokens, $is_var);
if(!$is_var) {
throw new InvalidUsageException("Accessor is not writable");
}
} else {
throw new InvalidUsageException("{set} and {add} accept only variable");
}
$before = $after = "";
if($scope->name == 'add') {
$before = "if(!isset($var)) {\n";
Expand Down
4 changes: 2 additions & 2 deletions src/Fenom/Modifier.php
Expand Up @@ -58,13 +58,13 @@ public static function date($date, $format = "Y m d")
* @param string $charset
* @return string
*/
public static function escape($text, $type = 'html', $charset = 'UTF-8')
public static function escape($text, $type = 'html', $charset = null)
{
switch (strtolower($type)) {
case "url":
return urlencode($text);
case "html";
return htmlspecialchars($text, ENT_COMPAT, $charset);
return htmlspecialchars($text, ENT_COMPAT, $charset ? $charset : \Fenom::$charset);
case "js":
return json_encode($text, 64 | 256); // JSON_UNESCAPED_SLASHES = 64, JSON_UNESCAPED_UNICODE = 256
default:
Expand Down
185 changes: 106 additions & 79 deletions src/Fenom/Template.php
Expand Up @@ -428,9 +428,9 @@ private function _getMacrosArray()
{
if ($this->macros) {
$macros = array();
foreach ($this->macros as $m) {
foreach ($this->macros as $name => $m) {
if ($m["recursive"]) {
$macros[] = "\t\t'" . $m["name"] . "' => function (\$var, \$tpl) {\n?>" . $m["body"] . "<?php\n}";
$macros[] = "\t\t'" . $name . "' => function (\$var, \$tpl) {\n?>" . $m["body"] . "<?php\n}";
}
}
return "array(\n" . implode(",\n", $macros) . ")";
Expand Down Expand Up @@ -490,7 +490,7 @@ public function out($data, $escape = null)
$escape = $this->_options & Fenom::AUTO_ESCAPE;
}
if ($escape) {
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');";
return "echo htmlspecialchars($data, ENT_COMPAT, ".var_export(Fenom::$charset, true).");";
} else {
return "echo $data;";
}
Expand Down Expand Up @@ -782,86 +782,107 @@ public function parseTerm(Tokenizer $tokens, &$is_var = false, $allows = -1)
} else {
$unary = "";
}
if ($tokens->is(T_LNUMBER, T_DNUMBER)) {
$code = $unary . $this->parseScalar($tokens, true);
} elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING, '"', T_ENCAPSED_AND_WHITESPACE)) {
if ($unary) {
throw new UnexpectedTokenException($tokens->back());
}
$code = $this->parseScalar($tokens, true);
} elseif ($tokens->is(T_VARIABLE)) {
$code = $this->parseVariable($tokens);
if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
if ($this->_options & Fenom::DENY_METHODS) {
throw new \LogicException("Forbidden to call methods");
switch($tokens->key()) {
case T_LNUMBER:
case T_DNUMBER:
$code = $unary . $this->parseScalar($tokens, true);
break;
case T_CONSTANT_ENCAPSED_STRING:
case '"':
case T_ENCAPSED_AND_WHITESPACE:
if ($unary) {
throw new UnexpectedTokenException($tokens->back());
}
$code = $unary . $this->parseChain($tokens, $code);
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
if($this->_options & Fenom::FORCE_VERIFY) {
$code = $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)';
$code = $this->parseScalar($tokens, true);
break;
case '$':
$code = $this->parseAccessor($tokens, $is_var);
if(!$is_var) {
$code = $unary . $code;
break;
}
case T_VARIABLE:
if(!isset($code)) {
$code = $this->parseVariable($tokens);
}
if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
if ($this->_options & Fenom::DENY_METHODS) {
throw new \LogicException("Forbidden to call methods");
}
$code = $unary . $this->parseChain($tokens, $code);
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
if($this->_options & Fenom::FORCE_VERIFY) {
$code = $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)';
} else {
$code = $unary . $code . $tokens->getAndNext();
}
} else {
$code = $unary . $code . $tokens->getAndNext();
if($this->_options & Fenom::FORCE_VERIFY) {
$code = $unary . '(isset(' . $code . ') ? ' . $code . ' : null)';
} else {
$is_var = true;
$code = $unary . $code;
}
}
} else {
break;
case T_DEC:
case T_INC:
if($this->_options & Fenom::FORCE_VERIFY) {
$code = $unary . '(isset(' . $code . ') ? ' . $code . ' : null)';
$var = $this->parseVariable($tokens);
$code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)';
} else {
$is_var = true;
$code = $unary . $code;
$code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
}
}
} elseif ($tokens->is('$')) {
$is_var = false;
$code = $unary . $this->parseAccessor($tokens);
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
if($this->_options & Fenom::FORCE_VERIFY) {
$var = $this->parseVariable($tokens);
$code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)';
} else {
$code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
}
} elseif ($tokens->is("(")) {
$tokens->next();
$code = $unary . "(" . $this->parseExpr($tokens) . ")";
$tokens->need(")")->next();
} elseif ($tokens->is(T_STRING)) {
if ($tokens->isSpecialVal()) {
$code = $unary . $tokens->getAndNext();
} elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) {
$func = $this->_fenom->getModifier($modifier = $tokens->current(), $this);
if (!$func) {
throw new \Exception("Function " . $tokens->getAndNext() . " not found");
break;
case '(':
$tokens->next();
$code = $unary . "(" . $this->parseExpr($tokens) . ")";
$tokens->need(")")->next();
break;
case T_STRING:
if ($tokens->isSpecialVal()) {
$code = $unary . $tokens->getAndNext();
} elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) {
$func = $this->_fenom->getModifier($modifier = $tokens->current(), $this);
if (!$func) {
throw new \Exception("Function " . $tokens->getAndNext() . " not found");
}
if (!is_string($func)) { // dynamic modifier
$call = 'call_user_func_array($tpl->getStorage()->getModifier("' . $modifier . '"), array'.$this->parseArgs($tokens->next()).')'; // @todo optimize
} else {
$call = $func . $this->parseArgs($tokens->next());
}
$code = $unary . $this->parseChain($tokens, $call);
} elseif ($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) {
$method = $this->parseStatic($tokens);
$args = $this->parseArgs($tokens);
$code = $unary . $this->parseChain($tokens, $method . $args);
} else {
return false;
}
if (!is_string($func)) { // dynamic modifier
$call = 'call_user_func_array($tpl->getStorage()->getModifier("' . $modifier . '"), array'.$this->parseArgs($tokens->next()).')'; // @todo optimize
break;
case T_ISSET:
case T_EMPTY:
$func = $tokens->getAndNext();
if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
$code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")";
$tokens->need(')')->next();
} else {
$call = $func . $this->parseArgs($tokens->next());
throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables");
}
break;
case '[':
if ($unary) {
throw new UnexpectedTokenException($tokens->back());
}
$code = $this->parseArray($tokens);
break;
default:
if ($unary) {
throw new UnexpectedTokenException($tokens->back());
} else {
return false;
}
$code = $unary . $this->parseChain($tokens, $call);
} elseif ($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) {
$method = $this->parseStatic($tokens);
$args = $this->parseArgs($tokens);
$code = $unary . $this->parseChain($tokens, $method . $args);
} else {
return false;
}
} elseif ($tokens->is(T_ISSET, T_EMPTY)) {
$func = $tokens->getAndNext();
if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
$code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")";
$tokens->need(')')->next();
} else {
throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables");
}
} elseif ($tokens->is('[')) {
if ($unary) {
throw new UnexpectedTokenException($tokens->back());
}
$code = $this->parseArray($tokens);
} elseif ($unary) {
throw new UnexpectedTokenException($tokens->back());
} else {
return false;
}
if (($allows & self::TERM_MODS) && $tokens->is('|')) {
$code = $this->parseModifier($tokens, $code);
Expand Down Expand Up @@ -958,14 +979,20 @@ public function parseVariable(Tokenizer $tokens, $var = null)
/**
* Parse accessor
* @param Tokenizer $tokens
* @param bool $is_var
* @return string
*/
public function parseAccessor(Tokenizer $tokens)
public function parseAccessor(Tokenizer $tokens, &$is_var = false)
{
$accessor = $tokens->need('$')->next()->need('.')->next()->current();
$callback = $this->getStorage()->getAccessor($accessor);
if($callback) {
return call_user_func($callback, $tokens->next(), $this);
$parser = $this->getStorage()->getAccessor($accessor);
$is_var = false;
if($parser) {
if(is_string($parser)) {
return call_user_func_array($parser, array($tokens->next(), $this, &$is_var));
} else {
return call_user_func_array($parser['parser'], array($parser['accessor'], $tokens->next(), $this, &$is_var));
}
} else {
throw new \RuntimeException("Unknown accessor '$accessor'");
}
Expand Down Expand Up @@ -1342,6 +1369,7 @@ public function parseMacroCall(Tokenizer $tokens, $name)
{
$recursive = false;
$macro = false;

if (isset($this->macros[$name])) {
$macro = $this->macros[$name];
$recursive = $macro['recursive'];
Expand Down Expand Up @@ -1414,7 +1442,6 @@ public function parseStatic(Tokenizer $tokens)
* (1 + 2.3, 'string', $var, [2,4])
*
* @param Tokenizer $tokens
* @param bool $as_string
* @return string
*/
public function parseArgs(Tokenizer $tokens)
Expand Down

0 comments on commit 56dfcfc

Please sign in to comment.