Dartfmt all the things
diff --git a/lib/mustache.dart b/lib/mustache.dart
index 70fa97d..7cdbaa7 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -6,109 +6,104 @@
/// Use new Template(source) instead.
@deprecated
-Template parse(String source, {bool lenient : false})
- => new Template(source, lenient: lenient);
+Template parse(String source, {bool lenient: false}) =>
+ new Template(source, lenient: lenient);
/// A Template can be efficiently rendered multiple times with different
/// values.
abstract class Template {
-
/// The constructor parses the template source and throws [TemplateException]
/// if the syntax of the source is invalid.
/// Tag names may only contain characters a-z, A-Z, 0-9, underscore, and minus,
/// unless lenient mode is specified.
factory Template(String source,
{bool lenient,
- bool htmlEscapeValues,
- String name,
- PartialResolver partialResolver,
- String delimiters}) = t.Template.fromSource;
+ bool htmlEscapeValues,
+ String name,
+ PartialResolver partialResolver,
+ String delimiters}) = t.Template.fromSource;
String get name;
String get source;
-
- /// [values] can be a combination of Map, List, String. Any non-String object
- /// will be converted using toString(). Null values will cause a
- /// [TemplateException], unless lenient module is enabled.
- String renderString(values);
- /// [values] can be a combination of Map, List, String. Any non-String object
- /// will be converted using toString(). Null values will cause a
- /// [TemplateException], unless lenient module is enabled.
- void render(values, StringSink sink);
+ /// [values] can be a combination of Map, List, String. Any non-String object
+ /// will be converted using toString(). Null values will cause a
+ /// [TemplateException], unless lenient module is enabled.
+ String renderString(values);
+
+ /// [values] can be a combination of Map, List, String. Any non-String object
+ /// will be converted using toString(). Null values will cause a
+ /// [TemplateException], unless lenient module is enabled.
+ void render(values, StringSink sink);
}
-
@deprecated
-abstract class MustacheFormatException implements FormatException {
-}
+abstract class MustacheFormatException implements FormatException {}
typedef Template PartialResolver(String templateName);
typedef Object LambdaFunction(LambdaContext context);
/// Passed as an argument to a mustache lambda function. The methods on
-/// this object may only be called before the lambda function returns. If a
+/// this object may only be called before the lambda function returns. If a
/// method is called after it has returned an exception will be thrown.
abstract class LambdaContext {
-
/// Render the current section tag in the current context and return the
/// result as a string. If provided, value will be added to the top of the
/// context's stack.
String renderString({Object value});
-
+
/// Render and directly output the current section tag. If provided, value
/// will be added to the top of the context's stack.
void render({Object value});
-
+
/// Output a string. The output will not be html escaped, and will be written
/// before the output returned from the lambda.
void write(Object object);
-
+
/// Get the unevaluated template source for the current section tag.
String get source;
-
+
/// Evaluate the string as a mustache template using the current context. If
/// provided, value will be added to the top of the context's stack.
- String renderSource(String source, {Object value});
-
- /// Lookup the value of a variable in the current context.
+ String renderSource(String source, {Object value});
+
+ /// Lookup the value of a variable in the current context.
Object lookup(String variableName);
}
-const MustacheMirrorsUsedAnnotation mustache = const MustacheMirrorsUsedAnnotation();
+const MustacheMirrorsUsedAnnotation mustache =
+ const MustacheMirrorsUsedAnnotation();
class MustacheMirrorsUsedAnnotation {
const MustacheMirrorsUsedAnnotation();
}
-
/// [TemplateException] is used to obtain the line and column numbers
/// of the token which caused parse or render to fail.
abstract class TemplateException implements MustacheFormatException, Exception {
-
/// A message describing the problem parsing or rendering the template.
String get message;
/// The name used to identify the template, as passed to the Template
/// constructor.
String get templateName;
-
+
/// The 1-based line number of the token where formatting error was found.
int get line;
/// The 1-based column number of the token where formatting error was found.
- int get column;
-
+ int get column;
+
/// The character offset within the template source.
int get offset;
/// The template source.
String get source;
-
+
/// A short source substring of the source at the point the problem occurred
/// with parsing or rendering.
String get context;
-
+
String toString();
}
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index 130e445..8b00d74 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -9,28 +9,27 @@
/// Passed as an argument to a mustache lambda function.
class LambdaContext implements m.LambdaContext {
-
final Node _node;
final Renderer _renderer;
final bool _isSection;
bool _closed = false;
-
+
LambdaContext(this._node, this._renderer, {bool isSection: true})
: _isSection = isSection;
-
+
void close() {
_closed = true;
}
-
+
void _checkClosed() {
if (_closed) throw _error('LambdaContext accessed outside of callback.');
}
-
+
TemplateException _error(String msg) {
- return new TemplateException(msg, _renderer.templateName, _renderer.source,
- _node.start);
+ return new TemplateException(
+ msg, _renderer.templateName, _renderer.source, _node.start);
}
-
+
/// Render the current section tag in the current context and return the
/// result as a string.
String renderString({Object value}) {
@@ -48,7 +47,7 @@
if (value != null) renderer.push(value);
renderer.render(section.children);
}
-
+
void render({Object value}) {
_checkClosed();
if (_node is! SectionNode) _error(
@@ -60,21 +59,21 @@
_checkClosed();
_renderer.write(object);
}
-
+
/// Get the unevaluated template source for the current section tag.
String get source {
_checkClosed();
-
+
if (_node is! SectionNode) return '';
-
+
SectionNode node = _node;
-
+
var nodes = node.children;
-
+
if (nodes.isEmpty) return '';
-
+
if (nodes.length == 1 && nodes.first is TextNode) return nodes.first.text;
-
+
return _renderer.source.substring(node.contentStart, node.contentEnd);
}
@@ -82,26 +81,20 @@
String renderSource(String source, {Object value}) {
_checkClosed();
var sink = new StringBuffer();
-
+
// Lambdas used for sections should parse with the current delimiters.
var delimiters = '{{ }}';
if (_node is SectionNode) {
SectionNode node = _node;
delimiters = node.delimiters;
}
-
- var nodes = parser.parse(source,
- _renderer.lenient,
- _renderer.templateName,
- delimiters);
-
+
+ var nodes = parser.parse(
+ source, _renderer.lenient, _renderer.templateName, delimiters);
+
var renderer = new Renderer.lambda(
- _renderer,
- source,
- _renderer.indent,
- sink,
- delimiters);
-
+ _renderer, source, _renderer.indent, sink, delimiters);
+
if (value != null) renderer.push(value);
renderer.render(nodes);
@@ -113,5 +106,4 @@
_checkClosed();
return _renderer.resolveValue(variableName);
}
-
-}
\ No newline at end of file
+}
diff --git a/lib/src/node.dart b/lib/src/node.dart
index a0be614..046baff 100644
--- a/lib/src/node.dart
+++ b/lib/src/node.dart
@@ -2,15 +2,15 @@
abstract class Node {
Node(this.start, this.end);
-
+
// The offset of the start of the token in the file. Unless this is a section
// or inverse section, then this stores the start of the content of the
// section.
final int start;
final int end;
-
+
void accept(Visitor visitor);
- void visitChildren(Visitor visitor) { }
+ void visitChildren(Visitor visitor) {}
}
abstract class Visitor {
@@ -21,42 +21,38 @@
}
class TextNode extends Node {
-
TextNode(this.text, int start, int end) : super(start, end);
-
+
final String text;
-
+
String toString() => '(TextNode "$_debugText" $start $end)';
-
+
String get _debugText {
var t = text.replaceAll('\n', '\\n');
return t.length < 50 ? t : t.substring(0, 48) + '...';
}
-
- void accept(Visitor visitor) => visitor.visitText(this);
+
+ void accept(Visitor visitor) => visitor.visitText(this);
}
class VariableNode extends Node {
-
VariableNode(this.name, int start, int end, {this.escape: true})
- : super(start, end);
-
+ : super(start, end);
+
final String name;
final bool escape;
void accept(Visitor visitor) => visitor.visitVariable(this);
-
- String toString() => '(VariableNode "$name" escape: $escape $start $end)';
+
+ String toString() => '(VariableNode "$name" escape: $escape $start $end)';
}
-
class SectionNode extends Node {
-
SectionNode(this.name, int start, int end, this.delimiters,
{this.inverse: false})
- : contentStart = end,
- super(start, end);
-
+ : contentStart = end,
+ super(start, end);
+
final String name;
final String delimiters;
final bool inverse;
@@ -65,26 +61,24 @@
final List<Node> children = <Node>[];
void accept(Visitor visitor) => visitor.visitSection(this);
-
+
void visitChildren(Visitor visitor) {
children.forEach((node) => node.accept(visitor));
}
-
- toString() => '(SectionNode $name inverse: $inverse $start $end)';
+
+ toString() => '(SectionNode $name inverse: $inverse $start $end)';
}
class PartialNode extends Node {
+ PartialNode(this.name, int start, int end, this.indent) : super(start, end);
- PartialNode(this.name, int start, int end, this.indent)
- : super(start, end);
-
final String name;
-
+
// Used to store the preceding whitespace before a partial tag, so that
// it's content can be correctly indented.
final String indent;
void accept(Visitor visitor) => visitor.visitPartial(this);
-
- toString() => '(PartialNode $name $start $end "$indent")';
+
+ toString() => '(PartialNode $name $start $end "$indent")';
}
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index 1ec0c10..779afaa 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -5,10 +5,8 @@
import 'template_exception.dart';
import 'token.dart';
-List<Node> parse(String source,
- bool lenient,
- String templateName,
- String delimiters) {
+List<Node> parse(
+ String source, bool lenient, String templateName, String delimiters) {
var parser = new Parser(source, templateName, delimiters, lenient: lenient);
return parser.parse();
}
@@ -24,7 +22,7 @@
class TagType {
const TagType(this.name);
final String name;
-
+
static const TagType openSection = const TagType('openSection');
static const TagType openInverseSection = const TagType('openInverseSection');
static const TagType closeSection = const TagType('closeSection');
@@ -36,83 +34,78 @@
static const TagType changeDelimiter = const TagType('changeDelimiter');
}
-
class Parser {
-
Parser(String source, String templateName, String delimiters,
- {lenient: false})
+ {lenient: false})
: _source = source,
_templateName = templateName,
_delimiters = delimiters,
_lenient = lenient,
- _scanner = new Scanner(
- source, templateName, delimiters, lenient: lenient);
-
+ _scanner =
+ new Scanner(source, templateName, delimiters, lenient: lenient);
+
final String _source;
final bool _lenient;
final String _templateName;
- final String _delimiters;
- final Scanner _scanner;
+ final String _delimiters;
+ final Scanner _scanner;
final List<SectionNode> _stack = <SectionNode>[];
List<Token> _tokens;
- String _currentDelimiters;
+ String _currentDelimiters;
int _offset = 0;
-
List<Node> parse() {
-
- _tokens = _scanner.scan();
- _currentDelimiters = _delimiters;
+ _tokens = _scanner.scan();
+ _currentDelimiters = _delimiters;
_stack.clear();
- _stack.add(new SectionNode('root', 0, 0, _delimiters));
-
+ _stack.add(new SectionNode('root', 0, 0, _delimiters));
+
// Handle a standalone tag on first line, including special case where the
// first line is empty.
var lineEnd = _readIf(TokenType.lineEnd, eofOk: true);
if (lineEnd != null) _appendTextToken(lineEnd);
_parseLine();
-
+
for (var token = _peek(); token != null; token = _peek()) {
- switch(token.type) {
-
+ switch (token.type) {
case TokenType.text:
- case TokenType.whitespace:
- _read();
- _appendTextToken(token);
+ case TokenType.whitespace:
+ _read();
+ _appendTextToken(token);
break;
-
+
case TokenType.openDelimiter:
var tag = _readTag();
var node = _createNodeFromTag(tag);
if (tag != null) _appendTag(tag, node);
break;
-
+
case TokenType.changeDelimiter:
_read();
_currentDelimiters = token.value;
break;
-
+
case TokenType.lineEnd:
_appendTextToken(_read());
_parseLine();
break;
-
+
default:
throw new Exception('Unreachable code.');
}
}
-
+
if (_stack.length != 1) {
throw new TemplateException("Unclosed tag: '${_stack.last.name}'.",
_templateName, _source, _stack.last.start);
}
-
+
return _stack.last.children;
}
-
+
// Returns null on EOF.
Token _peek() => _offset < _tokens.length ? _tokens[_offset] : null;
-
+
// Returns null on EOF.
Token _read() {
var t = null;
@@ -122,7 +115,7 @@
}
return t;
}
-
+
Token _expect(TokenType type) {
var token = _read();
if (token == null) throw _errorEof();
@@ -131,24 +124,24 @@
}
return token;
}
-
+
Token _readIf(TokenType type, {eofOk: false}) {
var token = _peek();
if (!eofOk && token == null) throw _errorEof();
return token != null && token.type == type ? _read() : null;
}
-
+
TemplateException _errorEof() =>
_error('Unexpected end of input.', _source.length - 1);
-
- TemplateException _error(String msg, int offset) =>
+
+ TemplateException _error(String msg, int offset) =>
new TemplateException(msg, _templateName, _source, offset);
-
+
// Add a text node to top most section on the stack and merge consecutive
// text nodes together.
void _appendTextToken(Token token) {
assert(const [TokenType.text, TokenType.lineEnd, TokenType.whitespace]
- .contains(token.type));
+ .contains(token.type));
var children = _stack.last.children;
if (children.isEmpty || children.last is! TextNode) {
children.add(new TextNode(token.value, token.start, token.end));
@@ -158,30 +151,33 @@
children.add(node);
}
}
-
+
// Add the node to top most section on the stack. If a section node then
// push it onto the stack, if a close section tag, then pop the stack.
void _appendTag(Tag tag, Node node) {
switch (tag.type) {
-
+
// {{#...}} {{^...}}
case TagType.openSection:
case TagType.openInverseSection:
_stack.last.children.add(node);
_stack.add(node);
break;
-
+
// {{/...}}
case TagType.closeSection:
if (tag.name != _stack.last.name) {
- throw new TemplateException("Mismatched tag, expected: "
+ throw new TemplateException(
+ "Mismatched tag, expected: "
"'${_stack.last.name}', was: '${tag.name}'",
- _templateName, _source, tag.start);
+ _templateName,
+ _source,
+ tag.start);
}
var node = _stack.removeLast();
node.contentEnd = tag.start;
- break;
-
+ break;
+
// {{...}} {{&...}} {{{...}}}
case TagType.variable:
case TagType.unescapedVariable:
@@ -189,7 +185,7 @@
case TagType.partial:
if (node != null) _stack.last.children.add(node);
break;
-
+
case TagType.comment:
case TagType.changeDelimiter:
// Ignore.
@@ -199,163 +195,165 @@
throw new Exception('Unreachable code.');
}
}
-
+
// Handle standalone tags and indented partials.
//
// A "standalone tag" in the spec is a tag one a line where the line only
// contains whitespace. During rendering the whitespace is ommitted.
- // Standalone partials also indent their content to match the tag during
+ // Standalone partials also indent their content to match the tag during
// rendering.
-
+
// match:
// lineEnd whitespace openDelimiter any* closeDelimiter whitespace lineEnd
//
// Where lineEnd can also mean start/end of the source.
- void _parseLine() {
+ void _parseLine() {
// Continue parsing standalone lines until we find one than isn't a
- // standalone line.
+ // standalone line.
while (_peek() != null) {
_readIf(TokenType.lineEnd, eofOk: true);
- var precedingWhitespace = _readIf(TokenType.whitespace, eofOk: true);
+ var precedingWhitespace = _readIf(TokenType.whitespace, eofOk: true);
var indent = precedingWhitespace == null ? '' : precedingWhitespace.value;
var tag = _readTag();
var tagNode = _createNodeFromTag(tag, partialIndent: indent);
var followingWhitespace = _readIf(TokenType.whitespace, eofOk: true);
-
+
const standaloneTypes = const [
TagType.openSection,
TagType.closeSection,
TagType.openInverseSection,
TagType.partial,
TagType.comment,
- TagType.changeDelimiter];
-
+ TagType.changeDelimiter
+ ];
+
if (tag != null &&
(_peek() == null || _peek().type == TokenType.lineEnd) &&
- standaloneTypes.contains(tag.type)) {
+ standaloneTypes.contains(tag.type)) {
// This is a tag on a "standalone line", so do not create text nodes
// for whitespace, or the following newline.
_appendTag(tag, tagNode);
// Now continue to loop and parse the next line.
} else {
- // This is not a standalone line so add the whitespace to the ast.
+ // This is not a standalone line so add the whitespace to the ast.
if (precedingWhitespace != null) _appendTextToken(precedingWhitespace);
if (tag != null) _appendTag(tag, tagNode);
- if (followingWhitespace != null) _appendTextToken(followingWhitespace);
+ if (followingWhitespace != null) _appendTextToken(followingWhitespace);
// Done parsing standalone lines. Exit the loop.
break;
}
}
}
-
+
final RegExp _validIdentifier = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
-
+
static const _tagTypeMap = const {
- '#': TagType.openSection,
- '^': TagType.openInverseSection,
- '/': TagType.closeSection,
- '&': TagType.unescapedVariable,
- '>': TagType.partial,
- '!': TagType.comment};
+ '#': TagType.openSection,
+ '^': TagType.openInverseSection,
+ '/': TagType.closeSection,
+ '&': TagType.unescapedVariable,
+ '>': TagType.partial,
+ '!': TagType.comment
+ };
// If open delimiter, or change delimiter token then return a tag.
// If EOF or any another token then return null.
Tag _readTag() {
-
var t = _peek();
if (t == null ||
(t.type != TokenType.changeDelimiter &&
- t.type != TokenType.openDelimiter)) {
+ t.type != TokenType.openDelimiter)) {
return null;
-
- } else if (t.type == TokenType.changeDelimiter) {
+ } else if (t.type == TokenType.changeDelimiter) {
_read();
// Remember the current delimiters.
_currentDelimiters = t.value;
-
+
// Change delimiter tags are already parsed by the scanner.
// So just create a tag and return it.
return new Tag(TagType.changeDelimiter, t.value, t.start, t.end);
}
-
+
// Start parsing a typical tag.
-
+
var open = _expect(TokenType.openDelimiter);
-
+
_readIf(TokenType.whitespace);
-
+
// A sigil is the character which identifies which sort of tag it is,
// i.e. '#', '/', or '>'.
// Variable tags and triple mustache tags don't have a sigil.
TagType tagType;
-
+
if (open.value == '{{{') {
tagType = TagType.tripleMustache;
} else {
var sigil = _readIf(TokenType.sigil);
tagType = sigil == null ? TagType.variable : _tagTypeMap[sigil.value];
}
-
+
_readIf(TokenType.whitespace);
-
+
// TODO split up names here instead of during render.
// Also check that they are valid token types.
// TODO split up names here instead of during render.
- // Also check that they are valid token types.
+ // Also check that they are valid token types.
var list = <Token>[];
for (var t = _peek();
- t != null && t.type != TokenType.closeDelimiter; t = _peek()) {
+ t != null && t.type != TokenType.closeDelimiter;
+ t = _peek()) {
_read();
list.add(t);
- }
+ }
var name = list.map((t) => t.value).join().trim();
if (_peek() == null) throw _errorEof();
-
+
// Check to see if the tag name is valid.
- if (tagType != TagType.comment) {
- if (name == '') throw _error('Empty tag name.', open.start);
+ if (tagType != TagType.comment) {
+ if (name == '') throw _error('Empty tag name.', open.start);
if (!_lenient) {
if (name.contains('\t') || name.contains('\n') || name.contains('\r')) {
throw _error('Tags may not contain newlines or tabs.', open.start);
- }
-
+ }
+
if (!_validIdentifier.hasMatch(name)) {
- throw _error('Unless in lenient mode, tags may only contain the '
- 'characters a-z, A-Z, minus, underscore and period.', open.start);
+ throw _error(
+ 'Unless in lenient mode, tags may only contain the '
+ 'characters a-z, A-Z, minus, underscore and period.',
+ open.start);
}
}
}
-
+
var close = _expect(TokenType.closeDelimiter);
-
+
return new Tag(tagType, name, open.start, close.end);
}
-
+
Node _createNodeFromTag(Tag tag, {String partialIndent: ''}) {
// Handle EOF case.
if (tag == null) return null;
-
+
Node node = null;
switch (tag.type) {
-
case TagType.openSection:
case TagType.openInverseSection:
bool inverse = tag.type == TagType.openInverseSection;
- node = new SectionNode(tag.name, tag.start, tag.end,
- _currentDelimiters, inverse: inverse);
+ node = new SectionNode(tag.name, tag.start, tag.end, _currentDelimiters,
+ inverse: inverse);
break;
-
+
case TagType.variable:
case TagType.unescapedVariable:
case TagType.tripleMustache:
bool escape = tag.type == TagType.variable;
node = new VariableNode(tag.name, tag.start, tag.end, escape: escape);
break;
-
+
case TagType.partial:
node = new PartialNode(tag.name, tag.start, tag.end, partialIndent);
break;
-
+
case TagType.closeSection:
case TagType.comment:
case TagType.changeDelimiter:
@@ -366,5 +364,5 @@
throw new Exception('Unreachable code');
}
return node;
- }
+ }
}
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart
index 36f646b..9735195 100644
--- a/lib/src/renderer.dart
+++ b/lib/src/renderer.dart
@@ -14,52 +14,30 @@
const Object noSuchProperty = const Object();
class Renderer extends Visitor {
-
- Renderer(this.sink,
- List stack,
- this.lenient,
- this.htmlEscapeValues,
- this.partialResolver,
- this.templateName,
- this.indent,
- this.source)
- : _stack = new List.from(stack);
-
+ Renderer(this.sink, List stack, this.lenient, this.htmlEscapeValues,
+ this.partialResolver, this.templateName, this.indent, this.source)
+ : _stack = new List.from(stack);
+
Renderer.partial(Renderer ctx, Template partial, String indent)
- : this(ctx.sink,
- ctx._stack,
- ctx.lenient,
- ctx.htmlEscapeValues,
- ctx.partialResolver,
- ctx.templateName,
- ctx.indent + indent,
- partial.source);
+ : this(
+ ctx.sink,
+ ctx._stack,
+ ctx.lenient,
+ ctx.htmlEscapeValues,
+ ctx.partialResolver,
+ ctx.templateName,
+ ctx.indent + indent,
+ partial.source);
Renderer.subtree(Renderer ctx, StringSink sink)
- : this(sink,
- ctx._stack,
- ctx.lenient,
- ctx.htmlEscapeValues,
- ctx.partialResolver,
- ctx.templateName,
- ctx.indent,
- ctx.source);
+ : this(sink, ctx._stack, ctx.lenient, ctx.htmlEscapeValues,
+ ctx.partialResolver, ctx.templateName, ctx.indent, ctx.source);
- Renderer.lambda(
- Renderer ctx,
- String source,
- String indent,
- StringSink sink,
+ Renderer.lambda(Renderer ctx, String source, String indent, StringSink sink,
String delimiters)
- : this(sink,
- ctx._stack,
- ctx.lenient,
- ctx.htmlEscapeValues,
- ctx.partialResolver,
- ctx.templateName,
- ctx.indent + indent,
- source);
-
+ : this(sink, ctx._stack, ctx.lenient, ctx.htmlEscapeValues,
+ ctx.partialResolver, ctx.templateName, ctx.indent + indent, source);
+
final StringSink sink;
final List _stack;
final bool lenient;
@@ -70,34 +48,31 @@
final String source;
void push(value) => _stack.add(value);
-
+
Object pop() => _stack.removeLast();
-
+
write(Object output) => sink.write(output.toString());
-
+
void render(List<Node> nodes) {
-
- if (indent == null || indent == '') {
- nodes.forEach((n) => n.accept(this));
-
+ if (indent == null || indent == '') {
+ nodes.forEach((n) => n.accept(this));
} else if (nodes.isNotEmpty) {
// Special case to make sure there is not an extra indent after the last
- // line in the partial file.
+ // line in the partial file.
write(indent);
-
+
nodes.take(nodes.length - 1).forEach((n) => n.accept(this));
-
+
var node = nodes.last;
if (node is TextNode) {
- visitText(node, lastNode: true);
+ visitText(node, lastNode: true);
} else {
node.accept(this);
}
}
}
-
+
void visitText(TextNode node, {bool lastNode: false}) {
-
if (node.text == '') return;
if (indent == null || indent == '') {
write(node.text);
@@ -110,119 +85,113 @@
write(node.text.replaceAll('\n', '\n${indent}'));
}
}
-
+
void visitVariable(VariableNode node) {
var value = resolveValue(node.name);
-
+
if (value is Function) {
var context = new LambdaContext(node, this, isSection: false);
value = value(context);
context.close();
}
-
+
if (value == noSuchProperty) {
- if (!lenient)
- throw error('Value was missing for variable tag: ${node.name}.',
- node);
+ if (!lenient) throw error(
+ 'Value was missing for variable tag: ${node.name}.', node);
} else {
var valueString = (value == null) ? '' : value.toString();
var output = !node.escape || !htmlEscapeValues
- ? valueString
- : _htmlEscape(valueString);
+ ? valueString
+ : _htmlEscape(valueString);
if (output != null) write(output);
- }
+ }
}
-
+
void visitSection(SectionNode node) {
- if (node.inverse) _renderInvSection(node); else _renderSection(node);
+ if (node.inverse) _renderInvSection(node);
+ else _renderSection(node);
}
-
- //TODO can probably combine Inv and Normal to shorten.
- void _renderSection(SectionNode node) {
- var value = resolveValue(node.name);
-
- if (value == null) {
- // Do nothing.
-
- } else if (value is Iterable) {
- value.forEach((v) => _renderWithValue(node, v));
-
- } else if (value is Map) {
- _renderWithValue(node, value);
-
- } else if (value == true) {
- _renderWithValue(node, value);
-
- } else if (value == false) {
- // Do nothing.
-
- } else if (value == noSuchProperty) {
- if (!lenient)
- throw error('Value was missing for section tag: ${node.name}.', node);
-
- } else if (value is Function) {
- var context = new LambdaContext(node, this, isSection: true);
- var output = value(context);
- context.close();
- if (output != null) write(output);
-
- } else if (lenient) {
- // We consider all other values as 'true' in lenient mode.
- _renderWithValue(node, null);
- } else {
- throw error('Invalid value type for section, '
- 'section: ${node.name}, '
- 'type: ${value.runtimeType}.', node);
- }
- }
-
- void _renderInvSection(SectionNode node) {
- var value = resolveValue(node.name);
-
- if (value == null) {
- _renderWithValue(node, null);
-
- } else if ((value is Iterable && value.isEmpty) || value == false) {
- _renderWithValue(node, node.name);
-
- } else if (value == true || value is Map || value is Iterable) {
- // Do nothing.
-
- } else if (value == noSuchProperty) {
- if (lenient) {
- _renderWithValue(node, null);
- } else {
- throw error('Value was missing for inverse section: ${node.name}.', node);
- }
-
- } else if (value is Function) {
- // Do nothing.
- //TODO in strict mode should this be an error?
-
- } else if (lenient) {
- // We consider all other values as 'true' in lenient mode. Since this
- // is an inverted section, we do nothing.
+ //TODO can probably combine Inv and Normal to shorten.
+ void _renderSection(SectionNode node) {
+ var value = resolveValue(node.name);
- } else {
- throw error(
- 'Invalid value type for inverse section, '
- 'section: ${node.name}, '
- 'type: ${value.runtimeType}.', node);
- }
- }
-
- void _renderWithValue(SectionNode node, value) {
- push(value);
- node.visitChildren(this);
- pop();
- }
-
+ if (value == null) {
+ // Do nothing.
+
+ } else if (value is Iterable) {
+ value.forEach((v) => _renderWithValue(node, v));
+ } else if (value is Map) {
+ _renderWithValue(node, value);
+ } else if (value == true) {
+ _renderWithValue(node, value);
+ } else if (value == false) {
+ // Do nothing.
+
+ } else if (value == noSuchProperty) {
+ if (!lenient) throw error(
+ 'Value was missing for section tag: ${node.name}.', node);
+ } else if (value is Function) {
+ var context = new LambdaContext(node, this, isSection: true);
+ var output = value(context);
+ context.close();
+ if (output != null) write(output);
+ } else if (lenient) {
+ // We consider all other values as 'true' in lenient mode.
+ _renderWithValue(node, null);
+ } else {
+ throw error(
+ 'Invalid value type for section, '
+ 'section: ${node.name}, '
+ 'type: ${value.runtimeType}.',
+ node);
+ }
+ }
+
+ void _renderInvSection(SectionNode node) {
+ var value = resolveValue(node.name);
+
+ if (value == null) {
+ _renderWithValue(node, null);
+ } else if ((value is Iterable && value.isEmpty) || value == false) {
+ _renderWithValue(node, node.name);
+ } else if (value == true || value is Map || value is Iterable) {
+ // Do nothing.
+
+ } else if (value == noSuchProperty) {
+ if (lenient) {
+ _renderWithValue(node, null);
+ } else {
+ throw error(
+ 'Value was missing for inverse section: ${node.name}.', node);
+ }
+ } else if (value is Function) {
+ // Do nothing.
+ //TODO in strict mode should this be an error?
+
+ } else if (lenient) {
+ // We consider all other values as 'true' in lenient mode. Since this
+ // is an inverted section, we do nothing.
+
+ } else {
+ throw error(
+ 'Invalid value type for inverse section, '
+ 'section: ${node.name}, '
+ 'type: ${value.runtimeType}.',
+ node);
+ }
+ }
+
+ void _renderWithValue(SectionNode node, value) {
+ push(value);
+ node.visitChildren(this);
+ pop();
+ }
+
void visitPartial(PartialNode node) {
var partialName = node.name;
- Template template = partialResolver == null
- ? null
- : partialResolver(partialName);
+ Template template =
+ partialResolver == null ? null : partialResolver(partialName);
if (template != null) {
var renderer = new Renderer.partial(this, template, node.indent);
var nodes = getTemplateNodes(template);
@@ -231,9 +200,9 @@
// do nothing
} else {
throw error('Partial not found: $partialName.', node);
- }
+ }
}
-
+
// Walks up the stack looking for the variable.
// Handles dotted names of the form "a.b.c".
Object resolveValue(String name) {
@@ -256,28 +225,26 @@
}
return object;
}
-
+
// Returns the property of the given object by name. For a map,
// which contains the key name, this is object[name]. For other
// objects, this is object.name or object.name(). If no property
// by the given name exists, this method returns noSuchProperty.
_getNamedProperty(object, name) {
-
- if (object is Map && object.containsKey(name))
- return object[name];
-
- if (object is List && _integerTag.hasMatch(name))
- return object[int.parse(name)];
-
- if (lenient && !_validTag.hasMatch(name))
- return noSuchProperty;
-
+ if (object is Map && object.containsKey(name)) return object[name];
+
+ if (object is List && _integerTag.hasMatch(name)) return object[
+ int.parse(name)];
+
+ if (lenient && !_validTag.hasMatch(name)) return noSuchProperty;
+
var instance = reflect(object);
var field = instance.type.instanceMembers[new Symbol(name)];
if (field == null) return noSuchProperty;
-
+
var invocation = null;
- if ((field is VariableMirror) || ((field is MethodMirror) && (field.isGetter))) {
+ if ((field is VariableMirror) ||
+ ((field is MethodMirror) && (field.isGetter))) {
invocation = instance.getField(field.simpleName);
} else if ((field is MethodMirror) && (field.parameters.length == 0)) {
invocation = instance.invoke(field.simpleName, []);
@@ -287,31 +254,30 @@
}
return invocation.reflectee;
}
-
- m.TemplateException error(String message, Node node)
- => new TemplateException(message, templateName, source, node.start);
- static const Map<String,String> _htmlEscapeMap = const {
+ m.TemplateException error(String message, Node node) =>
+ new TemplateException(message, templateName, source, node.start);
+
+ static const Map<String, String> _htmlEscapeMap = const {
_AMP: '&',
_LT: '<',
_GT: '>',
_QUOTE: '"',
_APOS: ''',
- _FORWARD_SLASH: '/'
+ _FORWARD_SLASH: '/'
};
-
+
String _htmlEscape(String s) {
-
var buffer = new StringBuffer();
int startIndex = 0;
int i = 0;
for (int c in s.runes) {
- if (c == _AMP
- || c == _LT
- || c == _GT
- || c == _QUOTE
- || c == _APOS
- || c == _FORWARD_SLASH) {
+ if (c == _AMP ||
+ c == _LT ||
+ c == _GT ||
+ c == _QUOTE ||
+ c == _APOS ||
+ c == _FORWARD_SLASH) {
buffer.write(s.substring(startIndex, i));
buffer.write(_htmlEscapeMap[c]);
startIndex = i + 1;
@@ -321,7 +287,6 @@
buffer.write(s.substring(startIndex));
return buffer.toString();
}
-
}
const int _AMP = 38;
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index b537b57..c1bd497 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -4,48 +4,43 @@
import 'template_exception.dart';
class Scanner {
-
Scanner(String source, this._templateName, String delimiters,
{bool lenient: true})
- : _source = source,
- _lenient = lenient,
- _itr = source.runes.iterator {
-
+ : _source = source,
+ _lenient = lenient,
+ _itr = source.runes.iterator {
if (source == '') {
_c = _EOF;
} else {
_itr.moveNext();
_c = _itr.current;
}
-
+
if (delimiters == null) {
_openDelimiter = _openDelimiterInner = _OPEN_MUSTACHE;
_closeDelimiter = _closeDelimiterInner = _CLOSE_MUSTACHE;
-
} else if (delimiters.length == 3) {
_openDelimiter = delimiters.codeUnits[0];
_closeDelimiter = delimiters.codeUnits[2];
-
} else if (delimiters.length == 5) {
_openDelimiter = delimiters.codeUnits[0];
_openDelimiterInner = delimiters.codeUnits[1];
_closeDelimiterInner = delimiters.codeUnits[3];
_closeDelimiter = delimiters.codeUnits[4];
-
} else {
throw new TemplateException(
- 'Invalid delimiter string $delimiters', null, null, null);
+ 'Invalid delimiter string $delimiters', null, null, null);
}
}
final String _templateName;
final String _source;
final bool _lenient;
-
+
final Iterator<int> _itr;
int _offset = 0;
int _c = 0;
-
+
final List<Token> _tokens = new List<Token>();
// These can be changed by the change delimiter tag.
@@ -53,61 +48,55 @@
int _openDelimiterInner;
int _closeDelimiterInner;
int _closeDelimiter;
-
+
List<Token> scan() {
-
for (int c = _peek(); c != _EOF; c = _peek()) {
-
// Scan text tokens.
if (c != _openDelimiter) {
_scanText();
continue;
}
-
+
int start = _offset;
-
+
// Read first open delimiter character.
_read();
-
+
// If only a single delimiter character then create a text token.
if (_openDelimiterInner != null && _peek() != _openDelimiterInner) {
var value = new String.fromCharCode(_openDelimiter);
_append(TokenType.text, value, start, _offset);
continue;
}
-
+
if (_openDelimiterInner != null) _expect(_openDelimiterInner);
-
+
// Handle triple mustache.
if (_openDelimiterInner == _OPEN_MUSTACHE &&
_openDelimiter == _OPEN_MUSTACHE &&
_peek() == _OPEN_MUSTACHE) {
-
_read();
_append(TokenType.openDelimiter, '{{{', start, _offset);
_scanTagContent();
_scanCloseTripleMustache();
-
} else {
-
// Check to see if this is a change delimiter tag. {{= | | =}}
// Need to skip whitespace and check for "=".
int wsStart = _offset;
var ws = _readWhile(_isWhitespace);
-
+
if (_peek() == _EQUAL) {
_parseChangeDelimiterTag(start);
-
} else {
// Scan standard mustache tag.
var value = new String.fromCharCodes(_openDelimiterInner == null
? [_openDelimiter]
: [_openDelimiter, _openDelimiterInner]);
-
+
_append(TokenType.openDelimiter, value, start, wsStart);
-
+
if (ws != '') _append(TokenType.whitespace, ws, wsStart, _offset);
-
+
_scanTagContent();
_scanCloseDelimiter();
}
@@ -115,18 +104,18 @@
}
return _tokens;
}
-
+
int _peek() => _c;
-
+
int _read() {
int c = _c;
_offset++;
_c = _itr.moveNext() ? _itr.current : _EOF;
return c;
}
-
+
String _readWhile(bool test(int charCode)) {
- if (_c == _EOF) return '';
+ if (_c == _EOF) return '';
int start = _offset;
while (_peek() != _EOF && test(_peek())) {
_read();
@@ -134,28 +123,30 @@
int end = _peek() == _EOF ? _source.length : _offset;
return _source.substring(start, end);
}
-
+
_expect(int expectedCharCode) {
int c = _read();
if (c == _EOF) {
- throw new TemplateException('Unexpected end of input',
- _templateName, _source, _offset - 1);
-
+ throw new TemplateException(
+ 'Unexpected end of input', _templateName, _source, _offset - 1);
} else if (c != expectedCharCode) {
- throw new TemplateException('Unexpected character, '
- 'expected: ${new String.fromCharCode(expectedCharCode)}, '
- 'was: ${new String.fromCharCode(c)}',
- _templateName, _source, _offset - 1);
+ throw new TemplateException(
+ 'Unexpected character, '
+ 'expected: ${new String.fromCharCode(expectedCharCode)}, '
+ 'was: ${new String.fromCharCode(c)}',
+ _templateName,
+ _source,
+ _offset - 1);
}
}
-
+
_append(TokenType type, String value, int start, int end) =>
_tokens.add(new Token(type, value, start, end));
-
- bool _isWhitespace(int c)
- => const [_SPACE, _TAB , _NEWLINE, _RETURN].contains(c);
-
+
+ bool _isWhitespace(int c) =>
+ const [_SPACE, _TAB, _NEWLINE, _RETURN].contains(c);
+
// Scan text. This adds text tokens, line end tokens, and whitespace
// tokens for whitespace at the begining of a line. This is because the
// mustache spec requires special handing of whitespace.
@@ -163,23 +154,23 @@
int start = 0;
TokenType token;
String value;
-
+
for (int c = _peek(); c != _EOF && c != _openDelimiter; c = _peek()) {
start = _offset;
-
+
switch (c) {
case _SPACE:
case _TAB:
value = _readWhile((c) => c == _SPACE || c == _TAB);
- token = TokenType.whitespace;
+ token = TokenType.whitespace;
break;
-
+
case _NEWLINE:
_read();
token = TokenType.lineEnd;
value = '\n';
break;
-
+
case _RETURN:
_read();
if (_peek() == _NEWLINE) {
@@ -191,43 +182,41 @@
value = '\r';
}
break;
-
+
default:
value = _readWhile((c) => c != _openDelimiter && c != _NEWLINE);
token = TokenType.text;
}
-
+
_append(token, value, start, _offset);
}
}
-
+
// Scan contents of a tag and the end delimiter token.
void _scanTagContent() {
-
int start;
TokenType token;
String value;
-
- bool isCloseDelimiter(int c) =>
- (_closeDelimiterInner == null && c == _closeDelimiter)
- || (_closeDelimiterInner != null && c == _closeDelimiterInner);
-
+
+ bool isCloseDelimiter(int c) =>
+ (_closeDelimiterInner == null && c == _closeDelimiter) ||
+ (_closeDelimiterInner != null && c == _closeDelimiterInner);
+
for (int c = _peek(); c != _EOF && !isCloseDelimiter(c); c = _peek()) {
-
start = _offset;
-
+
switch (c) {
case _HASH:
case _CARET:
case _FORWARD_SLASH:
case _GT:
case _AMP:
- case _EXCLAIM:
+ case _EXCLAIM:
_read();
token = TokenType.sigil;
value = new String.fromCharCode(c);
break;
-
+
case _SPACE:
case _TAB:
case _NEWLINE:
@@ -235,86 +224,94 @@
token = TokenType.whitespace;
value = _readWhile(_isWhitespace);
break;
-
+
case _PERIOD:
_read();
token = TokenType.dot;
- value = '.';
+ value = '.';
break;
-
- default:
+
+ default:
// Identifier can be any other character in lenient mode.
token = TokenType.identifier;
- value = _readWhile((c) => !(const [ _HASH, _CARET, _FORWARD_SLASH,
- _GT, _AMP, _EXCLAIM, _SPACE, _TAB, _NEWLINE, _RETURN,
- _PERIOD].contains(c)) &&
- c != _closeDelimiterInner &&
- c != _closeDelimiter);
+ value = _readWhile((c) => !(const [
+ _HASH,
+ _CARET,
+ _FORWARD_SLASH,
+ _GT,
+ _AMP,
+ _EXCLAIM,
+ _SPACE,
+ _TAB,
+ _NEWLINE,
+ _RETURN,
+ _PERIOD
+ ].contains(c)) &&
+ c != _closeDelimiterInner &&
+ c != _closeDelimiter);
}
- _append(token, value, start, _offset);
- }
+ _append(token, value, start, _offset);
+ }
}
// Scan close delimiter token.
void _scanCloseDelimiter() {
-
if (_peek() != _EOF) {
int start = _offset;
-
+
if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
_expect(_closeDelimiter);
-
+
String value = new String.fromCharCodes(_closeDelimiterInner == null
? [_closeDelimiter]
: [_closeDelimiterInner, _closeDelimiter]);
-
+
_append(TokenType.closeDelimiter, value, start, _offset);
- }
+ }
}
// Scan close triple mustache delimiter token.
void _scanCloseTripleMustache() {
if (_peek() != _EOF) {
int start = _offset;
-
+
_expect(_CLOSE_MUSTACHE);
- _expect(_CLOSE_MUSTACHE);
_expect(_CLOSE_MUSTACHE);
-
+ _expect(_CLOSE_MUSTACHE);
+
_append(TokenType.closeDelimiter, '}}}', start, _offset);
- }
- }
+ }
+ }
// Open delimiter characters have already been read.
void _parseChangeDelimiterTag(int start) {
-
_expect(_EQUAL);
-
+
var delimiterInner = _closeDelimiterInner;
var delimiter = _closeDelimiter;
-
+
_readWhile(_isWhitespace);
-
+
int c;
c = _read();
-
+
if (c == _EQUAL) throw _error('Incorrect change delimiter tag.');
_openDelimiter = c;
-
+
c = _read();
if (_isWhitespace(c)) {
_openDelimiterInner = null;
} else {
_openDelimiterInner = c;
}
-
+
_readWhile(_isWhitespace);
-
+
c = _read();
-
- if (_isWhitespace(c) || c == _EQUAL)
- throw _error('Incorrect change delimiter tag.');
-
+
+ if (_isWhitespace(c) ||
+ c == _EQUAL) throw _error('Incorrect change delimiter tag.');
+
if (_isWhitespace(_peek()) || _peek() == _EQUAL) {
_closeDelimiterInner = null;
_closeDelimiter = c;
@@ -322,34 +319,33 @@
_closeDelimiterInner = c;
_closeDelimiter = _read();
}
-
+
_readWhile(_isWhitespace);
-
+
_expect(_EQUAL);
-
- _readWhile(_isWhitespace);
-
+
+ _readWhile(_isWhitespace);
+
if (delimiterInner != null) _expect(delimiterInner);
- _expect(delimiter);
-
- // Create delimiter string.
- var buffer = new StringBuffer();
- buffer.writeCharCode(_openDelimiter);
- if (_openDelimiterInner != null) buffer.writeCharCode(_openDelimiterInner);
- buffer.write(' ');
- if (_closeDelimiterInner != null) {
- buffer.writeCharCode(_closeDelimiterInner);
- }
- buffer.writeCharCode(_closeDelimiter);
- var value = buffer.toString();
-
- _append(TokenType.changeDelimiter, value, start, _offset);
+ _expect(delimiter);
+
+ // Create delimiter string.
+ var buffer = new StringBuffer();
+ buffer.writeCharCode(_openDelimiter);
+ if (_openDelimiterInner != null) buffer.writeCharCode(_openDelimiterInner);
+ buffer.write(' ');
+ if (_closeDelimiterInner != null) {
+ buffer.writeCharCode(_closeDelimiterInner);
+ }
+ buffer.writeCharCode(_closeDelimiter);
+ var value = buffer.toString();
+
+ _append(TokenType.changeDelimiter, value, start, _offset);
}
-
+
TemplateException _error(String message) {
return new TemplateException(message, _templateName, _source, _offset);
}
-
}
const int _EOF = -1;
@@ -381,5 +377,3 @@
const int _UNDERSCORE = 95;
const int _MINUS = 45;
-
-
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 1753138..2a33b28 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -6,19 +6,18 @@
import 'renderer.dart';
class Template implements m.Template {
-
Template.fromSource(String source,
- {bool lenient: false,
- bool htmlEscapeValues : true,
- String name,
- m.PartialResolver partialResolver,
- String delimiters: "{{ }}"})
- : source = source,
- _nodes = parser.parse(source, lenient, name, delimiters),
- _lenient = lenient,
- _htmlEscapeValues = htmlEscapeValues,
- _name = name,
- _partialResolver = partialResolver;
+ {bool lenient: false,
+ bool htmlEscapeValues: true,
+ String name,
+ m.PartialResolver partialResolver,
+ String delimiters: "{{ }}"})
+ : source = source,
+ _nodes = parser.parse(source, lenient, name, delimiters),
+ _lenient = lenient,
+ _htmlEscapeValues = htmlEscapeValues,
+ _name = name,
+ _partialResolver = partialResolver;
final String source;
final List<Node> _nodes;
diff --git a/lib/src/template_exception.dart b/lib/src/template_exception.dart
index d4db9c0..94db0a9 100644
--- a/lib/src/template_exception.dart
+++ b/lib/src/template_exception.dart
@@ -3,19 +3,18 @@
import 'package:mustache/mustache.dart' as m;
class TemplateException implements m.TemplateException {
-
TemplateException(this.message, this.templateName, this.source, this.offset);
final String message;
final String templateName;
final String source;
final int offset;
-
+
bool _isUpdated = false;
int _line;
int _column;
String _context;
-
+
int get line {
_update();
return _line;
@@ -30,13 +29,13 @@
_update();
return _context;
}
-
+
String toString() {
var list = [];
if (templateName != null) list.add(templateName);
if (line != null) list.add(line);
if (column != null) list.add(column);
- var location = list.isEmpty ? '' : ' (${list.join(':')})';
+ var location = list.isEmpty ? '' : ' (${list.join(':')})';
return '$message$location\n$context';
}
@@ -44,12 +43,11 @@
void _update() {
if (_isUpdated) return;
_isUpdated = true;
-
- if (source == null
- || offset == null
- || (offset < 0 || offset > source.length))
- return;
-
+
+ if (source == null ||
+ offset == null ||
+ (offset < 0 || offset > source.length)) return;
+
// Find line and character column.
int lineNum = 1;
int lineStart = 0;
@@ -68,7 +66,7 @@
lastWasCR = true;
}
}
-
+
_line = lineNum;
_column = offset - lineStart + 1;
@@ -105,9 +103,7 @@
}
String slice = source.substring(start, end);
int markOffset = offset - start + prefix.length;
-
+
_context = "$prefix$slice$postfix\n${" " * markOffset}^\n";
}
-
}
-
diff --git a/lib/src/token.dart b/lib/src/token.dart
index afb42b8..e4d7b59 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -1,12 +1,10 @@
library mustache.token;
-
class TokenType {
-
const TokenType(this.name);
-
+
final String name;
-
+
String toString() => '(TokenType $name)';
static const TokenType text = const TokenType('text');
@@ -18,23 +16,20 @@
static const TokenType sigil = const TokenType('sigil');
static const TokenType identifier = const TokenType('identifier');
static const TokenType dot = const TokenType('dot');
-
+
static const TokenType changeDelimiter = const TokenType('changeDelimiter');
static const TokenType whitespace = const TokenType('whitespace');
static const TokenType lineEnd = const TokenType('lineEnd');
-
}
-
class Token {
-
Token(this.type, this.value, this.start, this.end);
-
+
final TokenType type;
final String value;
-
+
final int start;
final int end;
-
+
String toString() => "(Token ${type.name} \"$value\" $start $end)";
}
diff --git a/test/all.dart b/test/all.dart
index d319eac..253942c 100644
--- a/test/all.dart
+++ b/test/all.dart
@@ -7,4 +7,3 @@
test.main();
parser.main();
}
-
diff --git a/test/mustache_specs.dart b/test/mustache_specs.dart
index c55e376..01c5144 100644
--- a/test/mustache_specs.dart
+++ b/test/mustache_specs.dart
@@ -25,19 +25,17 @@
defineTests();
}
-defineTests () {
+defineTests() {
var specs_dir = new Directory('test/spec/specs');
- specs_dir
- .listSync()
- .forEach((f) {
- if (f is File) {
- var filename = f.path;
- if (shouldRun(filename)) {
- var text = f.readAsStringSync(encoding: UTF8);
- _defineGroupFromFile(filename, text);
- }
+ specs_dir.listSync().forEach((f) {
+ if (f is File) {
+ var filename = f.path;
+ if (shouldRun(filename)) {
+ var text = f.readAsStringSync(encoding: UTF8);
+ _defineGroupFromFile(filename, text);
}
- });
+ }
+ });
}
_defineGroupFromFile(filename, text) {
@@ -45,20 +43,21 @@
var tests = json['tests'];
filename = filename.substring(filename.lastIndexOf('/') + 1);
group("Specs of $filename", () {
-
//Make sure that we reset the state of the Interpolation - Multiple Calls test
//as for some reason dart can run the group more than once causing the test
//to fail the second time it runs
- tearDown (() =>lambdas['Interpolation - Multiple Calls'].reset());
-
- tests.forEach( (t) {
+ tearDown(() => lambdas['Interpolation - Multiple Calls'].reset());
+
+ tests.forEach((t) {
var testDescription = new StringBuffer(t['name']);
testDescription.write(': ');
testDescription.write(t['desc']);
var template = t['template'];
var data = t['data'];
- var templateOneline = template.replaceAll('\n', '\\n').replaceAll('\r', '\\r');
- var reason = new StringBuffer("Could not render right '''$templateOneline'''");
+ var templateOneline =
+ template.replaceAll('\n', '\\n').replaceAll('\r', '\\r');
+ var reason =
+ new StringBuffer("Could not render right '''$templateOneline'''");
var expected = t['expected'];
var partials = t['partials'];
var partial = (String name) {
@@ -67,7 +66,7 @@
}
return partials[name];
};
-
+
//swap the data.lambda with a dart real function
if (data['lambda'] != null) {
data['lambda'] = lambdas[t['name']];
@@ -76,8 +75,11 @@
if (partials != null) {
reason.write(" and partial: $partials");
}
- test(testDescription.toString(), () => expect(render(template, data, partial: partial), expected, reason: reason.toString()));
- });
+ test(
+ testDescription.toString(),
+ () => expect(render(template, data, partial: partial), expected,
+ reason: reason.toString()));
+ });
});
}
@@ -93,23 +95,27 @@
//we provide the lambdas at the test here
class _DummyCallableWithState {
var _callCounter = 0;
-
- call (arg) => "${++_callCounter}";
-
- reset () => _callCounter = 0;
+
+ call(arg) => "${++_callCounter}";
+
+ reset() => _callCounter = 0;
}
-Function wrapLambda(Function f) => (LambdaContext ctx) => ctx.renderSource(f(ctx.source).toString());
+Function wrapLambda(Function f) =>
+ (LambdaContext ctx) => ctx.renderSource(f(ctx.source).toString());
var lambdas = {
- 'Interpolation' : wrapLambda((t) => 'world'),
- 'Interpolation - Expansion': wrapLambda((t) => '{{planet}}'),
- 'Interpolation - Alternate Delimiters': wrapLambda((t) => "|planet| => {{planet}}"),
- 'Interpolation - Multiple Calls': new _DummyCallableWithState(), //function() { return (g=(function(){return this})()).calls=(g.calls||0)+1 }
- 'Escaping': wrapLambda((t) => '>'),
- 'Section': wrapLambda((txt) => txt == "{{x}}" ? "yes" : "no"),
- 'Section - Expansion': wrapLambda((txt) => "$txt{{planet}}$txt"),
- 'Section - Alternate Delimiters': wrapLambda((txt) => "$txt{{planet}} => |planet|$txt"),
- 'Section - Multiple Calls': wrapLambda((t) => "__${t}__"),
- 'Inverted Section': wrapLambda((txt) => false)
+ 'Interpolation': wrapLambda((t) => 'world'),
+ 'Interpolation - Expansion': wrapLambda((t) => '{{planet}}'),
+ 'Interpolation - Alternate Delimiters':
+ wrapLambda((t) => "|planet| => {{planet}}"),
+ 'Interpolation - Multiple Calls':
+ new _DummyCallableWithState(), //function() { return (g=(function(){return this})()).calls=(g.calls||0)+1 }
+ 'Escaping': wrapLambda((t) => '>'),
+ 'Section': wrapLambda((txt) => txt == "{{x}}" ? "yes" : "no"),
+ 'Section - Expansion': wrapLambda((txt) => "$txt{{planet}}$txt"),
+ 'Section - Alternate Delimiters':
+ wrapLambda((txt) => "$txt{{planet}} => |planet|$txt"),
+ 'Section - Multiple Calls': wrapLambda((t) => "__${t}__"),
+ 'Inverted Section': wrapLambda((txt) => false)
};
diff --git a/test/mustache_test.dart b/test/mustache_test.dart
index caa2208..10dd19a 100644
--- a/test/mustache_test.dart
+++ b/test/mustache_test.dart
@@ -12,76 +12,80 @@
const VALUE_MISSING = 'Value was missing';
const UNCLOSED_TAG = 'Unclosed tag';
-Template parse(String source, {bool lenient: false})
- => new Template(source, lenient: lenient);
+Template parse(String source, {bool lenient: false}) =>
+ new Template(source, lenient: lenient);
main() {
- group('Basic', () {
- test('Variable', () {
- var output = parse('_{{var}}_')
- .renderString({"var": "bob"});
- expect(output, equals('_bob_'));
- });
- test('Comment', () {
- var output = parse('_{{! i am a\n comment ! }}_').renderString({});
- expect(output, equals('__'));
- });
- });
- group('Section', () {
- test('Map', () {
- var output = parse('{{#section}}_{{var}}_{{/section}}')
- .renderString({"section": {"var": "bob"}});
- expect(output, equals('_bob_'));
- });
- test('List', () {
- var output = parse('{{#section}}_{{var}}_{{/section}}')
- .renderString({"section": [{"var": "bob"}, {"var": "jim"}]});
- expect(output, equals('_bob__jim_'));
- });
- test('Empty List', () {
- var output = parse('{{#section}}_{{var}}_{{/section}}')
- .renderString({"section": []});
- expect(output, equals(''));
- });
- test('False', () {
- var output = parse('{{#section}}_{{var}}_{{/section}}')
- .renderString({"section": false});
- expect(output, equals(''));
- });
- test('Invalid value', () {
- var ex = renderFail(
- '{{#section}}_{{var}}_{{/section}}',
- {"section": 42});
- expect(ex is TemplateException, isTrue);
- expect(ex.message, startsWith(BAD_VALUE_SECTION));
- });
- test('Invalid value - lenient mode', () {
- var output = parse('{{#var}}_{{var}}_{{/var}}', lenient: true)
- .renderString({'var' : 42});
- expect(output, equals('_42_'));
- });
+ group('Basic', () {
+ test('Variable', () {
+ var output = parse('_{{var}}_').renderString({"var": "bob"});
+ expect(output, equals('_bob_'));
+ });
+ test('Comment', () {
+ var output = parse('_{{! i am a\n comment ! }}_').renderString({});
+ expect(output, equals('__'));
+ });
+ });
+ group('Section', () {
+ test('Map', () {
+ var output = parse('{{#section}}_{{var}}_{{/section}}').renderString({
+ "section": {"var": "bob"}
+ });
+ expect(output, equals('_bob_'));
+ });
+ test('List', () {
+ var output = parse('{{#section}}_{{var}}_{{/section}}').renderString({
+ "section": [
+ {"var": "bob"},
+ {"var": "jim"}
+ ]
+ });
+ expect(output, equals('_bob__jim_'));
+ });
+ test('Empty List', () {
+ var output = parse('{{#section}}_{{var}}_{{/section}}')
+ .renderString({"section": []});
+ expect(output, equals(''));
+ });
+ test('False', () {
+ var output = parse('{{#section}}_{{var}}_{{/section}}')
+ .renderString({"section": false});
+ expect(output, equals(''));
+ });
+ test('Invalid value', () {
+ var ex = renderFail('{{#section}}_{{var}}_{{/section}}', {"section": 42});
+ expect(ex is TemplateException, isTrue);
+ expect(ex.message, startsWith(BAD_VALUE_SECTION));
+ });
+ test('Invalid value - lenient mode', () {
+ var output = parse('{{#var}}_{{var}}_{{/var}}', lenient: true)
+ .renderString({'var': 42});
+ expect(output, equals('_42_'));
+ });
- test('True', () {
- var output = parse('{{#section}}_ok_{{/section}}')
- .renderString({"section": true});
- expect(output, equals('_ok_'));
- });
+ test('True', () {
+ var output =
+ parse('{{#section}}_ok_{{/section}}').renderString({"section": true});
+ expect(output, equals('_ok_'));
+ });
- test('Nested', () {
- var output = parse('{{#section}}.{{var}}.{{#nested}}_{{nestedvar}}_{{/nested}}.{{/section}}')
- .renderString({"section": {
- "var": "bob",
- "nested": [
- {"nestedvar": "jim"},
- {"nestedvar": "sally"}
- ]
- }});
- expect(output, equals('.bob._jim__sally_.'));
- });
+ test('Nested', () {
+ var output = parse(
+ '{{#section}}.{{var}}.{{#nested}}_{{nestedvar}}_{{/nested}}.{{/section}}')
+ .renderString({
+ "section": {
+ "var": "bob",
+ "nested": [
+ {"nestedvar": "jim"},
+ {"nestedvar": "sally"}
+ ]
+ }
+ });
+ expect(output, equals('.bob._jim__sally_.'));
+ });
test('isNotEmpty', () {
- var t = new Template(
-'''{{^ section }}
+ var t = new Template('''{{^ section }}
Empty.
{{/ section }}
{{# section.isNotEmpty }}
@@ -92,66 +96,119 @@
</ul>
{{/ section.isNotEmpty }}
''');
- expect(t.renderString({"section": [1, 2 ,3] }), equals(
-''' <ul>
+ expect(
+ t.renderString({
+ "section": [1, 2, 3]
+ }),
+ equals(''' <ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
'''));
- expect(t.renderString({"section": [] }), equals('Empty.\n'));
+ expect(t.renderString({"section": []}), equals('Empty.\n'));
});
- test('Whitespace in section tags', () {
- expect(parse('{{#foo.bar}}oi{{/foo.bar}}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{# foo.bar}}oi{{/foo.bar}}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{#foo.bar }}oi{{/foo.bar}}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{# foo.bar }}oi{{/foo.bar}}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{#foo.bar}}oi{{/ foo.bar}}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{#foo.bar}}oi{{/foo.bar }}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{#foo.bar}}oi{{/ foo.bar }}').renderString({'foo': {'bar': true}}), equals('oi'));
- expect(parse('{{# foo.bar }}oi{{/ foo.bar }}').renderString({'foo': {'bar': true}}), equals('oi'));
- });
+ test('Whitespace in section tags', () {
+ expect(
+ parse('{{#foo.bar}}oi{{/foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{# foo.bar}}oi{{/foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{#foo.bar }}oi{{/foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{# foo.bar }}oi{{/foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{#foo.bar}}oi{{/ foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{#foo.bar}}oi{{/foo.bar }}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{#foo.bar}}oi{{/ foo.bar }}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ expect(
+ parse('{{# foo.bar }}oi{{/ foo.bar }}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('oi'));
+ });
test('Whitespace in variable tags', () {
- expect(parse('{{foo.bar}}').renderString({'foo': {'bar': true}}), equals('true'));
- expect(parse('{{ foo.bar}}').renderString({'foo': {'bar': true}}), equals('true'));
- expect(parse('{{foo.bar }}').renderString({'foo': {'bar': true}}), equals('true'));
- expect(parse('{{ foo.bar }}').renderString({'foo': {'bar': true}}), equals('true'));
+ expect(
+ parse('{{foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('true'));
+ expect(
+ parse('{{ foo.bar}}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('true'));
+ expect(
+ parse('{{foo.bar }}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('true'));
+ expect(
+ parse('{{ foo.bar }}').renderString({
+ 'foo': {'bar': true}
+ }),
+ equals('true'));
});
-
-
test('Odd whitespace in tags', () {
+ render(source, values, output) => expect(
+ parse(source, lenient: true).renderString(values), equals(output));
- render(source, values, output)
- => expect(parse(source, lenient: true)
- .renderString(values), equals(output));
+ render("{{\t# foo}}oi{{\n/foo}}", {'foo': true}, 'oi');
render(
- "{{\t# foo}}oi{{\n/foo}}",
- {'foo': true},
- 'oi');
+ "{{ # # foo }} {{ oi }} {{ / # foo }}",
+ {
+ '# foo': [
+ {'oi': 'OI!'}
+ ]
+ },
+ ' OI! ');
render(
- "{{ # # foo }} {{ oi }} {{ / # foo }}",
- {'# foo': [{'oi': 'OI!'}]},
- ' OI! ');
+ "{{ #foo }} {{ oi }} {{ /foo }}",
+ {
+ 'foo': [
+ {'oi': 'OI!'}
+ ]
+ },
+ ' OI! ');
render(
- "{{ #foo }} {{ oi }} {{ /foo }}",
- {'foo': [{'oi': 'OI!'}]},
- ' OI! ');
+ "{{\t#foo }} {{ oi }} {{ /foo }}",
+ {
+ 'foo': [
+ {'oi': 'OI!'}
+ ]
+ },
+ ' OI! ');
- render(
- "{{\t#foo }} {{ oi }} {{ /foo }}",
- {'foo': [{'oi': 'OI!'}]},
- ' OI! ');
-
- render(
- "{{{ #foo }}} {{{ /foo }}}",
- {'#foo': 1, '/foo': 2},
- '1 2');
+ render("{{{ #foo }}} {{{ /foo }}}", {'#foo': 1, '/foo': 2}, '1 2');
// Invalid - I'm ok with that for now.
// render(
@@ -159,25 +216,13 @@
// {'{': 1},
// '1');
- render(
- "{{\nfoo}}",
- {'foo': 'bar'},
- 'bar');
+ render("{{\nfoo}}", {'foo': 'bar'}, 'bar');
- render(
- "{{\tfoo}}",
- {'foo': 'bar'},
- 'bar');
+ render("{{\tfoo}}", {'foo': 'bar'}, 'bar');
- render(
- "{{\t# foo}}oi{{\n/foo}}",
- {'foo': true},
- 'oi');
+ render("{{\t# foo}}oi{{\n/foo}}", {'foo': true}, 'oi');
- render(
- "{{{\tfoo\t}}}",
- {'foo': true},
- 'true');
+ render("{{{\tfoo\t}}}", {'foo': true}, 'true');
//FIXME empty, or error in strict mode.
// render(
@@ -199,142 +244,146 @@
test('Bad tag', () {
expect(() => new Template('{{{ foo }|'), throwsException);
});
+ });
- });
+ group('Inverse Section', () {
+ test('Map', () {
+ var output = parse('{{^section}}_{{var}}_{{/section}}').renderString({
+ "section": {"var": "bob"}
+ });
+ expect(output, equals(''));
+ });
+ test('List', () {
+ var output = parse('{{^section}}_{{var}}_{{/section}}').renderString({
+ "section": [
+ {"var": "bob"},
+ {"var": "jim"}
+ ]
+ });
+ expect(output, equals(''));
+ });
+ test('Empty List', () {
+ var output =
+ parse('{{^section}}_ok_{{/section}}').renderString({"section": []});
+ expect(output, equals('_ok_'));
+ });
+ test('False', () {
+ var output = parse('{{^section}}_ok_{{/section}}')
+ .renderString({"section": false});
+ expect(output, equals('_ok_'));
+ });
+ test('Invalid value', () {
+ var ex = renderFail('{{^section}}_{{var}}_{{/section}}', {"section": 42});
+ expect(ex is TemplateException, isTrue);
+ expect(ex.message, startsWith(BAD_VALUE_INV_SECTION));
+ });
+ test('Invalid value - lenient mode', () {
+ var output = parse('{{^var}}_ok_{{/var}}', lenient: true)
+ .renderString({'var': 42});
+ expect(output, equals(''));
+ });
+ test('True', () {
+ var output =
+ parse('{{^section}}_ok_{{/section}}').renderString({"section": true});
+ expect(output, equals(''));
+ });
+ });
- group('Inverse Section', () {
- test('Map', () {
- var output = parse('{{^section}}_{{var}}_{{/section}}')
- .renderString({"section": {"var": "bob"}});
- expect(output, equals(''));
- });
- test('List', () {
- var output = parse('{{^section}}_{{var}}_{{/section}}')
- .renderString({"section": [{"var": "bob"}, {"var": "jim"}]});
- expect(output, equals(''));
- });
- test('Empty List', () {
- var output = parse('{{^section}}_ok_{{/section}}')
- .renderString({"section": []});
- expect(output, equals('_ok_'));
- });
- test('False', () {
- var output = parse('{{^section}}_ok_{{/section}}')
- .renderString({"section": false});
- expect(output, equals('_ok_'));
- });
- test('Invalid value', () {
- var ex = renderFail(
- '{{^section}}_{{var}}_{{/section}}',
- {"section": 42});
- expect(ex is TemplateException, isTrue);
- expect(ex.message, startsWith(BAD_VALUE_INV_SECTION));
- });
- test('Invalid value - lenient mode', () {
- var output = parse('{{^var}}_ok_{{/var}}', lenient: true)
- .renderString({'var' : 42});
- expect(output, equals(''));
- });
- test('True', () {
- var output = parse('{{^section}}_ok_{{/section}}')
- .renderString({"section": true});
- expect(output, equals(''));
- });
- });
+ group('Html escape', () {
+ test('Escape at start', () {
+ var output = parse('_{{var}}_').renderString({"var": "&."});
+ expect(output, equals('_&._'));
+ });
- group('Html escape', () {
+ test('Escape at end', () {
+ var output = parse('_{{var}}_').renderString({"var": ".&"});
+ expect(output, equals('_.&_'));
+ });
- test('Escape at start', () {
- var output = parse('_{{var}}_')
- .renderString({"var": "&."});
- expect(output, equals('_&._'));
- });
+ test('&', () {
+ var output = parse('_{{var}}_').renderString({"var": "&"});
+ expect(output, equals('_&_'));
+ });
- test('Escape at end', () {
- var output = parse('_{{var}}_')
- .renderString({"var": ".&"});
- expect(output, equals('_.&_'));
- });
+ test('<', () {
+ var output = parse('_{{var}}_').renderString({"var": "<"});
+ expect(output, equals('_<_'));
+ });
- test('&', () {
- var output = parse('_{{var}}_')
- .renderString({"var": "&"});
- expect(output, equals('_&_'));
- });
+ test('>', () {
+ var output = parse('_{{var}}_').renderString({"var": ">"});
+ expect(output, equals('_>_'));
+ });
- test('<', () {
- var output = parse('_{{var}}_')
- .renderString({"var": "<"});
- expect(output, equals('_<_'));
- });
+ test('"', () {
+ var output = parse('_{{var}}_').renderString({"var": '"'});
+ expect(output, equals('_"_'));
+ });
- test('>', () {
- var output = parse('_{{var}}_')
- .renderString({"var": ">"});
- expect(output, equals('_>_'));
- });
+ test("'", () {
+ var output = parse('_{{var}}_').renderString({"var": "'"});
+ expect(output, equals('_'_'));
+ });
- test('"', () {
- var output = parse('_{{var}}_')
- .renderString({"var": '"'});
- expect(output, equals('_"_'));
- });
+ test("/", () {
+ var output = parse('_{{var}}_').renderString({"var": "/"});
+ expect(output, equals('_/_'));
+ });
+ });
- test("'", () {
- var output = parse('_{{var}}_')
- .renderString({"var": "'"});
- expect(output, equals('_'_'));
- });
+ group('Invalid format', () {
+ test('Mismatched tag', () {
+ var source = '{{#section}}_{{var}}_{{/notsection}}';
+ var ex = renderFail(source, {
+ "section": {"var": "bob"}
+ });
+ expectFail(ex, 1, 22, 'Mismatched tag');
+ });
- test("/", () {
- var output = parse('_{{var}}_')
- .renderString({"var": "/"});
- expect(output, equals('_/_'));
- });
+ test('Unexpected EOF', () {
+ var source = '{{#section}}_{{var}}_{{/section';
+ var ex = renderFail(source, {
+ "section": {"var": "bob"}
+ });
+ expectFail(ex, 1, 31, UNEXPECTED_EOF);
+ });
- });
+ test('Bad tag name, open section', () {
+ var source = r'{{#section$%$^%}}_{{var}}_{{/section}}';
+ var ex = renderFail(source, {
+ "section": {"var": "bob"}
+ });
+ expectFail(ex, null, null, BAD_TAG_NAME);
+ });
- group('Invalid format', () {
- test('Mismatched tag', () {
- var source = '{{#section}}_{{var}}_{{/notsection}}';
- var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, 1, 22, 'Mismatched tag');
- });
+ test('Bad tag name, close section', () {
+ var source = r'{{#section}}_{{var}}_{{/section$%$^%}}';
+ var ex = renderFail(source, {
+ "section": {"var": "bob"}
+ });
+ expectFail(ex, null, null, BAD_TAG_NAME);
+ });
- test('Unexpected EOF', () {
- var source = '{{#section}}_{{var}}_{{/section';
- var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, 1, 31, UNEXPECTED_EOF);
- });
+ test('Bad tag name, variable', () {
+ var source = r'{{#section}}_{{var$%$^%}}_{{/section}}';
+ var ex = renderFail(source, {
+ "section": {"var": "bob"}
+ });
+ expectFail(ex, null, null, BAD_TAG_NAME);
+ });
- test('Bad tag name, open section', () {
- var source = r'{{#section$%$^%}}_{{var}}_{{/section}}';
- var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, null, null, BAD_TAG_NAME);
- });
-
- test('Bad tag name, close section', () {
- var source = r'{{#section}}_{{var}}_{{/section$%$^%}}';
- var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, null, null, BAD_TAG_NAME);
- });
-
- test('Bad tag name, variable', () {
- var source = r'{{#section}}_{{var$%$^%}}_{{/section}}';
- var ex = renderFail(source, {"section": {"var": "bob"}});
- expectFail(ex, null, null, BAD_TAG_NAME);
- });
-
- test('Missing variable', () {
+ test('Missing variable', () {
var source = r'{{#section}}_{{var}}_{{/section}}';
var ex = renderFail(source, {"section": {}});
expectFail(ex, null, null, VALUE_MISSING);
- });
+ });
- // Null variables shouldn't be a problem.
+ // Null variables shouldn't be a problem.
test('Null variable', () {
var t = new Template('{{#section}}_{{var}}_{{/section}}');
- var output = t.renderString({"section": {'var': null}});
+ var output = t.renderString({
+ "section": {'var': null}
+ });
expect(output, equals('__'));
});
@@ -343,33 +392,38 @@
var ex = renderFail(source, {"section": {}});
expectFail(ex, null, null, UNCLOSED_TAG);
});
+ });
- });
-
- group('Lenient', () {
- test('Odd section name', () {
- var output = parse(r'{{#section$%$^%}}_{{var}}_{{/section$%$^%}}', lenient: true)
- .renderString({r'section$%$^%': {'var': 'bob'}});
- expect(output, equals('_bob_'));
- });
-
- test('Odd variable name', () {
- var output = parse(r'{{#section}}_{{var$%$^%}}_{{/section}}', lenient: true)
- .renderString({'section': {r'var$%$^%': 'bob'}});
+ group('Lenient', () {
+ test('Odd section name', () {
+ var output = parse(r'{{#section$%$^%}}_{{var}}_{{/section$%$^%}}',
+ lenient: true).renderString({
+ r'section$%$^%': {'var': 'bob'}
+ });
expect(output, equals('_bob_'));
- });
+ });
- test('Null variable', () {
- var output = parse(r'{{#section}}_{{var}}_{{/section}}', lenient: true)
- .renderString({'section': {'var': null}});
- expect(output, equals('__'));
- });
+ test('Odd variable name', () {
+ var output = parse(r'{{#section}}_{{var$%$^%}}_{{/section}}',
+ lenient: true).renderString({
+ 'section': {r'var$%$^%': 'bob'}
+ });
+ expect(output, equals('_bob_'));
+ });
- test('Null section', () {
- var output = parse('{{#section}}_{{var}}_{{/section}}', lenient: true)
- .renderString({"section": null});
- expect(output, equals(''));
- });
+ test('Null variable', () {
+ var output = parse(r'{{#section}}_{{var}}_{{/section}}', lenient: true)
+ .renderString({
+ 'section': {'var': null}
+ });
+ expect(output, equals('__'));
+ });
+
+ test('Null section', () {
+ var output = parse('{{#section}}_{{var}}_{{/section}}', lenient: true)
+ .renderString({"section": null});
+ expect(output, equals(''));
+ });
// Known failure
// test('Null inverse section', () {
@@ -377,51 +431,43 @@
// .renderString({"section": null}, lenient: true);
// expect(output, equals(''));
// });
+ });
- });
+ group('Escape tags', () {
+ test('{{{ ... }}}', () {
+ var output = parse('{{{blah}}}').renderString({'blah': '&'});
+ expect(output, equals('&'));
+ });
+ test('{{& ... }}', () {
+ var output = parse('{{{blah}}}').renderString({'blah': '&'});
+ expect(output, equals('&'));
+ });
+ });
- group('Escape tags', () {
- test('{{{ ... }}}', () {
- var output = parse('{{{blah}}}')
- .renderString({'blah': '&'});
- expect(output, equals('&'));
- });
- test('{{& ... }}', () {
- var output = parse('{{{blah}}}')
- .renderString({'blah': '&'});
- expect(output, equals('&'));
- });
- });
-
- group('Partial tag', () {
-
- String _partialTest(Map values, Map sources, String renderTemplate, {bool lenient: false}) {
- var templates = new Map<String, Template>();
- var resolver = (name) => templates[name];
- for (var k in sources.keys) {
- templates[k] = new Template(sources[k],
- name: k, lenient: lenient, partialResolver: resolver);
- }
- var t = resolver(renderTemplate);
- return t.renderString(values);
- }
+ group('Partial tag', () {
+ String _partialTest(Map values, Map sources, String renderTemplate,
+ {bool lenient: false}) {
+ var templates = new Map<String, Template>();
+ var resolver = (name) => templates[name];
+ for (var k in sources.keys) {
+ templates[k] = new Template(sources[k],
+ name: k, lenient: lenient, partialResolver: resolver);
+ }
+ var t = resolver(renderTemplate);
+ return t.renderString(values);
+ }
test('basic', () {
- var output = _partialTest(
- {'foo': 'bar'},
- {'root': '{{>partial}}', 'partial': '{{foo}}'},
- 'root');
+ var output = _partialTest({'foo': 'bar'},
+ {'root': '{{>partial}}', 'partial': '{{foo}}'}, 'root');
expect(output, 'bar');
});
test('missing partial strict', () {
var threw = false;
try {
- _partialTest(
- {'foo': 'bar'},
- {'root': '{{>partial}}'},
- 'root',
- lenient: false);
+ _partialTest({'foo': 'bar'}, {'root': '{{>partial}}'}, 'root',
+ lenient: false);
} catch (e) {
expect(e is TemplateException, isTrue);
threw = true;
@@ -431,61 +477,55 @@
test('missing partial lenient', () {
var output = _partialTest(
- {'foo': 'bar'},
- {'root': '{{>partial}}'},
- 'root',
+ {'foo': 'bar'}, {'root': '{{>partial}}'}, 'root',
lenient: true);
expect(output, equals(''));
});
test('context', () {
- var output = _partialTest(
- {'text': 'content'},
- {'root': '"{{>partial}}"',
- 'partial': '*{{text}}*'},
- 'root',
- lenient: true);
+ var output = _partialTest({
+ 'text': 'content'
+ }, {
+ 'root': '"{{>partial}}"',
+ 'partial': '*{{text}}*'
+ }, 'root', lenient: true);
expect(output, equals('"*content*"'));
});
test('recursion', () {
- var output = _partialTest(
- { 'content': "X", 'nodes': [ { 'content': "Y", 'nodes': [] } ] },
- {'root': '{{>node}}',
- 'node': '{{content}}<{{#nodes}}{{>node}}{{/nodes}}>'},
- 'root',
- lenient: true);
+ var output = _partialTest({
+ 'content': "X",
+ 'nodes': [
+ {'content': "Y", 'nodes': []}
+ ]
+ }, {
+ 'root': '{{>node}}',
+ 'node': '{{content}}<{{#nodes}}{{>node}}{{/nodes}}>'
+ }, 'root', lenient: true);
expect(output, equals('X<Y<>>'));
});
-
test('standalone without previous', () {
var output = _partialTest(
- { },
- {'root': ' {{>partial}}\n>',
- 'partial': '>\n>'},
- 'root',
+ {}, {'root': ' {{>partial}}\n>', 'partial': '>\n>'}, 'root',
lenient: true);
expect(output, equals(' >\n >>'));
});
-
test('standalone indentation', () {
- var output = _partialTest(
- { 'content': "<\n->" },
- {'root': "\\\n {{>partial}}\n\/\n",
- 'partial': "|\n{{{content}}}\n|\n"},
- 'root',
- lenient: true);
+ var output = _partialTest({
+ 'content': "<\n->"
+ }, {
+ 'root': "\\\n {{>partial}}\n\/\n",
+ 'partial': "|\n{{{content}}}\n|\n"
+ }, 'root', lenient: true);
expect(output, equals("\\\n |\n <\n->\n |\n\/\n"));
});
-
- });
+ });
group('Lambdas', () {
-
- _lambdaTest({template, lambda, output}) =>
- expect(parse(template).renderString({'lambda': lambda}), equals(output));
+ _lambdaTest({template, lambda, output}) => expect(
+ parse(template).renderString({'lambda': lambda}), equals(output));
test('basic', () {
_lambdaTest(
@@ -518,12 +558,14 @@
test("seth's use case", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'markdown': (ctx) => ctx.renderString().toLowerCase(), 'content': 'OI YOU!'};
+ var values = {
+ 'markdown': (ctx) => ctx.renderString().toLowerCase(),
+ 'content': 'OI YOU!'
+ };
var output = '<oi you!>';
expect(parse(template).renderString(values), equals(output));
});
-
test("Lambda v2", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
var values = {'markdown': (ctx) => ctx.source, 'content': 'OI YOU!'};
@@ -531,7 +573,6 @@
expect(parse(template).renderString(values), equals(output));
});
-
test("Lambda v2...", () {
var template = '<{{#markdown}}dsfsf dsfsdf dfsdfsd{{/markdown}}>';
var values = {'markdown': (ctx) => ctx.source};
@@ -540,15 +581,16 @@
});
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 values = {
+ 'planet': 'world',
+ 'lambda': (LambdaContext ctx) =>
+ ctx.renderSource('|planet| => {{planet}}')
+ };
var output = 'Hello, (|planet| => world)!';
@@ -556,17 +598,17 @@
});
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 values = {
+ 'planet': 'Earth',
+ 'lambda': (LambdaContext ctx) {
+ var txt = ctx.source;
+ return ctx.renderSource('$txt{{planet}} => |planet|$txt');
+ }
};
var output = '<-{{planet}} => Earth->';
@@ -586,57 +628,56 @@
t.renderString({'foo': (lc) => lc2 = lc, 'bar': 'jim'});
expect(() => lc2.lookup('foo'), throwsException);
});
-
});
- group('Other', () {
- test('Standalone line', () {
- var val = parse('|\n{{#bob}}\n{{/bob}}\n|').renderString({'bob': []});
- expect(val, equals('|\n|'));
- });
- });
+ group('Other', () {
+ test('Standalone line', () {
+ var val = parse('|\n{{#bob}}\n{{/bob}}\n|').renderString({'bob': []});
+ expect(val, equals('|\n|'));
+ });
+ });
- group('Array indexing', () {
- test('Basic', () {
- var val = parse('{{array.1}}').renderString({'array': [1, 2, 3]});
- expect(val, equals('2'));
- });
- test('RangeError', () {
- var error = renderFail('{{array.5}}', {'array': [1, 2, 3]});
- expect(error, isRangeError);
- });
- });
+ group('Array indexing', () {
+ test('Basic', () {
+ var val = parse('{{array.1}}').renderString({
+ 'array': [1, 2, 3]
+ });
+ expect(val, equals('2'));
+ });
+ test('RangeError', () {
+ var error = renderFail('{{array.5}}', {
+ 'array': [1, 2, 3]
+ });
+ expect(error, isRangeError);
+ });
+ });
- group('Mirrors', () {
+ group('Mirrors', () {
test('Simple field', () {
- var output = parse('_{{bar}}_')
- .renderString(new Foo()..bar = 'bob');
+ var output = parse('_{{bar}}_').renderString(new Foo()..bar = 'bob');
expect(output, equals('_bob_'));
});
test('Simple field', () {
- var output = parse('_{{jim}}_')
- .renderString(new Foo());
+ var output = parse('_{{jim}}_').renderString(new Foo());
expect(output, equals('_bob_'));
});
test('Lambda', () {
- var output = parse('_{{lambda}}_')
- .renderString(new Foo()..lambda = (_) => 'yo');
+ var output =
+ parse('_{{lambda}}_').renderString(new Foo()..lambda = (_) => 'yo');
expect(output, equals('_yo_'));
});
});
group('Delimiters', () {
test('Basic', () {
- var val = parse('{{=<% %>=}}(<%text%>)')
- .renderString({'text': 'Hey!'});
+ var val = parse('{{=<% %>=}}(<%text%>)').renderString({'text': 'Hey!'});
expect(val, equals('(Hey!)'));
});
test('Single delimiters', () {
- var val = parse('({{=[ ]=}}[text])')
- .renderString({'text': 'It worked!'});
+ var val = parse('({{=[ ]=}}[text])').renderString({'text': 'It worked!'});
expect(val, equals('(It worked!)'));
});
});
@@ -650,82 +691,90 @@
});
group('Lambda context', () {
-
test("LambdaContext write", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'markdown': (ctx) {
- ctx.write('foo');
- }};
+ var values = {
+ 'markdown': (ctx) {
+ ctx.write('foo');
+ }
+ };
var output = '<foo>';
expect(parse(template).renderString(values), equals(output));
});
test("LambdaContext render", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'content': 'bar', 'markdown': (ctx) {
- ctx.render();
- }};
+ var values = {
+ 'content': 'bar',
+ 'markdown': (ctx) {
+ ctx.render();
+ }
+ };
var output = '<bar>';
expect(parse(template).renderString(values), equals(output));
});
test("LambdaContext render with value", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'markdown': (LambdaContext ctx) {
- ctx.render(value: {'content': 'oi!'});
- }};
+ var values = {
+ 'markdown': (LambdaContext ctx) {
+ ctx.render(value: {'content': 'oi!'});
+ }
+ };
var output = '<oi!>';
expect(parse(template).renderString(values), equals(output));
});
test("LambdaContext renderString with value", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'markdown': (LambdaContext ctx) {
- return ctx.renderString(value: {'content': 'oi!'});
- }};
+ var values = {
+ 'markdown': (LambdaContext ctx) {
+ return ctx.renderString(value: {'content': 'oi!'});
+ }
+ };
var output = '<oi!>';
expect(parse(template).renderString(values), equals(output));
});
test("LambdaContext write and return", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'markdown': (LambdaContext ctx) {
- ctx.write('foo');
- return 'bar';
- }};
+ var values = {
+ 'markdown': (LambdaContext ctx) {
+ ctx.write('foo');
+ return 'bar';
+ }
+ };
var output = '<foobar>';
expect(parse(template).renderString(values), equals(output));
});
test("LambdaContext renderSource with value", () {
var template = '<{{#markdown}}{{content}}{{/markdown}}>';
- var values = {'markdown': (LambdaContext ctx) {
- return ctx.renderSource(ctx.source, value: {'content': 'oi!'});
- }};
+ var values = {
+ 'markdown': (LambdaContext ctx) {
+ return ctx.renderSource(ctx.source, value: {'content': 'oi!'});
+ }
+ };
var output = '<oi!>';
expect(parse(template).renderString(values), equals(output));
});
-
});
}
renderFail(source, values) {
- try {
- parse(source).renderString(values);
- return null;
- } catch (e) {
- return e;
- }
+ try {
+ parse(source).renderString(values);
+ return null;
+ } catch (e) {
+ return e;
+ }
}
expectFail(ex, int line, int column, [String msgStartsWith]) {
- expect(ex is TemplateException, isTrue);
- if (line != null)
- expect(ex.line, equals(line));
- if (column != null)
- expect(ex.column, equals(column));
- if (msgStartsWith != null)
- expect(ex.message, startsWith(msgStartsWith));
+ expect(ex is TemplateException, isTrue);
+ if (line != null) expect(ex.line, equals(line));
+ if (column != null) expect(ex.column, equals(column));
+ if (msgStartsWith != null) expect(ex.message, startsWith(msgStartsWith));
}
class Foo {
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 8554348..9600042 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -7,18 +7,16 @@
import 'package:mustache/src/token.dart';
main() {
-
group('Scanner', () {
-
test('scan text', () {
var source = 'abc';
var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
var tokens = scanner.scan();
- expectTokens(tokens, [ new Token(TokenType.text, 'abc', 0, 3)]);
+ expectTokens(tokens, [new Token(TokenType.text, 'abc', 0, 3)]);
});
-
+
test('scan tag', () {
- var source = 'abc{{foo}}def';
+ var source = 'abc{{foo}}def';
var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
var tokens = scanner.scan();
expectTokens(tokens, [
@@ -29,84 +27,83 @@
new Token(TokenType.text, 'def', 10, 13)
]);
});
-
- test('scan tag whitespace', () {
- var source = 'abc{{ foo }}def';
- var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
- var tokens = scanner.scan();
- expectTokens(tokens, [
- new Token(TokenType.text, 'abc', 0, 3),
- new Token(TokenType.openDelimiter, '{{', 3, 5),
- new Token(TokenType.whitespace, ' ', 5, 6),
- new Token(TokenType.identifier, 'foo', 6, 9),
- new Token(TokenType.whitespace, ' ', 9, 10),
- new Token(TokenType.closeDelimiter, '}}', 10, 12),
- new Token(TokenType.text, 'def', 12, 15)
- ]);
- });
-
- test('scan tag sigil', () {
- var source = 'abc{{ # foo }}def';
- var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
- var tokens = scanner.scan();
- expectTokens(tokens, [
- new Token(TokenType.text, 'abc', 0, 3),
- new Token(TokenType.openDelimiter, '{{', 3, 5),
- new Token(TokenType.whitespace, ' ', 5, 6),
- new Token(TokenType.sigil, '#', 6, 7),
- new Token(TokenType.whitespace, ' ', 7, 8),
- new Token(TokenType.identifier, 'foo', 8, 11),
- new Token(TokenType.whitespace, ' ', 11, 12),
- new Token(TokenType.closeDelimiter, '}}', 12, 14),
- new Token(TokenType.text, 'def', 14, 17)
- ]);
- });
- test('scan tag dot', () {
- var source = 'abc{{ foo.bar }}def';
- var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
- var tokens = scanner.scan();
- expectTokens(tokens, [
- new Token(TokenType.text, 'abc', 0, 3),
- new Token(TokenType.openDelimiter, '{{', 3, 5),
- new Token(TokenType.whitespace, ' ', 5, 6),
- new Token(TokenType.identifier, 'foo', 6, 9),
- new Token(TokenType.dot, '.', 9, 10),
- new Token(TokenType.identifier, 'bar', 10, 13),
- new Token(TokenType.whitespace, ' ', 13, 14),
- new Token(TokenType.closeDelimiter, '}}', 14, 16),
- new Token(TokenType.text, 'def', 16, 19)
- ]);
- });
+ test('scan tag whitespace', () {
+ var source = 'abc{{ foo }}def';
+ var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
+ var tokens = scanner.scan();
+ expectTokens(tokens, [
+ new Token(TokenType.text, 'abc', 0, 3),
+ new Token(TokenType.openDelimiter, '{{', 3, 5),
+ new Token(TokenType.whitespace, ' ', 5, 6),
+ new Token(TokenType.identifier, 'foo', 6, 9),
+ new Token(TokenType.whitespace, ' ', 9, 10),
+ new Token(TokenType.closeDelimiter, '}}', 10, 12),
+ new Token(TokenType.text, 'def', 12, 15)
+ ]);
+ });
- test('scan triple mustache', () {
- var source = 'abc{{{foo}}}def';
- var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
- var tokens = scanner.scan();
- expectTokens(tokens, [
- new Token(TokenType.text, 'abc', 0, 3),
- new Token(TokenType.openDelimiter, '{{{', 3, 6),
- new Token(TokenType.identifier, 'foo', 6, 9),
- new Token(TokenType.closeDelimiter, '}}}', 9, 12),
- new Token(TokenType.text, 'def', 12, 15)
- ]);
- });
+ test('scan tag sigil', () {
+ var source = 'abc{{ # foo }}def';
+ var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
+ var tokens = scanner.scan();
+ expectTokens(tokens, [
+ new Token(TokenType.text, 'abc', 0, 3),
+ new Token(TokenType.openDelimiter, '{{', 3, 5),
+ new Token(TokenType.whitespace, ' ', 5, 6),
+ new Token(TokenType.sigil, '#', 6, 7),
+ new Token(TokenType.whitespace, ' ', 7, 8),
+ new Token(TokenType.identifier, 'foo', 8, 11),
+ new Token(TokenType.whitespace, ' ', 11, 12),
+ new Token(TokenType.closeDelimiter, '}}', 12, 14),
+ new Token(TokenType.text, 'def', 14, 17)
+ ]);
+ });
-
- test('scan triple mustache whitespace', () {
- var source = 'abc{{{ foo }}}def';
- var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
- var tokens = scanner.scan();
- expectTokens(tokens, [
- new Token(TokenType.text, 'abc', 0, 3),
- new Token(TokenType.openDelimiter, '{{{', 3, 6),
- new Token(TokenType.whitespace, ' ', 6, 7),
- new Token(TokenType.identifier, 'foo', 7, 10),
- new Token(TokenType.whitespace, ' ', 10, 11),
- new Token(TokenType.closeDelimiter, '}}}', 11, 14),
- new Token(TokenType.text, 'def', 14, 17)
- ]);
- });
+ test('scan tag dot', () {
+ var source = 'abc{{ foo.bar }}def';
+ var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
+ var tokens = scanner.scan();
+ expectTokens(tokens, [
+ new Token(TokenType.text, 'abc', 0, 3),
+ new Token(TokenType.openDelimiter, '{{', 3, 5),
+ new Token(TokenType.whitespace, ' ', 5, 6),
+ new Token(TokenType.identifier, 'foo', 6, 9),
+ new Token(TokenType.dot, '.', 9, 10),
+ new Token(TokenType.identifier, 'bar', 10, 13),
+ new Token(TokenType.whitespace, ' ', 13, 14),
+ new Token(TokenType.closeDelimiter, '}}', 14, 16),
+ new Token(TokenType.text, 'def', 16, 19)
+ ]);
+ });
+
+ test('scan triple mustache', () {
+ var source = 'abc{{{foo}}}def';
+ var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
+ var tokens = scanner.scan();
+ expectTokens(tokens, [
+ new Token(TokenType.text, 'abc', 0, 3),
+ new Token(TokenType.openDelimiter, '{{{', 3, 6),
+ new Token(TokenType.identifier, 'foo', 6, 9),
+ new Token(TokenType.closeDelimiter, '}}}', 9, 12),
+ new Token(TokenType.text, 'def', 12, 15)
+ ]);
+ });
+
+ test('scan triple mustache whitespace', () {
+ var source = 'abc{{{ foo }}}def';
+ var scanner = new Scanner(source, 'foo', '{{ }}', lenient: false);
+ var tokens = scanner.scan();
+ expectTokens(tokens, [
+ new Token(TokenType.text, 'abc', 0, 3),
+ new Token(TokenType.openDelimiter, '{{{', 3, 6),
+ new Token(TokenType.whitespace, ' ', 6, 7),
+ new Token(TokenType.identifier, 'foo', 7, 10),
+ new Token(TokenType.whitespace, ' ', 10, 11),
+ new Token(TokenType.closeDelimiter, '}}}', 11, 14),
+ new Token(TokenType.text, 'def', 14, 17)
+ ]);
+ });
test('scan tag with equals', () {
var source = '{{foo=bar}}';
@@ -130,272 +127,258 @@
new Token(TokenType.closeDelimiter, '}}', 10, 12),
]);
});
-
});
-
+
group('Parser', () {
-
- test('parse variable', () {
- var source = 'abc{{foo}}def';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc', 0, 3),
- new VariableNode('foo', 3, 10, escape: true),
- new TextNode('def', 10, 13)
- ]);
- });
+ test('parse variable', () {
+ var source = 'abc{{foo}}def';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new TextNode('abc', 0, 3),
+ new VariableNode('foo', 3, 10, escape: true),
+ new TextNode('def', 10, 13)
+ ]);
+ });
- test('parse variable whitespace', () {
- var source = 'abc{{ foo }}def';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc', 0, 3),
- new VariableNode('foo', 3, 12, escape: true),
- new TextNode('def', 12, 15)
- ]);
- });
-
- test('parse section', () {
- var source = 'abc{{#foo}}def{{/foo}}ghi';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc', 0, 3),
- new SectionNode('foo', 3, 11, '{{ }}'),
- new TextNode('ghi', 22, 25)
- ]);
- expectNodes(nodes[1].children, [new TextNode('def', 11, 14)]);
- });
+ test('parse variable whitespace', () {
+ var source = 'abc{{ foo }}def';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new TextNode('abc', 0, 3),
+ new VariableNode('foo', 3, 12, escape: true),
+ new TextNode('def', 12, 15)
+ ]);
+ });
- test('parse section standalone tag whitespace', () {
- var source = 'abc\n{{#foo}}\ndef\n{{/foo}}\nghi';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc\n', 0, 4),
- new SectionNode('foo', 4, 12, '{{ }}'),
- new TextNode('ghi', 26, 29)
- ]);
- expectNodes(nodes[1].children, [new TextNode('def\n', 13, 17)]);
- });
+ test('parse section', () {
+ var source = 'abc{{#foo}}def{{/foo}}ghi';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new TextNode('abc', 0, 3),
+ new SectionNode('foo', 3, 11, '{{ }}'),
+ new TextNode('ghi', 22, 25)
+ ]);
+ expectNodes(nodes[1].children, [new TextNode('def', 11, 14)]);
+ });
- test('parse section standalone tag whitespace consecutive', () {
- var source = 'abc\n{{#foo}}\ndef\n{{/foo}}\n{{#foo}}\ndef\n{{/foo}}\nghi';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc\n', 0, 4),
- new SectionNode('foo', 4, 12, '{{ }}'),
- new SectionNode('foo', 26, 34, '{{ }}'),
- new TextNode('ghi', 48, 51),
- ]);
- expectNodes(nodes[1].children, [new TextNode('def\n', 13, 17)]);
- });
-
- test('parse section standalone tag whitespace on first line', () {
- var source = ' {{#foo}} \ndef\n{{/foo}}\nghi';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new SectionNode('foo', 2, 10, '{{ }}'),
- new TextNode('ghi', 26, 29)
- ]);
- expectNodes(nodes[0].children, [new TextNode('def\n', 13, 17)]);
- });
+ test('parse section standalone tag whitespace', () {
+ var source = 'abc\n{{#foo}}\ndef\n{{/foo}}\nghi';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new TextNode('abc\n', 0, 4),
+ new SectionNode('foo', 4, 12, '{{ }}'),
+ new TextNode('ghi', 26, 29)
+ ]);
+ expectNodes(nodes[1].children, [new TextNode('def\n', 13, 17)]);
+ });
- test('parse section standalone tag whitespace on last line', () {
- var source = '{{#foo}}def\n {{/foo}} ';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new SectionNode('foo', 0, 8, '{{ }}')
- ]);
- expectNodes(nodes[0].children, [new TextNode('def\n', 8, 12)]);
- });
-
- test('parse whitespace', () {
- var source = 'abc\n ';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc\n ', 0, 7),
- ]);
- });
-
- test('parse partial', () {
- var source = 'abc\n {{>foo}}def';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new TextNode('abc\n ', 0, 7),
- new PartialNode('foo', 7, 15, ' '),
- new TextNode('def', 15, 18)
- ]);
- });
+ test('parse section standalone tag whitespace consecutive', () {
+ var source = 'abc\n{{#foo}}\ndef\n{{/foo}}\n{{#foo}}\ndef\n{{/foo}}\nghi';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new TextNode('abc\n', 0, 4),
+ new SectionNode('foo', 4, 12, '{{ }}'),
+ new SectionNode('foo', 26, 34, '{{ }}'),
+ new TextNode('ghi', 48, 51),
+ ]);
+ expectNodes(nodes[1].children, [new TextNode('def\n', 13, 17)]);
+ });
- test('parse change delimiters', () {
- var source = '{{= | | =}}<|#lambda|-|/lambda|>';
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- var nodes = parser.parse();
- expect(nodes[1].delimiters, equals('| |'));
- expectNodes(nodes, [
- new TextNode('<', 11, 12),
- new SectionNode('lambda', 12, 21, '| |'),
- new TextNode('>', 31, 32),
- ]);
- expectNodes(nodes[1].children, [new TextNode('-', 21, 22)]);
- });
-
- test('corner case strict', () {
- var source = "{{{ #foo }}} {{{ /foo }}}";
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- try {
- parser.parse();
- fail('Should fail.');
- } catch (e) {
- expect(e is TemplateException, isTrue);
- }
- });
+ test('parse section standalone tag whitespace on first line', () {
+ var source = ' {{#foo}} \ndef\n{{/foo}}\nghi';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new SectionNode('foo', 2, 10, '{{ }}'),
+ new TextNode('ghi', 26, 29)
+ ]);
+ expectNodes(nodes[0].children, [new TextNode('def\n', 13, 17)]);
+ });
- test('corner case lenient', () {
- var source = "{{{ #foo }}} {{{ /foo }}}";
- var parser = new Parser(source, 'foo', '{{ }}', lenient: true);
- var nodes = parser.parse();
- expectNodes(nodes, [
- new VariableNode('#foo', 0, 12, escape: false),
- new TextNode(' ', 12, 13),
- new VariableNode('/foo', 13, 25, escape: false)
- ]);
- });
-
- test('toString', () {
- new TextNode('foo', 1, 3).toString();
- new VariableNode('foo', 1, 3).toString();
- new PartialNode('foo', 1, 3, ' ').toString();
- new SectionNode('foo', 1, 3, '{{ }}').toString();
- new Token(TokenType.closeDelimiter, 'foo', 1, 3).toString();
- TokenType.closeDelimiter.toString();
- });
-
- test('exception', () {
- var source = "'{{ foo }} sdfffffffffffffffffffffffffffffffffffffffffffff "
- "dsfsdf sdfdsa fdsfads fsdfdsfadsf dsfasdfsdf sdfdsfsadf sdfadsfsdf ";
- var ex = new TemplateException('boom!', 'foo.mustache', source, 2);
- ex.toString();
- });
-
- parseFail(source) {
- try {
- var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
- parser.parse();
- fail('Did not throw.');
- return null;
- } catch (ex, st) {
- if (ex is! TemplateException) {
- print(ex);
- print(st);
- }
- return ex;
- }
- }
-
- test('parse eof', () {
- expectTemplateEx(ex) => expect(ex is TemplateException, isTrue);
-
- expectTemplateEx(parseFail('{{#foo}}{{bar}}{{/foo}'));
- expectTemplateEx(parseFail('{{#foo}}{{bar}}{{/foo'));
- expectTemplateEx(parseFail('{{#foo}}{{bar}}{{/'));
- expectTemplateEx(parseFail('{{#foo}}{{bar}}{{'));
- expectTemplateEx(parseFail('{{#foo}}{{bar}}{'));
- expectTemplateEx(parseFail('{{#foo}}{{bar}}'));
- expectTemplateEx(parseFail('{{#foo}}{{bar}'));
- expectTemplateEx(parseFail('{{#foo}}{{bar'));
- expectTemplateEx(parseFail('{{#foo}}{{'));
- expectTemplateEx(parseFail('{{#foo}}{'));
- expectTemplateEx(parseFail('{{#foo}}'));
- expectTemplateEx(parseFail('{{#foo}'));
- expectTemplateEx(parseFail('{{#'));
- expectTemplateEx(parseFail('{{'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / foo }'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / foo '));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / foo'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / '));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ /'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ '));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }}'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar }'));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar '));
- expectTemplateEx(parseFail('{{ # foo }}{{ bar'));
- expectTemplateEx(parseFail('{{ # foo }}{{ '));
- expectTemplateEx(parseFail('{{ # foo }}{{'));
- expectTemplateEx(parseFail('{{ # foo }}{'));
- expectTemplateEx(parseFail('{{ # foo }}'));
- expectTemplateEx(parseFail('{{ # foo }'));
- expectTemplateEx(parseFail('{{ # foo '));
- expectTemplateEx(parseFail('{{ # foo'));
- expectTemplateEx(parseFail('{{ # '));
- expectTemplateEx(parseFail('{{ #'));
- expectTemplateEx(parseFail('{{ '));
- expectTemplateEx(parseFail('{{'));
-
- expectTemplateEx(parseFail('{{= || || =}'));
- expectTemplateEx(parseFail('{{= || || ='));
- expectTemplateEx(parseFail('{{= || || '));
- expectTemplateEx(parseFail('{{= || ||'));
- expectTemplateEx(parseFail('{{= || |'));
- expectTemplateEx(parseFail('{{= || '));
- expectTemplateEx(parseFail('{{= ||'));
- expectTemplateEx(parseFail('{{= |'));
- expectTemplateEx(parseFail('{{= '));
- expectTemplateEx(parseFail('{{='));
- });
-
+ test('parse section standalone tag whitespace on last line', () {
+ var source = '{{#foo}}def\n {{/foo}} ';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [new SectionNode('foo', 0, 8, '{{ }}')]);
+ expectNodes(nodes[0].children, [new TextNode('def\n', 8, 12)]);
+ });
+
+ test('parse whitespace', () {
+ var source = 'abc\n ';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [new TextNode('abc\n ', 0, 7),]);
+ });
+
+ test('parse partial', () {
+ var source = 'abc\n {{>foo}}def';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new TextNode('abc\n ', 0, 7),
+ new PartialNode('foo', 7, 15, ' '),
+ new TextNode('def', 15, 18)
+ ]);
+ });
+
+ test('parse change delimiters', () {
+ var source = '{{= | | =}}<|#lambda|-|/lambda|>';
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ var nodes = parser.parse();
+ expect(nodes[1].delimiters, equals('| |'));
+ expectNodes(nodes, [
+ new TextNode('<', 11, 12),
+ new SectionNode('lambda', 12, 21, '| |'),
+ new TextNode('>', 31, 32),
+ ]);
+ expectNodes(nodes[1].children, [new TextNode('-', 21, 22)]);
+ });
+
+ test('corner case strict', () {
+ var source = "{{{ #foo }}} {{{ /foo }}}";
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ try {
+ parser.parse();
+ fail('Should fail.');
+ } catch (e) {
+ expect(e is TemplateException, isTrue);
+ }
+ });
+
+ test('corner case lenient', () {
+ var source = "{{{ #foo }}} {{{ /foo }}}";
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: true);
+ var nodes = parser.parse();
+ expectNodes(nodes, [
+ new VariableNode('#foo', 0, 12, escape: false),
+ new TextNode(' ', 12, 13),
+ new VariableNode('/foo', 13, 25, escape: false)
+ ]);
+ });
+
+ test('toString', () {
+ new TextNode('foo', 1, 3).toString();
+ new VariableNode('foo', 1, 3).toString();
+ new PartialNode('foo', 1, 3, ' ').toString();
+ new SectionNode('foo', 1, 3, '{{ }}').toString();
+ new Token(TokenType.closeDelimiter, 'foo', 1, 3).toString();
+ TokenType.closeDelimiter.toString();
+ });
+
+ test('exception', () {
+ var source = "'{{ foo }} sdfffffffffffffffffffffffffffffffffffffffffffff "
+ "dsfsdf sdfdsa fdsfads fsdfdsfadsf dsfasdfsdf sdfdsfsadf sdfadsfsdf ";
+ var ex = new TemplateException('boom!', 'foo.mustache', source, 2);
+ ex.toString();
+ });
+
+ parseFail(source) {
+ try {
+ var parser = new Parser(source, 'foo', '{{ }}', lenient: false);
+ parser.parse();
+ fail('Did not throw.');
+ return null;
+ } catch (ex, st) {
+ if (ex is! TemplateException) {
+ print(ex);
+ print(st);
+ }
+ return ex;
+ }
+ }
+
+ test('parse eof', () {
+ expectTemplateEx(ex) => expect(ex is TemplateException, isTrue);
+
+ expectTemplateEx(parseFail('{{#foo}}{{bar}}{{/foo}'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar}}{{/foo'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar}}{{/'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar}}{{'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar}}{'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar}}'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar}'));
+ expectTemplateEx(parseFail('{{#foo}}{{bar'));
+ expectTemplateEx(parseFail('{{#foo}}{{'));
+ expectTemplateEx(parseFail('{{#foo}}{'));
+ expectTemplateEx(parseFail('{{#foo}}'));
+ expectTemplateEx(parseFail('{{#foo}'));
+ expectTemplateEx(parseFail('{{#'));
+ expectTemplateEx(parseFail('{{'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / foo }'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / foo '));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / foo'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ / '));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ /'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{ '));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{{'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}{'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }}'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar }'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar '));
+ expectTemplateEx(parseFail('{{ # foo }}{{ bar'));
+ expectTemplateEx(parseFail('{{ # foo }}{{ '));
+ expectTemplateEx(parseFail('{{ # foo }}{{'));
+ expectTemplateEx(parseFail('{{ # foo }}{'));
+ expectTemplateEx(parseFail('{{ # foo }}'));
+ expectTemplateEx(parseFail('{{ # foo }'));
+ expectTemplateEx(parseFail('{{ # foo '));
+ expectTemplateEx(parseFail('{{ # foo'));
+ expectTemplateEx(parseFail('{{ # '));
+ expectTemplateEx(parseFail('{{ #'));
+ expectTemplateEx(parseFail('{{ '));
+ expectTemplateEx(parseFail('{{'));
+
+ expectTemplateEx(parseFail('{{= || || =}'));
+ expectTemplateEx(parseFail('{{= || || ='));
+ expectTemplateEx(parseFail('{{= || || '));
+ expectTemplateEx(parseFail('{{= || ||'));
+ expectTemplateEx(parseFail('{{= || |'));
+ expectTemplateEx(parseFail('{{= || '));
+ expectTemplateEx(parseFail('{{= ||'));
+ expectTemplateEx(parseFail('{{= |'));
+ expectTemplateEx(parseFail('{{= '));
+ expectTemplateEx(parseFail('{{='));
+ });
});
-
}
nodeEqual(a, b) {
if (a is TextNode) {
- return b is TextNode
- && a.text == b.text
- && a.start == b.start
- && a.end == b.end;
-
+ return b is TextNode &&
+ a.text == b.text &&
+ a.start == b.start &&
+ a.end == b.end;
} else if (a is VariableNode) {
- return a is VariableNode
- && a.name == b.name
- && a.escape == b.escape
- && a.start == b.start
- && a.end == b.end;
-
+ return a is VariableNode &&
+ a.name == b.name &&
+ a.escape == b.escape &&
+ a.start == b.start &&
+ a.end == b.end;
} else if (a is SectionNode) {
- return a is SectionNode
- && a.name == b.name
- && a.delimiters == b.delimiters
- && a.inverse == b.inverse
- && a.start == b.start
- && a.end == b.end;
-
+ return a is SectionNode &&
+ a.name == b.name &&
+ a.delimiters == b.delimiters &&
+ a.inverse == b.inverse &&
+ a.start == b.start &&
+ a.end == b.end;
} else if (a is PartialNode) {
- return a is PartialNode
- && a.name == b.name
- && a.indent == b.indent;
-
+ return a is PartialNode && a.name == b.name && a.indent == b.indent;
} else {
return false;
}
}
-tokenEqual(Token a, Token b) {
- return a is Token
- && a.type == b.type
- && a.value == b.value
- && a.start == b.start
- && a.end == b.end;
+tokenEqual(Token a, Token b) {
+ return a is Token &&
+ a.type == b.type &&
+ a.value == b.value &&
+ a.start == b.start &&
+ a.end == b.end;
}
expectTokens(List<Token> a, List<Token> b) {