Experimental hack to unify mustache and mustache_template libraries
diff --git a/lib/mustache.dart b/lib/mustache.dart index 687459c..f6c6ccc 100644 --- a/lib/mustache.dart +++ b/lib/mustache.dart
@@ -12,7 +12,8 @@ bool htmlEscapeValues, String name, PartialResolver partialResolver, - String delimiters}) = t.Template.fromSource; + String delimiters, + ValueResolver valueResolver}) = t.Template.fromSource; String get name; String get source; @@ -30,6 +31,27 @@ typedef PartialResolver = Template Function(String); +const Object noSuchProperty = Object(); + +typedef ValueResolver = Object Function(Object, Object); + +final RegExp _integerTag = RegExp(r'^[0-9]+$'); + +//FIXME should name be String?? +// 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. +Object defaultValueResolver(Object object, Object name) { + if (object is Map && object.containsKey(name)) return object[name]; + + if (object is List && _integerTag.hasMatch(name)) { + return object[int.parse(name)]; + } + + return noSuchProperty; +} + typedef LambdaFunction = Object Function(LambdaContext context); /// Passed as an argument to a mustache lambda function. The methods on
diff --git a/lib/mustache_with_mirrors.dart b/lib/mustache_with_mirrors.dart new file mode 100644 index 0000000..29bc8be --- /dev/null +++ b/lib/mustache_with_mirrors.dart
@@ -0,0 +1,66 @@ +import 'dart:mirrors'; + +export 'mustache.dart' hide Template; +import 'mustache.dart' as m; +import 'src/template.dart' as t; + +class Template extends t.Template { + Template(String source, + {bool lenient = false, + bool htmlEscapeValues = true, + String name, + m.PartialResolver partialResolver, + String delimiters = '{{ }}', + m.ValueResolver valueResolver}) + : super.fromSource(source, + lenient: lenient, + htmlEscapeValues: htmlEscapeValues, + name: name, + partialResolver: partialResolver, + delimiters: delimiters, + valueResolver: + valueResolver ?? + (lenient ? lenientMirrorValueResolver : mirrorValueResolver)); +} + +final RegExp _validTag = RegExp(r'^[0-9a-zA-Z\_\-\.]+$'); +final RegExp _integerTag = RegExp(r'^[0-9]+$'); + +Object mirrorValueResolver(Object object, Object name) => + _mirrorValueResolver(object, name, lenient: false); + +Object lenientMirrorValueResolver(Object object, Object name) => + _mirrorValueResolver(object, name, lenient: true); + +//FIXME name should be string right? +// 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. +Object _mirrorValueResolver(Object object, Object name, + {bool lenient = false}) { + 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 m.noSuchProperty; + + var instance = reflect(object); + var field = instance.type.instanceMembers[Symbol(name)]; + if (field == null) return m.noSuchProperty; + + var invocation; + if ((field is VariableMirror) || + ((field is MethodMirror) && (field.isGetter))) { + invocation = instance.getField(field.simpleName); + } else if ((field is MethodMirror) && + (field.parameters.where((p) => !p.isOptional).isEmpty)) { + invocation = instance.invoke(field.simpleName, []); + } + if (invocation == null) { + return m.noSuchProperty; + } + return invocation.reflectee; +}
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart index 74ca3cf..56e95f0 100644 --- a/lib/src/renderer.dart +++ b/lib/src/renderer.dart
@@ -4,12 +4,18 @@ import 'template.dart'; import 'template_exception.dart'; -const Object noSuchProperty = Object(); -final RegExp _integerTag = RegExp(r'^[0-9]+$'); class Renderer extends Visitor { - Renderer(this.sink, List stack, this.lenient, this.htmlEscapeValues, - this.partialResolver, this.templateName, this.indent, this.source) + Renderer( + this.sink, + List stack, + this.lenient, + this.htmlEscapeValues, + this.partialResolver, + this.valueResolver, + this.templateName, + this.indent, + this.source) : _stack = List.from(stack); Renderer.partial(Renderer ctx, Template partial, String indent) @@ -19,24 +25,42 @@ ctx.lenient, ctx.htmlEscapeValues, ctx.partialResolver, + ctx.valueResolver, 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.valueResolver, + ctx.templateName, + ctx.indent, + ctx.source); 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.valueResolver, + ctx.templateName, + ctx.indent + indent, + source); final StringSink sink; final List _stack; final bool lenient; final bool htmlEscapeValues; final m.PartialResolver partialResolver; + final m.ValueResolver valueResolver; final String templateName; final String indent; final String source; @@ -92,7 +116,7 @@ context.close(); } - if (value == noSuchProperty) { + if (value == m.noSuchProperty) { if (!lenient) { throw error('Value was missing for variable tag: ${node.name}.', node); } @@ -130,7 +154,7 @@ } else if (value == false) { // Do nothing. - } else if (value == noSuchProperty) { + } else if (value == m.noSuchProperty) { if (!lenient) { throw error('Value was missing for section tag: ${node.name}.', node); } @@ -155,7 +179,7 @@ } else if (value == true || value is Map || value is Iterable) { // Do nothing. - } else if (value == noSuchProperty) { + } else if (value == m.noSuchProperty) { if (lenient) { _renderWithValue(node, null); } else { @@ -208,36 +232,22 @@ return _stack.last; } var parts = name.split('.'); - var object = noSuchProperty; + var object = m.noSuchProperty; for (var o in _stack.reversed) { - object = _getNamedProperty(o, parts[0]); - if (object != noSuchProperty) { + object = valueResolver(o, parts[0]); + if (object != m.noSuchProperty) { break; } } for (var i = 1; i < parts.length; i++) { - if (object == null || object == noSuchProperty) { - return noSuchProperty; + if (object == null || object == m.noSuchProperty) { + return m.noSuchProperty; } - object = _getNamedProperty(object, parts[i]); + object = valueResolver(object, parts[i]); } 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. - Object _getNamedProperty(dynamic object, dynamic name) { - if (object is Map && object.containsKey(name)) return object[name]; - - if (object is List && _integerTag.hasMatch(name)) { - return object[int.parse(name)]; - } - - return noSuchProperty; - } - m.TemplateException error(String message, Node node) => TemplateException(message, templateName, source, node.start);
diff --git a/lib/src/template.dart b/lib/src/template.dart index a38cc97..9f7349b 100644 --- a/lib/src/template.dart +++ b/lib/src/template.dart
@@ -9,13 +9,15 @@ bool htmlEscapeValues = true, String name, m.PartialResolver partialResolver, - String delimiters = '{{ }}'}) + String delimiters = '{{ }}', + m.ValueResolver valueResolver = m.defaultValueResolver}) : source = source, _nodes = parser.parse(source, lenient, name, delimiters), _lenient = lenient, _htmlEscapeValues = htmlEscapeValues, _name = name, - _partialResolver = partialResolver; + _partialResolver = partialResolver, + _valueResolver = valueResolver; @override final String source; @@ -24,6 +26,7 @@ final bool _htmlEscapeValues; final String _name; final m.PartialResolver _partialResolver; + final m.ValueResolver _valueResolver; @override String get name => _name; @@ -38,7 +41,7 @@ @override void render(values, StringSink sink) { var renderer = Renderer(sink, [values], _lenient, _htmlEscapeValues, - _partialResolver, _name, '', source); + _partialResolver, _valueResolver, _name, '', source); renderer.render(_nodes); } }