blob: 90be84ae3334bde9999f5ba2008c9c50dfc4e4cf [file] [log] [blame]
library mustache_context;
import 'dart:collection';
@MirrorsUsed(symbols: '*')
import 'dart:mirrors';
const USE_MIRRORS = const bool.fromEnvironment('MIRRORS', defaultValue: true);
const String DOT = '\.';
typedef NoParamLambda();
typedef OptionalParamLambda({nestedContext});
typedef TwoParamLambda(String s, {nestedContext});
abstract class MustacheContext {
factory MustacheContext(ctx,
{MustacheContext parent, assumeNullNonExistingProperty: true}) {
if (ctx is Iterable) {
return new _IterableMustacheContextDecorator(ctx,
parent: parent,
assumeNullNonExistingProperty: assumeNullNonExistingProperty);
}
return new _MustacheContext(ctx,
parent: parent,
assumeNullNonExistingProperty: assumeNullNonExistingProperty);
}
call([arg]);
bool get isFalsey;
bool get isLambda;
MustacheContext operator [](String key);
MustacheContext _getMustachContext(String key);
String get rootContextString;
}
abstract class MustacheToString {
dynamic get ctx;
MustacheContext get parent;
String get rootContextString =>
parent == null ? ctx.toString() : parent.rootContextString;
}
class _MustacheContext extends MustacheToString implements MustacheContext {
static final FALSEY_CONTEXT = new _MustacheContext(false);
final ctx;
final _MustacheContext parent;
final bool assumeNullNonExistingProperty;
bool useMirrors = USE_MIRRORS;
_ObjectReflector _ctxReflector;
_MustacheContext(this.ctx,
{_MustacheContext this.parent, this.assumeNullNonExistingProperty});
bool get isLambda => ctx is Function;
bool get isFalsey => ctx == null || ctx == false;
call([arg]) => isLambda ? callLambda(arg) : ctx.toString();
callLambda(arg) => ctx is NoParamLambda
? ctx()
: ctx is TwoParamLambda
? ctx(arg, nestedContext: this)
: ctx is OptionalParamLambda ? ctx(nestedContext: this) : ctx(arg);
operator [](String key) {
if (ctx == null) return null;
return _getInThisOrParent(key);
}
_getInThisOrParent(String key) {
var result = _getContextForKey(key);
//if the result is null, try the parent context
if (result == null && !_hasActualValueSlot(key) && parent != null) {
result = parent[key];
if (result != null) {
return _newMustachContextOrNull(result.ctx);
}
}
return result;
}
_getContextForKey(String key) {
if (key == DOT) {
return this;
}
if (key.contains(DOT)) {
Iterator<String> i = key.split(DOT).iterator;
var val = this;
while (i.moveNext()) {
val = val._getMustachContext(i.current);
if (val == null) {
return null;
}
}
return val;
}
//else
return _getMustachContext(key);
}
_getMustachContext(String key) {
var v = _getActualValue(key);
return _newMustachContextOrNull(v);
}
_newMustachContextOrNull(v) {
if (v == null) {
return null;
}
if (v is Iterable) {
return new _IterableMustacheContextDecorator(v,
parent: this,
assumeNullNonExistingProperty: this.assumeNullNonExistingProperty);
}
if (v == false) {
return FALSEY_CONTEXT;
}
return new _MustacheContext(v,
parent: this,
assumeNullNonExistingProperty: assumeNullNonExistingProperty);
}
dynamic _getActualValue(String key) {
//Try to make dart2js understand that when we define USE_MIRRORS = false
//we do not want to use any reflector as that inflates the generated
//javascript.
if (useMirrors && USE_MIRRORS) {
if (reflect(ctx).type.instanceMembers.containsKey(new Symbol("[]"))) {
MethodMirror m = reflect(ctx).type.instanceMembers[new Symbol("[]")];
TypeMirror reflectedString = reflectType(String);
if (reflectedString.isAssignableTo(m.parameters[0].type)) {
try {
return ctx[key];
} catch (NoSuchMethodError) {
//This should never happen unless we were trapping a lower level
//NoSuchMethodError before. Continue to do so to be bug-for-bug
//compatible.
}
}
}
return ctxReflector[key];
} else {
try {
return ctx[key];
} catch (NoSuchMethodError) {
return null;
}
}
}
bool _hasActualValueSlot(String key) {
if (assumeNullNonExistingProperty) {
return false;
}
if (ctx is Map) {
return (ctx as Map).containsKey(key);
} else if (useMirrors && USE_MIRRORS) {
//TODO test the case of no mirrors
return ctxReflector.hasSlot(key);
}
return false;
}
get ctxReflector {
if (_ctxReflector == null) {
_ctxReflector = new _ObjectReflector(ctx);
}
return _ctxReflector;
}
String toString() => "MustacheContext($ctx, $parent)";
}
class _IterableMustacheContextDecorator extends IterableBase<_MustacheContext>
with MustacheToString
implements MustacheContext {
final Iterable ctx;
final _MustacheContext parent;
final bool assumeNullNonExistingProperty;
_IterableMustacheContextDecorator(this.ctx,
{this.parent, this.assumeNullNonExistingProperty});
call([arg]) => throw new Exception('Iterable can be called as a function');
Iterator<_MustacheContext> get iterator =>
new _MustachContextIteratorDecorator(ctx.iterator,
parent: parent,
assumeNullNonExistingProperty: assumeNullNonExistingProperty);
int get length => ctx.length;
bool get isEmpty => ctx.isEmpty;
bool get isFalsey => isEmpty;
bool get isLambda => false;
operator [](String key) {
if (key == DOT) {
return this;
}
throw new Exception(
'Iterable can only be iterated. No [] implementation is available');
}
_getMustachContext(String key) {
if (key == 'empty' || key == 'isEmpty') {
return new _MustacheContext(isEmpty,
parent: parent,
assumeNullNonExistingProperty: assumeNullNonExistingProperty);
}
throw new Exception(
'Iterable can only be asked for empty or isEmpty keys or be iterated');
}
}
class _MustachContextIteratorDecorator extends Iterator<_MustacheContext> {
final Iterator delegate;
final _MustacheContext parent;
final bool assumeNullNonExistingProperty;
_MustacheContext current;
_MustachContextIteratorDecorator(this.delegate,
{this.parent, this.assumeNullNonExistingProperty});
bool moveNext() {
if (delegate.moveNext()) {
current = new _MustacheContext(delegate.current,
parent: parent,
assumeNullNonExistingProperty: assumeNullNonExistingProperty);
return true;
} else {
current = null;
return false;
}
}
}
/**
* Helper class which given an object it will try to get a value by key analyzing
* the object by reflection
*/
class _ObjectReflector {
final InstanceMirror m;
factory _ObjectReflector(o) {
return new _ObjectReflector._(reflect(o));
}
_ObjectReflector._(this.m);
operator [](String key) {
var declaration = getDeclaration(key);
if (declaration == null) {
return null;
}
return declaration.value;
}
bool hasSlot(String key) {
return getDeclaration(key) != null;
}
_ObjectReflectorDeclaration getDeclaration(String key) {
return new _ObjectReflectorDeclaration(m, key);
}
}
class _ObjectReflectorDeclaration {
final InstanceMirror mirror;
final MethodMirror declaration;
factory _ObjectReflectorDeclaration(
InstanceMirror m, String declarationName) {
var methodMirror = m.type.instanceMembers[new Symbol(declarationName)];
if (methodMirror == null) {
//try appending the word get to the name:
var nameWithGet =
"get${declarationName[0].toUpperCase()}${declarationName.substring(1)}";
methodMirror = m.type.instanceMembers[new Symbol(nameWithGet)];
}
return methodMirror == null
? null
: new _ObjectReflectorDeclaration._(m, methodMirror);
}
_ObjectReflectorDeclaration._(this.mirror, this.declaration);
bool get isLambda => declaration.parameters.length >= 1;
Function get lambda => (val, {MustacheContext nestedContext}) {
var im = mirror.invoke(
declaration.simpleName,
_createPositionalArguments(val),
_createNamedArguments(nestedContext));
return im is InstanceMirror ? im.reflectee : null;
};
_createPositionalArguments(val) {
var positionalParam = declaration.parameters
.firstWhere((p) => !p.isOptional, orElse: () => null);
if (positionalParam == null) {
return [];
} else {
return [val];
}
}
_createNamedArguments(MustacheContext ctx) {
var map = {};
var nestedContextParameterExists = declaration.parameters.firstWhere(
(p) => p.simpleName == new Symbol('nestedContext'),
orElse: () => null);
if (nestedContextParameterExists != null) {
map[nestedContextParameterExists.simpleName] = ctx;
}
return map;
}
get value {
if (isLambda) {
return lambda;
}
//Now we try to find out a field or a getter named after the given name
var im = null;
if (isVariableOrGetter) {
im = mirror.getField(declaration.simpleName);
} else if (isParameterlessMethod) {
im = mirror.invoke(declaration.simpleName, []);
}
if (im != null && im is InstanceMirror) {
return im.reflectee;
}
return null;
}
//TODO check if we really need the declaration is VariableMirror test
bool get isVariableOrGetter =>
(declaration is VariableMirror) ||
(declaration is MethodMirror && declaration.isGetter);
bool get isParameterlessMethod =>
declaration is MethodMirror && declaration.parameters.length == 0;
}