Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dynamic partial names #941

Merged
merged 4 commits into from
Jan 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions docs/compiler-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,21 @@ interface Statement <: Node { }

interface MustacheStatement <: Statement {
type: "MustacheStatement";
sexpr: SubExpression;
escaped: boolean;

path: PathExpression;
params: [ Expression ];
hash: Hash;

escaped: boolean;
strip: StripFlags | null;
}

interface BlockStatement <: Statement {
type: "BlockStatement";
sexpr: SubExpression;
path: PathExpression;
params: [ Expression ];
hash: Hash;

program: Program | null;
inverse: Program | null;

Expand All @@ -74,12 +80,19 @@ interface BlockStatement <: Statement {

interface PartialStatement <: Statement {
type: "PartialStatement";
sexpr: SubExpression;
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;

indent: string;
strip: StripFlags | null;
}
```

`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.


```java
interface ContentStatement <: Statement {
type: "ContentStatement";
value: string;
Expand Down Expand Up @@ -108,13 +121,9 @@ interface SubExpression <: Expression {
path: PathExpression;
params: [ Expression ];
hash: Hash;

isHelper: true | null;
}
```

`isHelper` is not required and is used to disambiguate between cases such as `{{foo}}` and `(foo)`, which have slightly different call behaviors.

##### Paths

```java
Expand Down Expand Up @@ -195,7 +204,7 @@ function ImportScanner() {
ImportScanner.prototype = new Visitor();

ImportScanner.prototype.PartialStatement = function(partial) {
this.partials.push({request: partial.sexpr.original});
this.partials.push({request: partial.name.original});

Visitor.prototype.PartialStatement.call(this, partial);
};
Expand All @@ -221,6 +230,8 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
- `name` is the current path component
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.

Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.

- `depthedLookup(name)`
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.

Expand Down
27 changes: 17 additions & 10 deletions lib/handlebars/compiler/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@ var AST = {
this.strip = strip;
},

MustacheStatement: function(sexpr, escaped, strip, locInfo) {
MustacheStatement: function(path, params, hash, escaped, strip, locInfo) {
this.loc = locInfo;
this.type = 'MustacheStatement';

this.sexpr = sexpr;
this.path = path;
this.params = params || [];
this.hash = hash;
this.escaped = escaped;

this.strip = strip;
},

BlockStatement: function(sexpr, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
BlockStatement: function(path, params, hash, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
this.loc = locInfo;

this.type = 'BlockStatement';
this.sexpr = sexpr;

this.path = path;
this.params = params || [];
this.hash = hash;
this.program = program;
this.inverse = inverse;

Expand All @@ -31,12 +35,15 @@ var AST = {
this.closeStrip = closeStrip;
},

PartialStatement: function(sexpr, strip, locInfo) {
PartialStatement: function(name, params, hash, strip, locInfo) {
this.loc = locInfo;
this.type = 'PartialStatement';
this.sexpr = sexpr;
this.indent = '';

this.name = name;
this.params = params || [];
this.hash = hash;

this.indent = '';
this.strip = strip;
},

Expand Down Expand Up @@ -112,8 +119,8 @@ var AST = {
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
// TODO: Make these public utility methods
helperExpression: function(sexpr) {
return !!(sexpr.isHelper || sexpr.params.length || sexpr.hash);
helperExpression: function(node) {
return !!(node.type === 'SubExpression' || node.params.length || node.hash);
},

scopedId: function(path) {
Expand Down
34 changes: 17 additions & 17 deletions lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,28 +110,27 @@ Compiler.prototype = {
},

BlockStatement: function(block) {
var sexpr = block.sexpr,
program = block.program,
var program = block.program,
inverse = block.inverse;

program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);

var type = this.classifySexpr(sexpr);
var type = this.classifySexpr(block);

if (type === 'helper') {
this.helperSexpr(sexpr, program, inverse);
this.helperSexpr(block, program, inverse);
} else if (type === 'simple') {
this.simpleSexpr(sexpr);
this.simpleSexpr(block);

// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue', sexpr.path.original);
this.opcode('blockValue', block.path.original);
} else {
this.ambiguousSexpr(sexpr, program, inverse);
this.ambiguousSexpr(block, program, inverse);

// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
Expand All @@ -145,29 +144,35 @@ Compiler.prototype = {
},

PartialStatement: function(partial) {
var partialName = partial.sexpr.path.original;
this.usePartial = true;

var params = partial.sexpr.params;
var params = partial.params;
if (params.length > 1) {
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
} else if (!params.length) {
params.push({type: 'PathExpression', parts: [], depth: 0});
}

this.setupFullMustacheParams(partial.sexpr, undefined, undefined, true);
var partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}

this.setupFullMustacheParams(partial, undefined, undefined, true);

var indent = partial.indent || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
indent = '';
}
this.opcode('invokePartial', partialName, indent);

this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},

MustacheStatement: function(mustache) {
this.accept(mustache.sexpr);
this.SubExpression(mustache);

if(mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
Expand Down Expand Up @@ -334,11 +339,6 @@ Compiler.prototype = {
pushParam: function(val) {
var value = val.value != null ? val.value : val.original || '';

// Force helper evaluation
if (val.type === 'SubExpression') {
val.isHelper = true;
}

if (this.stringParams) {
if (value.replace) {
value = value
Expand Down
22 changes: 12 additions & 10 deletions lib/handlebars/compiler/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,39 +52,40 @@ export function preparePath(data, parts, locInfo) {
return new this.PathExpression(data, depth, dig, original, locInfo);
}

export function prepareMustache(sexpr, open, strip, locInfo) {
export function prepareMustache(path, params, hash, open, strip, locInfo) {
/*jshint -W040 */
// Must use charAt to support IE pre-10
var escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';

return new this.MustacheStatement(sexpr, escaped, strip, this.locInfo(locInfo));
return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
}

export function prepareRawBlock(openRawBlock, content, close, locInfo) {
/*jshint -W040 */
if (openRawBlock.sexpr.path.original !== close) {
var errorNode = {loc: openRawBlock.sexpr.loc};
if (openRawBlock.path.original !== close) {
var errorNode = {loc: openRawBlock.path.loc};

throw new Exception(openRawBlock.sexpr.path.original + " doesn't match " + close, errorNode);
throw new Exception(openRawBlock.path.original + " doesn't match " + close, errorNode);
}

locInfo = this.locInfo(locInfo);
var program = new this.Program([content], null, {}, locInfo);

return new this.BlockStatement(
openRawBlock.sexpr, program, undefined,
openRawBlock.path, openRawBlock.params, openRawBlock.hash,
program, undefined,
{}, {}, {},
locInfo);
}

export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
/*jshint -W040 */
// When we are chaining inverse calls, we will not have a close path
if (close && close.path && openBlock.sexpr.path.original !== close.path.original) {
var errorNode = {loc: openBlock.sexpr.loc};
if (close && close.path && openBlock.path.original !== close.path.original) {
var errorNode = {loc: openBlock.path.loc};

throw new Exception(openBlock.sexpr.path.original + ' doesn\'t match ' + close.path.original, errorNode);
throw new Exception(openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
}

program.blockParams = openBlock.blockParams;
Expand All @@ -108,7 +109,8 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
}

return new this.BlockStatement(
openBlock.sexpr, program, inverse,
openBlock.path, openBlock.params, openBlock.hash,
program, inverse,
openBlock.strip, inverseStrip, close && close.strip,
this.locInfo(locInfo));
}
13 changes: 11 additions & 2 deletions lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,17 +644,26 @@ JavaScriptCompiler.prototype = {
//
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
invokePartial: function(name, indent) {
invokePartial: function(isDynamic, name, indent) {
var params = [],
options = this.setupParams(name, 1, params, false);

if (isDynamic) {
name = this.popStack();
delete options.name;
}

if (indent) {
options.indent = JSON.stringify(indent);
}
options.helpers = 'helpers';
options.partials = 'partials';

params.unshift(this.nameLookup('partials', name, 'partial'));
if (!isDynamic) {
params.unshift(this.nameLookup('partials', name, 'partial'));
} else {
params.unshift(name);
}

if (this.options.compat) {
options.depths = 'depths';
Expand Down
15 changes: 7 additions & 8 deletions lib/handlebars/compiler/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ PrintVisitor.prototype.Program = function(program) {
};

PrintVisitor.prototype.MustacheStatement = function(mustache) {
return this.pad('{{ ' + this.accept(mustache.sexpr) + ' }}');
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
};

PrintVisitor.prototype.BlockStatement = function(block) {
var out = "";

out = out + this.pad('BLOCK:');
this.padding++;
out = out + this.pad(this.accept(block.sexpr));
out = out + this.pad(this.SubExpression(block));
if (block.program) {
out = out + this.pad('PROGRAM:');
this.padding++;
Expand All @@ -74,13 +74,12 @@ PrintVisitor.prototype.BlockStatement = function(block) {
};

PrintVisitor.prototype.PartialStatement = function(partial) {
var sexpr = partial.sexpr,
content = 'PARTIAL:' + sexpr.path.original;
if(sexpr.params[0]) {
content += ' ' + this.accept(sexpr.params[0]);
var content = 'PARTIAL:' + partial.name.original;
if(partial.params[0]) {
content += ' ' + this.accept(partial.params[0]);
}
if (sexpr.hash) {
content += ' ' + this.accept(sexpr.hash);
if (partial.hash) {
content += ' ' + this.accept(partial.hash);
}
return this.pad('{{> ' + content + ' }}');
};
Expand Down
18 changes: 15 additions & 3 deletions lib/handlebars/compiler/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,24 @@ Visitor.prototype = {
},

MustacheStatement: function(mustache) {
this.acceptRequired(mustache, 'sexpr');
this.acceptRequired(mustache, 'path');
this.acceptArray(mustache.params);
this.acceptKey(mustache, 'hash');
},

BlockStatement: function(block) {
this.acceptRequired(block, 'sexpr');
this.acceptRequired(block, 'path');
this.acceptArray(block.params);
this.acceptKey(block, 'hash');

this.acceptKey(block, 'program');
this.acceptKey(block, 'inverse');
},

PartialStatement: function(partial) {
this.acceptRequired(partial, 'sexpr');
this.acceptRequired(partial, 'name');
this.acceptArray(partial.params);
this.acceptKey(partial, 'hash');
},

ContentStatement: function(/* content */) {},
Expand All @@ -92,6 +99,11 @@ Visitor.prototype = {
this.acceptArray(sexpr.params);
this.acceptKey(sexpr, 'hash');
},
PartialExpression: function(partial) {
this.acceptRequired(partial, 'name');
this.acceptArray(partial.params);
this.acceptKey(partial, 'hash');
},

PathExpression: function(/* path */) {},

Expand Down
Loading