diff --git a/CHANGELOG b/CHANGELOG index 5471e2943..8922b1700 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.40.0 (2019-XX-XX) + * added the "apply" tag as a replacement for the "filter" tag * allowed Twig\Loader\FilesystemLoader::findTemplate() to return "null" instead of "false" (same meaning) * added support for "Twig\Markup" instances in the "in" test * fixed Lexer when using custom options containing the # char diff --git a/doc/advanced.rst b/doc/advanced.rst index 9cf9ca6c1..3580073d4 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -400,16 +400,21 @@ Most of the time though, a tag is not needed: {{ '**markdown** text'|markdown }} If you want use this filter on large amounts of text, wrap it with the - :doc:`filter ` tag: + :doc:`apply ` tag: .. code-block:: jinja - {% filter markdown %} + {% apply markdown %} Title ===== Much better than creating a tag as you can **compose** filters. - {% endfilter %} + {% endapply %} + + .. note:: + + The ``apply`` tag was introduced in Twig 1.40; use the ``filter`` tag with + previous versions. * If your tag does not output anything, but only exists because of a side effect, create a **function** that returns nothing and call it via the diff --git a/doc/filters/spaceless.rst b/doc/filters/spaceless.rst index 5a88e87cc..f87ccfd26 100644 --- a/doc/filters/spaceless.rst +++ b/doc/filters/spaceless.rst @@ -18,19 +18,24 @@ whitespace within HTML tags or whitespace in plain text: {# output will be
foo
#} -You can combine ``spaceless`` with the ``filter`` tag to apply the -transformation on large amounts of HTML: +You can combine ``spaceless`` with the ``apply`` tag to apply the transformation +on large amounts of HTML: .. code-block:: jinja - {% filter spaceless %} + {% apply spaceless %}
foo
- {% endfilter %} + {% endapply %} {# output will be
foo
#} +.. note:: + + The ``apply`` tag was introduced in Twig 1.40; use the ``filter`` tag with + previous versions. + This tag is not meant to "optimize" the size of the generated HTML content but merely to avoid extra whitespace between HTML tags to avoid browser rendering quirks under some circumstances. diff --git a/doc/tags/apply.rst b/doc/tags/apply.rst new file mode 100644 index 000000000..cfb1398e4 --- /dev/null +++ b/doc/tags/apply.rst @@ -0,0 +1,20 @@ +``apply`` +========= + +The ``apply`` tag allows you to apply Twig filters on a block of template data: + +.. code-block:: jinja + + {% apply upper %} + This text becomes uppercase + {% endapply %} + +You can also chain filters and pass arguments to them: + +.. code-block:: jinja + + {% apply lower|escape('html') %} + SOME TEXT + {% endapply %} + + {# outputs "<strong>some text</strong>" #} diff --git a/doc/tags/filter.rst b/doc/tags/filter.rst index 90d7b472e..1cd02a37f 100644 --- a/doc/tags/filter.rst +++ b/doc/tags/filter.rst @@ -1,6 +1,11 @@ ``filter`` ========== +.. note:: + + As of Twig 1.40, you should use the ``apply`` tag instead which does the + same thing except that the wrapped template data is not scoped. + Filter sections allow you to apply regular Twig filters on a block of template data. Just wrap the code in the special ``filter`` section: diff --git a/doc/templates.rst b/doc/templates.rst index adebd9a14..4b3cbd2c0 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -167,17 +167,22 @@ example will join a list by commas: {{ list|join(', ') }} To apply a filter on a section of code, wrap it in the -:doc:`filter` tag: +:doc:`apply` tag: .. code-block:: jinja - {% filter upper %} + {% apply upper %} This text becomes uppercase - {% endfilter %} + {% endapply %} Go to the :doc:`filters` page to learn more about built-in filters. +.. note:: + + The ``apply`` tag was introduced in Twig 1.40; use the ``filter`` tag with + previous versions. + Functions --------- @@ -895,14 +900,19 @@ the modifiers on one side of a tag or on both sides: .. code-block:: jinja - {% filter spaceless %} + {% apply spaceless %}
foo bar
- {% endfilter %} + {% endapply %} {# output will be
foo bar
#} + .. note:: + + The ``apply`` tag was introduced in Twig 1.40; use the ``filter`` tag with + previous versions. + Extensions ---------- diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index e47e7cef1..b0362a70d 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -11,6 +11,7 @@ namespace Twig\Extension { use Twig\ExpressionParser; +use Twig\TokenParser\ApplyTokenParser; use Twig\TokenParser\BlockTokenParser; use Twig\TokenParser\DeprecatedTokenParser; use Twig\TokenParser\DoTokenParser; @@ -139,6 +140,7 @@ class CoreExtension extends AbstractExtension public function getTokenParsers() { return [ + new ApplyTokenParser(), new ForTokenParser(), new IfTokenParser(), new ExtendsTokenParser(), diff --git a/src/NodeVisitor/OptimizerNodeVisitor.php b/src/NodeVisitor/OptimizerNodeVisitor.php index e5ea9b7cc..164fd49ec 100644 --- a/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/src/NodeVisitor/OptimizerNodeVisitor.php @@ -111,6 +111,7 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor if (!$expression && 'Twig_Node' !== \get_class($node) && $prependedNodes = array_shift($this->prependedNodes)) { $nodes = []; foreach (array_unique($prependedNodes) as $name) { + die(); $nodes[] = new SetTempNode($name, $node->getTemplateLine()); } diff --git a/src/TokenParser/ApplyTokenParser.php b/src/TokenParser/ApplyTokenParser.php new file mode 100644 index 000000000..879879a2b --- /dev/null +++ b/src/TokenParser/ApplyTokenParser.php @@ -0,0 +1,58 @@ +getLine(); + $name = $this->parser->getVarName(); + + $ref = new TempNameExpression($name, $lineno); + $ref->setAttribute('always_defined', true); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new Node([ + new SetNode(true, $ref, $body, $lineno, $this->getTag()), + new PrintNode($filter, $lineno, $this->getTag()), + ]); + } + + public function decideApplyEnd(Token $token) + { + return $token->test('endapply'); + } + + public function getTag() + { + return 'apply'; + } +} diff --git a/test/Twig/Tests/Fixtures/tags/apply/basic.test b/test/Twig/Tests/Fixtures/tags/apply/basic.test new file mode 100644 index 000000000..4848ee025 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/basic.test @@ -0,0 +1,10 @@ +--TEST-- +"apply" tag applies a filter on its children +--TEMPLATE-- +{% apply upper %} +Some text with a {{ var }} +{% endapply %} +--DATA-- +return ['var' => 'var'] +--EXPECT-- +SOME TEXT WITH A VAR diff --git a/test/Twig/Tests/Fixtures/tags/apply/json_encode.test b/test/Twig/Tests/Fixtures/tags/apply/json_encode.test new file mode 100644 index 000000000..8a590b44a --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/json_encode.test @@ -0,0 +1,8 @@ +--TEST-- +"apply" tag applies a filter on its children +--TEMPLATE-- +{% apply json_encode|raw %}test{% endapply %} +--DATA-- +return [] +--EXPECT-- +"test" diff --git a/test/Twig/Tests/Fixtures/tags/apply/multiple.test b/test/Twig/Tests/Fixtures/tags/apply/multiple.test new file mode 100644 index 000000000..e16998a52 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/multiple.test @@ -0,0 +1,10 @@ +--TEST-- +"apply" tags accept multiple chained filters +--TEMPLATE-- +{% apply lower|title %} + {{ var }} +{% endapply %} +--DATA-- +return ['var' => 'VAR'] +--EXPECT-- + Var diff --git a/test/Twig/Tests/Fixtures/tags/apply/nested.test b/test/Twig/Tests/Fixtures/tags/apply/nested.test new file mode 100644 index 000000000..b64a6914c --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/nested.test @@ -0,0 +1,16 @@ +--TEST-- +"apply" tags can be nested at will +--TEMPLATE-- +{% apply lower|title %} + {{ var }} + {% apply upper %} + {{ var }} + {% endapply %} + {{ var }} +{% endapply %} +--DATA-- +return ['var' => 'var'] +--EXPECT-- + Var + Var + Var diff --git a/test/Twig/Tests/Fixtures/tags/apply/scope.test b/test/Twig/Tests/Fixtures/tags/apply/scope.test new file mode 100644 index 000000000..a87ff9116 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/scope.test @@ -0,0 +1,15 @@ +--TEST-- +"apply" tag does not create a new scope +--TEMPLATE-- +{% set foo = 'baz' %} +{% apply spaceless %} + {% set foo = 'foo' %} + {% set bar = 'bar' %} +{% endapply %} +{{ 'foo' == foo ? 'OK ' ~ foo : 'KO' }} +{{ 'bar' == bar ? 'OK ' ~ bar : 'KO' }} +--DATA-- +return [] +--EXPECT-- +OK foo +OK bar diff --git a/test/Twig/Tests/Fixtures/tags/apply/with_for_tag.test b/test/Twig/Tests/Fixtures/tags/apply/with_for_tag.test new file mode 100644 index 000000000..4453880b5 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/with_for_tag.test @@ -0,0 +1,13 @@ +--TEST-- +"apply" tag applies the filter on "for" tags +--TEMPLATE-- +{% apply upper %} +{% for item in items %} +{{ item }} +{% endfor %} +{% endapply %} +--DATA-- +return ['items' => ['a', 'b']] +--EXPECT-- +A +B diff --git a/test/Twig/Tests/Fixtures/tags/apply/with_if_tag.test b/test/Twig/Tests/Fixtures/tags/apply/with_if_tag.test new file mode 100644 index 000000000..ca7a592cb --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/apply/with_if_tag.test @@ -0,0 +1,29 @@ +--TEST-- +"apply" tag applies the filter on "if" tags +--TEMPLATE-- +{% apply upper %} +{% if items %} +{{ items|join(', ') }} +{% endif %} + +{% if items.3 is defined %} +FOO +{% else %} +{{ items.1 }} +{% endif %} + +{% if items.3 is defined %} +FOO +{% elseif items.1 %} +{{ items.0 }} +{% endif %} + +{% endapply %} +--DATA-- +return ['items' => ['a', 'b']] +--EXPECT-- +A, B + +B + +A diff --git a/test/Twig/Tests/Fixtures/tags/filter/scope.test b/test/Twig/Tests/Fixtures/tags/filter/scope.test new file mode 100644 index 000000000..889a46f6a --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/filter/scope.test @@ -0,0 +1,11 @@ +--TEST-- +"scope" tag creates a new scope +--TEMPLATE-- +{% filter spaceless %} +{% set item = 'foo' %} +{% endfilter %} +{{ item }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: Variable "item" does not exist in "index.twig" at line 5.