Backward incompatibilities:
* the self special variable has been renamed to _self
+ * the odd and even filters are now tests:
+ {{ foo|odd }} must now be written {{ foo is(odd) }}
+ * added "test" feature (accessible via the "is" operator)
* removed the debug tag (should be done in an extension)
* fixed trans tag when no vars are used in plural form
* fixed race condition when writing template cache
around the arguments, like a function call. This example will join a list by
commas: `{{ list|join(', ') }}`.
-The builtin filters section below describes all the builtin filters.
+The built-in filters section below describes all the built-in filters.
+
+Tests (new in Twig 0.9.9)
+-------------------------
+
+Beside filters, there are also so called "tests" available. Tests can be used
+to test a variable against a common expression. To test a variable or
+expression you add `is` plus the name of the test after the variable. For
+example to find out if a variable is odd, you can do `name is odd` which will
+then return `true` or `false` depending on if `name` is odd or not.
+
+Tests can accept arguments too:
+
+ [twig]
+ {% if loop.index is divisibleby(3) %}
+
+Tests can be negated by prepending them with `not`:
+
+ [twig]
+ {% if loop.index is not divisibleby(3) %}
+
+The built-in tests section below describes all the built-in tests.
Comments
--------
[twig]
{{ foo ? 'yes' : 'no' }}
-List of Builtin Filters
------------------------
+List of built-in Filters
+------------------------
### `date`
{{ string|format(foo, "bar") }}
{# returns I like foo and bar. (if the foo parameter equals to the foo string) #}
-### `even`
-
-The `even` filter returns `true` if the given number is even, `false`
-otherwise:
-
- [twig]
- {{ var|even ? 'even' : 'odd' }}
-
-### `odd`
-
-The `odd` filter returns `true` if the given number is odd, `false`
-otherwise:
-
- [twig]
- {{ var|odd ? 'odd' : 'even' }}
-
### `cycle`
The `cycle` filter can be used to cycle between an array of values:
{{ var|safe }} {# var won't be escaped #}
{% autoescape off %}
+List of built-in Tests (new in Twig 0.9.9)
+------------------------------------------
+
+### `divisibleby`
+
+`divisibleby` checks if a variable is divisible by a number:
+
+ [twig]
+ {% if loop.index is divisibleby(3) %}
+
+### `none`
+
+`none` returns `true` if the variable is `none`:
+
+ [twig]
+ {{ var is none }}
+
+### `even`
+
+`even` returns `true` if the given number is even:
+
+ [twig]
+ {{ var is even }}
+
+### `odd`
+
+`odd` returns `true` if the given number is odd:
+
+ [twig]
+ {{ var is odd }}
+
+### `sameas`
+
+`sameas` checks if a variable points to the same memory address than another
+variable:
+
+ [twig]
+ {% if foo.attribute is sameas(false) %}
+ the foo attribute really is the `false` PHP value
+ {% endif %}
+
Extensions
----------
public function getFilters();
/**
+ * Returns a list of tests to add to the existing list.
+ *
+ * @return array An array of tests
+ */
+ public function getTests();
+
+ /**
* Returns the name of the extension.
*
* @return string The extension name
protected $parsers;
protected $visitors;
protected $filters;
+ protected $tests;
protected $runtimeInitialized;
protected $loadedTemplates;
protected $strictVariables;
return $this->filters;
}
+ public function addTest($name, Twig_TestInterface $test)
+ {
+ if (null === $this->tests) {
+ $this->getTests();
+ }
+
+ $this->tests[$name] = $test;
+ }
+
+ public function getTests()
+ {
+ if (null === $this->tests) {
+ $this->tests = array();
+ foreach ($this->getExtensions() as $extension) {
+ $this->tests = array_merge($this->tests, $extension->getTests());
+ }
+ }
+
+ return $this->tests;
+ }
+
protected function writeCacheFile($file, $content)
{
$tmpFile = tempnam(dirname($file), basename($file));
$node = $this->parseFilterExpression($node);
break;
+ case 'is':
+ $stop = true;
+ $node = $this->parseTestExpression($node);
+ break;
+
default:
$stop = true;
break;
return $node;
}
+ public function parseTestExpression($node)
+ {
+ $stream = $this->parser->getStream();
+ $token = $stream->next();
+ $lineno = $token->getLine();
+
+ $negated = false;
+ if ($stream->test('not')) {
+ $stream->next();
+ $negated = true;
+ }
+
+ $name = $stream->expect(Twig_Token::NAME_TYPE);
+
+ $arguments = null;
+ if ($stream->test(Twig_Token::OPERATOR_TYPE, '(')) {
+ $arguments = $this->parseArguments($node);
+ }
+ $test = new Twig_Node_Expression_Test($node, $name->getValue(), $arguments, $lineno);
+
+ if ($negated) {
+ $test = new Twig_Node_Expression_Unary_Not($test, $lineno);
+ }
+
+ return $test;
+ }
+
public function parseRangeExpression($node)
{
$token = $this->parser->getStream()->next();
{
return array();
}
+
+ /**
+ * Returns a list of tests to add to the existing list.
+ *
+ * @return array An array of tests
+ */
+ public function getTests()
+ {
+ return array();
+ }
}
'date' => new Twig_Filter_Function('twig_date_format_filter'),
'format' => new Twig_Filter_Function('sprintf'),
- // numbers
- 'even' => new Twig_Filter_Function('twig_is_even_filter'),
- 'odd' => new Twig_Filter_Function('twig_is_odd_filter'),
-
// encoding
'urlencode' => new Twig_Filter_Function('twig_urlencode_filter', array('is_escaper' => true)),
}
/**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getTests()
+ {
+ return array(
+ 'even' => new Twig_Test_Function('twig_test_even'),
+ 'odd' => new Twig_Test_Function('twig_test_odd'),
+ //'defined' => new Twig_Test_Function(),
+ 'sameas' => new Twig_Test_Function('twig_test_sameas'),
+ 'none' => new Twig_Test_Function('twig_test_none'),
+ 'divisibleby' => new Twig_Test_Function('twig_test_divisibleby'),
+ );
+ }
+
+ /**
* Returns the name of the extension.
*
* @return string The extension name
return array_reverse($array);
}
-function twig_is_even_filter($value)
-{
- return $value % 2 == 0;
-}
-
-function twig_is_odd_filter($value)
-{
- return $value % 2 == 1;
-}
-
function twig_sort_filter($array)
{
asort($array);
// noop
return $array;
}
+
+function twig_test_sameas($value, $test)
+{
+ return $value === $test;
+}
+
+function twig_test_none($value)
+{
+ return null === $value;
+}
+
+function twig_test_divisibleby($value, $num)
+{
+ return 0 == $value % $num;
+}
+
+function twig_test_even($value)
+{
+ return $value % 2 == 0;
+}
+
+function twig_test_odd($value)
+{
+ return $value % 2 == 1;
+}
public function getFilters();
/**
+ * Returns a list of tests to add to the existing list.
+ *
+ * @return array An array of tests
+ */
+ public function getTests();
+
+ /**
* Returns the name of the extension.
*
* @return string The extension name
const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
const REGEX_STRING = '/(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')/Asm';
- const REGEX_OPERATOR = '/<=? | >=? | [!=]= | = | \/\/ | \.\. | [(){}.,%*\/+~|-] | \[ | \] | \? | \:/Ax';
+ const REGEX_OPERATOR = '/<=? | >=? | [!=]= | = | \/\/ | \.\. | is | [(){}.,%*\/+~|-] | \[ | \] | \? | \:/Ax';
public function __construct(Twig_Environment $env = null, array $options = array())
{
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Test extends Twig_Node_Expression
+{
+ public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
+ {
+ parent::__construct(array('node' => $node, 'arguments' => $arguments), array('name' => $name), $lineno);
+ }
+
+ public function compile($compiler)
+ {
+ $testMap = $compiler->getEnvironment()->getTests();
+ if (!isset($testMap[$this['name']])) {
+ throw new Twig_SyntaxError(sprintf('The test "%s" does not exist', $this['name']), $this->getLine());
+ }
+
+ $compiler
+ ->raw($testMap[$this['name']]->compile().'(')
+ ->subcompile($this->node)
+ ;
+
+ if (null !== $this->arguments) {
+ $compiler->raw(', ');
+
+ $max = count($this->arguments) - 1;
+ foreach ($this->arguments as $i => $node) {
+ $compiler->subcompile($node);
+
+ if ($i != $max) {
+ $compiler->raw(', ');
+ }
+ }
+ }
+
+ $compiler->raw(')');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a function template test.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Test_Function implements Twig_TestInterface
+{
+ protected $function;
+
+ public function __construct($function)
+ {
+ $this->function = $function;
+ }
+
+ public function compile()
+ {
+ return $this->function;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 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.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_TestInterface
+{
+ public function compile();
+}