Merging with latest master
This commit is contained in:
@@ -20,11 +20,11 @@ def remove_exports(string)
|
||||
match ? match[1] : string
|
||||
end
|
||||
|
||||
minimal_deps = %w(parser compiler ast visitor runtime utils).map do |file|
|
||||
minimal_deps = %w(parser compiler ast visitor runtime utils vm).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
debug_deps = %w(parser compiler ast visitor printer runtime utils).map do |file|
|
||||
debug_deps = %w(parser compiler ast visitor printer runtime utils vm).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
|
||||
+11
-8
@@ -1,13 +1,16 @@
|
||||
var Handlebars = require("handlebars/compiler").Handlebars;
|
||||
|
||||
Handlebars.AST = require("handlebars/ast").AST;
|
||||
Handlebars.HandlebarsLexer = require("handlebars/handlebars_lexer").Lexer;
|
||||
Handlebars.PrintVisitor = require("handlebars/printer").PrintVisitor;
|
||||
Handlebars.Runtime = require("handlebars/runtime").Runtime;
|
||||
Handlebars.Context = require("Handlebars/runtime").Context;
|
||||
Handlebars.Utils = require("handlebars/utils").Utils;
|
||||
Handlebars.SafeString = require("handlebars/utils").SafeString;
|
||||
Handlebars.Exception = require("handlebars/utils").Exception;
|
||||
Handlebars.AST = require("handlebars/ast").AST;
|
||||
Handlebars.HandlebarsLexer = require("handlebars/handlebars_lexer").Lexer;
|
||||
Handlebars.PrintVisitor = require("handlebars/printer").PrintVisitor;
|
||||
Handlebars.Runtime = require("handlebars/runtime").Runtime;
|
||||
Handlebars.Context = require("Handlebars/runtime").Context;
|
||||
Handlebars.Utils = require("handlebars/utils").Utils;
|
||||
Handlebars.SafeString = require("handlebars/utils").SafeString;
|
||||
Handlebars.Exception = require("handlebars/utils").Exception;
|
||||
Handlebars.Compiler = require("handlebars/vm").Compiler;
|
||||
Handlebars.JavaScriptCompiler = require("handlebars/vm").JavaScriptCompiler;
|
||||
Handlebars.VM = require("handlebars/vm").VM;
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ Handlebars.Exception = require("handlebars/utils").Exception;
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
this.type = "partial";
|
||||
|
||||
// TODO: disallow complex IDs
|
||||
|
||||
this.id = id;
|
||||
this.context = context;
|
||||
};
|
||||
@@ -64,8 +67,9 @@ Handlebars.Exception = require("handlebars/utils").Exception;
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
this.parts = dig;
|
||||
this.depth = depth;
|
||||
this.parts = dig;
|
||||
this.depth = depth;
|
||||
this.isSimple = (dig.length === 1) && (depth === 0)
|
||||
};
|
||||
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var handlebars = require("handlebars/parser").parser;
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars = {};
|
||||
var Handlebars = {};
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
|
||||
@@ -47,16 +47,27 @@ Handlebars.registerPartial = function(name, str) {
|
||||
this.partials[name] = str;
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, fn) {
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
|
||||
inverse = inverse || function() {};
|
||||
|
||||
var ret = "";
|
||||
var type = Object.prototype.toString.call(context);
|
||||
|
||||
if(type === "[object Function]") {
|
||||
context = context();
|
||||
}
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return "";
|
||||
} else if(Object.prototype.toString.call(context) === "[object Array]") {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
@@ -65,6 +76,28 @@ Handlebars.registerHelper('blockHelperMissing', function(context, fn) {
|
||||
}, function(context, fn) {
|
||||
return fn(context)
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('each', function(context, fn, inverse) {
|
||||
var ret = "";
|
||||
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(context, fn, inverse) {
|
||||
if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else {
|
||||
return fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
exports.Handlebars = Handlebars;
|
||||
|
||||
@@ -104,7 +104,7 @@ Handlebars.Runtime.prototype = {
|
||||
|
||||
mustache: function(mustache) {
|
||||
var idObj = this.ID(mustache.id);
|
||||
var params = mustache.params;
|
||||
var params = mustache.params.slice(0);
|
||||
var buf;
|
||||
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
@@ -153,7 +153,7 @@ Handlebars.Runtime.prototype = {
|
||||
|
||||
params.push(this.wrapProgram(block.program));
|
||||
result = data.apply(this.wrapContext(), params);
|
||||
this.buffer = this.buffer + result;
|
||||
this.buffer = this.buffer + ((result === undefined) ? "" : result);
|
||||
|
||||
if(block.program.inverse) {
|
||||
params.pop();
|
||||
|
||||
+38
-38
@@ -13,47 +13,47 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
}
|
||||
else if (string === null) {
|
||||
string = "";
|
||||
}
|
||||
(function() {
|
||||
var escape = {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
};
|
||||
|
||||
return string.toString().replace(/&(?!\w+;)|["\\<>]/g, function(str) {
|
||||
switch(str) {
|
||||
case "&":
|
||||
return "&";
|
||||
case '"':
|
||||
return "\"";
|
||||
case "\\":
|
||||
return "\\\\";
|
||||
case "<":
|
||||
return "<";
|
||||
case ">":
|
||||
return ">";
|
||||
default:
|
||||
return str;
|
||||
var badChars = /&(?!\w+;)|[<>]/g;
|
||||
var possible = /[&<>]/
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&"
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string === null) {
|
||||
string = "";
|
||||
}
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return true;
|
||||
} else if (value === null) {
|
||||
return true;
|
||||
} else if (value === false) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
isEmpty: function(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return true;
|
||||
} else if (value === null) {
|
||||
return true;
|
||||
} else if (value === false) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})();
|
||||
// END(BROWSER)
|
||||
|
||||
exports.Utils = Handlebars.Utils;
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
var Handlebars = {};
|
||||
Handlebars.parse = require("handlebars/compiler").Handlebars.parse;
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Compiler = function() {};
|
||||
Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
(function(Compiler, JavaScriptCompiler) {
|
||||
Compiler.OPCODE_MAP = {
|
||||
invokeContent: 1,
|
||||
getContext: 2,
|
||||
lookupWithFallback: 3,
|
||||
lookup: 4,
|
||||
append: 5,
|
||||
invokeMustache: 6,
|
||||
escape: 7,
|
||||
pushString: 8,
|
||||
truthyOrFallback: 9,
|
||||
functionOrFallback: 10,
|
||||
invokeProgram: 11,
|
||||
invokePartial: 12,
|
||||
push: 13,
|
||||
invokeInverse: 14
|
||||
};
|
||||
|
||||
Compiler.MULTI_PARAM_OPCODES = {
|
||||
invokeContent: 1,
|
||||
getContext: 1,
|
||||
lookupWithFallback: 1,
|
||||
lookup: 1,
|
||||
invokeMustache: 2,
|
||||
pushString: 1,
|
||||
truthyOrFallback: 1,
|
||||
functionOrFallback: 1,
|
||||
invokeProgram: 2,
|
||||
invokePartial: 1,
|
||||
push: 1,
|
||||
invokeInverse: 1
|
||||
};
|
||||
|
||||
Compiler.DISASSEMBLE_MAP = {}
|
||||
|
||||
for(prop in Compiler.OPCODE_MAP) {
|
||||
var value = Compiler.OPCODE_MAP[prop];
|
||||
Compiler.DISASSEMBLE_MAP[value] = prop;
|
||||
}
|
||||
|
||||
Compiler.multiParamSize = function(code) {
|
||||
return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
|
||||
};
|
||||
|
||||
Compiler.prototype = {
|
||||
disassemble: function() {
|
||||
var opcodes = this.opcodes, opcode, nextCode;
|
||||
var out = [], str, name, value;
|
||||
|
||||
for(var i=0, l=opcodes.length; i<l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
if(opcode === 'DECLARE') {
|
||||
name = opcodes[++i];
|
||||
value = opcodes[++i];
|
||||
out.push("DECLARE " + name + " = " + value);
|
||||
} else {
|
||||
str = Compiler.DISASSEMBLE_MAP[opcode];
|
||||
|
||||
var extraParams = Compiler.multiParamSize(opcode);
|
||||
var codes = [];
|
||||
|
||||
for(var j=0; j<extraParams; j++) {
|
||||
nextCode = opcodes[++i];
|
||||
|
||||
if(typeof nextCode === "string") {
|
||||
nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
|
||||
}
|
||||
|
||||
codes.push(nextCode);
|
||||
}
|
||||
|
||||
str = str + " " + codes.join(" ");
|
||||
|
||||
out.push(str);
|
||||
}
|
||||
}
|
||||
|
||||
return out.join("\n")
|
||||
},
|
||||
|
||||
guid: 0,
|
||||
|
||||
compile: function(program) {
|
||||
this.children = [];
|
||||
this.depths = {list: []};
|
||||
return this.program(program);
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
return this[node.type](node);
|
||||
},
|
||||
|
||||
program: function(program) {
|
||||
var statements = program.statements, statement;
|
||||
this.opcodes = [];
|
||||
|
||||
for(var i=0, l=statements.length; i<l; i++) {
|
||||
statement = statements[i];
|
||||
this[statement.type](statement);
|
||||
}
|
||||
|
||||
this.depths.list = this.depths.list.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
var result = new Compiler().compile(program);
|
||||
var guid = this.guid++;
|
||||
|
||||
this.children[guid] = result;
|
||||
|
||||
for(var i=0, l=result.depths.list.length; i<l; i++) {
|
||||
depth = result.depths.list[i];
|
||||
|
||||
if(depth < 2) { continue; }
|
||||
else { this.addDepth(depth - 1); }
|
||||
}
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
block: function(block) {
|
||||
var mustache = block.mustache;
|
||||
var params = mustache.params, depth, child, inverse;
|
||||
|
||||
this.pushParams(params);
|
||||
|
||||
// ID lookup is now on the stack
|
||||
this.ID(mustache.id);
|
||||
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
|
||||
if(block.program.inverse) {
|
||||
var inverseGuid = this.compileProgram(block.program.inverse);
|
||||
}
|
||||
|
||||
if(block.program.inverse) {
|
||||
this.declare('inverse', inverseGuid);
|
||||
}
|
||||
|
||||
this.opcode('invokeProgram', programGuid, params.length);
|
||||
this.declare('inverse', null);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
inverse: function(block) {
|
||||
this.ID(block.mustache.id);
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
|
||||
this.opcode('invokeInverse', programGuid);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
var id = partial.id;
|
||||
|
||||
if(partial.context) {
|
||||
this.ID(partial.context);
|
||||
} else {
|
||||
this.opcode('push', 'context');
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', id.original);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
content: function(content) {
|
||||
this.opcode('invokeContent', content.string);
|
||||
},
|
||||
|
||||
mustache: function(mustache) {
|
||||
var params = mustache.params;
|
||||
|
||||
this.pushParams(params);
|
||||
this.ID(mustache.id);
|
||||
|
||||
this.opcode('invokeMustache', params.length, mustache.id.original);
|
||||
|
||||
if(mustache.escaped) { this.opcode('escape') }
|
||||
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
this.opcode('lookupWithFallback', id.parts[0] || null);
|
||||
|
||||
for(var i=1, l=id.parts.length; i<l; i++) {
|
||||
this.opcode('lookup', id.parts[i]);
|
||||
}
|
||||
},
|
||||
|
||||
STRING: function(string) {
|
||||
this.opcode('pushString', string.string);
|
||||
},
|
||||
|
||||
comment: function() {},
|
||||
|
||||
pushParams: function(params) {
|
||||
var i = params.length, param;
|
||||
|
||||
while(i--) {
|
||||
param = params[i];
|
||||
this[param.type](param);
|
||||
}
|
||||
},
|
||||
|
||||
opcode: function(name, val1, val2) {
|
||||
this.opcodes.push(Compiler.OPCODE_MAP[name]);
|
||||
if(val1 !== undefined) { this.opcodes.push(val1); }
|
||||
if(val2 !== undefined) { this.opcodes.push(val2); }
|
||||
},
|
||||
|
||||
declare: function(name, value) {
|
||||
this.opcodes.push('DECLARE');
|
||||
this.opcodes.push(name);
|
||||
this.opcodes.push(value);
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
if(depth === 0) { return; }
|
||||
|
||||
if(!this.depths[depth]) {
|
||||
this.depths[depth] = true;
|
||||
this.depths.list.push(depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JavaScriptCompiler.prototype = {
|
||||
compile: function(environment) {
|
||||
this.preamble();
|
||||
this.stackSlot = 0
|
||||
this.stackVars = [];
|
||||
this.environment = environment;
|
||||
|
||||
this.compileChildren(environment);
|
||||
|
||||
//puts(environment.disassemble());
|
||||
//puts("")
|
||||
|
||||
var opcodes = environment.opcodes;
|
||||
var opcode, name;
|
||||
var declareName, declareVal;
|
||||
|
||||
for(var i=0, l=opcodes.length; i<l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
if(opcode === 'DECLARE') {
|
||||
declareName = opcodes[++i];
|
||||
declareVal = opcodes[++i];
|
||||
this[declareName] = declareVal;
|
||||
} else {
|
||||
name = Compiler.DISASSEMBLE_MAP[opcode];
|
||||
|
||||
var extraParams = Compiler.multiParamSize(opcode);
|
||||
var codes = [];
|
||||
|
||||
for(var j=0; j<extraParams; j++) {
|
||||
codes.push(opcodes[++i]);
|
||||
}
|
||||
|
||||
this[name].apply(this, codes);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createFunction();
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
var out = [];
|
||||
out.push("var buffer = '';");
|
||||
out.push("var currentContext = context, tmp1, tmp2;");
|
||||
out.push("helpers = helpers || Handlebars.helpers; partials = partials || Handlebars.partials;");
|
||||
out.push("");
|
||||
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.locals = 3;
|
||||
this.source = out;
|
||||
},
|
||||
|
||||
createFunction: function() {
|
||||
var container = {};
|
||||
|
||||
if(this.stackVars.length > 0) {
|
||||
this.source[this.locals] = "var " + this.stackVars.join(", ") + ";";
|
||||
}
|
||||
|
||||
this.source.push("return buffer;")
|
||||
|
||||
var params = ["context", "helpers", "partials"];
|
||||
|
||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
}
|
||||
|
||||
params.push(this.source.join("\n"));
|
||||
|
||||
var fn = Function.apply(this, params);
|
||||
fn.displayName = "Handlebars.js"
|
||||
|
||||
//puts(fn.toString())
|
||||
//puts("")
|
||||
|
||||
container.render = fn;
|
||||
|
||||
container.children = this.environment.children;
|
||||
|
||||
return function(context, helpers, partials, depth) {
|
||||
try {
|
||||
return container.render.apply(container, arguments)
|
||||
} catch(e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
invokeContent: function(content) {
|
||||
this.source.push("buffer = buffer + " + this.quotedString(content) + ";");
|
||||
},
|
||||
|
||||
append: function() {
|
||||
var local = this.popStack();
|
||||
this.source.push("buffer = buffer + ((" + local + " || " + local + " === 0) ? " + local + " : '');");
|
||||
},
|
||||
|
||||
getContext: function(depth) {
|
||||
if(this.lastContext !== depth) {
|
||||
this.lastContext = depth;
|
||||
|
||||
if(depth === 0) {
|
||||
this.source.push("currentContext = context;");
|
||||
} else {
|
||||
this.source.push("currentContext = depth" + depth + ";");
|
||||
}
|
||||
// TODO: handle depths other than 0
|
||||
}
|
||||
},
|
||||
|
||||
nameLookup: function(parent, name) {
|
||||
if(JavaScriptCompiler.RESERVED_WORDS[name]) {
|
||||
return parent + "['" + name + "']";
|
||||
} else {
|
||||
return parent + "." + name;
|
||||
}
|
||||
},
|
||||
|
||||
lookupWithFallback: function(name) {
|
||||
if(name) {
|
||||
this.pushStack(this.nameLookup('currentContext', name));
|
||||
var topStack = this.topStack();
|
||||
this.source.push("if(" + topStack + " == null) { " + topStack + " = " + this.nameLookup('helpers', name) + "; }");
|
||||
} else {
|
||||
this.pushStack("currentContext");
|
||||
}
|
||||
},
|
||||
|
||||
lookup: function(name) {
|
||||
var topStack = this.topStack();
|
||||
this.source.push(topStack + " = " + this.nameLookup(topStack, name) + ";");
|
||||
},
|
||||
|
||||
pushString: function(string) {
|
||||
this.pushStack(this.quotedString(string));
|
||||
},
|
||||
|
||||
push: function(name) {
|
||||
this.pushStack(name);
|
||||
},
|
||||
|
||||
invokeMustache: function(paramSize, original) {
|
||||
this.source.push("tmp1 = " + this.popStack() + ";");
|
||||
this.source.push("tmp2 = (typeof tmp1 === 'function');");
|
||||
|
||||
var params = ["context"];
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
params.push(this.popStack());
|
||||
}
|
||||
|
||||
var slot = "stack" + ++this.stackSlot;
|
||||
|
||||
var paramString = params.join(", ");
|
||||
var helperMissing = ["context"].concat(this.quotedString(original)).concat(params.slice(1));
|
||||
|
||||
if(paramSize === 0) {
|
||||
this.source.push("if(tmp2) { " + slot + " = tmp1.call(" + paramString + "); } else { " + slot + " = tmp1; }");
|
||||
} else {
|
||||
this.source.push("if(tmp2) { " + slot + " = tmp1.call(" + paramString + "); } else { " + slot + " = helpers.helperMissing.call(" + helperMissing + ") }");
|
||||
}
|
||||
},
|
||||
|
||||
invokeProgram: function(guid, paramSize) {
|
||||
var inverse = this.inverse;
|
||||
|
||||
if(inverse != null) {
|
||||
var programParams = ["this.children[" + inverse + "]", "helpers", "partials"];
|
||||
|
||||
var depths = this.environment.rawChildren[guid + 1].depths.list;
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("context"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
this.source.push("tmp2 = Handlebars.VM.program(" + programParams.join(", ") + ");");
|
||||
} else {
|
||||
this.source.push("tmp2 = Handlebars.VM.noop;");
|
||||
}
|
||||
|
||||
var id = this.topStack();
|
||||
var fn = this.popStack();
|
||||
|
||||
var params = ["context"];
|
||||
var blockMissingParams = ["context", id];
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
var param = this.popStack();
|
||||
params.push(param);
|
||||
blockMissingParams.push(param);
|
||||
}
|
||||
|
||||
programParams = ["this.children[" + guid + "]", "helpers", "partials"];
|
||||
var depths = this.environment.rawChildren[guid].depths.list, depth;
|
||||
|
||||
for(var i=0, l= depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("context"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
if(depths.length === 0) {
|
||||
this.source.push("tmp1 = Handlebars.VM.program(" + programParams.join(", ") + ")");
|
||||
} else {
|
||||
this.source.push("tmp1 = Handlebars.VM.programWithDepth(" + programParams.join(", ") + ")");
|
||||
}
|
||||
|
||||
params.push("tmp1");
|
||||
params.push("tmp2");
|
||||
blockMissingParams.push("tmp1");
|
||||
blockMissingParams.push("tmp2");
|
||||
|
||||
var nextStack = this.nextStack();
|
||||
|
||||
this.source.push("if(typeof " + id + " === 'function') { " + nextStack + " = " + id + ".call(" + params.join(", ") + "); }")
|
||||
this.source.push("else { " + nextStack + " = helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + "); }");
|
||||
},
|
||||
|
||||
invokeInverse: function(guid) {
|
||||
var depths = this.environment.rawChildren[guid].depths.list;
|
||||
|
||||
programParams = ["this.children[" + guid + "]", "helpers", "partials"];
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("context"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
if(depths.length === 0) {
|
||||
this.source.push("tmp1 = Handlebars.VM.program(" + programParams.join(", ") + ")");
|
||||
} else {
|
||||
this.source.push("tmp1 = Handlebars.VM.programWithDepth(" + programParams.join(", ") + ")");
|
||||
}
|
||||
|
||||
var blockMissingParams = ["context", this.topStack(), "Handlebars.VM.noop", "tmp1"];
|
||||
this.pushStack("helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + ")");
|
||||
},
|
||||
|
||||
invokePartial: function(context) {
|
||||
this.pushStack("Handlebars.VM.invokePartial(" + this.nameLookup('partials', context) + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
|
||||
},
|
||||
|
||||
escape: function() {
|
||||
this.source.push(this.topStack() + " = Handlebars.Utils.escapeExpression(" + this.topStack() + ");");
|
||||
// TODO: Escaping
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
|
||||
compileChildren: function(environment) {
|
||||
var children = environment.children, child, compiler;
|
||||
var compiled = [];
|
||||
|
||||
for(var i=0, l=children.length; i<l; i++) {
|
||||
child = children[i];
|
||||
compiler = new JavaScriptCompiler();
|
||||
|
||||
compiled[i] = compiler.compile(child);
|
||||
}
|
||||
|
||||
environment.rawChildren = children;
|
||||
environment.children = compiled;
|
||||
},
|
||||
|
||||
pushStack: function(item) {
|
||||
this.source.push(this.nextStack() + " = " + item + ";");
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
nextStack: function() {
|
||||
this.stackSlot++;
|
||||
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
popStack: function() {
|
||||
return "stack" + this.stackSlot--;
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r') + '"';
|
||||
}
|
||||
}
|
||||
|
||||
var reservedWords = ("break case catch continue default delete do else finally " +
|
||||
"for function if in instanceof new return switch this throw " +
|
||||
"try typeof var void while with null true false").split(" ");
|
||||
|
||||
compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
||||
|
||||
for(var i=0, l=reservedWords.length; i<l; i++) {
|
||||
compilerWords[reservedWords[i]] = true;
|
||||
}
|
||||
|
||||
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler)
|
||||
|
||||
Handlebars.VM = {
|
||||
programWithDepth: function(fn, helpers, partials, depth) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
return function(context) {
|
||||
return fn.apply(this, [context].concat(args));
|
||||
}
|
||||
},
|
||||
program: function(fn, helpers, partials) {
|
||||
return function(context) {
|
||||
return fn(context, helpers, partials);
|
||||
}
|
||||
},
|
||||
noop: function() {},
|
||||
compile: function(string) {
|
||||
var ast = Handlebars.parse(string);
|
||||
var environment = new Handlebars.Compiler().compile(ast);
|
||||
return new Handlebars.JavaScriptCompiler().compile(environment);
|
||||
},
|
||||
invokePartial: function(partial, name, context, helpers, partials) {
|
||||
if(partial instanceof Function) {
|
||||
return partial(context, helpers, partials)
|
||||
} else {
|
||||
partials[name] = Handlebars.VM.compile(partial);
|
||||
return partials[name](context, helpers, partials);
|
||||
}
|
||||
}
|
||||
};
|
||||
// END(BROWSER)
|
||||
|
||||
exports.Compiler = Handlebars.Compiler;
|
||||
exports.JavaScriptCompiler = Handlebars.JavaScriptCompiler;
|
||||
exports.VM = Handlebars.VM;
|
||||
+43
-29
@@ -7,7 +7,7 @@ Handlebars.registerHelper('helperMissing', function(helper, context) {
|
||||
});
|
||||
|
||||
var shouldCompileTo = function(string, hash, expected, message) {
|
||||
var template = Handlebars.compile(string);
|
||||
var template = Handlebars.VM.compile(string);
|
||||
if(Object.prototype.toString.call(hash) === "[object Array]") {
|
||||
if(hash[1]) {
|
||||
for(var prop in Handlebars.helpers) {
|
||||
@@ -43,7 +43,7 @@ test("compiling with a basic context", function() {
|
||||
});
|
||||
|
||||
test("comments", function() {
|
||||
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
|
||||
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
|
||||
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
||||
"comments are ignored");
|
||||
});
|
||||
@@ -80,7 +80,7 @@ test("escaping expressions", function() {
|
||||
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
"expressions with 3 handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo("{{awesome}}", {awesome: "&\"\\<>"}, '&\"\\\\<>',
|
||||
shouldCompileTo("{{awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
"by default expressions should be escaped");
|
||||
|
||||
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
@@ -90,7 +90,7 @@ test("escaping expressions", function() {
|
||||
|
||||
test("functions returning safestrings shouldn't be escaped", function() {
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
|
||||
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
|
||||
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
|
||||
"functions returning safestrings aren't escaped");
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ test("functions", function() {
|
||||
});
|
||||
|
||||
test("functions with context argument", function() {
|
||||
shouldCompileTo("{{awesome frank}}",
|
||||
shouldCompileTo("{{awesome frank}}",
|
||||
{awesome: function(context) { return context; },
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
@@ -121,7 +121,7 @@ test("--- TODO --- bad idea nested paths", function() {
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldThrow(function() {
|
||||
Handlebars.compile("{{#goodbyes}}{{../name/../name}}{{/goodbyes}}")(hash);
|
||||
}, Handlebars.Exception,
|
||||
}, Handlebars.Exception,
|
||||
"Cannot jump (..) into previous context after moving into a context.");
|
||||
|
||||
var string = "{{#goodbyes}}{{.././world}} {{/goodbyes}}";
|
||||
@@ -140,8 +140,8 @@ test("complex but empty paths", function() {
|
||||
test("this keyword in paths", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
|
||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
||||
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
|
||||
"This keyword in paths evaluates to current context");
|
||||
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
|
||||
"This keyword in paths evaluates to current context");
|
||||
|
||||
string = "{{#hellos}}{{this/text}}{{/hellos}}"
|
||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
||||
@@ -161,7 +161,7 @@ test("inverted section with false value", function() {
|
||||
var hash = {goodbyes: false};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
|
||||
});
|
||||
|
||||
|
||||
test("inverted section with empty set", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
var hash = {goodbyes: []};
|
||||
@@ -218,10 +218,10 @@ test("block with complex lookup", function() {
|
||||
});
|
||||
|
||||
test("helper with complex lookup", function() {
|
||||
var string = "{{#goodbyes}}{{{link}}}{{/goodbyes}}"
|
||||
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"
|
||||
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var fallback = {link: function() {
|
||||
return "<a href='" + this.__get__("../prefix") + "/" + this.url + "'>" + this.text + "</a>"
|
||||
var fallback = {link: function(prefix) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>"
|
||||
}};
|
||||
shouldCompileTo(string, [hash, fallback], "<a href='/root/goodbye'>Goodbye</a>")
|
||||
});
|
||||
@@ -229,11 +229,11 @@ test("helper with complex lookup", function() {
|
||||
test("helper block with complex lookup expression", function() {
|
||||
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"
|
||||
var hash = {name: "Alan"};
|
||||
var fallback = {goodbyes: function(context, fn) {
|
||||
var fallback = {goodbyes: function(fn) {
|
||||
var out = "";
|
||||
var byes = ["Goodbye", "goodbye", "GOODBYE"];
|
||||
for (var i = 0,j = byes.length; i < j; i++) {
|
||||
out += byes[i] + " " + fn(context) + "! ";
|
||||
out += byes[i] + " " + fn(this) + "! ";
|
||||
}
|
||||
return out;
|
||||
}};
|
||||
@@ -241,10 +241,10 @@ test("helper block with complex lookup expression", function() {
|
||||
});
|
||||
|
||||
test("helper with complex lookup and nested template", function() {
|
||||
var string = "{{#goodbyes}}{{#link}}{{text}}{{/link}}{{/goodbyes}}";
|
||||
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
||||
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var fallback = {link: function (context, fn) {
|
||||
return "<a href='" + this.__get__("../prefix") + "/" + this.url + "'>" + fn(this) + "</a>";
|
||||
var fallback = {link: function (prefix, fn) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>";
|
||||
}};
|
||||
shouldCompileTo(string, [hash, fallback], "<a href='/root/goodbye'>Goodbye</a>")
|
||||
});
|
||||
@@ -272,10 +272,10 @@ test("block helper staying in the same context", function() {
|
||||
equal(result, "<form><p>Yehuda</p></form>");
|
||||
});
|
||||
|
||||
test("block helper should have wrapped context in this", function() {
|
||||
test("block helper should have context in this", function() {
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
var link = function(context, fn) {
|
||||
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
|
||||
var link = function(fn) {
|
||||
return '<a href="/people/' + this.id + '">' + fn(this) + '</a>';
|
||||
};
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
@@ -315,33 +315,47 @@ test("nested block helpers", function() {
|
||||
});
|
||||
|
||||
test("block inverted sections", function() {
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{../none}}{{/people}}", {none: "No people"},
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
|
||||
"No people");
|
||||
});
|
||||
|
||||
test("block inverted sections with empty arrays", function() {
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
|
||||
"No people");
|
||||
});
|
||||
|
||||
test("block helper inverted sections", function() {
|
||||
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"
|
||||
var list = function(context, fn) {
|
||||
var list = function(context, fn, inverse) {
|
||||
if (context.length > 0) {
|
||||
var out = "<ul>";
|
||||
for(var i = 0,j=context.length; i < j; i++) {
|
||||
out += "<li>";
|
||||
out += "<li>";
|
||||
out += fn(context[i]);
|
||||
out += "</li>";
|
||||
}
|
||||
out += "</ul>";
|
||||
return out;
|
||||
} else {
|
||||
return "<p>" + inverse(this) + "</p>";
|
||||
}
|
||||
};
|
||||
|
||||
list.not = function(context, fn) {
|
||||
return "<p>" + fn(context, this) + "</p>";
|
||||
};
|
||||
var hash = {list: list, people: [{name: "Alan"}, {name: "Yehuda"}]};
|
||||
var empty = {list: list, people: []};
|
||||
var rootMessage = {
|
||||
list: function(context, fn, inverse) { if(context.length === 0) { return "<p>" + inverse(this) + "</p>"; } },
|
||||
people: [],
|
||||
message: "Nobody's here"
|
||||
}
|
||||
|
||||
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
|
||||
|
||||
// the meaning here may be kind of hard to catch, but list.not is always called,
|
||||
// so we should see the output of both
|
||||
shouldCompileTo(string, hash, "<ul><li>Alan</li><li>Yehuda</li></ul><p><em>Nobody's here</em></p>", "Not is called when block inverted section is encountered.");
|
||||
shouldCompileTo(string, hash, "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
|
||||
shouldCompileTo(string, empty, "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
|
||||
shouldCompileTo(messageString, rootMessage, "<p>Nobody's here</p>", "the context of an inverse is the parent of the block");
|
||||
});
|
||||
|
||||
module("fallback hash");
|
||||
@@ -390,7 +404,7 @@ test("partial in a partial", function() {
|
||||
|
||||
test("rendering undefined partial throws an exception", function() {
|
||||
shouldThrow(function() {
|
||||
var template = Handlebars.compile("{{> whatever}}");
|
||||
var template = Handlebars.compile("{{> whatever}}");
|
||||
template();
|
||||
}, Handlebars.Exception, "Should throw exception");
|
||||
});
|
||||
@@ -452,7 +466,7 @@ test("block multi-params work", function() {
|
||||
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
|
||||
var hash = {cruel: "cruel", world: "world"}
|
||||
var fallback = {goodbye: function(cruel, world, fn) {
|
||||
return fn({greeting: "Goodbye", adj: "cruel", noun: "world"});
|
||||
return fn({greeting: "Goodbye", adj: cruel, noun: world});
|
||||
}}
|
||||
shouldCompileTo(string, [hash, fallback], "Message: Goodbye cruel world", "block helpers with multiple params");
|
||||
})
|
||||
|
||||
+6
-1
@@ -54,10 +54,14 @@ module Handlebars
|
||||
context["exports"] = nil
|
||||
|
||||
context["p"] = proc do |val|
|
||||
p val
|
||||
p val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts"] = proc do |val|
|
||||
puts val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts_node"] = proc do |val|
|
||||
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
|
||||
puts
|
||||
end
|
||||
@@ -75,6 +79,7 @@ module Handlebars
|
||||
Handlebars::Spec.js_load('lib/handlebars/printer.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/runtime.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/utils.js')
|
||||
Handlebars::Spec.js_load('lib/Handlebars/vm.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars.js')
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user