Initial hack at partials support.
diff --git a/lib/mustache.dart b/lib/mustache.dart
index ff98e45..818f4b2 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -5,6 +5,7 @@
part 'char_reader.dart';
part 'scanner.dart';
part 'template.dart';
+part 'mustache_context.dart';
/// [Mustache template documentation](http://mustache.github.com/mustache.5.html)
@@ -18,6 +19,7 @@
/// A Template can be rendered multiple times with different values.
abstract class Template {
+
/// [values] can be a combination of Map, List, String. Any non-String object
/// will be converted using toString(). Null values will cause a
/// FormatException, unless lenient module is enabled.
@@ -43,3 +45,19 @@
MustacheFormatException(this.message, this.line, this.column);
String toString() => message;
}
+
+//TODO does this require some sort of context to find partials nested in subdirs?
+typedef Template PartialResolver(String templateName);
+
+// Required for handing partials
+abstract class MustacheContext {
+
+ factory MustacheContext(PartialResolver partialResolver,
+ {bool lenient, bool htmlEscapeValues}) = _MustacheContext;
+
+ String renderString(String templateName, values);
+
+ void render(String templateName, values, StringSink sink);
+}
+
+
diff --git a/lib/mustache_context.dart b/lib/mustache_context.dart
new file mode 100644
index 0000000..9a722d3
--- /dev/null
+++ b/lib/mustache_context.dart
@@ -0,0 +1,27 @@
+part of mustache;
+
+class _MustacheContext implements MustacheContext {
+
+ _MustacheContext(PartialResolver partialResolver,
+ {bool lenient: false, bool htmlEscapeValues: true})
+ : _partialResolver = partialResolver,
+ _lenient = lenient,
+ _htmlEscapeValues = htmlEscapeValues;
+
+ final PartialResolver _partialResolver;
+ final bool _lenient;
+ final bool _htmlEscapeValues;
+
+ String renderString(String templateName, values) {
+ var template = _partialResolver(templateName);
+ return template.renderString(values,
+ lenient: _lenient, htmlEscapeValues: _htmlEscapeValues);
+ }
+
+ void render(String templateName, values, StringSink sink) {
+ var template = _partialResolver(templateName);
+ template.render(values, sink,
+ lenient: _lenient, htmlEscapeValues: _htmlEscapeValues);
+ }
+
+}
diff --git a/lib/template.dart b/lib/template.dart
index 3df8302..29a71ff 100644
--- a/lib/template.dart
+++ b/lib/template.dart
@@ -14,9 +14,9 @@
_Node _parseTokens(List<_Token> tokens, bool lenient) {
var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0));
for (var t in tokens) {
- if (t.type == _TEXT || t.type == _VARIABLE || t.type == _UNESC_VARIABLE) {
+ if (const [_TEXT, _VARIABLE, _UNESC_VARIABLE, _PARTIAL].contains(t.type)) {
if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)
- _checkTagChars(t, lenient);
+ _checkTagChars(t, lenient);
stack.last.children.add(new _Node.fromToken(t));
} else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
@@ -58,7 +58,9 @@
}
class _Template implements Template {
- _Template(this._root, this._lenient) {
+
+ _Template(this._root, this._lenient)
+ {
_htmlEscapeMap[_AMP] = '&';
_htmlEscapeMap[_LT] = '<';
_htmlEscapeMap[_GT] = '>';
@@ -68,9 +70,18 @@
}
final _Node _root;
+
+ //FIXME careful there is a potential concurrency bug as _stack is mutated
+ // during rendering, and if async lazy loading of partials is added this
+ // could be mutated by multiple async calls to render running concurrently.
+ // Perhaps pass this around as an argument, or create a render context object.
+ // Same is true with sink.
final List _stack = new List();
final Map _htmlEscapeMap = new Map<int, String>();
final bool _lenient;
+
+ //FIXME quick ugly hack.
+ PartialResolver partialResolver;
bool _htmlEscapeValues;
StringSink _sink;
@@ -90,6 +101,17 @@
_sink = null;
}
+ //TODO Share code with render.
+ void _renderWithStack(List stack, StringSink sink,
+ {bool lenient : false, bool htmlEscapeValues : true}) {
+ _sink = sink;
+ _htmlEscapeValues = htmlEscapeValues;
+ _stack.clear();
+ _stack.addAll(stack);
+ _root.children.forEach(_renderNode);
+ _sink = null;
+ }
+
_write(String output) => _sink.write(output);
_renderNode(node) {
@@ -109,6 +131,9 @@
case _OPEN_INV_SECTION:
_renderInvSection(node);
break;
+ case _PARTIAL:
+ _renderPartial(node);
+ break;
case _COMMENT:
break; // Do nothing.
default:
@@ -251,6 +276,12 @@
}
}
+ _renderPartial(_Node node) {
+ var partialName = node.value;
+ _Template template = partialResolver(partialName);
+ template._renderWithStack(_stack, _sink);
+ }
+
String _htmlEscape(String s) {
var buffer = new StringBuffer();
int startIndex = 0;
diff --git a/test/mustache_test.dart b/test/mustache_test.dart
index bb020eb..30fd6b7 100644
--- a/test/mustache_test.dart
+++ b/test/mustache_test.dart
@@ -234,11 +234,23 @@
});
});
- group('Patial tag', () {
- test('Unimplemented', () {
- var fn = () => parse('{{>partial}}').renderString({});
- expect(fn, throwsUnimplementedError);
- });
+ solo_group('Partial tag', () {
+
+ test('MustacheContext', () {
+ var template = parse('{{>partial}}');
+ var includedTemplate = parse('{{foo}}');
+ var resolver = (name) =>
+ {'root': template, 'partial': includedTemplate}[name];
+
+ //FIXME Need a sensible way to initialise the resolver.
+ template.partialResolver = resolver;
+ includedTemplate.partialResolver = resolver;
+
+ var ctx = new MustacheContext(resolver);
+ var output = ctx.renderString('root', {'foo': 'bar'});
+ expect(output, 'bar');
+ });
+
});
group('Other', () {