Fix handling of delimiters in lambda sections - all spec tests pass woot
diff --git a/lib/mustache.dart b/lib/mustache.dart
index 23b95df..d8ef0ae 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -27,7 +27,8 @@
{bool lenient,
bool htmlEscapeValues,
String name,
- PartialResolver partialResolver}) = _Template.fromSource;
+ PartialResolver partialResolver,
+ Delimiters delimiters}) = _Template.fromSource;
String get name;
String get source;
@@ -124,3 +125,57 @@
class MustacheMirrorsUsedAnnotation {
const MustacheMirrorsUsedAnnotation();
}
+
+
+//FIXME Don't expose this. Just take a string in the api.
+class Delimiters {
+
+ const Delimiters.standard() : this(
+ _OPEN_MUSTACHE,
+ _OPEN_MUSTACHE,
+ _CLOSE_MUSTACHE,
+ _CLOSE_MUSTACHE);
+
+ // Assume single space between delimiters.
+ factory Delimiters.fromString(String delimiters) {
+ if (delimiters.length == 3) {
+ return new Delimiters(
+ delimiters.codeUnits[0],
+ null,
+ null,
+ delimiters.codeUnits[2]);
+
+ } else if (delimiters.length == 5) {
+ return new Delimiters(
+ delimiters.codeUnits[0],
+ delimiters.codeUnits[1],
+ delimiters.codeUnits[3],
+ delimiters.codeUnits[4]);
+ } else {
+ throw 'Invalid delimiter string'; //FIXME
+ }
+ }
+
+ const Delimiters(this.open, this.openInner, this.closeInner, this.close);
+
+ final int open;
+ final int openInner;
+ final int closeInner;
+ final int close;
+
+ String toString() {
+ var value = new String.fromCharCode(open);
+
+ if (openInner != null)
+ value += new String.fromCharCode(openInner);
+
+ value += ' ';
+
+ if (closeInner != null)
+ value += new String.fromCharCode(closeInner);
+
+ value += new String.fromCharCode(close);
+
+ return value;
+ }
+}
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index 84b6d04..af913e5 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -5,9 +5,11 @@
final _Node _node;
final _Renderer _renderer;
+ final bool _isSection;
bool _closed = false;
- _LambdaContext(this._node, this._renderer);
+ _LambdaContext(this._node, this._renderer, {bool isSection: true})
+ : _isSection = isSection;
void close() {
_closed = true;
@@ -58,9 +60,21 @@
String renderSource(String source) {
_checkClosed();
var sink = new StringBuffer();
- var node = _parse(source, _renderer._lenient, _renderer._templateName);
+ // Lambdas used for sections should parse with the current delimiters.
+ var delimiters = _isSection
+ ? new Delimiters.fromString(_renderer._delimiters)
+ : new Delimiters.standard();
+ var node = _parse(source,
+ _renderer._lenient,
+ _renderer._templateName,
+ delimiters);
var renderer = new _Renderer.lambda(
- _renderer, node, source, _renderer._indent, sink);
+ _renderer,
+ node,
+ source,
+ _renderer._indent,
+ sink,
+ _renderer._delimiters);
renderer.render();
return sink.toString();
}
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index 2b8cb38..7edf81f 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -1,6 +1,7 @@
part of mustache;
-List<_Token> _scan(String source, bool lenient) => _trim(new _Scanner(source).scan());
+List<_Token> _scan(String source, bool lenient, Delimiters delimiters)
+ => _trim(new _Scanner(source, null, delimiters).scan());
//FIXME use enums
const int _TEXT = 1;
@@ -145,18 +146,25 @@
}
class _Scanner {
- _Scanner(String source, [this._templateName])
- : _r = new _CharReader(source);
+
+ _Scanner(String source, [this._templateName, Delimiters initial])
+ : _r = new _CharReader(source),
+ _openDelimiter = (initial == null) ? _OPEN_MUSTACHE : initial.open,
+ _openDelimiterInner =
+ (initial == null) ? _OPEN_MUSTACHE : initial.openInner,
+ _closeDelimiterInner =
+ (initial == null) ? _CLOSE_MUSTACHE : initial.closeInner,
+ _closeDelimiter = (initial == null) ? _CLOSE_MUSTACHE : initial.close;
final String _templateName;
_CharReader _r;
List<_Token> _tokens = new List<_Token>();
// These can be changed by the change delimiter tag.
- int _openDelimiter = _OPEN_MUSTACHE;
- int _openDelimiterInner = _OPEN_MUSTACHE;
- int _closeDelimiterInner = _CLOSE_MUSTACHE;
- int _closeDelimiter = _CLOSE_MUSTACHE;
+ int _openDelimiter;
+ int _openDelimiterInner;
+ int _closeDelimiterInner;
+ int _closeDelimiter;
List<_Token> scan() {
while(true) {
@@ -329,19 +337,13 @@
_expect(delimiterInner);
_expect(delimiter);
-
- var value = new String.fromCharCode(_openDelimiter);
-
- if (_openDelimiterInner != null)
- value += new String.fromCharCode(_openDelimiterInner);
-
- value += ' ';
-
- if (_closeDelimiterInner != null)
- value += new String.fromCharCode(_closeDelimiterInner);
-
- value += new String.fromCharCode(_closeDelimiter);
-
+
+ var value = new Delimiters(
+ _openDelimiter,
+ _openDelimiterInner,
+ _closeDelimiterInner,
+ _closeDelimiter).toString();
+
_tokens.add(new _Token(_CHANGE_DELIMITER, value, line, col));
}
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 1ce4a6d..6172147 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -35,7 +35,10 @@
stack.removeLast();
- } else if (t.type == _COMMENT || t.type == _CHANGE_DELIMITER) {
+ } else if (t.type == _CHANGE_DELIMITER) {
+ stack.last.children.add(new _Node.fromToken(t));
+
+ } else if (t.type == _COMMENT) {
// Do nothing
} else {
@@ -56,23 +59,24 @@
}
}
-_Node _parse(String source, bool lenient, String templateName) {
+_Node _parse(String source, bool lenient, String templateName,
+ Delimiters delimiters) {
if (source == null) throw new ArgumentError.notNull('Template source');
- var tokens = _scan(source, lenient);
+ var tokens = _scan(source, lenient, delimiters);
var ast = _parseTokens(tokens, lenient, templateName);
return ast;
}
-
class _Template implements Template {
_Template.fromSource(String source,
{bool lenient: false,
bool htmlEscapeValues : true,
String name,
- PartialResolver partialResolver})
+ PartialResolver partialResolver,
+ Delimiters delimiters : const Delimiters.standard()})
: source = source,
- _root = _parse(source, lenient, name),
+ _root = _parse(source, lenient, name, delimiters),
_lenient = lenient,
_htmlEscapeValues = htmlEscapeValues,
_name = name,
@@ -95,7 +99,8 @@
void render(values, StringSink sink) {
var renderer = new _Renderer(_root, sink, values, [values],
- _lenient, _htmlEscapeValues, _partialResolver, _name, '', source);
+ _lenient, _htmlEscapeValues, _partialResolver, _name, '', source,
+ '{{ }}');
renderer.render();
}
}
@@ -112,7 +117,8 @@
this._partialResolver,
this._templateName,
this._indent,
- this._source)
+ this._source,
+ this._delimiters)
: _stack = new List.from(stack);
_Renderer.partial(_Renderer renderer, _Template partial, String indent)
@@ -125,7 +131,8 @@
renderer._partialResolver,
renderer._templateName,
renderer._indent + indent,
- partial.source);
+ partial.source,
+ '{{ }}');
_Renderer.subtree(_Renderer renderer, _Node node, StringSink sink)
: this(node,
@@ -137,14 +144,16 @@
renderer._partialResolver,
renderer._templateName,
renderer._indent,
- renderer._source);
+ renderer._source,
+ '{{ }}');
_Renderer.lambda(
_Renderer renderer,
_Node node,
String source,
String indent,
- StringSink sink)
+ StringSink sink,
+ String delimiters)
: this(node,
sink,
renderer._values,
@@ -154,7 +163,8 @@
renderer._partialResolver,
renderer._templateName,
renderer._indent + indent,
- source);
+ source,
+ delimiters);
final _Node _root;
final StringSink _sink;
@@ -167,6 +177,8 @@
final String _indent;
final String _source;
+ String _delimiters;
+
void render() {
if (_indent == null || _indent == '') {
_root.children.forEach(_renderNode);
@@ -196,7 +208,7 @@
_write(Object output) => _sink.write(output.toString());
- _renderNode(node) {
+ _renderNode(_Node node) {
switch (node.type) {
case _TEXT:
_renderText(node);
@@ -219,7 +231,8 @@
case _COMMENT:
break; // Do nothing.
case _CHANGE_DELIMITER:
- break; // Do nothing
+ _delimiters = node.value;
+ break;
default:
throw new UnimplementedError();
}
@@ -298,7 +311,7 @@
var value = _resolveValue(node.value);
if (value is Function) {
- var context = new _LambdaContext(node, this);
+ var context = new _LambdaContext(node, this, isSection: false);
value = value(context);
context.close();
}
@@ -355,7 +368,7 @@
_templateName, node.line, node.column);
} else if (value is Function) {
- var context = new _LambdaContext(node, this);
+ var context = new _LambdaContext(node, this, isSection: true);
var output = value(context);
context.close();
_write(output);
@@ -391,7 +404,7 @@
}
} else if (value is Function) {
- var context = new _LambdaContext(node, this);
+ var context = new _LambdaContext(node, this, isSection: true);
var output = value(context);
context.close();
diff --git a/test/mustache_test.dart b/test/mustache_test.dart
index 3af1374..5395336 100644
--- a/test/mustache_test.dart
+++ b/test/mustache_test.dart
@@ -466,6 +466,41 @@
var output = '<dsfsf dsfsdf dfsdfsd>';
expect(parse(template).renderString(values), equals(output));
});
+
+ test('Alternate Delimiters', () {
+
+ // A lambda's return value should parse with the default delimiters.
+
+ var template = '{{= | | =}}\nHello, (|&lambda|)!';
+
+ //function() { return "|planet| => {{planet}}" }
+ var values = {'planet': 'world',
+ 'lambda': (LambdaContext ctx) => ctx.renderSource(
+ '|planet| => {{planet}}') };
+
+ var output = 'Hello, (|planet| => world)!';
+
+ expect(parse(template).renderString(values), equals(output));
+ });
+
+ test('Alternate Delimiters 2', () {
+
+ // Lambdas used for sections should parse with the current delimiters.
+
+ var template = '{{= | | =}}<|#lambda|-|/lambda|>';
+
+ //function() { return "|planet| => {{planet}}" }
+ var values = {'planet': 'Earth',
+ 'lambda': (LambdaContext ctx) {
+ var txt = ctx.source;
+ return ctx.renderSource('$txt{{planet}} => |planet|$txt');
+ }
+ };
+
+ var output = '<-{{planet}} => Earth->';
+
+ expect(parse(template).renderString(values), equals(output));
+ });
});
group('Other', () {