Pass template name in error messages
diff --git a/lib/char_reader.dart b/lib/char_reader.dart
index ff3a3f1..1c95597 100644
--- a/lib/char_reader.dart
+++ b/lib/char_reader.dart
@@ -9,7 +9,7 @@
_CharReader(String source)
: _source = source,
- _itr = source.runes.iterator { //FIXME runes etc. Not sure if this is the right count.
+ _itr = source.runes.iterator {
if (source == null)
throw new ArgumentError('Source is null.');
@@ -50,8 +50,11 @@
String readWhile(bool test(int charCode)) {
+ //FIXME provide template name. Or perhaps this is a programmer error
+ // and this shouldn't actually happen.
if (peek() == _EOF)
- throw new MustacheFormatException('Unexpected end of input.', line, column);
+ throw new MustacheFormatException(
+ 'Unexpected end of input', null, line, column);
int start = _i;
diff --git a/lib/mustache.dart b/lib/mustache.dart
index fff1d98..163f0bc 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -13,16 +13,18 @@
/// with substituted values.
/// Tag names may only contain characters a-z, A-Z, 0-9, underscore, and minus,
/// unless lenient mode is specified.
-/// Throws [FormatException] if the syntax of the source is invalid.
+/// Throws [MustacheFormatException] if the syntax of the source is invalid.
Template parse(String source,
- {bool lenient : false}) => _parse(source, lenient: lenient);
+ {bool lenient : false, String templateName}) =>
+ _parse(source, lenient: lenient, templateName: templateName);
+
/// A Template can be rendered multiple times with different values.
abstract class Template {
/// [values] can be a combination of Map, List, String. Any non-String object
/// will be converted using toString(). Null values will cause a
- /// FormatException, unless lenient module is enabled.
+ /// [MustacheFormatException], unless lenient module is enabled.
String renderString(values, {bool lenient : false, bool htmlEscapeValues : true});
/// [values] can be a combination of Map, List, String. Any non-String object
@@ -31,37 +33,54 @@
void render(values, StringSink sink, {bool lenient : false, bool htmlEscapeValues : true});
}
-//TODO consider using FormatException, which prints nicer error messages.
-/// MustacheFormatException is used to obtain the line and column numbers
+
+/// [MustacheFormatException] is used to obtain the line and column numbers
/// of the token which caused parse or render to fail.
class MustacheFormatException implements Exception {
+ factory MustacheFormatException(
+ String message, String template, int line, int column) {
+
+ var at = template == null
+ ? '$line:$column'
+ : '$template:$line:$column';
+
+ return new MustacheFormatException._private(
+ '$message, at: $at.', template, line, column);
+ }
+
+ MustacheFormatException._private(
+ this.message, this.templateName, this.line, this.column);
+
final String message;
+ final String templateName;
+
/// The 1-based line number of the token where formatting error was found.
final int line;
/// The 1-based column number of the token where formatting error was found.
- final int column;
-
- MustacheFormatException(this.message, this.line, this.column);
+ final int column;
String toString() => message;
}
+
//TODO does this require some sort of context to find partials nested in subdirs?
typedef Template PartialResolver(String templateName);
+
// Required for handing partials
abstract class TemplateRenderer {
- factory TemplateRenderer(PartialResolver partialResolver,
- {bool lenient, bool htmlEscapeValues}) = _TemplateRenderer;
+ factory TemplateRenderer(
+ PartialResolver partialResolver,
+ {bool lenient,
+ bool htmlEscapeValues}) = _TemplateRenderer;
String renderString(String templateName, values);
void render(String templateName, values, StringSink sink);
}
-
diff --git a/lib/scanner.dart b/lib/scanner.dart
index 6a591b9..d7f6ea2 100644
--- a/lib/scanner.dart
+++ b/lib/scanner.dart
@@ -141,8 +141,10 @@
}
class _Scanner {
- _Scanner(String source) : _r = new _CharReader(source);
+ _Scanner(String source, [this._templateName])
+ : _r = new _CharReader(source);
+ final String _templateName;
_CharReader _r;
List<_Token> _tokens = new List<_Token>();
@@ -166,13 +168,14 @@
int c = _read();
if (c == _EOF) {
- throw new MustacheFormatException('Unexpected end of input.', _r.line, _r.column);
+ throw new MustacheFormatException('Unexpected end of input',
+ _templateName, _r.line, _r.column);
} else if (c != expectedCharCode) {
throw new MustacheFormatException('Unexpected character, '
'expected: ${new String.fromCharCode(expectedCharCode)} ($expectedCharCode), '
- 'was: ${new String.fromCharCode(c)} ($c), '
- 'at: ${_r.line}:${_r.column}', _r.line, _r.column);
+ 'was: ${new String.fromCharCode(c)} ($c)',
+ _templateName, _r.line, _r.column);
}
}
@@ -254,7 +257,8 @@
switch(_peek()) {
case _EOF:
- throw new MustacheFormatException('Unexpected end of input.', _r.line, _r.column);
+ throw new MustacheFormatException('Unexpected end of input',
+ _templateName, _r.line, _r.column);
// Escaped text {{{ ... }}}
case _OPEN_MUSTACHE:
diff --git a/lib/template.dart b/lib/template.dart
index 49b1c44..c41f857 100644
--- a/lib/template.dart
+++ b/lib/template.dart
@@ -5,34 +5,36 @@
final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
final RegExp _integerTag = new RegExp(r'^[0-9]+$');
-Template _parse(String source, {bool lenient : false, PartialResolver partialResolver}) {
+Template _parse(String source,
+ {bool lenient : false,
+ PartialResolver partialResolver,
+ String templateName}) {
var tokens = _scan(source, lenient);
- var ast = _parseTokens(tokens, lenient);
- return new _Template(ast, lenient);
+ var ast = _parseTokens(tokens, lenient, templateName);
+ return new _Template(ast, lenient, templateName);
}
-_Node _parseTokens(List<_Token> tokens, bool lenient) {
+_Node _parseTokens(List<_Token> tokens, bool lenient, String templateName) {
var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0));
for (var t in tokens) {
if (const [_TEXT, _VARIABLE, _UNESC_VARIABLE, _PARTIAL].contains(t.type)) {
if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)
- _checkTagChars(t, lenient);
+ _checkTagChars(t, lenient, templateName);
stack.last.children.add(new _Node.fromToken(t));
} else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
- _checkTagChars(t, lenient);
+ _checkTagChars(t, lenient, templateName);
var child = new _Node.fromToken(t);
stack.last.children.add(child);
stack.add(child);
} else if (t.type == _CLOSE_SECTION) {
- _checkTagChars(t, lenient);
+ _checkTagChars(t, lenient, templateName);
if (stack.last.value != t.value) {
- throw new MustacheFormatException('Mismatched tag, '
- "expected: '${stack.last.value}', "
- "was: '${t.value}', "
- 'at: ${t.line}:${t.column}.', t.line, t.column);
+ throw new MustacheFormatException(
+ "Mismatched tag, expected: '${stack.last.value}', was: '${t.value}'",
+ templateName, t.line, t.column);
}
stack.removeLast();
@@ -41,6 +43,7 @@
// Do nothing
} else {
+ //FIXME Use switch with enums so this becomes a compile time error.
throw new UnimplementedError();
}
}
@@ -48,20 +51,21 @@
return stack.last;
}
-_checkTagChars(_Token t, bool lenient) {
+_checkTagChars(_Token t, bool lenient, String templateName) {
if (!lenient && !_validTag.hasMatch(t.value)) {
throw new MustacheFormatException(
- 'Tag contained invalid characters in name, '
- 'allowed: 0-9, a-z, A-Z, underscore, and minus, '
- 'at: ${t.line}:${t.column}.', t.line, t.column);
+ 'Tag contained invalid characters in name, '
+ 'allowed: 0-9, a-z, A-Z, underscore, and minus',
+ templateName, t.line, t.column);
}
}
class _Template implements Template {
- _Template(this._root, this._lenient);
+ _Template(this._root, this._lenient, this._name);
+ final String _name;
final _Node _root;
final bool _lenient;
@@ -80,7 +84,7 @@
bool htmlEscapeValues : true,
PartialResolver partialResolver}) {
var renderer = new _Renderer(_root, sink, values, [values],
- lenient, htmlEscapeValues, partialResolver);
+ lenient, htmlEscapeValues, partialResolver, _name);
renderer.render();
}
}
@@ -94,7 +98,8 @@
this._stack,
this._lenient,
this._htmlEscapeValues,
- this._partialResolver);
+ this._partialResolver,
+ this._templateName);
_Renderer.partial(_Renderer renderer, _Template partial)
: this(partial._root,
@@ -103,7 +108,8 @@
renderer._stack,
renderer._lenient,
renderer._htmlEscapeValues,
- renderer._partialResolver);
+ renderer._partialResolver,
+ renderer._templateName);
final _Node _root;
final StringSink _sink;
@@ -112,6 +118,7 @@
final bool _lenient;
final bool _htmlEscapeValues;
final PartialResolver _partialResolver;
+ final String _templateName;
void render() {
_root.children.forEach(_renderNode);
@@ -210,9 +217,8 @@
if (value == _NO_SUCH_PROPERTY) {
if (!_lenient)
throw new MustacheFormatException(
- 'Value was missing, '
- 'variable: ${node.value}, '
- 'at: ${node.line}:${node.column}.', node.line, node.column);
+ 'Value was missing, variable: ${node.value}',
+ _templateName, node.line, node.column);
} else {
var valueString = (value == null) ? '' : value.toString();
var output = !escape || !_htmlEscapeValues
@@ -243,15 +249,14 @@
} else if (value == _NO_SUCH_PROPERTY) {
if (!_lenient)
throw new MustacheFormatException(
- 'Value was missing, '
- 'section: ${node.value}, '
- 'at: ${node.line}:${node.column}.', node.line, node.column);
+ 'Value was missing, section: ${node.value}',
+ _templateName, node.line, node.column);
} else {
throw new MustacheFormatException(
- 'Invalid value type for section, '
+ 'Invalid value type for section, '
'section: ${node.value}, '
- 'type: ${value.runtimeType}, '
- 'at: ${node.line}:${node.column}.', node.line, node.column);
+ 'type: ${value.runtimeType}',
+ _templateName, node.line, node.column);
}
}
@@ -268,16 +273,15 @@
_renderSectionWithValue(node, null);
} else {
throw new MustacheFormatException(
- 'Value was missing, '
- 'inverse-section: ${node.value}, '
- 'at: ${node.line}:${node.column}.', node.line, node.column);
+ 'Value was missing, inverse-section: ${node.value}',
+ _templateName, node.line, node.column);
}
} else {
throw new MustacheFormatException(
- 'Invalid value type for inverse section, '
+ 'Invalid value type for inverse section, '
'section: ${node.value}, '
- 'type: ${value.runtimeType}, '
- 'at: ${node.line}:${node.column}.', node.line, node.column);
+ 'type: ${value.runtimeType}, ',
+ _templateName, node.line, node.column);
}
}