Skip to content
Browse files

final bugfixes & all tests pass for new version

  • Loading branch information...
1 parent 467cb25 commit 1f01afce31ea4cf7631711f12e51eae539b92a37 @jlongster committed Oct 11, 2012
View
10 browser/nunjucks-dev.js
@@ -2424,7 +2424,7 @@ var Compiler = Object.extend({
compileBlock: function(node, frame) {
this.emitLine(this.buffer + ' += context.getBlock("' +
- node.name.value + '")(env, context, frame);');
+ node.name.value + '")(env, context, frame, runtime);');
},
compileExtends: function(node, frame) {
@@ -2491,8 +2491,8 @@ var Compiler = Object.extend({
'b_' + name + ', ' +
'runtime);');
- var tmpFrame = frame.push();
- frame.set('super', 'l_super');
+ var tmpFrame = new Frame();
+ tmpFrame.set('super', 'l_super');
this.compile(block.body, tmpFrame);
this.emitFuncEnd();
@@ -3159,9 +3159,9 @@ var Template = Object.extend({
});
// var fs = modules["fs"];
-// //var src = fs.readFileSync('test.html', 'utf-8');
+// var src = fs.readFileSync('test.html', 'utf-8');
// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}';
-// var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
+// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
// var env = new Environment();
// console.log(compiler.compile(src));
View
4 browser/nunjucks.js
@@ -841,9 +841,9 @@ var Template = Object.extend({
});
// var fs = modules["fs"];
-// //var src = fs.readFileSync('test.html', 'utf-8');
+// var src = fs.readFileSync('test.html', 'utf-8');
// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}';
-// var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
+// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
// var env = new Environment();
// console.log(compiler.compile(src));
View
6 src/compiler.js
@@ -517,7 +517,7 @@ var Compiler = Object.extend({
compileBlock: function(node, frame) {
this.emitLine(this.buffer + ' += context.getBlock("' +
- node.name.value + '")(env, context, frame);');
+ node.name.value + '")(env, context, frame, runtime);');
},
compileExtends: function(node, frame) {
@@ -584,8 +584,8 @@ var Compiler = Object.extend({
'b_' + name + ', ' +
'runtime);');
- var tmpFrame = frame.push();
- frame.set('super', 'l_super');
+ var tmpFrame = new Frame();
+ tmpFrame.set('super', 'l_super');
this.compile(block.body, tmpFrame);
this.emitFuncEnd();
View
4 src/environment.js
@@ -257,9 +257,9 @@ var Template = Object.extend({
});
// var fs = require('fs');
-// //var src = fs.readFileSync('test.html', 'utf-8');
+// var src = fs.readFileSync('test.html', 'utf-8');
// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}';
-// var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
+// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
// var env = new Environment();
// console.log(compiler.compile(src));
View
1 src/item.html
@@ -1 +0,0 @@
-{{ i }}
View
19 tests/compiler.js
@@ -168,11 +168,22 @@ describe('compiler', function() {
'{{ foo(1, 10, 20) }}');
s.should.equal('11020');
- s = render('{% macro foo(bar, bazbar, baz="foobar") %}' +
- 'This is a macro {{ bar }} {{ bazbar }} {{ baz }}' +
+ s = render('{% extends "base.html" %}' +
+ '{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
+ '{% endmacro %}' +
+ '{% block block1 %}' +
+ '{{ foo(1) }}' +
+ '{% endblock %}');
+ s.should.equal('Foo125BazFizzle');
+
+ s = render('{% block bar %}' +
+ '{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
'{% endmacro %}' +
- '{{ foo("arg1", "arg2") }}');
- s.should.equal('This is a macro arg1 arg2 foobar');
+ '{% endblock %}' +
+ '{% block baz %}' +
+ '{{ foo(1) }}' +
+ '{% endblock %}');
+ s.should.equal('125');
});
it('should import templates', function() {
View
956 tests/express/nunjucks-dev.js
@@ -128,6 +128,21 @@ exports.repeat = function(char_, n) {
return str;
};
+exports.each = function(obj, func, context) {
+ if(obj == null) {
+ return;
+ }
+
+ if(ArrayProto.each && obj.each == ArrayProto.each) {
+ obj.forEach(func, context);
+ }
+ else if(obj.length === +obj.length) {
+ for(var i=0, l=obj.length; i<l; i++) {
+ func.call(context, obj[i], i, obj);
+ }
+ }
+};
+
exports.map = function(obj, func) {
var results = [];
if(obj == null) {
@@ -155,169 +170,109 @@ var util = modules["util"];
var lib = modules["lib"];
var Object = modules["object"];
-// TODO: Don't use a children array, but rather a single node "body"
var Node = Object.extend("Node", {
- init: function(lineno, colno, children) {
- if(children && !lib.isArray(children)) {
- throw new Error("Node.init: third argument must be " +
- "an array (the children)");
- }
-
- this.children = children || [];
+ init: function(lineno, colno) {
+ var args = lib.toArray(arguments).slice(2);
this.lineno = lineno;
this.colno = colno;
- },
- findAll: function(type) {
- var res = [];
+ lib.each(this.fields, function(field, i) {
+ var val = args[i];
- // TODO: clean this up
- for(var k in this) {
- var obj = this[k];
+ // Fields should never be undefined, but null. It makes
+ // testing easier to normalize values.
+ if(val === undefined) {
+ val = null;
+ }
- if(lib.isArray(obj)) {
- for(var i=0; i<obj.length; i++) {
- var n = obj[i];
+ this[field] = val;
+ }, this);
+ },
- if(n instanceof type) {
- res.push(n);
- }
+ findAll: function(type) {
+ var res = [];
- if(n instanceof Node) {
- res = res.concat(n.findAll(type));
- }
- };
+ function check(obj) {
+ if(obj instanceof type) {
+ res.push(obj);
}
- else if(obj instanceof Node) {
+
+ if(obj instanceof Node) {
res = res.concat(obj.findAll(type));
- }
+ }
}
- return res;
- },
-
- addChild: function(node) {
- this.children.push(node);
- },
-
- getChild: function(i) {
- return this.children[i];
- },
+ if(this instanceof NodeList) {
+ lib.each(this.children, function(node) {
+ check(node);
+ }, this);
+ }
+ else {
+ lib.each(this.fields, function(field) {
+ var obj = this[field];
+ check(obj);
+ }, this);
+ }
- numChildren: function() {
- return this.children.length;
+ return res;
},
- iterChildren: function(func) {
- for(var i=0; i<this.children.length; i++) {
- func(this.children[i]);
- }
+ iterFields: function(func) {
+ lib.each(this.fields, function(field) {
+ func(this[field], field);
+ }, this);
}
});
// Abstract nodes
-var Expr = Node.extend("Expr");
-var Value = Expr.extend("Value", {
- init: function (lineno, colno, value) {
- this.value = value;
- this.parent(lineno, colno);
- }
-});
+var Value = Node.extend("Value", { fields: ['value'] });
// Concrete nodes
-var Root = Node.extend("Root");
-var NodeList = Node.extend("NodeList");
-var Literal = Value.extend("Literal");
-var Symbol = Value.extend("Symbol");
-
-var Group = Expr.extend("Group");
-var Array = Expr.extend("Array");
-var Pair = Node.extend("Pair", {
- init: function(lineno, colno, key, val) {
- this.parent(lineno, colno, [key, val]);
- },
+var NodeList = Node.extend("NodeList", {
+ fields: ['children'],
- getKey: function() {
- return this.children[0];
+ init: function(lineno, colno, nodes) {
+ this.parent(lineno, colno, nodes || []);
},
- getValue: function() {
- return this.children[1];
- }
-});
-var Dict = Expr.extend("Dict");
-var LookupVal = Expr.extend("LookupVal", {
- init: function(lineno, colno, target, val) {
- this.target = target;
- this.val = val;
- this.parent(lineno, colno);
- }
-});
-
-var If = Node.extend("If", {
- init: function(lineno, colno, cond, body, else_) {
- this.cond = cond;
- this.body = body;
- this.else_ = else_;
- this.parent(lineno, colno);
- }
-});
-
-var For = Node.extend("For", {
- init: function(lineno, colno, arr, name, body) {
- this.arr = arr;
- this.name = name;
- this.body = body;
- this.parent(lineno, colno);
+ addChild: function(node) {
+ this.children.push(node);
}
});
-var FunCall = Node.extend("FunCall", {
- init: function(lineno, colno, name, args) {
- this.name = name;
- this.parent(lineno, colno, args);
+var Root = NodeList.extend("Root");
+var Literal = Value.extend("Literal");
+var Symbol = Value.extend("Symbol");
+var Group = NodeList.extend("Group");
+var Array = NodeList.extend("Array");
+var Pair = Node.extend("Pair", { fields: ['key', 'value'] });
+var Dict = NodeList.extend("Dict");
+var LookupVal = Node.extend("LookupVal", { fields: ['target', 'val'] });
+var If = Node.extend("If", { fields: ['cond', 'body', 'else_'] });
+var For = Node.extend("For", { fields: ['arr', 'name', 'body'] });
+var Macro = Node.extend("Macro", { fields: ['name', 'args', 'body'] });
+var Import = Node.extend("Import", { fields: ['template', 'target'] });
+var FromImport = Node.extend("FromImport", {
+ fields: ['template', 'names'],
+
+ init: function(lineno, colno, template, names) {
+ this.parent(lineno, colno,
+ template,
+ names || new NodeList());
}
});
-
+var FunCall = Node.extend("FunCall", { fields: ['name', 'args'] });
var Filter = FunCall.extend("Filter");
-
-var Block = Node.extend("Block", {
- init: function(lineno, colno, name, body) {
- this.name = name;
- this.body = body;
- this.parent(lineno, colno);
- }
-});
-
-var TemplateRef = Node.extend("TemplateRef", {
- init: function(lineno, colno, template) {
- this.template = template;
- this.parent(lineno, colno);
- }
-});
-
+var KeywordArgs = Dict.extend("KeywordArgs");
+var Block = Node.extend("Block", { fields: ['name', 'body'] });
+var TemplateRef = Node.extend("TemplateRef", { fields: ['template'] });
var Extends = TemplateRef.extend("Extends");
var Include = TemplateRef.extend("Include");
-var Set = Node.extend("Set");
-
-var Output = Node.extend("Output");
+var Set = Node.extend("Set", { fields: ['targets', 'value'] });
+var Output = NodeList.extend("Output");
var TemplateData = Literal.extend("TemplateData");
-
-var UnaryOp = Expr.extend("UnaryOp", {
- init: function(lineno, colno, target) {
- this.target = target;
- this.parent(lineno, colno);
- }
-});
-
-var BinOp = Expr.extend("BinOp", {
- init: function(lineno, colno, left, right) {
- this.left = left;
- this.right = right;
- this.parent(lineno, colno);
- }
-});
-
+var UnaryOp = Node.extend("UnaryOp", { fields: ['target'] });
+var BinOp = Node.extend("BinOp", { fields: ['left', 'right'] });
var Or = BinOp.extend("Or");
var And = BinOp.extend("And");
var Not = UnaryOp.extend("Not");
@@ -330,28 +285,19 @@ var Mod = BinOp.extend("Mod");
var Pow = BinOp.extend("Pow");
var Neg = UnaryOp.extend("Neg");
var Pos = UnaryOp.extend("Pos");
-
-var Compare = Expr.extend("Compare", {
- init: function(lineno, colno, expr, ops) {
- this.expr = expr;
- this.ops = ops;
- this.parent(lineno, colno);
- }
-});
-var CompareOperand = Expr.extend("CompareOperand", {
- init: function(lineno, colno, expr, type) {
- this.expr = expr;
- this.type = type;
- this.parent(lineno, colno);
- }
+var Compare = Node.extend("Compare", { fields: ['expr', 'ops'] });
+var CompareOperand = Node.extend("CompareOperand", {
+ fields: ['expr', 'type']
});
+// Print the AST in a nicely formatted tree format for debuggin
function printNodes(node, indent) {
- // TODO: spend more then 30 seconds and clean this up
indent = indent || 0;
- function print(str, inline) {
+ // This is hacky, but this is just a debugging function anyway
+ function print(str, indent, inline) {
var lines = str.split("\n");
+
for(var i=0; i<lines.length; i++) {
if(lines[i]) {
if((inline && i > 0) || !inline) {
@@ -370,88 +316,57 @@ function printNodes(node, indent) {
}
}
- print(node.typename + ": ");
-
- if(node instanceof FunCall) {
- print("\n");
- printNodes(node.name, indent+2);
+ print(node.typename + ": ", indent);
- node.iterChildren(function(node) {
- printNodes(node, indent+2);
+ if(node instanceof NodeList) {
+ print('\n');
+ lib.each(node.children, function(n) {
+ printNodes(n, indent + 2);
});
}
- else if(node instanceof For) {
- print("\n");
- printNodes(node.name, indent+2);
- printNodes(node.arr, indent+2);
- printNodes(node.body, indent+2);
- }
- else if(node instanceof If) {
- print("\n");
- printNodes(node.cond, indent+2);
- printNodes(node.body, indent+2);
+ else {
+ var nodes = null;
+ var props = null;
- if(node.else_) {
- printNodes(node.else_, indent+2);
+ node.iterFields(function(val, field) {
+ if(val instanceof Node) {
+ nodes = nodes || {};
+ nodes[field] = val;
+ }
+ else {
+ props = props || {};
+ props[field] = val;
+ }
+ });
+
+ if(props) {
+ print(util.inspect(props, true, null) + '\n', null, true);
}
- }
- else if(node instanceof Block) {
- print("\n");
- printNodes(node.name, indent+2);
- printNodes(node.body, indent+2);
- }
- else if(node instanceof Set) {
- print("\n");
- for(var i=0; i<node.targets.length; i++) {
- printNodes(node.targets[i], indent+2);
+ else {
+ print('\n');
}
- printNodes(node.value, indent+2);
- }
- else if(node instanceof LookupVal) {
- print("\n");
- printNodes(node.target, indent+2);
- printNodes(node.val, indent+2);
- }
- else if(node instanceof UnaryOp) {
- print("\n");
- printNodes(node.target, indent+2);
- }
- else if(node instanceof BinOp) {
- print("\n");
- printNodes(node.left, indent+2);
- printNodes(node.right, indent+2);
- }
- else if(node instanceof Compare) {
- print("\n");
- printNodes(node.expr, indent+2);
- for(var i=0; i<node.ops.length; i++) {
- printNodes(node.ops[i], indent+2);
+ if(nodes) {
+ for(var k in nodes) {
+ printNodes(nodes[k], indent + 2);
+ }
}
- }
- else if(node instanceof CompareOperand) {
- console.log("(" + node.type + ")");
- printNodes(node.expr, indent+2);
-
- }
- else {
- var children = node.children;
- delete node.children;
- print(util.inspect(node, true, null) + "\n", true);
- node.children = children;
-
- node.iterChildren(function(node) {
- printNodes(node, indent+2);
- });
}
}
+// var t = new NodeList(0, 0,
+// [new Value(0, 0, 3),
+// new Value(0, 0, 10),
+// new Pair(0, 0,
+// new Value(0, 0, 'key'),
+// new Value(0, 0, 'value'))]);
+// printNodes(t);
+
modules['nodes'] = {
Node: Node,
Root: Root,
NodeList: NodeList,
- Expr: Expr,
Value: Value,
Literal: Literal,
Symbol: Symbol,
@@ -463,8 +378,12 @@ modules['nodes'] = {
TemplateData: TemplateData,
If: If,
For: For,
+ Macro: Macro,
+ Import: Import,
+ FromImport: FromImport,
FunCall: FunCall,
Filter: Filter,
+ KeywordArgs: KeywordArgs,
Block: Block,
Extends: Extends,
Include: Include,
@@ -534,8 +453,78 @@ var Frame = Object.extend({
}
});
-modules['runtime'] = {
- Frame: Frame
+function makeMacro(argNames, kwargNames, func) {
+ return function() {
+ var argCount = numArgs(arguments);
+ var args;
+ var kwargs = getKeywordArgs(arguments);
+
+ if(argCount > argNames.length) {
+ args = Array.prototype.slice.call(arguments, 0, argNames.length);
+
+ // Positional arguments that should be passed in as
+ // keyword arguments (essentially default values)
+ var vals = Array.prototype.slice.call(arguments, args.length, argCount);
+ for(var i=0; i<vals.length; i++) {
+ if(i < kwargNames.length) {
+ kwargs[kwargNames[i]] = vals[i];
+ }
+ }
+
+ args.push(kwargs);
+ }
+ else if(argCount < argNames.length) {
+ args = Array.prototype.slice.call(arguments, 0, argCount);
+
+ for(var i=argCount; i<argNames.length; i++) {
+ var arg = argNames[i];
+
+ // Keyword arguments that should be passed as
+ // positional arguments, i.e. the caller explicitly
+ // used the name of a positional arg
+ args.push(kwargs[arg]);
+ delete kwargs[arg];
+ }
+
+ args.push(kwargs);
+ }
+ else {
+ args = arguments;
+ }
+
+ return func.apply(this, args);
+ };
+}
+
+function makeKeywordArgs(obj) {
+ obj.__keywords = true;
+ return obj;
+}
+
+function getKeywordArgs(args) {
+ if(args.length && args[args.length - 1].__keywords) {
+ return args[args.length - 1];
+ }
+ return {};
+}
+
+function numArgs(args) {
+ if(args.length === 0) {
+ return 0;
+ }
+ else if(args[args.length - 1].__keywords) {
+ return args.length - 1;
+ }
+ else {
+ return args.length;
+ }
+}
+
+modules['runtime'] = {
+ Frame: Frame,
+ makeMacro: makeMacro,
+ makeKeywordArgs: makeKeywordArgs,
+ numArgs: numArgs
};
})();
(function() {
@@ -609,7 +598,7 @@ Tokenizer.prototype.nextToken = function() {
}
else if(cur == "\"" || cur == "'") {
// We've hit a string
- return token(TOKEN_STRING, this.parseString(), lineno, colno);
+ return token(TOKEN_STRING, this.parseString(cur), lineno, colno);
}
else if((tok = this._extract(whitespaceChars))) {
// We hit some whitespace
@@ -687,8 +676,8 @@ Tokenizer.prototype.nextToken = function() {
// Parse out the template text, breaking on tag
// delimiters because we need to look for block/variable start
// tags (don't use the full delimChars for optimization)
- var beginChars = (BLOCK_START[0] +
- VARIABLE_START[0] +
+ var beginChars = (BLOCK_START[0] +
+ VARIABLE_START[0] +
COMMENT_START[0] +
COMMENT_END[0]);
var tok;
@@ -759,14 +748,14 @@ Tokenizer.prototype.nextToken = function() {
throw new Error("Could not parse text");
};
-Tokenizer.prototype.parseString = function() {
+Tokenizer.prototype.parseString = function(delimiter) {
this.forward();
-
+
var lineno = this.lineno;
var colno = this.colno;
var str = "";
-
- while(this.current() != "\"" && this.current() != "'") {
+
+ while(this.current() != delimiter) {
var cur = this.current();
if(cur == "\\") {
@@ -953,10 +942,6 @@ modules['lexer'] = {
})();
(function() {
-// Does not support:
-//
-// Conditional expression: "yes" if True else "no"
-
var lexer = modules["lexer"];
var nodes = modules["nodes"];
var Object = modules["object"];
@@ -1014,7 +999,7 @@ var Parser = Object.extend({
}
if(lineno && colno) {
- msg = '[' + lineno + ',' + colno + '] ' + msg;
+ msg = '[Line ' + (lineno + 1) + ', Column ' + (colno + 1) + '] ' + msg;
}
throw new Error(msg);
@@ -1058,10 +1043,17 @@ var Parser = Object.extend({
advanceAfterBlockEnd: function(name) {
if(!name) {
- if(this.peekToken().type != lexer.TOKEN_SYMBOL) {
+ var tok = this.peekToken();
+
+ if(!tok) {
+ this.fail('unexpected end of file');
+ }
+
+ if(tok.type != lexer.TOKEN_SYMBOL) {
this.fail("advanceAfterBlockEnd: expected symbol token or " +
"explicit name to be passed");
}
+
name = this.nextToken().value;
}
@@ -1086,7 +1078,7 @@ var Parser = Object.extend({
node.name = this.parsePrimary();
- if(!node.name instanceof nodes.Symbol) {
+ if(!(node.name instanceof nodes.Symbol)) {
this.fail('variable name expected');
}
@@ -1111,6 +1103,104 @@ var Parser = Object.extend({
return node;
},
+ parseMacro: function() {
+ var macroTok = this.peekToken();
+ if(!this.skipSymbol('macro')) {
+ this.fail("expected macro");
+ }
+
+ var name = this.parsePrimary(true);
+ var args = this.parseSignature();
+ var node = new nodes.Macro(macroTok.lineno,
+ macroTok.colno,
+ name,
+ args);
+
+ this.advanceAfterBlockEnd(macroTok.value);
+ node.body = this.parseUntilBlocks('endmacro');
+ this.advanceAfterBlockEnd();
+
+ return node;
+ },
+
+ parseImport: function() {
+ var importTok = this.peekToken();
+ if(!this.skipSymbol('import')) {
+ this.fail("expected import");
+ }
+
+ var template = this.parsePrimary();
+
+ if(!this.skipSymbol('as')) {
+ throw new Error('expected "as" keyword');
+ }
+
+ var target = this.parsePrimary();
+ var node = new nodes.Import(importTok.lineno,
+ importTok.colno,
+ template,
+ target);
+ this.advanceAfterBlockEnd(importTok.value);
+
+ return node;
+ },
+
+ parseFrom: function() {
+ var fromTok = this.peekToken();
+ if(!this.skipSymbol('from')) {
+ this.fail("expected from");
+ }
+
+ var template = this.parsePrimary();
+ var node = new nodes.FromImport(fromTok.lineno,
+ fromTok.colno,
+ template,
+ new nodes.NodeList());
+
+ if(!this.skipSymbol('import')) {
+ throw new Error("expected import");
+ }
+
+ var names = node.names;
+
+ while(1) {
+ var type = this.peekToken().type;
+ if(type == lexer.TOKEN_BLOCK_END) {
+ if(!names.children.length) {
+ this.fail('Expected at least one import name');
+ }
+
+ this.nextToken();
+ break;
+ }
+
+ if(names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) {
+ throw new Error('expected comma');
+ }
+
+ var name = this.parsePrimary();
+ if(name.value.charAt(0) == '_') {
+ this.fail('names starting with an underscore cannot be ' +
+ 'imported',
+ name.lineno,
+ name.colno);
+ }
+
+ if(this.skipSymbol('as')) {
+ var alias = this.parsePrimary();
+ names.addChild(new nodes.Pair(name.lineno,
+ name.colno,
+ name,
+ alias));
+ }
+ else {
+ names.addChild(name);
+ }
+ }
+
+ return node;
+ },
+
parseBlock: function() {
var tag = this.peekToken();
if(!this.skipSymbol('block')) {
@@ -1120,7 +1210,7 @@ var Parser = Object.extend({
var node = new nodes.Block(tag.lineno, tag.colno);
node.name = this.parsePrimary();
- if(!node.name instanceof nodes.Symbol) {
+ if(!(node.name instanceof nodes.Symbol)) {
this.fail('variable name expected');
}
@@ -1147,8 +1237,9 @@ var Parser = Object.extend({
node.template = this.parsePrimary();
if(!(node.template instanceof nodes.Literal &&
- lib.isString(node.template.value))) {
- this.fail('parseExtends: string expected');
+ lib.isString(node.template.value)) &&
+ !(node.template instanceof nodes.Symbol)) {
+ this.fail('parseExtends: string or value expected');
}
this.advanceAfterBlockEnd(tag.value);
@@ -1203,8 +1294,7 @@ var Parser = Object.extend({
this.fail('expected set');
}
- var node = new nodes.Set(tag.lineno, tag.colno);
- node.targets = [];
+ var node = new nodes.Set(tag.lineno, tag.colno, []);
var target;
while((target = this.parsePrimary())) {
@@ -1239,14 +1329,17 @@ var Parser = Object.extend({
}
switch(tok.value) {
- case 'raw': node = this.parseRaw(); break;
- case 'if': node = this.parseIf(); break;
- case 'for': node = this.parseFor(); break;
- case 'block': node = this.parseBlock(); break;
- case 'extends': node = this.parseExtends(); break;
- case 'include': node = this.parseInclude(); break;
- case 'set': node = this.parseSet(); break;
- default: this.fail('unknown block tag: ' + tok.value);
+ case 'raw': node = this.parseRaw(); break;
+ case 'if': node = this.parseIf(); break;
+ case 'for': node = this.parseFor(); break;
+ case 'block': node = this.parseBlock(); break;
+ case 'extends': node = this.parseExtends(); break;
+ case 'include': node = this.parseInclude(); break;
+ case 'set': node = this.parseSet(); break;
+ case 'macro': node = this.parseMacro(); break;
+ case 'import': node = this.parseImport(); break;
+ case 'from': node = this.parseFrom(); break;
+ default: this.fail('unknown block tag: ' + tok.value);
}
return node;
@@ -1305,11 +1398,10 @@ var Parser = Object.extend({
while(tok) {
if(tok.type == lexer.TOKEN_LEFT_PAREN) {
// Function call
- var list = this.parseAggregate();
- node = new nodes.FunCall(tok.lineno,
- tok.colno,
- node,
- list.children);
+ node = new nodes.FunCall(tok.lineno,
+ tok.colno,
+ node,
+ this.parseSignature());
}
else if(tok.type == lexer.TOKEN_LEFT_BRACKET) {
// Reference
@@ -1550,7 +1642,7 @@ var Parser = Object.extend({
return node;
},
- parsePrimary: function () {
+ parsePrimary: function (noPostfix) {
var tok = this.nextToken();
var val = null;
var node = null;
@@ -1586,7 +1678,10 @@ var Parser = Object.extend({
}
else if(tok.type == lexer.TOKEN_SYMBOL) {
node = new nodes.Symbol(tok.lineno, tok.colno, tok.value);
- node = this.parsePostfix(node);
+
+ if(!noPostfix) {
+ node = this.parsePostfix(node);
+ }
}
else {
// See if it's an aggregate type, we need to push the
@@ -1599,7 +1694,9 @@ var Parser = Object.extend({
return node;
}
else {
- throw new Error("parseExpression: invalid token: " + tok.value);
+ this.fail('unexpected token: ' + tok.value,
+ tok.lineno,
+ tok.colno);
}
},
@@ -1612,18 +1709,23 @@ var Parser = Object.extend({
name += '.' + this.expect(lexer.TOKEN_SYMBOL).value;
}
- node = new nodes.Filter(tok.lineno,
- tok.colno,
- new nodes.Symbol(tok.lineno,
- tok.colno,
- name),
- [node]);
+ node = new nodes.Filter(
+ tok.lineno,
+ tok.colno,
+ new nodes.Symbol(tok.lineno,
+ tok.colno,
+ name),
+ new nodes.NodeList(
+ tok.lineno,
+ tok.colno,
+ [node])
+ );
if(this.peekToken().type == lexer.TOKEN_LEFT_PAREN) {
// Get a FunCall node and add the parameters to the
// filter
var call = this.parsePostfix(node);
- node.children = node.children.concat(call.children);
+ node.args.children = node.args.children.concat(call.args.children);
}
}
@@ -1654,7 +1756,7 @@ var Parser = Object.extend({
break;
}
- if(node.numChildren() > 0) {
+ if(node.children.length > 0) {
if(!this.skip(lexer.TOKEN_COMMA)) {
throw new Error("parseAggregate: expected comma after expression");
}
@@ -1687,6 +1789,49 @@ var Parser = Object.extend({
return node;
},
+ parseSignature: function() {
+ var tok = this.nextToken();
+ var args = new nodes.NodeList(tok.lineno, tok.colno);
+ var kwargs = new nodes.KeywordArgs(tok.lineno, tok.colno);
+ var kwnames = [];
+ var checkComma = false;
+
+ while(1) {
+ var type = this.peekToken().type;
+ if(type == lexer.TOKEN_RIGHT_PAREN) {
+ this.nextToken();
+ break;
+ }
+
+ if(checkComma && !this.skip(lexer.TOKEN_COMMA)) {
+ throw new Error("parseSignature: expected comma after expression");
+ }
+ else {
+ var arg = this.parsePrimary();
+
+ if(this.skipValue(lexer.TOKEN_OPERATOR, '=')) {
+ kwargs.addChild(
+ new nodes.Pair(arg.lineno,
+ arg.colno,
+ arg,
+ this.parseExpression())
+ );
+ }
+ else {
+ args.addChild(arg);
+ }
+ }
+
+ checkComma = true;
+ }
+
+ if(kwargs.children.length) {
+ args.addChild(kwargs);
+ }
+
+ return args;
+ },
+
parseUntilBlocks: function(/* blockNames */) {
var prev = this.breakOnBlocks;
this.breakOnBlocks = lib.toArray(arguments);
@@ -1748,7 +1893,7 @@ var util = modules["util"];
// console.log(util.inspect(t));
// }
-// var p = new Parser(lexer.lex('{% set x, y = 3 %}'));
+// var p = new Parser(lexer.lex('{{ foo(1, 2, 3, foo=3) }}'));
// var n = p.parse();
// nodes.printNodes(n);
@@ -1778,7 +1923,7 @@ var compareOps = {
'>=': '>='
};
-// A common pattern is to emit binary operators
+// A common pattern is to emit binary operators
function binOpEmitter(str) {
return function(node, frame) {
this.compile(node.left, frame);
@@ -1787,6 +1932,13 @@ function binOpEmitter(str) {
};
}
+// Generate an array of strings
+function quotedArray(arr) {
+ return '[' +
+ lib.map(arr, function(x) { return '"' + x + '"'; }) +
+ ']';
+}
+
var Compiler = Object.extend({
init: function() {
this.codebuf = [];
@@ -1803,9 +1955,15 @@ var Compiler = Object.extend({
this.emit(code + "\n");
},
+ emitLines: function() {
+ lib.each(lib.toArray(arguments), function(line) {
+ this.emitLine(line);
+ }, this);
+ },
+
emitFuncBegin: function(name) {
this.buffer = 'output';
- this.emitLine('function ' + name + '(env, context, frame) {');
+ this.emitLine('function ' + name + '(env, context, frame, runtime) {');
this.emitLine('var ' + this.buffer + ' = "";');
},
@@ -1824,10 +1982,9 @@ var Compiler = Object.extend({
},
_compileChildren: function(node, frame) {
- var _this = this;
- node.iterChildren(function(n) {
- _this.compile(n, frame);
- });
+ lib.each(node.children, function(n) {
+ this.compile(n, frame);
+ }, this);
},
_compileAggregate: function(node, frame, startChar, endChar) {
@@ -1855,6 +2012,8 @@ var Compiler = Object.extend({
nodes.Filter,
nodes.LookupVal,
nodes.Compare,
+ nodes.And,
+ nodes.Or,
nodes.Not);
this.compile(node, frame);
},
@@ -1918,14 +2077,14 @@ var Compiler = Object.extend({
},
compilePair: function(node, frame) {
- var key = node.getKey();
- var val = node.getValue();
+ var key = node.key;
+ var val = node.value;
if(key instanceof nodes.Symbol) {
key = new nodes.Literal(key.lineno, key.colno, key.value);
}
else if(!(key instanceof nodes.Literal &&
- typeof node.value == "string")) {
+ typeof key.value == "string")) {
throw new Error("Dict keys must be strings or names");
}
@@ -1967,15 +2126,15 @@ var Compiler = Object.extend({
this.emit('-');
this.compile(node.target, frame);
},
-
+
compilePos: function(node, frame) {
this.emit('+');
this.compile(node.target, frame);
},
compileCompare: function(node, frame) {
this.compile(node.expr, frame);
-
+
for(var i=0; i<node.ops.length; i++) {
var n = node.ops[i];
this.emit(' ' + compareOps[n.type] + ' ');
@@ -1993,29 +2152,51 @@ var Compiler = Object.extend({
},
compileFunCall: function(node, frame) {
+ this.emit('(');
this._compileExpression(node.name, frame);
- this._compileAggregate(node, frame, '(', ')');
+ this.emit(')');
+ this._compileAggregate(node.args, frame, '(', ')');
},
compileFilter: function(node, frame) {
var name = node.name;
this.assertType(name, nodes.Symbol);
this.emit('env.getFilter("' + name.value + '")');
- this._compileAggregate(node, frame, '(', ')');
+ this._compileAggregate(node.args, frame, '(', ')');
+ },
+
+ compileKeywordArgs: function(node, frame) {
+ var names = [];
+
+ lib.each(node.children, function(pair) {
+ names.push(pair.key.value);
+ });
+
+ this.emit('runtime.makeKeywordArgs(');
+ this.compileDict(node, frame);
+ this.emit(')');
},
compileSet: function(node, frame) {
- var val = this.tmpid();
+ var id = this.tmpid();
- this.emit('var ' + val + ' = ');
+ this.emit('var ' + id + ' = ');
this._compileExpression(node.value);
this.emitLine(';');
-
+
for(var i=0; i<node.targets.length; i++) {
- var t = node.targets[i];
- this.emitLine('context.setVariable("' + t.value + '", ' +
- val + ');');
+ var name = node.targets[i].value;
+ frame.set(name, id);
+
+ this.emitLine('frame.set("' + name + '", ' + id + ');');
+
+ this.emitLine('if(!frame.parent) {');
+ this.emitLine('context.setVariable("' + name + '", ' + id + ');');
+ if(name.charAt(0) != '_') {
+ this.emitLine('context.addExport("' + name + '");');
+ }
+ this.emitLine('}');
}
},
@@ -2038,7 +2219,7 @@ var Compiler = Object.extend({
var arr = this.tmpid();
frame = frame.push();
- this.emitLine('frame = frame.push()');
+ this.emitLine('frame = frame.push();');
this.emit('var ' + arr + ' = ');
this._compileExpression(node.arr, frame);
@@ -2089,16 +2270,168 @@ var Compiler = Object.extend({
this.emitLine('frame = frame.pop();');
},
+ _emitMacroBegin: function(node, frame) {
+ var args = [];
+ var kwargs = null;
+ var funcId = 'macro_' + this.tmpid();
+
+ // Type check the definition of the args
+ lib.each(node.args.children, function(arg, i) {
+ if(i === node.args.children.length - 1 &&
+ arg instanceof nodes.Dict) {
+ kwargs = arg;
+ }
+ else {
+ this.assertType(arg, nodes.Symbol);
+ args.push(arg);
+ }
+ }, this);
+
+ var realNames = lib.map(args, function(n) { return 'l_' + n.value; });
+ realNames.push('kwargs');
+
+ // Quoted argument names
+ var argNames = lib.map(args, function(n) { return '"' + n.value + '"'; });
+ var kwargNames = lib.map((kwargs && kwargs.children) || [],
+ function(n) { return '"' + n.key.value + '"'; });
+
+ // We pass a function to makeMacro which destructures the
+ // arguments so support setting positional args with keywords
+ // args and passing keyword args as positional args
+ // (essentially default values). See runtime.js.
+ this.emitLines(
+ 'var ' + funcId + ' = runtime.makeMacro(',
+ '[' + argNames.join(', ') + '], ',
+ '[' + kwargNames.join(', ') + '], ',
+ 'function (' + realNames.join(', ') + ') {',
+ 'frame = frame.push();',
+ 'kwargs = kwargs || {};'
+ );
+
+ // Expose the arguments to the template. Don't need to use
+ // random names because the function
+ // will create a new run-time scope for us
+ lib.each(args, function(arg) {
+ this.emitLine('frame.set("' + arg.value + '", ' +
+ 'l_' + arg.value + ');');
+ frame.set(arg.value, 'l_' + arg.value);
+ }, this);
+
+ // Expose the keyword arguments
+ if(kwargs) {
+ lib.each(kwargs.children, function(pair) {
+ var name = pair.key.value;
+ this.emit('frame.set("' + name + '", ' +
+ 'kwargs.hasOwnProperty("' + name + '") ? ' +
+ 'kwargs["' + name + '"] : ');
+ this._compileExpression(pair.value);
+ this.emitLine(');');
+ }, this);
+ }
+
+ return funcId;
+ },
+
+ _emitMacroEnd: function() {
+ this.emitLine('frame = frame.pop();');
+ this.emitLine('return ' + this.buffer + ';');
+ this.emitLine('});');
+ },
+
+ compileMacro: function(node, frame) {
+ frame = frame.push();
+ var funcId = this._emitMacroBegin(node, frame);
+
+ // Start a new output buffer, and set the old one back after
+ // we're done
+ var prevBuffer = this.buffer;
+ this.buffer = 'output';
+ this.emitLine('var ' + this.buffer + '= "";');
+
+ this.compile(node.body, frame);
+
+ this._emitMacroEnd();
+ this.buffer = prevBuffer;
+
+ // Expose the macro to the templates
+ var name = node.name.value;
+ frame = frame.pop();
+ frame.set(name, funcId);
+
+ if(frame.parent) {
+ this.emitLine('frame.set("' + name + '", ' + funcId + ');');
+ }
+ else {
+ if(node.name.value.charAt(0) != '_') {
+ this.emitLine('context.addExport("' + name + '");');
+ }
+ this.emitLine('context.setVariable("' + name + '", ' + funcId + ');');
+ }
+ },
+
+ compileImport: function(node, frame) {
+ var id = this.tmpid();
+ var target = node.target.value;
+
+ this.emit('var ' + id + ' = env.getTemplate(');
+ this._compileExpression(node.template, frame);
+ this.emitLine(').getExported();');
+ frame.set(target, id);
+
+ if(frame.parent) {
+ this.emitLine('frame.set("' + target + '", ' + id + ');');
+ }
+ else {
+ this.emitLine('context.setVariable("' + target + '", ' + id + ');');
+ }
+ },
+
+ compileFromImport: function(node, frame) {
+ this.emit('var imported = env.getTemplate(');
+ this.compile(node.template, frame);
+ this.emitLine(').getExported();');
+
+ lib.each(node.names.children, function(nameNode) {
+ var name;
+ var alias;
+ var id = this.tmpid();
+
+ if(nameNode instanceof nodes.Pair) {
+ name = nameNode.key.value;
+ alias = nameNode.value.value;
+ }
+ else {
+ name = nameNode.value;
+ alias = name;
+ }
+
+ this.emitLine('if(imported.hasOwnProperty("' + name + '")) {');
+ this.emitLine('var ' + id + ' = imported.' + name + ';');
+ this.emitLine('} else {');
+ this.emitLine('throw new Error("cannot import \'' + name + '\'")');
+ this.emitLine('}');
+
+ frame.set(alias, id);
+
+ if(frame.parent) {
+ this.emitLine('frame.set("' + alias + '", ' + id + ');');
+ }
+ else {
+ this.emitLine('context.setVariable("' + alias + '", ' + id + ');');
+ }
+ }, this);
+ },
+
compileBlock: function(node, frame) {
this.emitLine(this.buffer + ' += context.getBlock("' +
- node.name.value + '")(env, context, frame);');
+ node.name.value + '")(env, context, frame, runtime);');
},
compileExtends: function(node, frame) {
if(this.isChild) {
throw new Error('cannot extend multiple times');
}
-
+
this.emit('var parentTemplate = env.getTemplate(');
this._compileExpression(node.template, frame);
this.emitLine(', true);');
@@ -2119,7 +2452,7 @@ var Compiler = Object.extend({
this.emitLine(');');
this.emitLine(this.buffer +
' += includeTemplate.render(' +
- 'context.getVariables(), frame);');
+ 'context.getVariables(), frame.push());');
},
compileTemplateData: function(node, frame) {
@@ -2143,7 +2476,7 @@ var Compiler = Object.extend({
this._compileChildren(node, frame);
if(this.isChild) {
this.emitLine('return ' +
- 'parentTemplate.rootRenderFunc(env, context, frame);');
+ 'parentTemplate.rootRenderFunc(env, context, frame, runtime);');
}
this.emitFuncEnd(this.isChild);
@@ -2153,12 +2486,13 @@ var Compiler = Object.extend({
var name = block.name.value;
this.emitFuncBegin('b_' + name);
- this.emitLine('var l_super = context.getSuper(env, ' +
+ this.emitLine('var l_super = context.getSuper(env, ' +
'"' + name + '", ' +
- 'b_' + name + ');');
+ 'b_' + name + ', ' +
+ 'runtime);');
- var tmpFrame = frame.push();
- frame.set('super', 'l_super');
+ var tmpFrame = new Frame();
+ tmpFrame.set('super', 'l_super');
this.compile(block.body, tmpFrame);
this.emitFuncEnd();
@@ -2190,7 +2524,7 @@ var Compiler = Object.extend({
// var fs = modules["fs"];
// var c = new Compiler();
-// var src = "{% for i in [3,45,1] %}{{ loop.index }}{% endfor %}";
+// var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}';
// var ns = parser.parse(src);
// nodes.printNodes(ns);
@@ -2566,14 +2900,14 @@ else {
}
})();
(function() {
-
var lib = modules["lib"];
var Object = modules["object"];
var lexer = modules["lexer"];
var compiler = modules["compiler"];
var builtin_filters = modules["filters"];
var builtin_loaders = modules["loaders"];
-var Frame = modules["runtime"].Frame;
+var runtime = modules["runtime"];
+var Frame = runtime.Frame;
var Environment = Object.extend({
init: function(loaders, tags) {
@@ -2662,8 +2996,8 @@ var Environment = Object.extend({
context = lib.extend(context, ctx);
- var res = env.render(name, ctx);
- k(null, res);
+ var res = env.render(name, context);
+ k(null, res);
};
},
@@ -2676,6 +3010,7 @@ var Context = Object.extend({
init: function(ctx, blocks) {
this.ctx = ctx;
this.blocks = {};
+ this.exported = [];
for(var name in blocks) {
this.addBlock(name, blocks[name]);
@@ -2689,7 +3024,7 @@ var Context = Object.extend({
setVariable: function(name, val) {
this.ctx[name] = val;
},
-
+
getVariables: function() {
return this.ctx;
},
@@ -2719,6 +3054,19 @@ var Context = Object.extend({
return blk(env, context);
};
+ },
+
+ addExport: function(name) {
+ this.exported.push(name);
+ },
+
+ getExported: function() {
+ var exported = {};
+ for(var i=0; i<this.exported.length; i++) {
+ var name = this.exported[i];
+ exported[name] = this.ctx[name];
+ }
+ return exported;
}
});
@@ -2759,13 +3107,28 @@ var Template = Object.extend({
var context = new Context(ctx || {}, this.blocks);
return this.rootRenderFunc(this.env,
context,
- frame || new Frame());
+ frame || new Frame(),
+ runtime);
},
isUpToDate: function() {
return this.upToDate();
},
+ getExported: function() {
+ if(!this.compiled) {
+ this._compile();
+ }
+
+ // Run the rootRenderFunc to populate the context with exported vars
+ var context = new Context({}, this.blocks);
+ this.rootRenderFunc(this.env,
+ context,
+ new Frame(),
+ runtime);
+ return context.getExported();
+ },
+
_compile: function() {
var props;
@@ -2776,7 +3139,7 @@ var Template = Object.extend({
var func = new Function(compiler.compile(this.tmplStr, this.env));
props = func();
}
-
+
this.blocks = this._getBlocks(props);
this.rootRenderFunc = props.root;
this.compiled = true;
@@ -2796,8 +3159,9 @@ var Template = Object.extend({
});
// var fs = modules["fs"];
-// //var src = fs.readFileSync('test.html', 'utf-8');
-// var src = "{% for i in [1,2,3] %}{% include 'item.html' %}{% endfor %}";
+// var src = fs.readFileSync('test.html', 'utf-8');
+// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}';
+// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
// var env = new Environment();
// console.log(compiler.compile(src));
View
2 tests/express/nunjucks-min.js
@@ -1 +1 @@
-(function(){var a={};(function(){function b(a,c,d){var e=Object.create(a.prototype),f=/xyz/.test(function(){xyz})?/\bparent\b/:/.*/;d=d||{};for(var g in d){var h=d[g],i=e[g];typeof i=="function"&&typeof h=="function"&&f.test(h)?e[g]=function(a,b){return function(){var c=this.parent;this.parent=b;var d=a.apply(this,arguments);return this.parent=c,d}}(h,i):e[g]=h}e.typename=c;var j=function(){e.init&&e.init.apply(this,arguments)};return j.prototype=e,j.prototype.constructor=j,j.extend=function(a,c){return typeof a=="object"&&(c=a,a="anonymous"),b(j,a,c)},j}a.object=b(Object,"Object",{})})(),function(){var b=Array.prototype,c=Object.prototype,d=a.lib={};d.isFunction=function(a){return c.toString.call(a)=="[object Function]"},d.isArray=Array.isArray||function(a){return c.toString.call(a)=="[object Array]"},d.isString=function(a){return c.toString.call(a)=="[object String]"},d.isObject=function(a){return a===Object(a)},d.groupBy=function(a,b){var c={},e=d.isFunction(b)?b:function(a){return a[b]};for(var f=0;f<a.length;f++){var g=a[f],h=e(g,f);(c[h]||(c[h]=[])).push(g)}return c},d.toArray=function(a){return Array.prototype.slice.call(a)},d.without=function(a){var b=[];if(!a)return b;var c=-1,e=a.length,f=d.toArray(arguments).slice(1);while(++c<e)f.indexOf(a[c])===-1&&b.push(a[c]);return b},d.extend=function(a,b){for(var c in b)a[c]=b[c];return a},d.repeat=function(a,b){var c="";for(var d=0;d<b;d++)c+=a;return c},d.map=function(a,c){var d=[];if(a==null)return d;if(b.map&&a.map===b.map)return a.map(c);for(var e=0;e<a.length;e++)d[d.length]=c(a[e],e);return a.length===+a.length&&(d.length=a.length),d}}(),function(){var b=a.lib,c={abs:function(a){return Math.abs(a)},batch:function(a,b,c){var d=[],e=[];for(var f=0;f<a.length;f++)f%b===0&&e.length&&(d.push(e),e=[]),e.push(a[f]);if(e.length){if(c)for(var f=e.length;f<b;f++)e.push(c);d.push(e)}return d},capitalize:function(a){return a=a.toLowerCase(),a[0].toUpperCase()+a.slice(1)},center:function(a,c){c=c||80;if(a.length>=c)return a;var d=c-a.length,e=b.repeat(" ",d/2-d%2),f=b.repeat(" ",d/2);return e+a+f},"default":function(a,b){return a?a:b},escape:function(a){return a.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},first:function(a){return a[0]},groupby:function(a,c){return b.groupBy(a,c)},indent:function(a,c,d){c=c||4;var e="",f=a.split("\n"),g=b.repeat(" ",c);for(var h=0;h<f.length;h++)h==0&&!d?e+=f[h]+"\n":e+=g+f[h]+"\n";return e},join:function(a,c,d){return c=c||"",d&&(a=b.map(a,function(a){return a[d]})),a.join(c)},last:function(a){return a[a.length-1]},length:function(a){return a.length},list:function(a){if(b.isString(a))return a.split("");if(b.isObject(a)){var c=[];if(Object.keys)c=Object.keys(a);else for(var d in a)c.push(d);return b.map(c,function(b){return{key:b,value:a[b]}})}throw new Error("list: type not iterable")},lower:function(a){return a.toLowerCase()},random:function(a){var b=Math.floor(Math.random()*a.length);return b==a.length&&b--,a[b]},replace:function(a,b,c,d){var e=a,f=e,g=1;e=e.replace(b,c);while(f!=e){if(g>=d)break;f=e,e=e.replace(b,c),g++}return e},reverse:function(a){var d;return b.isString(a)?d=c.list(a):d=b.map(a,function(a){return a}),d.reverse(),b.isString(a)?d.join(""):d},round:function(a,b,c){b=b||0;var d=Math.pow(10,b),e;return c=="ceil"?e=Math.ceil:c=="floor"?e=Math.floor:e=Math.round,e(a*d)/d},slice:function(a,b,c){var d=Math.floor(a.length/b),e=a.length%b,f=0,g=[];for(var h=0;h<b;h++){var i=f+h*d;h<e&&f++;var j=f+(h+1)*d,k=a.slice(i,j);c&&h>=e&&k.push(c),g.push(k)}return g},sort:function(a,c,d,e){return a=b.map(a,function(a){return a}),a.sort(function(a,f){var g,h;return e?(g=a[e],h=f[e]):(g=a,h=f),!d&&b.isString(g)&&b.isString(h)&&(g=g.toLowerCase(),h=h.toLowerCase()),g<h?c?1:-1:g>h?c?-1:1:0}),a},string:function(a){return a.toString()},title:function(a){return a.toUpperCase()},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},upper:function(a){return a.toUpperCase()},wordcount:function(a){return a.match(/\w+/g).length},"float":function(a,b){return parseFloat(a)||b},"int":function(a,b){return parseInt(a)||b}};c.d=c.default,c.e=c.escape,a.filters=c}(),function(){var b=a.object,c=b.extend({init:function(a){this.variables={},this.parent=a},set:function(a,b){var c=a.split("."),d=this.variables;for(var e=0;e<c.length-1;e++){var f=c[e];d[f]||(d[f]={}),d=d[f]}d[c[c.length-1]]=b},lookup:function(a){var b=this.parent;return this.variables[a]||b&&b.lookup(a)},push:function(){return new c(this)},pop:function(){return this.parent}});a.runtime={Frame:c}}(),function(){var b=a.lib,c=a.object,d=a.lexer,e=a.compiler,f=a.filters,g=a.loaders,h=a.runtime.Frame,i=c.extend({init:function(a,c){a?this.loaders=b.isArray(a)?a:[a]:g.FileSystemLoader?this.loaders=[new g.FileSystemLoader]:this.loaders=[new g.HttpLoader("/views")],c&&d.setTags(c),this.filters=f,this.cache={}},addFilter:function(a,b){this.filters[a]=b},getFilter:function(a){return this.filters[a]},getTemplate:function(a,b){var c=null,d=this.cache[a],e;if(!d||!d.isUpToDate()){for(var f=0;f<this.loaders.length;f++)if(c=this.loaders[f].getSource(a))break;if(!c)throw new Error("template not found: "+a);this.cache[a]=new k(c.src,this,c.path,c.upToDate,b)}return this.cache[a]},registerPrecompiled:function(a){for(var b in a)this.cache[b]=new k({type:"code",obj:a[b]},this,b,function(){return!0},!0)},express:function(a){var c=this;a.render=function(d,e,f){var g={};b.isFunction(e)&&(f=e,e={}),g=b.extend(g,a.locals),e._locals&&(g=b.extend(g,e._locals)),g=b.extend(g,e);var h=c.render(d,e);f(null,h)}},render:function(a,b){return this.getTemplate(a).render(b)}}),j=c.extend({init:function(a,b){this.ctx=a,this.blocks={};for(var c in b)this.addBlock(c,b[c])},lookup:function(a){return this.ctx[a]},setVariable:function(a,b){this.ctx[a]=b},getVariables:function(){return this.ctx},addBlock:function(a,b){this.blocks[a]=this.blocks[a]||[],this.blocks[a].push(b)},getBlock:function(a){if(!this.blocks[a])throw new Error('unknown block "'+a+'"');return this.blocks[a][0]},getSuper:function(a,b,c){var d=(this.blocks[b]||[]).indexOf(c),e=this.blocks[b][d+1],f=this;return function(){if(d==-1||!e)throw new Error('no super block available for "'+b+'"');return e(a,f)}}}),k=c.extend({init:function(a,c,d,e,f){this.env=c||new i;if(b.isObject(a))switch(a.type){case"code":this.tmplProps=a.obj;break;case"string":this.tmplStr=a.obj}else{if(!b.isString(a))throw new Error("src must be a string or an object describing the source");this.tmplStr=a}this.path=d,this.upToDate=e||function(){return!1},f?this._compile():this.compiled=!1},render:function(a,b){this.compiled||this._compile();var c=new j(a||{},this.blocks);return this.rootRenderFunc(this.env,c,b||new h)},isUpToDate:function(){return this.upToDate()},_compile:function(){var a;if(this.tmplProps)a=this.tmplProps;else{var b=new Function(e.compile(this.tmplStr,this.env));a=b()}this.blocks=this._getBlocks(a),this.rootRenderFunc=a.root,this.compiled=!0},_getBlocks:function(a){var b={};for(var c in a)c.slice(0,2)=="b_"&&(b[c.slice(2)]=a[c]);return b}});a.environment={Environment:i,Template:k}}();var b=a.environment,c=a.compiler,d=a.parser,e=a.lexer,f=a.loaders;window.nunjucks={},window.nunjucks.Environment=b.Environment,window.nunjucks.Template=b.Template,f.FileSystemLoader?window.nunjucks.FileSystemLoader=f.FileSystemLoader:window.nunjucks.HttpLoader=f.HttpLoader,window.nunjucks.compiler=c,window.nunjucks.parser=d,window.nunjucks.lexer=e,window.nunjucks.require=function(b){return a[b]}})();
+(function(){var a={};(function(){function b(a,c,d){var e=Object.create(a.prototype),f=/xyz/.test(function(){xyz})?/\bparent\b/:/.*/;d=d||{};for(var g in d){var h=d[g],i=e[g];typeof i=="function"&&typeof h=="function"&&f.test(h)?e[g]=function(a,b){return function(){var c=this.parent;this.parent=b;var d=a.apply(this,arguments);return this.parent=c,d}}(h,i):e[g]=h}e.typename=c;var j=function(){e.init&&e.init.apply(this,arguments)};return j.prototype=e,j.prototype.constructor=j,j.extend=function(a,c){return typeof a=="object"&&(c=a,a="anonymous"),b(j,a,c)},j}a.object=b(Object,"Object",{})})(),function(){var b=Array.prototype,c=Object.prototype,d=a.lib={};d.isFunction=function(a){return c.toString.call(a)=="[object Function]"},d.isArray=Array.isArray||function(a){return c.toString.call(a)=="[object Array]"},d.isString=function(a){return c.toString.call(a)=="[object String]"},d.isObject=function(a){return a===Object(a)},d.groupBy=function(a,b){var c={},e=d.isFunction(b)?b:function(a){return a[b]};for(var f=0;f<a.length;f++){var g=a[f],h=e(g,f);(c[h]||(c[h]=[])).push(g)}return c},d.toArray=function(a){return Array.prototype.slice.call(a)},d.without=function(a){var b=[];if(!a)return b;var c=-1,e=a.length,f=d.toArray(arguments).slice(1);while(++c<e)f.indexOf(a[c])===-1&&b.push(a[c]);return b},d.extend=function(a,b){for(var c in b)a[c]=b[c];return a},d.repeat=function(a,b){var c="";for(var d=0;d<b;d++)c+=a;return c},d.each=function(a,c,d){if(a==null)return;if(b.each&&a.each==b.each)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++)c.call(d,a[e],e,a)},d.map=function(a,c){var d=[];if(a==null)return d;if(b.map&&a.map===b.map)return a.map(c);for(var e=0;e<a.length;e++)d[d.length]=c(a[e],e);return a.length===+a.length&&(d.length=a.length),d}}(),function(){var b=a.lib,c={abs:function(a){return Math.abs(a)},batch:function(a,b,c){var d=[],e=[];for(var f=0;f<a.length;f++)f%b===0&&e.length&&(d.push(e),e=[]),e.push(a[f]);if(e.length){if(c)for(var f=e.length;f<b;f++)e.push(c);d.push(e)}return d},capitalize:function(a){return a=a.toLowerCase(),a[0].toUpperCase()+a.slice(1)},center:function(a,c){c=c||80;if(a.length>=c)return a;var d=c-a.length,e=b.repeat(" ",d/2-d%2),f=b.repeat(" ",d/2);return e+a+f},"default":function(a,b){return a?a:b},escape:function(a){return a.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},first:function(a){return a[0]},groupby:function(a,c){return b.groupBy(a,c)},indent:function(a,c,d){c=c||4;var e="",f=a.split("\n"),g=b.repeat(" ",c);for(var h=0;h<f.length;h++)h==0&&!d?e+=f[h]+"\n":e+=g+f[h]+"\n";return e},join:function(a,c,d){return c=c||"",d&&(a=b.map(a,function(a){return a[d]})),a.join(c)},last:function(a){return a[a.length-1]},length:function(a){return a.length},list:function(a){if(b.isString(a))return a.split("");if(b.isObject(a)){var c=[];if(Object.keys)c=Object.keys(a);else for(var d in a)c.push(d);return b.map(c,function(b){return{key:b,value:a[b]}})}throw new Error("list: type not iterable")},lower:function(a){return a.toLowerCase()},random:function(a){var b=Math.floor(Math.random()*a.length);return b==a.length&&b--,a[b]},replace:function(a,b,c,d){var e=a,f=e,g=1;e=e.replace(b,c);while(f!=e){if(g>=d)break;f=e,e=e.replace(b,c),g++}return e},reverse:function(a){var d;return b.isString(a)?d=c.list(a):d=b.map(a,function(a){return a}),d.reverse(),b.isString(a)?d.join(""):d},round:function(a,b,c){b=b||0;var d=Math.pow(10,b),e;return c=="ceil"?e=Math.ceil:c=="floor"?e=Math.floor:e=Math.round,e(a*d)/d},slice:function(a,b,c){var d=Math.floor(a.length/b),e=a.length%b,f=0,g=[];for(var h=0;h<b;h++){var i=f+h*d;h<e&&f++;var j=f+(h+1)*d,k=a.slice(i,j);c&&h>=e&&k.push(c),g.push(k)}return g},sort:function(a,c,d,e){return a=b.map(a,function(a){return a}),a.sort(function(a,f){var g,h;return e?(g=a[e],h=f[e]):(g=a,h=f),!d&&b.isString(g)&&b.isString(h)&&(g=g.toLowerCase(),h=h.toLowerCase()),g<h?c?1:-1:g>h?c?-1:1:0}),a},string:function(a){return a.toString()},title:function(a){return a.toUpperCase()},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},upper:function(a){return a.toUpperCase()},wordcount:function(a){return a.match(/\w+/g).length},"float":function(a,b){return parseFloat(a)||b},"int":function(a,b){return parseInt(a)||b}};c.d=c.default,c.e=c.escape,a.filters=c}(),function(){function d(a,b,c){return function(){var d=g(arguments),e,h=f(arguments);if(d>a.length){e=Array.prototype.slice.call(arguments,0,a.length);var i=Array.prototype.slice.call(arguments,e.length,d);for(var j=0;j<i.length;j++)j<b.length&&(h[b[j]]=i[j]);e.push(h)}else if(d<a.length){e=Array.prototype.slice.call(arguments,0,d);for(var j=d;j<a.length;j++){var k=a[j];e.push(h[k]),delete h[k]}e.push(h)}else e=arguments;return c.apply(this,e)}}function e(a){return a.__keywords=!0,a}function f(a){return a.length&&a[a.length-1].__keywords?a[a.length-1]:{}}function g(a){return a.length===0?0:a[a.length-1].__keywords?a.length-1:a.length}var b=a.object,c=b.extend({init:function(a){this.variables={},this.parent=a},set:function(a,b){var c=a.split("."),d=this.variables;for(var e=0;e<c.length-1;e++){var f=c[e];d[f]||(d[f]={}),d=d[f]}d[c[c.length-1]]=b},lookup:function(a){var b=this.parent;return this.variables[a]||b&&b.lookup(a)},push:function(){return new c(this)},pop:function(){return this.parent}});a.runtime={Frame:c,makeMacro:d,makeKeywordArgs:e,numArgs:g}}(),function(){var b=a.lib,c=a.object,d=a.lexer,e=a.compiler,f=a.filters,g=a.loaders,h=a.runtime,i=h.Frame,j=c.extend({init:function(a,c){a?this.loaders=b.isArray(a)?a:[a]:g.FileSystemLoader?this.loaders=[new g.FileSystemLoader]:this.loaders=[new g.HttpLoader("/views")],c&&d.setTags(c),this.filters=f,this.cache={}},addFilter:function(a,b){this.filters[a]=b},getFilter:function(a){return this.filters[a]},getTemplate:function(a,b){var c=null,d=this.cache[a],e;if(!d||!d.isUpToDate()){for(var f=0;f<this.loaders.length;f++)if(c=this.loaders[f].getSource(a))break;if(!c)throw new Error("template not found: "+a);this.cache[a]=new l(c.src,this,c.path,c.upToDate,b)}return this.cache[a]},registerPrecompiled:function(a){for(var b in a)this.cache[b]=new l({type:"code",obj:a[b]},this,b,function(){return!0},!0)},express:function(a){var c=this;a.render=function(d,e,f){var g={};b.isFunction(e)&&(f=e,e={}),g=b.extend(g,a.locals),e._locals&&(g=b.extend(g,e._locals)),g=b.extend(g,e);var h=c.render(d,g);f(null,h)}},render:function(a,b){return this.getTemplate(a).render(b)}}),k=c.extend({init:function(a,b){this.ctx=a,this.blocks={},this.exported=[];for(var c in b)this.addBlock(c,b[c])},lookup:function(a){return this.ctx[a]},setVariable:function(a,b){this.ctx[a]=b},getVariables:function(){return this.ctx},addBlock:function(a,b){this.blocks[a]=this.blocks[a]||[],this.blocks[a].push(b)},getBlock:function(a){if(!this.blocks[a])throw new Error('unknown block "'+a+'"');return this.blocks[a][0]},getSuper:function(a,b,c){var d=(this.blocks[b]||[]).indexOf(c),e=this.blocks[b][d+1],f=this;return function(){if(d==-1||!e)throw new Error('no super block available for "'+b+'"');return e(a,f)}},addExport:function(a){this.exported.push(a)},getExported:function(){var a={};for(var b=0;b<this.exported.length;b++){var c=this.exported[b];a[c]=this.ctx[c]}return a}}),l=c.extend({init:function(a,c,d,e,f){this.env=c||new j;if(b.isObject(a))switch(a.type){case"code":this.tmplProps=a.obj;break;case"string":this.tmplStr=a.obj}else{if(!b.isString(a))throw new Error("src must be a string or an object describing the source");this.tmplStr=a}this.path=d,this.upToDate=e||function(){return!1},f?this._compile():this.compiled=!1},render:function(a,b){this.compiled||this._compile();var c=new k(a||{},this.blocks);return this.rootRenderFunc(this.env,c,b||new i,h)},isUpToDate:function(){return this.upToDate()},getExported:function(){this.compiled||this._compile();var a=new k({},this.blocks);return this.rootRenderFunc(this.env,a,new i,h),a.getExported()},_compile:function(){var a;if(this.tmplProps)a=this.tmplProps;else{var b=new Function(e.compile(this.tmplStr,this.env));a=b()}this.blocks=this._getBlocks(a),this.rootRenderFunc=a.root,this.compiled=!0},_getBlocks:function(a){var b={};for(var c in a)c.slice(0,2)=="b_"&&(b[c.slice(2)]=a[c]);return b}});a.environment={Environment:j,Template:l}}();var b=a.environment,c=a.compiler,d=a.parser,e=a.lexer,f=a.loaders;window.nunjucks={},window.nunjucks.Environment=b.Environment,window.nunjucks.Template=b.Template,f.FileSystemLoader?window.nunjucks.FileSystemLoader=f.FileSystemLoader:window.nunjucks.HttpLoader=f.HttpLoader,window.nunjucks.compiler=c,window.nunjucks.parser=d,window.nunjucks.lexer=e,window.nunjucks.require=function(b){return a[b]}})();
View
137 tests/express/nunjucks.js
@@ -128,6 +128,21 @@ exports.repeat = function(char_, n) {
return str;
};
+exports.each = function(obj, func, context) {
+ if(obj == null) {
+ return;
+ }
+
+ if(ArrayProto.each && obj.each == ArrayProto.each) {
+ obj.forEach(func, context);
+ }
+ else if(obj.length === +obj.length) {
+ for(var i=0, l=obj.length; i<l; i++) {
+ func.call(context, obj[i], i, obj);
+ }
+ }
+};
+
exports.map = function(obj, func) {
var results = [];
if(obj == null) {
@@ -492,19 +507,89 @@ var Frame = Object.extend({
}
});
-modules['runtime'] = {
- Frame: Frame
+function makeMacro(argNames, kwargNames, func) {
+ return function() {
+ var argCount = numArgs(arguments);
+ var args;
+ var kwargs = getKeywordArgs(arguments);
+
+ if(argCount > argNames.length) {
+ args = Array.prototype.slice.call(arguments, 0, argNames.length);
+
+ // Positional arguments that should be passed in as
+ // keyword arguments (essentially default values)
+ var vals = Array.prototype.slice.call(arguments, args.length, argCount);
+ for(var i=0; i<vals.length; i++) {
+ if(i < kwargNames.length) {
+ kwargs[kwargNames[i]] = vals[i];
+ }
+ }
+
+ args.push(kwargs);
+ }
+ else if(argCount < argNames.length) {
+ args = Array.prototype.slice.call(arguments, 0, argCount);
+
+ for(var i=argCount; i<argNames.length; i++) {
+ var arg = argNames[i];
+
+ // Keyword arguments that should be passed as
+ // positional arguments, i.e. the caller explicitly
+ // used the name of a positional arg
+ args.push(kwargs[arg]);
+ delete kwargs[arg];
+ }
+
+ args.push(kwargs);
+ }
+ else {
+ args = arguments;
+ }
+
+ return func.apply(this, args);
+ };
+}
+
+function makeKeywordArgs(obj) {
+ obj.__keywords = true;
+ return obj;
+}
+
+function getKeywordArgs(args) {
+ if(args.length && args[args.length - 1].__keywords) {
+ return args[args.length - 1];
+ }
+ return {};
+}
+
+function numArgs(args) {
+ if(args.length === 0) {
+ return 0;
+ }
+ else if(args[args.length - 1].__keywords) {
+ return args.length - 1;
+ }
+ else {
+ return args.length;
+ }
+}
+
+modules['runtime'] = {
+ Frame: Frame,
+ makeMacro: makeMacro,
+ makeKeywordArgs: makeKeywordArgs,
+ numArgs: numArgs
};
})();
(function() {
-
var lib = modules["lib"];
var Object = modules["object"];
var lexer = modules["lexer"];
var compiler = modules["compiler"];
var builtin_filters = modules["filters"];
var builtin_loaders = modules["loaders"];
-var Frame = modules["runtime"].Frame;
+var runtime = modules["runtime"];
+var Frame = runtime.Frame;
var Environment = Object.extend({
init: function(loaders, tags) {
@@ -593,8 +678,8 @@ var Environment = Object.extend({
context = lib.extend(context, ctx);
- var res = env.render(name, ctx);
- k(null, res);
+ var res = env.render(name, context);
+ k(null, res);
};
},
@@ -607,6 +692,7 @@ var Context = Object.extend({
init: function(ctx, blocks) {
this.ctx = ctx;
this.blocks = {};
+ this.exported = [];
for(var name in blocks) {
this.addBlock(name, blocks[name]);
@@ -620,7 +706,7 @@ var Context = Object.extend({
setVariable: function(name, val) {
this.ctx[name] = val;
},
-
+
getVariables: function() {
return this.ctx;
},
@@ -650,6 +736,19 @@ var Context = Object.extend({
return blk(env, context);
};
+ },
+
+ addExport: function(name) {
+ this.exported.push(name);
+ },
+
+ getExported: function() {
+ var exported = {};
+ for(var i=0; i<this.exported.length; i++) {
+ var name = this.exported[i];
+ exported[name] = this.ctx[name];
+ }
+ return exported;
}
});
@@ -690,13 +789,28 @@ var Template = Object.extend({
var context = new Context(ctx || {}, this.blocks);
return this.rootRenderFunc(this.env,
context,
- frame || new Frame());
+ frame || new Frame(),
+ runtime);
},
isUpToDate: function() {
return this.upToDate();
},
+ getExported: function() {
+ if(!this.compiled) {
+ this._compile();
+ }
+
+ // Run the rootRenderFunc to populate the context with exported vars
+ var context = new Context({}, this.blocks);
+ this.rootRenderFunc(this.env,
+ context,
+ new Frame(),
+ runtime);
+ return context.getExported();
+ },
+
_compile: function() {
var props;
@@ -707,7 +821,7 @@ var Template = Object.extend({
var func = new Function(compiler.compile(this.tmplStr, this.env));
props = func();
}
-
+
this.blocks = this._getBlocks(props);
this.rootRenderFunc = props.root;
this.compiled = true;
@@ -727,8 +841,9 @@ var Template = Object.extend({
});
// var fs = modules["fs"];
-// //var src = fs.readFileSync('test.html', 'utf-8');
-// var src = "{% for i in [1,2,3] %}{% include 'item.html' %}{% endfor %}";
+// var src = fs.readFileSync('test.html', 'utf-8');
+// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}';
+// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}';
// var env = new Environment();
// console.log(compiler.compile(src));
View
8 tests/express/views/index.html
@@ -1,7 +1,15 @@
{% extends "base.html" %}
+{% macro foo(x, y, z=10) %}
+{{ x }} is better than {{ y }}!
+
+AND {{ z }}
+{% endmacro %}
+
{% block content %}
Hello, {{ username }}! This is just some content
+
+{{ foo(1, 2, 3) }}
{% endblock %}

0 comments on commit 1f01afc

Please sign in to comment.
Something went wrong with that request. Please try again.