Răsfoiți Sursa

Merge branch 'dev' into feature/test-coverage

Conflicts:
	Mustache.php
Justin Hileman 15 ani în urmă
părinte
comite
22864870ce

+ 111 - 2
Mustache.php

@@ -25,11 +25,18 @@ class Mustache {
 	// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8.
 	protected $charset = 'UTF-8';
 
+	const PRAGMA_DOT_NOTATION = 'DOT-NOTATION';
+
 	protected $tagRegEx;
 
 	protected $template = '';
 	protected $context  = array();
 	protected $partials = array();
+	protected $pragmas  = array();
+
+	protected $pragmasImplemented = array(
+		self::PRAGMA_DOT_NOTATION
+	);
 
 	/**
 	 * Mustache class constructor.
@@ -101,6 +108,7 @@ class Mustache {
 	 * @return string Rendered Mustache template.
 	 */
 	protected function _render($template, &$context) {
+		$template = $this->renderPragmas($template, $context);
 		$template = $this->renderSection($template, $context);
 		return $this->renderTags($template, $context);
 	}
@@ -158,6 +166,74 @@ class Mustache {
 		return $template;
 	}
 
+	/**
+	 * Initialize pragmas and remove all pragma tags.
+	 *
+	 * @access protected
+	 * @param string $template
+	 * @param array &$context
+	 * @return string
+	 */
+	protected function renderPragmas($template, &$context) {
+		// no pragmas
+		if (strpos($template, $this->otag . '%') === false) {
+			return $template;
+		}
+
+		$otag = $this->prepareRegEx($this->otag);
+		$ctag = $this->prepareRegEx($this->ctag);
+		$regex = '/' . $otag . '%([\\w_-]+)((?: [\\w]+=[\\w]+)*)' . $ctag . '\\n?/';
+		return preg_replace_callback($regex, array($this, 'renderPragma'), $template);
+	}
+
+	/**
+	 * A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma.
+	 *
+	 * @access protected
+	 * @param mixed $matches
+	 * @return void
+	 * @throws MustacheException unknown pragma
+	 */
+	protected function renderPragma($matches) {
+		$pragma         = $matches[0];
+		$pragma_name    = $matches[1];
+		$options_string = $matches[2];
+
+		if (!in_array($pragma_name, $this->pragmasImplemented)) {
+			throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
+		}
+
+		$options = array();
+		foreach (explode(' ', trim($options_string)) as $o) {
+			if ($p = trim($o)) {
+				$p = explode('=', trim($p));
+				$options[$p[0]] = $p[1];
+			}
+		}
+
+		if (empty($options)) {
+			$this->pragmas[$pragma_name] = true;
+		} else {
+			$this->pragmas[$pragma_name] = $options;
+		}
+
+		return '';
+	}
+
+	protected function hasPragma($pragma_name) {
+		if (array_key_exists($pragma_name, $this->pragmas) && $this->pragmas[$pragma_name]) {
+			return true;
+		}
+	}
+
+	protected function getPragmaOptions($pragma_name) {
+		if (!$this->hasPragma()) {
+			throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
+		}
+
+		return $this->pragmas[$pragma_name];
+	}
+
 	/**
 	 * Loop through and render individual Mustache tags.
 	 *
@@ -171,9 +247,11 @@ class Mustache {
 			return $template;
 		}
 
-		$otag  = $this->prepareRegEx($this->otag);
-		$ctag  = $this->prepareRegEx($this->ctag);
+		$otag = $this->prepareRegEx($this->otag);
+		$ctag = $this->prepareRegEx($this->ctag);
+
 		$this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/";
+
 		$html = '';
 		$matches = array();
 		while (preg_match($this->tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
@@ -347,6 +425,33 @@ class Mustache {
 	 * @return string
 	 */
 	protected function getVariable($tag_name, &$context) {
+		if ($this->hasPragma(self::PRAGMA_DOT_NOTATION)) {
+			$chunks = explode('.', $tag_name);
+			$first = array_shift($chunks);
+
+			$ret = $this->_getVariable($first, $context);
+			while ($next = array_shift($chunks)) {
+				// Slice off a chunk of context for dot notation traversal.
+				$c = array($ret);
+				$ret = $this->_getVariable($next, $c);
+			}
+			return $ret;
+		} else {
+			return $this->_getVariable($tag_name, $context);
+		}
+	}
+
+	/**
+	 * Get a variable from the context array. Internal helper used by getVariable() to abstract
+	 * variable traversal for dot notation.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param array &$context
+	 * @throws MustacheException Unknown variable name.
+	 * @return string
+	 */
+	protected function _getVariable($tag_name, &$context) {
 		foreach ($context as $view) {
 			if (is_object($view)) {
 				if (isset($view->$tag_name)) {
@@ -439,4 +544,8 @@ class MustacheException extends Exception {
 	// with no associated partial.
 	const UNKNOWN_PARTIAL          = 3;
 
+	// An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears
+	// which can't be handled by this Mustache instance.
+	const UNKNOWN_PRAGMA           = 4;
+
 }

+ 2 - 0
README.markdown

@@ -81,6 +81,8 @@ And render it:
 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.
  * Test coverage is incomplete.

+ 19 - 0
examples/dot_notation/DotNotation.php

@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * DotNotation example class. Uses DOT_NOTATION pragma.
+ *
+ * @extends Mustache
+ */
+class DotNotation extends Mustache {
+	public $person = array(
+		'name' => array('first' => 'Chris', 'last' => 'Firescythe'),
+		'age' => 24,
+		'hometown' => array(
+			'city'  => 'Cincinnati',
+			'state' => 'OH',
+		)
+	);
+
+	public $normal = 'Normal';
+}

+ 5 - 0
examples/dot_notation/dot_notation.mustache

@@ -0,0 +1,5 @@
+{{%DOT-NOTATION}}
+* {{person.name.first}} {{person.name.last}}
+* {{person.age}}
+* {{person.hometown.city}}, {{person.hometown.state}}
+* {{normal}}

+ 4 - 0
examples/dot_notation/dot_notation.txt

@@ -0,0 +1,4 @@
+* Chris Firescythe
+* 24
+* Cincinnati, OH
+* Normal

+ 34 - 0
test/MustachePragmaDotNotationTest.php

@@ -0,0 +1,34 @@
+<?php
+
+require_once '../Mustache.php';
+require_once 'PHPUnit/Framework.php';
+
+class MustachePragmaDotNotationTest extends PHPUnit_Framework_TestCase {
+
+	public function testDotTraversal() {
+		$m = new Mustache('', array('foo' => array('bar' => 'this worked')));
+
+		$this->assertEquals($m->render('{{foo.bar}}'), '',
+			'Dot notation not enabled, variable should have been replaced with nothing');
+		$this->assertEquals($m->render('{{%DOT-NOTATION}}{{foo.bar}}'), 'this worked',
+			'Dot notation enabled, variable should have been replaced by "this worked"');
+	}
+
+	public function testDeepTraversal() {
+		$data = array(
+			'foo' => array('bar' => array('baz' => array('qux' => array('quux' => 'WIN!')))),
+			'a' => array('b' => array('c' => array('d' => array('e' => 'abcs')))),
+			'one' => array(
+				'one'   => 'one-one',
+				'two'   => 'one-two',
+				'three' => 'one-three',
+			),
+		);
+
+		$m = new Mustache('', $data);
+		$this->assertEquals($m->render('{{%DOT-NOTATION}}{{foo.bar.baz.qux.quux}}'), 'WIN!');
+		$this->assertEquals($m->render('{{%DOT-NOTATION}}{{a.b.c.d.e}}'), 'abcs');
+		$this->assertEquals($m->render('{{%DOT-NOTATION}}{{one.one}}|{{one.two}}|{{one.three}}'), 'one-one|one-two|one-three');
+	}
+
+}

+ 39 - 0
test/MustachePragmaTest.php

@@ -0,0 +1,39 @@
+<?php
+
+require_once '../Mustache.php';
+require_once 'PHPUnit/Framework.php';
+
+class MustachePragmaTest extends PHPUnit_Framework_TestCase {
+
+	public function testUnknownPragmaException() {
+		$m = new Mustache();
+
+		try {
+			$m->render('{{%I-HAVE-THE-GREATEST-MUSTACHE}}');
+		} catch (MustacheException $e) {
+			$this->assertEquals(MustacheException::UNKNOWN_PRAGMA, $e->getCode(), 'Caught exception code was not MustacheException::UNKNOWN_PRAGMA');
+			return;
+		}
+
+		$this->fail('Mustache should have thrown an unknown pragma exception');
+	}
+
+	public function testPragmaReplace() {
+		$m = new Mustache();
+		$this->assertEquals($m->render('{{%DOT-NOTATION}}'), '', 'Pragma tag not removed');
+	}
+
+	public function testPragmaReplaceMultiple() {
+		$m = new Mustache();
+		$this->assertEquals($m->render("{{%DOT-NOTATION}}\n{{%DOT-NOTATION}}"), '', 'Multiple pragma tags not removed');
+		$this->assertEquals($m->render('{{%DOT-NOTATION}} {{%DOT-NOTATION}}'), ' ', 'Multiple pragma tags not removed');
+	}
+
+	public function testPragmaReplaceNewline() {
+		$m = new Mustache();
+		$this->assertEquals($m->render("{{%DOT-NOTATION}}\n"), '', 'Trailing newline after pragma tag not removed');
+		$this->assertEquals($m->render("\n{{%DOT-NOTATION}}\n"), "\n", 'Too many newlines removed with pragma tag');
+		$this->assertEquals($m->render("1\n2{{%DOT-NOTATION}}\n3"), "1\n23", 'Wrong newline removed with pragma tag');
+	}
+
+}