* Added a few public API methods to JavaScriptCompiler.prototype, so it can be subclassed.
* made it possible to define an alternate name lookup scheme (so that {{foo}} does not have to be
context.foo, but can instead be something like context.get('foo'))
* made it possible to substitute an alternate buffer instead of the default empty String and override how
the compiled template appends to the buffer
* Added the concept of template-local data. In order to enable template-local data, pass true
as the second parameter to the template compiler. Then, pass in the data as the fourth
parameter (context, helpers, partials, data). These signatures may change before the 1.0 release.
This commit is contained in:
@@ -106,10 +106,11 @@ Handlebars.logger = {
|
|||||||
|
|
||||||
// override in the host environment
|
// override in the host environment
|
||||||
log: function(level, str) {}
|
log: function(level, str) {}
|
||||||
}
|
};
|
||||||
|
|
||||||
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
||||||
|
|
||||||
// END(BROWSER)
|
// END(BROWSER)
|
||||||
|
|
||||||
module.exports = Handlebars;
|
module.exports = Handlebars;
|
||||||
|
|
||||||
|
|||||||
+54
-33
@@ -246,8 +246,28 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
};
|
};
|
||||||
|
|
||||||
JavaScriptCompiler.prototype = {
|
JavaScriptCompiler.prototype = {
|
||||||
compile: function(environment) {
|
// PUBLIC API: You can override these methods in a subclass to provide
|
||||||
|
// alternative compiled forms for name lookup and buffering semantics
|
||||||
|
nameLookup: function(parent, name, type) {
|
||||||
|
if(JavaScriptCompiler.RESERVED_WORDS[name]) {
|
||||||
|
return parent + "['" + name + "']";
|
||||||
|
} else {
|
||||||
|
return parent + "." + name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
appendToBuffer: function(string) {
|
||||||
|
return "buffer = buffer + " + string + ";";
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeBuffer: function() {
|
||||||
|
return this.quotedString("");
|
||||||
|
},
|
||||||
|
// END PUBLIC API
|
||||||
|
|
||||||
|
compile: function(environment, data) {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
this.preamble();
|
this.preamble();
|
||||||
|
|
||||||
@@ -255,7 +275,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
this.stackVars = [];
|
this.stackVars = [];
|
||||||
this.registers = {list: []};
|
this.registers = {list: []};
|
||||||
|
|
||||||
this.compileChildren(environment);
|
this.compileChildren(environment, data);
|
||||||
|
|
||||||
Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
|
Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
|
||||||
|
|
||||||
@@ -306,7 +326,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
|
|
||||||
preamble: function() {
|
preamble: function() {
|
||||||
var out = [];
|
var out = [];
|
||||||
out.push("var buffer = '', currentContext = context");
|
out.push("var buffer = " + this.initializeBuffer() + ", currentContext = context");
|
||||||
|
|
||||||
var copies = "helpers = helpers || Handlebars.helpers;";
|
var copies = "helpers = helpers || Handlebars.helpers;";
|
||||||
if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
|
if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
|
||||||
@@ -323,9 +343,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||||
invokePartial: Handlebars.VM.invokePartial,
|
invokePartial: Handlebars.VM.invokePartial,
|
||||||
programs: [],
|
programs: [],
|
||||||
program: function(i, helpers, partials) {
|
program: function(i, helpers, partials, data) {
|
||||||
var programWrapper = this.programs[i];
|
var programWrapper = this.programs[i];
|
||||||
if(programWrapper) {
|
if(data) {
|
||||||
|
return Handlebars.VM.program(this.children[i], helpers, partials, data);
|
||||||
|
} else if(programWrapper) {
|
||||||
return programWrapper;
|
return programWrapper;
|
||||||
} else {
|
} else {
|
||||||
programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
|
programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
|
||||||
@@ -347,11 +369,14 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
|
|
||||||
var params = ["Handlebars", "context", "helpers", "partials"];
|
var params = ["Handlebars", "context", "helpers", "partials"];
|
||||||
|
|
||||||
|
if(this.data) { params.push("data"); }
|
||||||
|
|
||||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||||
params.push("depth" + this.environment.depths.list[i]);
|
params.push("depth" + this.environment.depths.list[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(params.length === 3 && !this.environment.usePartial) { params.pop(); }
|
|
||||||
|
if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
|
||||||
|
|
||||||
params.push(this.source.join("\n"));
|
params.push(this.source.join("\n"));
|
||||||
|
|
||||||
@@ -364,9 +389,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
|
|
||||||
container.children = this.environment.children;
|
container.children = this.environment.children;
|
||||||
|
|
||||||
return function(context, helpers, partials, depth) {
|
return function(context, helpers, partials, data, $depth) {
|
||||||
try {
|
try {
|
||||||
return container.render.call(container, Handlebars, context, helpers, partials, depth);
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
args.unshift(Handlebars);
|
||||||
|
return container.render.apply(container, args);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -374,12 +401,12 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
},
|
},
|
||||||
|
|
||||||
appendContent: function(content) {
|
appendContent: function(content) {
|
||||||
this.source.push("buffer = buffer + " + this.quotedString(content) + ";");
|
this.source.push(this.appendToBuffer(this.quotedString(content)));
|
||||||
},
|
},
|
||||||
|
|
||||||
append: function() {
|
append: function() {
|
||||||
var local = this.popStack();
|
var local = this.popStack();
|
||||||
this.source.push("if(" + local + " || " + local + " === 0) { buffer = buffer + " + local + "; }");
|
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
||||||
},
|
},
|
||||||
|
|
||||||
appendEscaped: function() {
|
appendEscaped: function() {
|
||||||
@@ -390,7 +417,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
this.eat(opcode);
|
this.eat(opcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.source.push("buffer = buffer + this.escapeExpression(" + this.popStack() + ")" + extra + ";");
|
this.source.push(this.appendToBuffer("this.escapeExpression(" + this.popStack() + ")" + extra));
|
||||||
},
|
},
|
||||||
|
|
||||||
getContext: function(depth) {
|
getContext: function(depth) {
|
||||||
@@ -405,19 +432,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
nameLookup: function(parent, name) {
|
|
||||||
if(JavaScriptCompiler.RESERVED_WORDS[name]) {
|
|
||||||
return parent + "['" + name + "']";
|
|
||||||
} else {
|
|
||||||
return parent + "." + name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
lookupWithFallback: function(name) {
|
lookupWithFallback: function(name) {
|
||||||
if(name) {
|
if(name) {
|
||||||
this.pushStack(this.nameLookup('currentContext', name));
|
this.pushStack(this.nameLookup('currentContext', name, 'context'));
|
||||||
var topStack = this.topStack();
|
var topStack = this.topStack();
|
||||||
this.source.push("if(" + topStack + " === undefined) { " + topStack + " = " + this.nameLookup('helpers', name) + "; }");
|
this.source.push("if(" + topStack + " === undefined) { " + topStack + " = " + this.nameLookup('helpers', name, 'helper') + "; }");
|
||||||
} else {
|
} else {
|
||||||
this.pushStack("currentContext");
|
this.pushStack("currentContext");
|
||||||
}
|
}
|
||||||
@@ -425,7 +444,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
|
|
||||||
lookup: function(name) {
|
lookup: function(name) {
|
||||||
var topStack = this.topStack();
|
var topStack = this.topStack();
|
||||||
this.source.push(topStack + " = " + this.nameLookup(topStack, name) + ";");
|
this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";");
|
||||||
},
|
},
|
||||||
|
|
||||||
pushString: function(string) {
|
pushString: function(string) {
|
||||||
@@ -445,6 +464,8 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
params.push(this.popStack());
|
params.push(this.popStack());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.data) { params.push("data"); }
|
||||||
|
|
||||||
var paramString = params.join(", ");
|
var paramString = params.join(", ");
|
||||||
var helperMissing = ["context"].concat(this.quotedString(original)).concat(params.slice(1));
|
var helperMissing = ["context"].concat(this.quotedString(original)).concat(params.slice(1));
|
||||||
|
|
||||||
@@ -492,12 +513,12 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
},
|
},
|
||||||
|
|
||||||
invokePartial: function(context) {
|
invokePartial: function(context) {
|
||||||
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context) + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
|
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
|
||||||
},
|
},
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
|
|
||||||
compileChildren: function(environment) {
|
compileChildren: function(environment, data) {
|
||||||
var children = environment.children, child, compiler;
|
var children = environment.children, child, compiler;
|
||||||
var compiled = [];
|
var compiled = [];
|
||||||
|
|
||||||
@@ -505,7 +526,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
child = children[i];
|
child = children[i];
|
||||||
compiler = new JavaScriptCompiler();
|
compiler = new JavaScriptCompiler();
|
||||||
|
|
||||||
compiled[i] = compiler.compile(child);
|
compiled[i] = compiler.compile(child, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
environment.rawChildren = children;
|
environment.rawChildren = children;
|
||||||
@@ -519,6 +540,8 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
|
|
||||||
var depths = this.environment.rawChildren[guid].depths.list;
|
var depths = this.environment.rawChildren[guid].depths.list;
|
||||||
|
|
||||||
|
if(this.data) { programParams.push("data"); }
|
||||||
|
|
||||||
for(var i=0, l = depths.length; i<l; i++) {
|
for(var i=0, l = depths.length; i<l; i++) {
|
||||||
depth = depths[i];
|
depth = depths[i];
|
||||||
|
|
||||||
@@ -540,8 +563,6 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
programParams[0] = "this.children[" + guid + "]";
|
programParams[0] = "this.children[" + guid + "]";
|
||||||
return "this.programWithDepth(" + programParams.join(", ") + ")";
|
return "this.programWithDepth(" + programParams.join(", ") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "this.program(" + programParams.join(", ") + ")";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
register: function(name, val) {
|
register: function(name, val) {
|
||||||
@@ -597,22 +618,22 @@ Handlebars.JavaScriptCompiler = function() {};
|
|||||||
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
||||||
|
|
||||||
Handlebars.VM = {
|
Handlebars.VM = {
|
||||||
programWithDepth: function(fn, helpers, partials, depth) {
|
programWithDepth: function(fn) {
|
||||||
var args = [].slice.call(arguments, 1);
|
var args = Array.prototype.slice.call(arguments, 1);
|
||||||
return function(context) {
|
return function(context) {
|
||||||
return fn.apply(this, [context].concat(args));
|
return fn.apply(this, [context].concat(args));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
program: function(fn, helpers, partials) {
|
program: function(fn, helpers, partials, data) {
|
||||||
return function(context) {
|
return function(context) {
|
||||||
return fn(context, helpers, partials);
|
return fn(context, helpers, partials, data);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
noop: function() { return ""; },
|
noop: function() { return ""; },
|
||||||
compile: function(string) {
|
compile: function(string, data) {
|
||||||
var ast = Handlebars.parse(string);
|
var ast = Handlebars.parse(string);
|
||||||
var environment = new Handlebars.Compiler().compile(ast);
|
var environment = new Handlebars.Compiler().compile(ast);
|
||||||
return new Handlebars.JavaScriptCompiler().compile(environment);
|
return new Handlebars.JavaScriptCompiler().compile(environment, data);
|
||||||
},
|
},
|
||||||
invokePartial: function(partial, name, context, helpers, partials) {
|
invokePartial: function(partial, name, context, helpers, partials) {
|
||||||
if(partial === undefined) {
|
if(partial === undefined) {
|
||||||
|
|||||||
+66
-1
@@ -20,7 +20,7 @@ var shouldCompileTo = function(string, hash, expected, message) {
|
|||||||
|
|
||||||
result = template.apply(this, hash)
|
result = template.apply(this, hash)
|
||||||
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
||||||
}
|
};
|
||||||
|
|
||||||
var shouldThrow = function(fn, exception, message) {
|
var shouldThrow = function(fn, exception, message) {
|
||||||
var caught = false;
|
var caught = false;
|
||||||
@@ -519,3 +519,68 @@ test("each", function() {
|
|||||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||||
"each with array argument ignores the contents when empty");
|
"each with array argument ignores the contents when empty");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("overriding property lookup", function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("passing in data to a compiled function that expects data - works with helpers", function() {
|
||||||
|
var template = Handlebars.compile("{{hello}}", true);
|
||||||
|
|
||||||
|
var helpers = {
|
||||||
|
hello: function(data) {
|
||||||
|
return data.adjective + " " + this.noun;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = template({noun: "cat"}, helpers, null, {adjective: "happy"});
|
||||||
|
equals("happy cat", result);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
|
||||||
|
var template = Handlebars.compile("{{hello world}}", true);
|
||||||
|
|
||||||
|
var helpers = {
|
||||||
|
hello: function(noun, data) {
|
||||||
|
return data.adjective + " " + noun + (this.exclaim ? "!" : "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = template({exclaim: true, world: "world"}, helpers, null, {adjective: "happy"});
|
||||||
|
equals("happy world!", result);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("passing in data to a compiled function that expects data - works with block helpers", function() {
|
||||||
|
var template = Handlebars.compile("{{#hello}}{{world}}{{/hello}}", true);
|
||||||
|
|
||||||
|
var helpers = {
|
||||||
|
hello: function(fn) {
|
||||||
|
return fn(this);
|
||||||
|
},
|
||||||
|
world: function(data) {
|
||||||
|
return data.adjective + " world" + (this.exclaim ? "!" : "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = template({exclaim: true}, helpers, null, {adjective: "happy"});
|
||||||
|
equals("happy world!", result);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
|
||||||
|
var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", true);
|
||||||
|
|
||||||
|
var helpers = {
|
||||||
|
hello: function(fn) {
|
||||||
|
return fn({exclaim: "?"});
|
||||||
|
},
|
||||||
|
world: function(thing, data) {
|
||||||
|
return data.adjective + " " + thing + (this.exclaim || "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = template({exclaim: true, zomg: "world"}, helpers, null, {adjective: "happy"});
|
||||||
|
equals("happy world?", result);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ module Handlebars
|
|||||||
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
||||||
|
|
||||||
context["Handlebars"]["logger"]["log"] = proc do |level, str|
|
context["Handlebars"]["logger"]["log"] = proc do |level, str|
|
||||||
logger_level = context["Handlebars"]["logger"]["level"]
|
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
||||||
|
|
||||||
if logger_level <= level
|
if logger_level <= level
|
||||||
puts str
|
puts str
|
||||||
|
|||||||
Reference in New Issue
Block a user