Skip to content

Commit

Permalink
Add support for getting types in string mode
Browse files Browse the repository at this point in the history
This makes it possible to determine whether an
argument was passed as a string or as a path
when implementing helpers in string mode.
  • Loading branch information
wycats committed Jan 17, 2013
1 parent 5e5f0dc commit ccd6a22
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 22 deletions.
5 changes: 5 additions & 0 deletions lib/handlebars/compiler/ast.js
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
49 changes: 39 additions & 10 deletions lib/handlebars/compiler/compiler.js
Expand Up @@ -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);
Expand All @@ -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');
}

Expand All @@ -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]);
}
},
Expand Down Expand Up @@ -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);
}
Expand All @@ -338,7 +343,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('pushLiteral', '{}');
this.opcode('pushHash');
}

return params;
Expand All @@ -355,7 +360,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('pushLiteral', '{}');
this.opcode('pushHash');
}

return params;
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 + ";");
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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) {
Expand Down
50 changes: 38 additions & 12 deletions spec/qunit_spec.js
Expand Up @@ -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: ");
});
Expand Down Expand Up @@ -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});

Expand Down

2 comments on commit ccd6a22

@kpdecker
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I reading this correctly in that it allows helpers to differentiate between {{helper "foo"}} and {{helper bar}} where context.bar is foo by examining the types or hashTypes option?

@leshill
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

YES! Hello bind!

Please sign in to comment.