Procházet zdrojové kódy

Implement section (and inverted section) filters.

This is a super powerful construct. It's also prolly a violation of the "logic-less-ness" of Mustache templates :(
Justin Hileman před 13 roky
rodič
revize
e739e216a6

+ 17 - 4
src/Mustache/Compiler.php

@@ -178,7 +178,8 @@ class Mustache_Compiler
 
     const SECTION_CALL = '
         // %s section
-        $buffer .= $this->section%s($context, $indent, $context->%s(%s));
+        $value = $context->%s(%s);%s
+        $buffer .= $this->section%s($context, $indent, $value);
     ';
 
     const SECTION = '
@@ -216,6 +217,12 @@ class Mustache_Compiler
      */
     private function section($nodes, $id, $start, $end, $otag, $ctag, $level)
     {
+        $filters = '';
+
+        if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
+            list($id, $filters) = $this->getFilters($id, $level);
+        }
+
         $method   = $this->getFindMethod($id);
         $id       = var_export($id, true);
         $source   = var_export(substr($this->source, $start, $end - $start), true);
@@ -233,12 +240,12 @@ class Mustache_Compiler
             $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $delims, $this->walk($nodes, 2));
         }
 
-        return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $key, $method, $id);
+        return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
     }
 
     const INVERTED_SECTION = '
         // %s inverted section
-        $value = $context->%s(%s);
+        $value = $context->%s(%s);%s
         if (empty($value)) {
             %s
         }';
@@ -254,10 +261,16 @@ class Mustache_Compiler
      */
     private function invertedSection($nodes, $id, $level)
     {
+        $filters = '';
+
+        if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
+            list($id, $filters) = $this->getFilters($id, $level);
+        }
+
         $method = $this->getFindMethod($id);
         $id     = var_export($id, true);
 
-        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $this->walk($nodes, $level));
+        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level));
     }
 
     const PARTIAL = '

+ 101 - 0
test/Mustache/Test/FiveThree/Functional/SectionFiltersTest.php

@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of Mustache.php.
+ *
+ * (c) 2013 Justin Hileman
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * @group filters
+ * @group functional
+ */
+class Mustache_Test_FiveThree_Functional_SectionFiltersTest extends PHPUnit_Framework_TestCase
+{
+
+    private $mustache;
+
+    public function setUp()
+    {
+        $this->mustache = new Mustache_Engine;
+    }
+
+    public function testSingleFilter()
+    {
+        $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{# word | echo }}{{ . }}!{{/ word | echo }}');
+
+        $this->mustache->addHelper('echo', function($value) {
+            return array($value, $value, $value);
+        });
+
+        $this->assertEquals('bacon!bacon!bacon!', $tpl->render(array('word' => 'bacon')));
+    }
+
+    const CHAINED_FILTERS_TPL = <<<EOS
+{{% FILTERS }}
+{{# word | echo | with_index }}
+{{ key }}: {{ value }}
+{{/ word | echo | with_index }}
+EOS;
+
+    public function testChainedFilters()
+    {
+        $tpl = $this->mustache->loadTemplate(self::CHAINED_FILTERS_TPL);
+
+        $this->mustache->addHelper('echo', function($value) {
+            return array($value, $value, $value);
+        });
+
+        $this->mustache->addHelper('with_index', function($value) {
+            return array_map(function($k, $v) {
+                return array(
+                    'key'   => $k,
+                    'value' => $v,
+                );
+            }, array_keys($value), $value);
+        });
+
+        $this->assertEquals("0: bacon\n1: bacon\n2: bacon\n", $tpl->render(array('word' => 'bacon')));
+    }
+
+    public function testInterpolateFirst()
+    {
+        $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}');
+        $this->assertEquals('win!', $tpl->render(array(
+            'foo' => 'FOO',
+            'bar' => function($value) {
+                return ($value === 'FOO') ? 'win!' : 'fail :(';
+            },
+        )));
+    }
+
+    /**
+     * @expectedException Mustache_Exception_UnknownFilterException
+     * @dataProvider getBrokenPipes
+     */
+    public function testThrowsExceptionForBrokenPipes($tpl, $data)
+    {
+        $this->mustache
+            ->loadTemplate(sprintf('{{%% FILTERS }}{{# %s }}{{ . }}{{/ %s }}', $tpl, $tpl))
+                ->render($data);
+    }
+
+    public function getBrokenPipes()
+    {
+        return array(
+            array('foo | bar', array()),
+            array('foo | bar', array('foo' => 'FOO')),
+            array('foo | bar', array('foo' => 'FOO', 'bar' => 'BAR')),
+            array('foo | bar', array('foo' => 'FOO', 'bar' => array(1, 2))),
+            array('foo | bar | baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; })),
+            array('foo | bar | baz', array('foo' => 'FOO', 'baz' => function() { return 'BAZ'; })),
+            array('foo | bar | baz', array('bar' => function() { return 'BAR'; })),
+            array('foo | bar | baz', array('baz' => function() { return 'BAZ'; })),
+            array('foo | bar.baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; }, 'baz' => function() { return 'BAZ'; })),
+        );
+    }
+
+}