added the starts with, ends with, and matches operators
authorFabien Potencier <fabien.potencier@gmail.com>
Sun, 29 Sep 2013 07:40:36 +0000 (09:40 +0200)
committerFabien Potencier <fabien.potencier@gmail.com>
Wed, 2 Oct 2013 19:13:57 +0000 (21:13 +0200)
CHANGELOG
doc/templates.rst
lib/Twig/Extension/Core.php
lib/Twig/Node/Expression/Binary/EndsWith.php [new file with mode: 0644]
lib/Twig/Node/Expression/Binary/Matches.php [new file with mode: 0644]
lib/Twig/Node/Expression/Binary/StartsWith.php [new file with mode: 0644]
test/Twig/Tests/Fixtures/expressions/ends_with.test [new file with mode: 0644]
test/Twig/Tests/Fixtures/expressions/matches.test [new file with mode: 0644]
test/Twig/Tests/Fixtures/expressions/starts_with.test [new file with mode: 0644]

index ab39662..1c03184 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 * 1.14.0 (2013-XX-XX)
 
+ * added new operators: ends with, starts with, and matches
  * fixed some compatibility issues with HHVM
  * added a way to add custom escaping strategies
  * fixed the C extension compilation on Windows
index cad1fff..1894928 100644 (file)
@@ -586,9 +586,9 @@ even if you're not working with PHP you should feel comfortable with it.
 
     The operator precedence is as follows, with the lowest-precedence
     operators listed first: ``b-and``, ``b-xor``, ``b-or``, ``or``, ``and``,
-    ``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``..``, ``+``,
-    ``-``, ``~``, ``*``, ``/``, ``//``, ``%``, ``is``, ``**``, ``|``, ``[]``,
-    and ``.``:
+    ``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``matches``,
+    ``starts with``, ``ends with``, ``..``, ``+``, ``-``, ``~``, ``*``, ``/``,
+    ``//``, ``%``, ``is``, ``**``, ``|``, ``[]``, and ``.``:
 
     .. code-block:: jinja
 
@@ -707,6 +707,27 @@ Comparisons
 The following comparison operators are supported in any expression: ``==``,
 ``!=``, ``<``, ``>``, ``>=``, and ``<=``.
 
+You can also check if a string ``starts with`` or ``ends with`` another
+string:
+
+.. code-block:: jinja
+
+    {% if 'Fabien' starts with 'F' %}
+    {% endif %}
+
+    {% if 'Fabien' ends with 'n' %}
+    {% endif %}
+
+.. note::
+
+    For complex string comparisons, the ``matches`` operator allows you to use
+    `regular expressions`_:
+
+    .. code-block:: jinja
+
+        {% if phone matches '{^[\d\.]+$}' %}
+        {% endif %}
+
 Containment Operator
 ~~~~~~~~~~~~~~~~~~~~
 
@@ -794,8 +815,8 @@ categories:
       {{ foo ? 'yes' : 'no' }}
 
       {# as of Twig 1.12.0 #}
-      {{ foo ?: 'no' }} == {{ foo ? foo : 'no' }}
-      {{ foo ? 'yes' }} == {{ foo ? 'yes' : '' }}
+      {{ foo ?: 'no' }} is the same as {{ foo ? foo : 'no' }}
+      {{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }}
 
 String Interpolation
 ~~~~~~~~~~~~~~~~~~~~
@@ -882,3 +903,4 @@ Extension<creating_extensions>` chapter.
 .. _`other Twig syntax mode`:     https://github.com/muxx/Twig-HTML.mode
 .. _`Notepad++ Twig Highlighter`: https://github.com/Banane9/notepadplusplus-twig
 .. _`web-mode.el`:                http://web-mode.org/
+.. _`regular expressions`:        http://php.net/manual/en/pcre.pattern.php
index f63a249..e03bb3c 100644 (file)
@@ -253,30 +253,33 @@ class Twig_Extension_Core extends Twig_Extension
                 '+'   => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
             ),
             array(
-                'or'     => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'and'    => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'b-or'   => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'b-xor'  => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'b-and'  => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '=='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '!='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '<'      => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '>'      => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '>='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '<='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'in'     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '..'     => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '+'      => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '-'      => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '~'      => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '*'      => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '/'      => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '//'     => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '%'      => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'is'     => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
-                '**'     => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
+                'or'          => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'and'         => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'b-or'        => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'b-xor'       => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'b-and'       => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '=='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '!='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '<'           => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '>'           => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '>='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '<='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'not in'      => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'in'          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'matches'     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'ends with'   => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '..'          => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '+'           => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '-'           => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '~'           => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '*'           => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '/'           => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '//'          => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '%'           => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'is'          => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                'is not'      => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                '**'          => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
             ),
         );
     }
diff --git a/lib/Twig/Node/Expression/Binary/EndsWith.php b/lib/Twig/Node/Expression/Binary/EndsWith.php
new file mode 100644 (file)
index 0000000..5de6c72
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2013 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_Binary_EndsWith extends Twig_Node_Expression_Binary
+{
+    public function compile(Twig_Compiler $compiler)
+    {
+        $compiler
+            ->raw('(0 === substr_compare(')
+            ->subcompile($this->getNode('left'))
+            ->raw(', ')
+            ->subcompile($this->getNode('right'))
+            ->raw(', -strlen(')
+            ->subcompile($this->getNode('right'))
+            ->raw(')))')
+        ;
+    }
+
+    public function operator(Twig_Compiler $compiler)
+    {
+        return $compiler->raw('');
+    }
+}
diff --git a/lib/Twig/Node/Expression/Binary/Matches.php b/lib/Twig/Node/Expression/Binary/Matches.php
new file mode 100644 (file)
index 0000000..93bb292
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2013 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_Binary_Matches extends Twig_Node_Expression_Binary
+{
+    public function compile(Twig_Compiler $compiler)
+    {
+        $compiler
+            ->raw('preg_match(')
+            ->subcompile($this->getNode('right'))
+            ->raw(', ')
+            ->subcompile($this->getNode('left'))
+            ->raw(')')
+        ;
+    }
+
+    public function operator(Twig_Compiler $compiler)
+    {
+        return $compiler->raw('');
+    }
+}
diff --git a/lib/Twig/Node/Expression/Binary/StartsWith.php b/lib/Twig/Node/Expression/Binary/StartsWith.php
new file mode 100644 (file)
index 0000000..eb8c107
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2013 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_Binary_StartsWith extends Twig_Node_Expression_Binary
+{
+    public function compile(Twig_Compiler $compiler)
+    {
+        $compiler
+            ->raw('(0 === strpos(')
+            ->subcompile($this->getNode('left'))
+            ->raw(', ')
+            ->subcompile($this->getNode('right'))
+            ->raw('))')
+        ;
+    }
+
+    public function operator(Twig_Compiler $compiler)
+    {
+        return $compiler->raw('');
+    }
+}
diff --git a/test/Twig/Tests/Fixtures/expressions/ends_with.test b/test/Twig/Tests/Fixtures/expressions/ends_with.test
new file mode 100644 (file)
index 0000000..d259d11
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Twig supports the "ends with" operator
+--TEMPLATE--
+{{ 'foo' ends with 'o' ? 'OK' : 'KO' }}
+{{ not ('foo' ends with 'f') ? 'OK' : 'KO' }}
+{{ not ('foo' ends with 'foowaytoolong') ? 'OK' : 'KO' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
diff --git a/test/Twig/Tests/Fixtures/expressions/matches.test b/test/Twig/Tests/Fixtures/expressions/matches.test
new file mode 100644 (file)
index 0000000..b6c7716
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Twig supports the "matches" operator
+--TEMPLATE--
+{{ 'foo' matches '/o/' ? 'OK' : 'KO' }}
+{{ 'foo' matches '/^fo/' ? 'OK' : 'KO' }}
+{{ 'foo' matches '/O/i' ? 'OK' : 'KO' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
diff --git a/test/Twig/Tests/Fixtures/expressions/starts_with.test b/test/Twig/Tests/Fixtures/expressions/starts_with.test
new file mode 100644 (file)
index 0000000..84004ac
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Twig supports the "starts with" operator
+--TEMPLATE--
+{{ 'foo' starts with 'f' ? 'OK' : 'KO' }}
+{{ not ('foo' starts with 'oo') ? 'OK' : 'KO' }}
+{{ not ('foo' starts with 'foowaytoolong') ? 'OK' : 'KO' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK