added support for named arguments for filters, tests, and functions

This commit is contained in:
Fabien Potencier
2012-11-15 07:55:50 +01:00
parent 65637b7968
commit a59dcde3c2
46 changed files with 557 additions and 26 deletions
+1
View File
@@ -1,5 +1,6 @@
* 1.12.0 (2012-XX-XX)
* added support for named arguments for filters, tests, and functions
* moved filters/functions/tests syntax errors to the parser
* added support for extended ternary operator syntaxes
+6
View File
@@ -18,5 +18,11 @@ is the input charset:
them must be installed. In case both are installed, `mbstring`_ is used by
default (Twig before 1.8.1 uses `iconv`_ by default).
Arguments
---------
* ``from``: The input charset
* ``to``: The output charset
.. _`iconv`: http://php.net/iconv
.. _`mbstring`: http://php.net/mbstring
+6
View File
@@ -77,6 +77,12 @@ The default timezone can also be set globally by calling ``setTimezone()``:
$twig = new Twig_Environment($loader);
$twig->getExtension('core')->setTimezone('Europe/Paris');
Arguments
---------
* ``format``: The date format
* ``timezone``: The date timezone
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime
.. _`DateInterval`: http://www.php.net/DateInterval
+5
View File
@@ -14,5 +14,10 @@ The ``date_modify`` filter accepts strings (it must be in a format supported
by the `strtotime`_ function) or `DateTime`_ instances. You can easily combine
it with the :doc:`date<date>` filter for formatting.
Arguments
---------
* ``modifier``: The modifier
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime
+5
View File
@@ -26,3 +26,8 @@ undefined:
Read the documentation for the :doc:`defined<../tests/defined>` and
:doc:`empty<../tests/empty>` tests to learn more about their semantics.
Arguments
---------
* ``default``: The default value
+6
View File
@@ -84,4 +84,10 @@ The ``escape`` filter supports the following escaping strategies:
{{ var|escape(strategy)|raw }} {# won't be double-escaped #}
{% endautoescape %}
Arguments
---------
* ``strategy``: The escaping strategy
* ``charset``: The string charset
.. _`htmlspecialchars`: http://php.net/htmlspecialchars
+5
View File
@@ -16,3 +16,8 @@ define it with the optional first parameter:
{{ [1, 2, 3]|join('|') }}
{# returns 1|2|3 #}
Arguments
---------
* ``glue``: The separator
+5
View File
@@ -11,4 +11,9 @@ The ``json_encode`` filter returns the JSON representation of a string:
Internally, Twig uses the PHP `json_encode`_ function.
Arguments
---------
* ``options``: The options
.. _`json_encode`: http://php.net/json_encode
+7
View File
@@ -35,4 +35,11 @@ These defaults can be easily changed through the core extension:
The defaults set for ``number_format`` can be over-ridden upon each call using the
additional parameters.
Arguments
---------
* ``decimal``: The number of decimal points to display
* ``decimal_point``: The character(s) to use for the decimal point
* ``decimal_sep``: The character(s) to use for the thousands separator
.. _`number_format`: http://php.net/number_format
+5
View File
@@ -11,4 +11,9 @@ The ``replace`` filter formats a given string by replacing the placeholders
{# returns I like foo and bar
if the foo parameter equals to the foo string. #}
Arguments
---------
* ``replace_pairs``: The placeholder values
.. seealso:: :doc:`format<format>`
+7
View File
@@ -52,6 +52,13 @@ up until the end of the variable.
It also works with objects implementing the `Traversable`_ interface.
Arguments
---------
* ``start``: The start of the slice
* ``length``: The size of the slice
* ``preserve_keys``: Whether to preserve key or not (when the input is an array)
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
.. _`array_slice`: http://php.net/array_slice
.. _`substr`: http://php.net/substr
+6
View File
@@ -43,5 +43,11 @@ chunks. Length is set by the ``limit`` argument (one character by default).
Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is
empty) functions for string splitting.
Arguments
---------
* ``delimiter``: The delimiter
* ``limit``: The limit argument
.. _`explode`: http://php.net/explode
.. _`str_split`: http://php.net/str_split
+5
View File
@@ -21,4 +21,9 @@ and end of a string:
Internally, Twig uses the PHP `trim`_ function.
Arguments
---------
* ``character_mask``: The characters to strip
.. _`trim`: http://php.net/trim
+5
View File
@@ -18,3 +18,8 @@ The array can contain any number of values:
{% for i in 0..10 %}
{{ cycle(fruits, i) }}
{% endfor %}
Arguments
---------
* ``position``: The cycle position
+6
View File
@@ -43,4 +43,10 @@ If no argument is passed, the function returns the current date:
$twig = new Twig_Environment($loader);
$twig->getExtension('core')->setTimezone('Europe/Paris');
Arguments
---------
* ``date``: The date
* ``timezone``: The timezone
.. _`date`: http://www.php.net/date
+5
View File
@@ -60,5 +60,10 @@ dumped:
Internally, Twig uses the PHP `var_dump`_ function.
Arguments
---------
* ``context``: The context to dump
.. _`XDebug`: http://xdebug.org/docs/display
.. _`var_dump`: http://php.net/var_dump
+5
View File
@@ -21,4 +21,9 @@ parameter type:
{{ random() }} {# example output: 15386094 (works as native PHP `mt_rand`_ function) #}
{{ random(5) }} {# example output: 3 #}
Arguments
---------
* ``values``: The values
.. _`mt_rand`: http://php.net/mt_rand
+7
View File
@@ -35,4 +35,11 @@ function (with a step of 1):
The ``range`` function works as the native PHP `range`_ function.
Arguments
---------
* ``low``: The first value of the sequence.
* ``high``: The highest possible value of the sequence.
* ``step``: The increment between elements of the sequence.
.. _`range`: http://php.net/range
+5
View File
@@ -25,3 +25,8 @@ The ``template_from_string`` function loads a template from a string:
Even if you will probably always use the ``template_from_string`` function
with the ``include`` tag, you can use it with any tag or function that
takes a template as an argument (like the ``embed`` or ``extends`` tags).
Arguments
---------
* ``template``: The template
+50
View File
@@ -190,6 +190,56 @@ progression of integers:
Go to the :doc:`functions<functions/index>` page to learn more about the
built-in functions.
Named Arguments
---------------
.. versionadded:: 1.12
Support for named arguments was added in Twig 1.12.
Arguments for filters and functions can also be passed as *named arguments*:
.. code-block:: jinja
{% for i in range(low=1, high=10, step=2) %}
{{ i }},
{% endfor %}
Using named arguments makes your templates more explicit about the meaning of
the values you pass as arguments:
.. code-block:: jinja
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
{# versus #}
{{ data|convert_encoding(from='iso-2022-jp', to='UTF-8') }}
Named arguments also allow you to skip some arguments for which you don't want
to change the default value::
.. code-block:: jinja
{# the first argument is the date format, which defaults to the global date format if null is passed #}
{{ "now"|date(null, "Europe/Paris") }}
{# or skip the format value by using a named argument for the timezone #}
{{ "now"|date(timezone="Europe/Paris") }}
You can also use both positional and named arguments in one call, which is not
recommended as it can be confusing:
.. code-block:: jinja
{# both work #}
{{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}
{{ "now"|date(timezone="Europe/Paris", 'd/m/Y H:i') }}
.. tip::
Each function and filter documentation page has a section where the names
of all arguments are listed when supported.
Control Structure
-----------------
+32 -7
View File
@@ -297,9 +297,9 @@ class Twig_ExpressionParser
public function getFunctionNode($name, $line)
{
$args = $this->parseArguments();
switch ($name) {
case 'parent':
$args = $this->parseArguments();
if (!count($this->parser->getBlockStack())) {
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
}
@@ -310,8 +310,9 @@ class Twig_ExpressionParser
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
case 'block':
return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line);
return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
case 'attribute':
$args = $this->parseArguments();
if (count($args) < 2) {
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
@@ -320,7 +321,7 @@ class Twig_ExpressionParser
default:
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new Twig_Node_Expression_Array(array(), $line);
foreach ($args as $n) {
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
@@ -330,6 +331,7 @@ class Twig_ExpressionParser
return $node;
}
$args = $this->parseArguments(true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
@@ -416,7 +418,7 @@ class Twig_ExpressionParser
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = new Twig_Node();
} else {
$arguments = $this->parseArguments();
$arguments = $this->parseArguments(true);
}
$class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
@@ -433,17 +435,40 @@ class Twig_ExpressionParser
return $node;
}
public function parseArguments()
/**
* Parses arguments.
*
* @param Boolean $namedArguments Whether to allow named arguments or not
* @param Boolean $definition Whether we are parsing arguments for a function definition
*/
public function parseArguments($namedArguments = false, $definition = false)
{
$args = array();
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis');
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
if (!empty($args)) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
}
$args[] = $this->parseExpression();
$value = $this->parseExpression();
$name = null;
if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
$token = $stream->next();
if (!$value instanceof Twig_Node_Expression_Name) {
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine());
}
$name = $value->getAttribute('name');
$value = $definition ? $this->parsePrimaryExpression() : $this->parseExpression();
}
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
}
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
+6 -6
View File
@@ -267,7 +267,7 @@ class Twig_Extension_Core extends Twig_Extension
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$arguments = null;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = $parser->getExpressionParser()->parseArguments();
$arguments = $parser->getExpressionParser()->parseArguments(true);
}
$class = $this->getTestNodeClass($parser, $name, $node->getLine());
@@ -305,18 +305,18 @@ class Twig_Extension_Core extends Twig_Extension
/**
* Cycles over a value.
*
* @param ArrayAccess|array $values An array or an ArrayAccess instance
* @param integer $i The cycle value
* @param ArrayAccess|array $values An array or an ArrayAccess instance
* @param integer $position The cycle position
*
* @return string The next value in the cycle
*/
function twig_cycle($values, $i)
function twig_cycle($values, $position)
{
if (!is_array($values) && !$values instanceof ArrayAccess) {
return $values;
}
return $values[$i % count($values)];
return $values[$position % count($values)];
}
/**
@@ -410,7 +410,7 @@ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $
* Returns a new date object modified
*
* <pre>
* {{ post.published_at|modify("-1day")|date("m/d/Y") }}
* {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
+7 -1
View File
@@ -15,7 +15,7 @@
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Twig_Filter implements Twig_FilterInterface
abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
{
protected $options;
protected $arguments = array();
@@ -27,6 +27,7 @@ abstract class Twig_Filter implements Twig_FilterInterface
'needs_context' => false,
'pre_escape' => null,
'preserves_safety' => null,
'callable' => null,
), $options);
}
@@ -72,4 +73,9 @@ abstract class Twig_Filter implements Twig_FilterInterface
{
return $this->options['pre_escape'];
}
public function getCallable()
{
return $this->options['callable'];
}
}
+2
View File
@@ -21,6 +21,8 @@ class Twig_Filter_Function extends Twig_Filter
public function __construct($function, array $options = array())
{
$options['callable'] = $function;
parent::__construct($options);
$this->function = $function;
+2
View File
@@ -22,6 +22,8 @@ class Twig_Filter_Method extends Twig_Filter
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
$options['callable'] = array($extension, $method);
parent::__construct($options);
$this->extension = $extension;
+21
View File
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template filter.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
interface Twig_FilterCallableInterface
{
public function getCallable();
}
+7 -1
View File
@@ -15,7 +15,7 @@
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Twig_Function implements Twig_FunctionInterface
abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
{
protected $options;
protected $arguments = array();
@@ -25,6 +25,7 @@ abstract class Twig_Function implements Twig_FunctionInterface
$this->options = array_merge(array(
'needs_environment' => false,
'needs_context' => false,
'callable' => null,
), $options);
}
@@ -60,4 +61,9 @@ abstract class Twig_Function implements Twig_FunctionInterface
return array();
}
public function getCallable()
{
return $this->options['callable'];
}
}
+2
View File
@@ -22,6 +22,8 @@ class Twig_Function_Function extends Twig_Function
public function __construct($function, array $options = array())
{
$options['callable'] = $function;
parent::__construct($options);
$this->function = $function;
+2
View File
@@ -23,6 +23,8 @@ class Twig_Function_Method extends Twig_Function
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
$options['callable'] = array($extension, $method);
parent::__construct($options);
$this->extension = $extension;
+21
View File
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template function.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
interface Twig_FunctionCallableInterface
{
public function getCallable();
}
+85 -2
View File
@@ -10,7 +10,7 @@
*/
abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
{
public function compileArguments(Twig_Compiler $compiler)
protected function compileArguments(Twig_Compiler $compiler)
{
$compiler->raw('(');
@@ -48,7 +48,11 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
}
if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) {
foreach ($this->getNode('arguments') as $node) {
$callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
}
@@ -59,4 +63,83 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
$compiler->raw(')');
}
protected function getArguments($callable, $arguments)
{
$parameters = array();
$named = false;
foreach ($arguments as $name => $node) {
if (!is_int($name)) {
$named = true;
$name = $this->normalizeName($name);
}
$parameters[$name] = $node;
}
if (!$named) {
return $parameters;
}
if (!$callable) {
throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
}
// manage named arguments
if (is_array($callable)) {
$r = new \ReflectionMethod($callable[0], $callable[1]);
} elseif (is_object($callable) && !$callable instanceof \Closure) {
$r = new \ReflectionObject($callable);
$r = $r->getMethod('__invoke');
} else {
$r = new \ReflectionFunction($callable);
}
$definition = $r->getParameters();
if ($this->hasNode('node')) {
array_shift($definition);
}
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
array_shift($definition);
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
array_shift($definition);
}
if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
array_shift($definition);
}
}
$arguments = array();
$pos = 0;
foreach ($definition as $param) {
$name = $this->normalizeName($param->name);
if (array_key_exists($name, $parameters)) {
$arguments[] = $parameters[$name];
unset($parameters[$name]);
} elseif (array_key_exists($pos, $parameters)) {
$arguments[] = $parameters[$pos];
unset($parameters[$pos]);
++$pos;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
} elseif ($param->isOptional()) {
break;
} else {
throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
}
foreach (array_keys($parameters) as $name) {
throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
return $arguments;
}
protected function normalizeName($name)
{
return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name));
}
}
+7 -1
View File
@@ -18,13 +18,19 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression_Call
public function compile(Twig_Compiler $compiler)
{
$filter = $compiler->getEnvironment()->getFilter($this->getNode('filter')->getAttribute('value'));
$name = $this->getNode('filter')->getAttribute('value');
$filter = $compiler->getEnvironment()->getFilter($name);
$compiler->raw($filter->compile());
$this->setAttribute('name', $name);
$this->setAttribute('type', 'filter');
$this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments());
if ($filter instanceof Twig_FilterCallableInterface) {
$this->setAttribute('callable', $filter->getCallable());
}
$this->compileArguments($compiler);
}
+7 -1
View File
@@ -17,13 +17,19 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression_Call
public function compile(Twig_Compiler $compiler)
{
$function = $compiler->getEnvironment()->getFunction($this->getAttribute('name'));
$name = $this->getAttribute('name');
$function = $compiler->getEnvironment()->getFunction($name);
$compiler->raw($function->compile());
$this->setAttribute('name', $name);
$this->setAttribute('type', 'function');
$this->setAttribute('needs_environment', $function->needsEnvironment());
$this->setAttribute('needs_context', $function->needsContext());
$this->setAttribute('arguments', $function->getArguments());
if ($function instanceof Twig_FunctionCallableInterface) {
$this->setAttribute('callable', $function->getCallable());
}
$this->compileArguments($compiler);
}
+8 -1
View File
@@ -19,8 +19,15 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression_Call
{
$name = $this->getAttribute('name');
$testMap = $compiler->getEnvironment()->getTests();
$test = $testMap[$name];
$compiler->raw($testMap[$name]->compile());
$this->setAttribute('name', $name);
$this->setAttribute('type', 'test');
if ($test instanceof Twig_TestCallableInterface) {
$this->setAttribute('callable', $test->getCallable());
}
$compiler->raw($test->compile());
$this->compileArguments($compiler);
}
+34
View File
@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template test.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface
{
protected $options;
protected $arguments = array();
public function __construct(array $options = array())
{
$this->options = array_merge(array(
'callable' => null,
), $options);
}
public function getCallable()
{
return $this->options['callable'];
}
}
+6 -2
View File
@@ -15,12 +15,16 @@
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Test_Function implements Twig_TestInterface
class Twig_Test_Function extends Twig_Test
{
protected $function;
public function __construct($function)
public function __construct($function, array $options = array())
{
$options['callable'] = $function;
parent::__construct($options);
$this->function = $function;
}
+6 -2
View File
@@ -15,13 +15,17 @@
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Test_Method implements Twig_TestInterface
class Twig_Test_Method extends Twig_Test
{
protected $extension;
protected $method;
public function __construct(Twig_ExtensionInterface $extension, $method)
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
$options['callable'] = array($extension, $method);
parent::__construct($options);
$this->extension = $extension;
$this->method = $method;
}
+4 -2
View File
@@ -15,12 +15,14 @@
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Test_Node implements Twig_TestInterface
class Twig_Test_Node extends Twig_Test
{
protected $class;
public function __construct($class)
public function __construct($class, array $options = array())
{
parent::__construct($options);
$this->class = $class;
}
+21
View File
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template test.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
interface Twig_TestCallableInterface
{
public function getCallable();
}
+22
View File
@@ -215,6 +215,28 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
);
}
/**
* @expectedException Twig_Error_Syntax
*/
public function testAttributeCallDoesNotSupportNamedArguments()
{
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$parser = new Twig_Parser($env);
$parser->parse($env->tokenize('{{ foo.bar(name="Foo") }}', 'index'));
}
/**
* @expectedException Twig_Error_Syntax
*/
public function testMacroCallDoesNotSupportNamedArguments()
{
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$parser = new Twig_Parser($env);
$parser->parse($env->tokenize('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index'));
}
/**
* @expectedException Twig_Error_Syntax
* @expectedExceptionMessage The function "cycl" does not exist. Did you mean "cycle" in "index" at line 1
@@ -0,0 +1,15 @@
--TEST--
"date" filter
--TEMPLATE--
{{ date|date(format='d/m/Y H:i:s P', timezone='America/Chicago') }}
{{ date|date(timezone='America/Chicago', format='d/m/Y H:i:s P') }}
{{ date|date(timezone='America/Chicago', 'd/m/Y H:i:s P') }}
{{ date|date('d/m/Y H:i:s P', timezone='America/Chicago') }}
--DATA--
date_default_timezone_set('UTC');
return array('date' => mktime(13, 45, 0, 10, 4, 2010))
--EXPECT--
04/10/2010 08:45:00 -05:00
04/10/2010 08:45:00 -05:00
04/10/2010 08:45:00 -05:00
04/10/2010 08:45:00 -05:00
@@ -4,9 +4,15 @@
{{ [1, 2, 3, 4]|reverse|join('') }}
{{ '1234évènement'|reverse }}
{{ arr|reverse|join('') }}
{{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }}
{{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }}
{{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }}
--DATA--
return array('arr' => new ArrayObject(array(1, 2, 3, 4)))
--EXPECT--
4321
tnemenèvé4321
4321
a,c
a,c
a,c
@@ -0,0 +1,11 @@
--TEST--
"date" function
--TEMPLATE--
{{ date(date, "America/New_York")|date('d/m/Y H:i:s P', false) }}
{{ date(timezone="America/New_York", date=date)|date('d/m/Y H:i:s P', false) }}
--DATA--
date_default_timezone_set('UTC');
return array('date' => mktime(13, 45, 0, 10, 4, 2010))
--EXPECT--
04/10/2010 09:45:00 -04:00
04/10/2010 09:45:00 -04:00
@@ -0,0 +1,8 @@
--TEST--
"range" function
--TEMPLATE--
{{ range(low=0+1, high=10+0, step=2)|join(',') }}
--DATA--
return array()
--EXPECT--
1,3,5,7,9
@@ -49,9 +49,65 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Test_NodeTestCase
$tests[] = array($node, 'twig_number_format_filter($this->env, strtoupper("foo"), 2, ".", ",")');
}
// named arguments
$date = new Twig_Node_Expression_Constant(0, 1);
$node = $this->createFilter($date, 'date', array(
'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
'format' => new Twig_Node_Expression_Constant('d/m/Y H:i:s P', 1),
));
$tests[] = array($node, 'twig_date_format_filter($this->env, 0, "d/m/Y H:i:s P", "America/Chicago")');
// skip an optional argument
$date = new Twig_Node_Expression_Constant(0, 1);
$node = $this->createFilter($date, 'date', array(
'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
));
$tests[] = array($node, 'twig_date_format_filter($this->env, 0, null, "America/Chicago")');
// underscores vs camelCase for named arguments
$string = new Twig_Node_Expression_Constant('abc', 1);
$node = $this->createFilter($string, 'reverse', array(
'preserve_keys' => new Twig_Node_Expression_Constant(true, 1),
));
$tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)');
$node = $this->createFilter($string, 'reverse', array(
'preserveKeys' => new Twig_Node_Expression_Constant(true, 1),
));
$tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)');
return $tests;
}
/**
* @expectedException Twig_Error_Syntax
* @expectedExceptionMessage Unknown argument "foobar" for filter "date".
*/
public function testCompileWithWrongNamedArgumentName()
{
$date = new Twig_Node_Expression_Constant(0, 1);
$node = $this->createFilter($date, 'date', array(
'foobar' => new Twig_Node_Expression_Constant('America/Chicago', 1),
));
$compiler = $this->getCompiler();
$compiler->compile($node);
}
/**
* @expectedException Twig_Error_Syntax
* @expectedExceptionMessage Value for argument "from" is required for filter "replace".
*/
public function testCompileWithMissingNamedArgument()
{
$value = new Twig_Node_Expression_Constant(0, 1);
$node = $this->createFilter($value, 'replace', array(
'to' => new Twig_Node_Expression_Constant('foo', 1),
));
$compiler = $this->getCompiler();
$compiler->compile($node);
}
protected function createFilter($node, $name, array $arguments = array())
{
$name = new Twig_Node_Expression_Constant($name, 1);
@@ -67,6 +67,13 @@ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Test_NodeTestCase
$node = $this->createFunction('foobar', array(new Twig_Node_Expression_Constant('bar', 1)));
$tests[] = array($node, 'foobar($this->env, $context, "bar")', $environment);
// named arguments
$node = $this->createFunction('date', array(
'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
'date' => new Twig_Node_Expression_Constant(0, 1),
));
$tests[] = array($node, 'twig_date_converter($this->env, 0, "America/Chicago")');
return $tests;
}