Bläddra i källkod

Merge branch 'dev' into feature/pragma-dot-notation

Justin Hileman 15 år sedan
förälder
incheckning
f0d171aed6

+ 24 - 11
Mustache.php

@@ -106,7 +106,7 @@ class Mustache {
 	}
 
 	/**
-	 * Render boolean and enumerable sections.
+	 * Render boolean, enumerable and inverted sections.
 	 *
 	 * @access protected
 	 * @param string $template
@@ -120,23 +120,36 @@ class Mustache {
 
 		$otag  = $this->prepareRegEx($this->otag);
 		$ctag  = $this->prepareRegEx($this->ctag);
-		$regex = '/' . $otag . '\\#(.+?)' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\1' . $ctag . '\\s*/m';
+		$regex = '/' . $otag . '(\\^|\\#)(.+?)' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\2' . $ctag . '\\s*/m';
 
 		$matches = array();
 		while (preg_match($regex, $template, $matches, PREG_OFFSET_CAPTURE)) {
 			$section  = $matches[0][0];
 			$offset   = $matches[0][1];
-			$tag_name = trim($matches[1][0]);
-			$content  = $matches[2][0];
+			$type     = $matches[1][0];
+			$tag_name = trim($matches[2][0]);
+			$content  = $matches[3][0];
 
 			$replace = '';
 			$val = $this->getVariable($tag_name, $context);
-			if (is_array($val)) {
-				foreach ($val as $local_context) {
-					$replace .= $this->_render($content, $this->getContext($context, $local_context));
-				}
-			} else if ($val) {
-				$replace .= $content;
+			switch($type) {
+				// inverted section
+				case '^':
+					if (empty($val)) {
+						$replace .= $content;
+					}
+					break;
+
+				// regular section
+				case '#':
+					if (is_array($val)) {
+						foreach ($val as $local_context) {
+							$replace .= $this->_render($content, $this->getContext($context, $local_context));
+						}
+					} else if ($val) {
+						$replace .= $content;
+					}
+					break;
 			}
 
 			$template = substr_replace($template, $replace, $offset, strlen($section));
@@ -293,7 +306,7 @@ class Mustache {
 
 		$otag  = $this->prepareRegEx($this->otag);
 		$ctag  = $this->prepareRegEx($this->ctag);
-		$this->tagRegEx = '/' . $otag . "(#|\/|=|!|>|\\{|&)?([^\/#]+?)\\1?" . $ctag . "+/";
+		$this->tagRegEx = '/' . $otag . "(#|\/|=|!|>|\\{|&)?([^\/#\^]+?)\\1?" . $ctag . "+/";
 		return '';
 	}
 

+ 0 - 1
README.markdown

@@ -83,7 +83,6 @@ Known Issues
 
  * There's no way to toggle a pragma other than checking out the feature branch for each pragma... Need a clean way to do this.
  * Sections don't respect delimiter changes -- `delimiters` example currently fails with an "unclosed section" exception.
- * Since `complex` example emulates some fancy swizzling available in Ruby, it fails. Need to convert example to PHPisms (In PHP, Mustache class doesn't maintain current context stack -- available context is passed to methods via params).
  * Test coverage is incomplete.
 
 

+ 2 - 2
examples/complex/complex.mustache

@@ -5,9 +5,9 @@
     {{#current}}
       <li><strong>{{name}}</strong></li>
     {{/current}}
-    {{#isLink}}
+    {{^current}}
       <li><a href="{{url}}">{{name}}</a></li>
-    {{/isLink}}
+    {{/current}}
   {{/item}}
   </ul>
 {{/notEmpty}}

+ 0 - 5
examples/complex/complex.php

@@ -9,11 +9,6 @@ class Complex extends Mustache {
 		array('name' => 'blue', 'current' => false, 'url' => '#Blue'),
 	);
 	
-	public function isLink() {
-		// Exploit the fact that the current iteration item is at the top of the context stack.
-		return $this->getVariable('current', $this->context) != true;
-	}
-	
 	public function notEmpty() {
 		return !($this->isEmpty());
 	}

+ 5 - 0
examples/inverted_section/InvertedSection.php

@@ -0,0 +1,5 @@
+<?php
+
+class InvertedSection extends Mustache {
+	public $repo = array();
+}

+ 2 - 0
examples/inverted_section/inverted_section.mustache

@@ -0,0 +1,2 @@
+{{#repo}}<b>{{name}}</b>{{/repo}}
+{{^repo}}No repos :({{/repo}}

+ 1 - 0
examples/inverted_section/inverted_section.txt

@@ -0,0 +1 @@
+No repos :(

+ 110 - 0
test/MustacheTest.php

@@ -0,0 +1,110 @@
+<?php
+
+require_once '../Mustache.php';
+require_once 'PHPUnit/Framework.php';
+
+/**
+ * A PHPUnit test case for Mustache.php.
+ *
+ * This is a very basic, very rudimentary unit test case. It's probably more important to have tests
+ * than to have elegant tests, so let's bear with it for a bit.
+ *
+ * This class assumes an example directory exists at `../examples` with the following structure:
+ *
+ * @code
+ *    examples
+ *        foo
+ *            Foo.php
+ *            foo.mustache
+ *            foo.txt
+ *        bar
+ *            Bar.php
+ *            bar.mustache
+ *            bar.txt
+ * @endcode
+ *
+ * To use this test:
+ *
+ *  1. {@link http://www.phpunit.de/manual/current/en/installation.html Install PHPUnit}
+ *  2. run phpunit from the `test` directory:
+ *        `phpunit MustacheTest`
+ *  3. Fix bugs. Lather, rinse, repeat.
+ *
+ * @extends PHPUnit_Framework_TestCase
+ */
+class MustacheTest extends PHPUnit_Framework_TestCase {
+
+	/**
+	 * Test everything in the `examples` directory.
+	 *
+	 * @dataProvider getExamples
+	 * @access public
+	 * @param mixed $class
+	 * @param mixed $template
+	 * @param mixed $output
+	 * @return void
+	 */
+	public function testExamples($class, $template, $output) {
+		$m = new $class;
+		$this->assertEquals($output, $m->render($template));
+	}
+
+
+	/**
+	 * Data provider for testExamples method.
+	 *
+	 * Assumes that an `examples` directory exists inside parent directory.
+	 * This examples directory should contain any number of subdirectories, each of which contains
+	 * three files: one Mustache class (.php), one Mustache template (.mustache), and one output file
+	 * (.txt).
+	 *
+	 * This whole mess will be refined later to be more intuitive and less prescriptive, but it'll
+	 * do for now. Especially since it means we can have unit tests :)
+	 *
+	 * @access public
+	 * @return array
+	 */
+	public function getExamples() {
+		$basedir = dirname(__FILE__) . '/../examples/';
+
+		$ret = array();
+
+		$files = new RecursiveDirectoryIterator($basedir);
+		while ($files->valid()) {
+
+			if ($files->hasChildren() && $children = $files->getChildren()) {
+				$example  = $files->getSubPathname();
+				$class    = null;
+				$template = null;
+				$output   = null;
+
+				foreach ($children as $file) {
+					if (!$file->isFile()) continue;
+
+					$filename = $file->getPathInfo();
+					$info = pathinfo($filename);
+
+					switch($info['extension']) {
+						case 'php':
+							$class = $info['filename'];
+							include_once($filename);
+							break;
+
+						case 'mustache':
+							$template = file_get_contents($filename);
+							break;
+
+						case 'txt':
+							$output = file_get_contents($filename);
+							break;
+					}
+				}
+
+				$ret[$example] = array($class, $template, $output);
+			}
+
+			$files->next();
+		}
+		return $ret;
+	}
+}