Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #95 from gigafied/master

{% extends someVar %} support.
  • Loading branch information...
commit 2670e39fcab5fa7a6a1ea70c4a8ea3a2c78c55ba 2 parents 8c22dd2 + 28ec144
@paularmstrong authored
View
57 index.js
@@ -39,6 +39,28 @@ function TemplateError(error) {
}};
}
+function createRenderFunc(code) {
+ // The compiled render function - this is all we need
+ return new Function('_context', '_parents', '_filters', '_', '_ext', [
+ '_parents = _parents ? _parents.slice() : [];',
+ '_context = _context || {};',
+ // Prevents circular includes (which will crash node without warning)
+ 'var j = _parents.length,',
+ ' _output = "",',
+ ' _this = this;',
+ // Note: this loop averages much faster than indexOf across all cases
+ 'while (j--) {',
+ ' if (_parents[j] === this.id) {',
+ ' return "Circular import of template " + this.id + " in " + _parents[_parents.length-1];',
+ ' }',
+ '}',
+ // Add this template as a parent to all includes in its scope
+ '_parents.push(this.id);',
+ code,
+ 'return _output;',
+ ].join(''));
+}
+
function createTemplate(data, id) {
var template = {
// Allows us to include templates from the compiled code
@@ -56,37 +78,30 @@ function createTemplate(data, id) {
// The template token tree before compiled into javascript
if (_config.allowErrors) {
- template.tokens = parser.parse.call(template, data, _config.tags, _config.autoescape);
+ tokens = parser.parse.call(template, data, _config.tags, _config.autoescape);
} else {
try {
- template.tokens = parser.parse.call(template, data, _config.tags, _config.autoescape);
+ tokens = parser.parse.call(template, data, _config.tags, _config.autoescape);
} catch (e) {
return new TemplateError(e);
}
}
+ template.tokens = tokens;
+
// The raw template code
code = parser.compile.call(template);
- // The compiled render function - this is all we need
- render = new Function('_context', '_parents', '_filters', '_', '_ext', [
- '_parents = _parents ? _parents.slice() : [];',
- '_context = _context || {};',
- // Prevents circular includes (which will crash node without warning)
- 'var j = _parents.length,',
- ' _output = "",',
- ' _this = this;',
- // Note: this loop averages much faster than indexOf across all cases
- 'while (j--) {',
- ' if (_parents[j] === this.id) {',
- ' return "Circular import of template " + this.id + " in " + _parents[_parents.length-1];',
- ' }',
- '}',
- // Add this template as a parent to all includes in its scope
- '_parents.push(this.id);',
- code,
- 'return _output;',
- ].join(''));
+ if (code !== false) {
+ render = createRenderFunc(code);
+ } else {
+ render = function (_context, _parents, _filters, _, _ext) {
+ template.tokens = tokens;
+ code = parser.compile.call(template, null, '', _context);
+ var fn = createRenderFunc(code);
+ return fn.call(this, _context, _parents, _filters, _, _ext);
+ };
+ }
template.render = function (context, parents) {
if (_config.allowErrors) {
View
22 index.test.js
@@ -70,6 +70,28 @@ exports.compileFile = testCase({
test.done();
},
+ 'using context var with extends tag' : function (test) {
+ swig.init({
+ root: __dirname + '/tests/templates',
+ allowErrors: true
+ });
+
+ var tpl, r1, r2, r3, r4;
+
+ tpl = swig.compileFile('extends_dynamic.html');
+ r1 = tpl.render({baseTmpl: "extends_base.html"});
+ r2 = tpl.render({baseTmpl: "extends_base2.html"});
+ r3 = tpl.render({baseTmpl: "extends_base.html"});
+ r4 = tpl.render({baseTmpl: "extends_base2.html"});
+
+ test.strictEqual(r1, r3, "rendering the same template with the same context twice, should return identically.");
+ test.strictEqual(r2, r4, "rendering the same template with the same context twice, should return identically.");
+
+ test.notEqual(r1, r2, "these should not be equal, as they use different base templates.");
+
+ test.done();
+ },
+
'absolute path': function (test) {
swig.init({
root: __dirname + '/tests/templates',
View
94 lib/parser.js
@@ -28,6 +28,14 @@ function getArgs(input) {
return doubleEscape(input.replace(/^[\w\.]+\(|\)$/g, ''));
}
+function getContextVar(varName, context) {
+ var a = varName.split(".");
+ while (a.length) {
+ context = context[a.splice(0, 1)[0]];
+ }
+ return context;
+}
+
function getTokenArgs(token, parts) {
parts = _.map(parts, doubleEscape);
@@ -294,7 +302,7 @@ exports.parse = function (data, tags, autoescape) {
return stack[index];
};
-exports.compile = function compile(indent, parentBlock) {
+exports.compile = function compile(indent, parentBlock, context) {
var code = '',
tokens = [],
sets = [],
@@ -302,49 +310,71 @@ exports.compile = function compile(indent, parentBlock) {
filepath,
blockname,
varOutput,
- wrappedInMethod;
+ wrappedInMethod,
+ extendsHasVar;
indent = indent || '';
// Precompile - extract blocks and create hierarchy based on 'extends' tags
// TODO: make block and extends tags accept context variables
if (this.type === TEMPLATE) {
+
_.each(this.tokens, function (token, index) {
- // Load the parent template
- if (token.name === 'extends') {
- filepath = token.args[0];
- if (!helpers.isStringLiteral(filepath) || token.args.length > 1) {
- throw new Error('Extends tag on line ' + token.line + ' accepts exactly one string literal as an argument.');
- }
- if (index > 0) {
- throw new Error('Extends tag must be the first tag in the template, but "extends" found on line ' + token.line + '.');
- }
- token.template = this.compileFile(filepath.replace(/['"]/g, ''));
- this.parent = token.template;
- } else if (token.name === 'block') { // Make a list of blocks
- blockname = token.args[0];
- if (!helpers.isValidBlockName(blockname) || token.args.length !== 1) {
- throw new Error('Invalid block tag name "' + blockname + '" on line ' + token.line + '.');
- }
- if (this.type !== TEMPLATE) {
- throw new Error('Block "' + blockname + '" found nested in another block tag on line' + token.line + '.');
- }
- try {
- if (this.hasOwnProperty('parent') && this.parent.blocks.hasOwnProperty(blockname)) {
- this.blocks[blockname] = compile.call(token, indent + ' ', this.parent.blocks[blockname]);
- } else if (this.hasOwnProperty('blocks')) {
- this.blocks[blockname] = compile.call(token, indent + ' ');
+
+ if (!extendsHasVar) {
+ // Load the parent template
+ if (token.name === 'extends') {
+ filepath = token.args[0];
+
+ if (!helpers.isStringLiteral(filepath)) {
+
+ if (!context) {
+ extendsHasVar = true;
+ return;
+ }
+ filepath = "\"" + getContextVar(filepath, context) + "\"";
+ }
+
+ if (!helpers.isStringLiteral(filepath) || token.args.length > 1) {
+ throw new Error('Extends tag on line ' + token.line + ' accepts exactly one string literal as an argument.');
+ }
+ if (index > 0) {
+ throw new Error('Extends tag must be the first tag in the template, but "extends" found on line ' + token.line + '.');
+ }
+ token.template = this.compileFile(filepath.replace(/['"]/g, ''));
+ this.parent = token.template;
+
+ } else if (token.name === 'block') { // Make a list of blocks
+ blockname = token.args[0];
+ if (!helpers.isValidBlockName(blockname) || token.args.length !== 1) {
+ throw new Error('Invalid block tag name "' + blockname + '" on line ' + token.line + '.');
+ }
+ if (this.type !== TEMPLATE) {
+ throw new Error('Block "' + blockname + '" found nested in another block tag on line' + token.line + '.');
+ }
+ try {
+ if (this.hasOwnProperty('parent') && this.parent.blocks.hasOwnProperty(blockname)) {
+ this.blocks[blockname] = compile.call(token, indent + ' ', this.parent.blocks[blockname]);
+ } else if (this.hasOwnProperty('blocks')) {
+ this.blocks[blockname] = compile.call(token, indent + ' ');
+ }
+ } catch (error) {
+ throw new Error('Circular extends found on line ' + token.line + ' of "' + this.id + '"!');
}
- } catch (error) {
- throw new Error('Circular extends found on line ' + token.line + ' of "' + this.id + '"!');
+ } else if (token.name === 'set') {
+ sets.push(token);
+ return;
}
- } else if (token.name === 'set') {
- sets.push(token);
- return;
+ tokens.push(token);
}
- tokens.push(token);
}, this);
+ // If extendsHasVar == true, then we know {% extends %} is not using a string literal, thus we can't
+ // compile until render is called, so we return false.
+ if (extendsHasVar) {
+ return false;
+ }
+
if (tokens.length && tokens[0].name === 'extends') {
this.blocks = _.extend({}, this.parent.blocks, this.blocks);
this.tokens = sets.concat(this.parent.tokens);
View
9 tests/templates/extends_base2.html
@@ -0,0 +1,9 @@
+This is from the "extends_base2.html" template.
+
+{% block one %}
+ This is the default content in block 'one'
+{% endblock %}
+
+{% block two %}
+ This is the default content in block 'two'
+{% endblock %}
View
6 tests/templates/extends_dynamic.html
@@ -0,0 +1,6 @@
+{% extends baseTmpl %}
+This is content from "extends_dynamic.html", you should not see it
+
+{% block one %}
+ This is the "extends_dynamic.html" content in block 'one'
+{% endblock %}
Please sign in to comment.
Something went wrong with that request. Please try again.