Add support for getting types in string mode

This makes it possible to determine whether an
argument was passed as a string or as a path
when implementing helpers in string mode.
This commit is contained in:
Yehuda Katz
2013-01-16 22:43:25 -08:00
parent 5e5f0dce9c
commit ccd6a22ea5
3 changed files with 82 additions and 22 deletions
+5
View File
@@ -88,6 +88,8 @@ var Handlebars = require('./base');
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
this.stringModeValue = this.string;
};
Handlebars.AST.PartialNameNode = function(name) {
@@ -103,16 +105,19 @@ var Handlebars = require('./base');
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
this.stringModeValue = string;
};
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
this.integer = integer;
this.stringModeValue = Number(integer);
};
Handlebars.AST.BooleanNode = function(bool) {
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = Boolean(bool);
};
Handlebars.AST.CommentNode = function(comment) {
+39 -10
View File
@@ -129,7 +129,7 @@ Handlebars.JavaScriptCompiler = function() {};
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('pushLiteral', '{}');
this.opcode('pushHash');
this.opcode('blockValue');
} else {
this.ambiguousMustache(mustache, program, inverse);
@@ -138,7 +138,7 @@ Handlebars.JavaScriptCompiler = function() {};
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('pushLiteral', '{}');
this.opcode('pushHash');
this.opcode('ambiguousBlockValue');
}
@@ -148,13 +148,18 @@ Handlebars.JavaScriptCompiler = function() {};
hash: function(hash) {
var pairs = hash.pairs, pair, val;
this.opcode('push', '{}');
this.opcode('pushHash');
for(var i=0, l=pairs.length; i<l; i++) {
pair = pairs[i];
val = pair[1];
this.accept(val);
if (this.options.stringParams) {
this.opcode('pushStringParam', val.stringModeValue, val.type);
} else {
this.accept(val);
}
this.opcode('assignToHash', pair[0]);
}
},
@@ -324,7 +329,7 @@ Handlebars.JavaScriptCompiler = function() {};
}
this.opcode('getContext', param.depth || 0);
this.opcode('pushStringParam', param.string);
this.opcode('pushStringParam', param.stringModeValue, param.type);
} else {
this[param.type](param);
}
@@ -338,7 +343,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('pushLiteral', '{}');
this.opcode('pushHash');
}
return params;
@@ -355,7 +360,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('pushLiteral', '{}');
this.opcode('pushHash');
}
return params;
@@ -677,9 +682,24 @@ Handlebars.JavaScriptCompiler = function() {};
// This opcode is designed for use in string mode, which
// provides the string value of a parameter along with its
// depth rather than resolving it immediately.
pushStringParam: function(string) {
pushStringParam: function(string, type) {
this.pushStackLiteral('depth' + this.lastContext);
this.pushString(string);
this.pushString(type);
if (typeof string === 'string') {
this.pushString(string);
} else {
this.pushStackLiteral(string);
}
},
pushHash: function() {
this.push('{}');
if (this.options.stringParams) {
this.register('hashTypes', '{}');
}
},
// [pushString]
@@ -817,6 +837,12 @@ Handlebars.JavaScriptCompiler = function() {};
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack();
if (this.options.stringParams) {
var type = this.popStack();
this.source.push("hashTypes['" + key + "'] = " + type + ";");
}
var hash = this.topStack();
this.source.push(hash + "['" + key + "'] = " + value + ";");
@@ -962,7 +988,7 @@ Handlebars.JavaScriptCompiler = function() {};
// the params and contexts arguments are passed in arrays
// to fill in
setupParams: function(paramSize, params) {
var options = [], contexts = [], param, inverse, program;
var options = [], contexts = [], types = [], param, inverse, program;
options.push("hash:" + this.popStack());
@@ -991,12 +1017,15 @@ Handlebars.JavaScriptCompiler = function() {};
params.push(param);
if(this.options.stringParams) {
types.push(this.popStack());
contexts.push(this.popStack());
}
}
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
options.push("hashTypes:hashTypes");
}
if(this.options.data) {
+38 -12
View File
@@ -174,18 +174,6 @@ test("literal paths", function() {
"Goodbye beautiful world!", "Literal paths can be used");
});
test("--- TODO --- bad idea nested paths", function() {
return;
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldThrow(function() {
CompilerContext.compile("{{#goodbyes}}{{../name/../name}}{{/goodbyes}}")(hash);
}, Handlebars.Exception,
"Cannot jump (..) into previous context after moving into a context.");
var string = "{{#goodbyes}}{{.././world}} {{/goodbyes}}";
shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths");
});
test("that current context path ({{.}}) doesn't hit helpers", function() {
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
});
@@ -1205,6 +1193,44 @@ test("when inside a block in String mode, .. passes the appropriate context in t
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
});
test("in string mode, information about the types is passed along", function() {
var template = CompilerContext.compile('{{tomdale "need" dad.joke true}}', { stringParams: true });
var helpers = {
tomdale: function(desire, noun, bool, options) {
equal(options.types[0], 'STRING', "the string type is passed");
equal(options.types[1], 'ID', "the expression type is passed");
equal(options.types[2], 'BOOLEAN', "the expression type is passed");
equal(desire, "need", "the string form is passed for strings");
equal(noun, "dad.joke", "the string form is passed for expressions");
equal(bool, true, "raw booleans are passed through");
return "Helper called";
}
};
var result = template({}, { helpers: helpers });
equal(result, "Helper called");
});
test("in string mode, hash parameters get type information", function() {
var template = CompilerContext.compile('{{tomdale desire="need" noun=dad.joke bool=true}}', { stringParams: true });
var helpers = {
tomdale: function(options) {
equal(options.hashTypes.desire, "STRING");
equal(options.hashTypes.noun, "ID");
equal(options.hashTypes.bool, "BOOLEAN");
equal(options.hash.desire, "need");
equal(options.hash.noun, "dad.joke");
equal(options.hash.bool, true);
return "Helper called";
}
};
var result = template({}, { helpers: helpers });
equal(result, "Helper called");
});
test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});