diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f2a19f2..b4aa2dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,103 +1,3 @@ -Changelog for versions previous to v0.1.9 are located at http://nunjucks.tumblr.com/. +Changelog is available at https://github.com/mozilla/nunjucks/releases -# v1.0.6 (August 15, 2014) - -* Added the `addGlobal` method to the Environment object -* import/extends/include now can take an arbitrary expression -* fix bugs in `set` -* improve express integration (allows rendering templates without an extension) - -# v1.0.5 (May 1, 2014) - -* Added support for browserify -* Added option to specify template output path when precompiling templates -* Keep version comment in browser minified files -* Speed up SafeString implementation -* Handle null and non-matching cases for word count filter -* Added support for node-webkit -* Other various minor bugfixes - -# v1.0.4 (April 4, 2014) - -* Use v0.8.2 of chokidar from NPM rather than Github - -# v1.0.2 (March 25, 2014) - -* Use chokidar for watching file changes. This should fix a lot of problems on OS X machines. -* Always use `/` in paths when precompiling templates -* Fix bug where async filters hang indefinitely inside `if` statements -* Extensions now can override autoescaping with an `autoescape` property -* Other various minor bugfixes - -# v1.0.1 (December 16, 2013) - -This is mostly bugfix and code cleanup release. The only added things are: - -* New `nunjucks.compile` function which takes a string and returns a `Template` object -* The `urlize` filter has been added - -# v1.0.0 (October 24, 2013) - -We've hit 1.0! Thanks for helping nunjucks stabilize and become -awesome. I've added many good features recently and several people -have been using them, and everything seems stable. I think it's time -to cut 1.0. - -## Big changes: - -* An asynchronous API is now available, and async filters, extensions, and - loaders is supported. The async API is optional and if you don't do - anything async (the default), nothing changes for you. You can read - more about this - [here](http://jlongster.github.io/nunjucks/api.html#asynchronous-support). (fixes #41) -* Much simpler higher-level API for initiating/configuring nunjucks is - available. Read more - [here](http://jlongster.github.io/nunjucks/api.html#simple-api). -* An official grunt plugin is available for precompiling templates: [grunt-nunjucks](https://github.com/jlongster/grunt-nunjucks) -* **The browser files have been renamed**. nunjucks.js is now the full - library with compiler, and nunjucks-slim.js is the small version - that only works with precompiled templates - -## Smaller changes: - -* urlencode filter has been added -* The express integration has been refactored and isn't a kludge - anymore. Should avoid some bugs and be more future-proof; -* The order in which variables are lookup up in the context and frame - lookup has been reversed. It will now look in the frame first, and - then the context. This means that if a `for` loop introduces a new - var, like `{% for name in names %}`, and if you have `name` in the - context as well, it will properly reference `name` from the for loop - inside the loop. (fixes #122 and #119) - -# v0.1.10 (August 9, 2013) - -This is a minor version update that includes several bugfixes. - -* fix hang when parsing an unclosed string that hits the end of file (fixes #85) -* Adds IE8 support -* `super()` calls are marked safe by default if using autoescaping -* exposed a `markSafe` function in the runtime module for marking strings as safe inside filters -* iterating over any "falsey" values will output nothing -* make "this" be the context object in filters (fixes #109) - -# v0.1.9 (May 31, 2013) - -* autoescaping ([docs](http://nunjucks.jlongster.com/api#Autoescaping)) -* support for custom tags ([docs](http://nunjucks.jlongster.com/api#Custom-Tags-%2526-Extensions)) -* the API for the `Environment` object changed slightly ([docs](http://nunjucks.jlongster.com/api#new-Environment%28%255Bloaders%255D%252C-%255Boptions%255D%29)) -* tests now use expect.js instead of should.js, can be run [in the browser](http://jlongster.github.io/nunjucks/tests/browser/)! -* ternary conditional operator added (foo if bar else baz) -* various optimizations, compilation is now 1.4x faster -* fix too aggressive caching of templates from HTTP loader -* truncate filter has been added -* improve title filter -* many improvements to error messages -* add AMD support for precompiled templates -* fix multiple levels of super (issue #61) -* support passing a single file to nunjucks-precompile -* fix usage of `set` in an `if` block -* fixed passing a false-y value as the last argument to a macro -* `range`, `cycler`, and `joiner` globals have been added -* fix nested blocks -* add bower.json so the client-side lib can be installed through bower +For versions previous to v0.1.9 are located at http://nunjucks.tumblr.com/. diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 3a3cd49a..4f8c6d79 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -5,15 +5,15 @@ Nunjucks attempts to adhere to semantic versioning. The API is very stable, so f 1. Do a `pull` from github to make sure you have all the latest updates -2. View all the changes and update CHANGELOG.md: +2. View all the changes since the last version: ``` $ git log --oneline v1.2.3..master ``` -Replace `v1.2.3` with whatever the last version was, and you'll see all the changes going out in this version. Add a new version to CHANGELOG.md and write some notes about what's going out. +Replace `v1.2.3` with whatever the last version was, and you'll see all the changes going out in this version. -3. Update the version in `package.json` +3. Draft a new release and write the changelog in the description, describing the changes in see from #2. The title should be the version. 4. Run the command to make sure the ready-made files for the browser are up-to-date. @@ -21,17 +21,17 @@ Replace `v1.2.3` with whatever the last version was, and you'll see all the chan $ make browserfiles ``` -5. Commit above changes and push to `master` +5. Update the version in `package.json` -6. Publish to npm: +6. Commit above changes and push to `master` + +7. Publish to npm: ``` npm publish ``` -7. Go to https://github.com/mozilla/nunjucks/releases and click "Draft a new release". Fill out title and copy what you entered in CHANGELOG.md in the description. (CHANGELOG.md could go away I guess with github's release stuff) - -8. Make sure docs are up-to-date. If anything, you need to copy all the nunjucks*.js files in `browser/` to the [nunjucks-docs repo](https://github.com/mozilla/nunjucks-docs) in the `files` directory. This is where the "download" link points to in the docs. In `nunjucks-docs`, build the docs: +8. Make sure docs are up-to-date. You need to at least copy all the nunjucks*.js files in `browser/` to the [nunjucks-docs repo](https://github.com/mozilla/nunjucks-docs) in the `files` directory. This is where the "download" link points to in the docs. In `nunjucks-docs`, build the docs: ``` cd path/to/nunjucks-docs && make prod diff --git a/browser/nunjucks-slim.js b/browser/nunjucks-slim.js index 2fc23912..964ceac7 100644 --- a/browser/nunjucks-slim.js +++ b/browser/nunjucks-slim.js @@ -1,4 +1,4 @@ -// Browser bundle of nunjucks 1.0.7 (slim, only works with precompiled templates) +// Browser bundle of nunjucks 1.1.0 (slim, only works with precompiled templates) (function() { var modules = {}; @@ -198,7 +198,7 @@ exports.without = function(array) { contains = exports.toArray(arguments).slice(1); while(++index < length) { - if(contains.indexOf(array[index]) === -1) { + if(exports.indexOf(contains, array[index]) === -1) { result.push(array[index]); } } @@ -293,37 +293,35 @@ exports.asyncFor = function(obj, iter, cb) { next(); }; -if(!Array.prototype.indexOf) { - Array.prototype.indexOf = function(array, searchElement /*, fromIndex */) { - if (array == null) { - throw new TypeError(); - } - var t = Object(array); - var len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 2) { - n = Number(arguments[2]); - if (n != n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n != 0 && n != Infinity && n != -Infinity) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill +exports.indexOf = Array.prototype.indexOf ? + function (arr, searchElement, fromIndex) { + return Array.prototype.indexOf.call(arr, searchElement, fromIndex); + } : + function (arr, searchElement, fromIndex) { + var length = this.length >>> 0; // Hack to convert object.length to a UInt32 + + fromIndex = +fromIndex || 0; + + if(Math.abs(fromIndex) === Infinity) { + fromIndex = 0; } - if (n >= len) { - return -1; + + if(fromIndex < 0) { + fromIndex += length; + if (fromIndex < 0) { + fromIndex = 0; + } } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; + + for(;fromIndex < length; fromIndex++) { + if (arr[fromIndex] === searchElement) { + return fromIndex; } } + return -1; }; -} if(!Array.prototype.map) { Array.prototype.map = function() { @@ -978,6 +976,10 @@ var filters = { }, replace: function(str, old, new_, maxCount) { + if (old instanceof RegExp) { + return str.replace(old, new_); + } + var res = str; var last = res; var count = 1; @@ -1175,8 +1177,10 @@ var filters = { }).map(function(word) { var matches = word.match(puncRE); + var possibleUrl = matches && matches[1] || word; + // url that starts with http or https if (httpHttpsRE.test(possibleUrl)) return '' + possibleUrl.substr(0, length) + ''; @@ -1193,7 +1197,7 @@ var filters = { if (tldRE.test(possibleUrl)) return '' + possibleUrl.substr(0, length) + ''; - return possibleUrl; + return word; }); @@ -1226,12 +1230,12 @@ modules['filters'] = filters; function cycler(items) { var index = -1; - var current = null; + this.current = null; return { reset: function() { index = -1; - current = null; + this.current = null; }, next: function() { @@ -1240,9 +1244,9 @@ function cycler(items) { index = 0; } - current = items[index]; - return current; - } + this.current = items[index]; + return this.current; + }, }; } @@ -1312,6 +1316,7 @@ var Environment = Obj.extend({ // defaults to false opts = opts || {}; this.dev = !!opts.dev; + this.lexerTags = opts.tags; // The autoescape flag sets global autoescaping. If true, // every string variable will be escaped by default. @@ -1338,10 +1343,6 @@ var Environment = Obj.extend({ this.extensions = {}; this.extensionsList = []; - if(opts.tags) { - lexer.setTags(opts.tags); - } - for(var name in builtin_filters) { this.addFilter(name, builtin_filters[name]); } @@ -1572,7 +1573,7 @@ var Context = Obj.extend({ }, getSuper: function(env, name, block, frame, runtime, cb) { - var idx = (this.blocks[name] || []).indexOf(block); + var idx = lib.indexOf(this.blocks[name] || [], block); var blk = this.blocks[name][idx + 1]; var context = this; @@ -1686,7 +1687,8 @@ var Template = Obj.extend({ var source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, - this.path); + this.path, + this.env.lexerTags); var func = new Function(source); props = func(); } diff --git a/browser/nunjucks-slim.min.js b/browser/nunjucks-slim.min.js index f2fda10e..2ebe580a 100644 --- a/browser/nunjucks-slim.min.js +++ b/browser/nunjucks-slim.min.js @@ -1,3 +1,3 @@ -// Browser bundle of nunjucks 1.0.7 (slim, only works with precompiled templates) +// Browser bundle of nunjucks 1.1.0 (slim, only works with precompiled templates) -(function(){var modules={};(function(){function extend(cls,name,props){var F=function(){};F.prototype=cls.prototype;var prototype=new F;var fnTest=/xyz/.test(function(){xyz})?/\bparent\b/:/.*/;props=props||{};for(var k in props){var src=props[k];var parent=prototype[k];if(typeof parent=="function"&&typeof src=="function"&&fnTest.test(src)){prototype[k]=function(src,parent){return function(){var tmp=this.parent;this.parent=parent;var res=src.apply(this,arguments);this.parent=tmp;return res}}(src,parent)}else{prototype[k]=src}}prototype.typename=name;var new_cls=function(){if(prototype.init){prototype.init.apply(this,arguments)}};new_cls.prototype=prototype;new_cls.prototype.constructor=new_cls;new_cls.extend=function(name,props){if(typeof name=="object"){props=name;name="anonymous"}return extend(new_cls,name,props)};return new_cls}modules["object"]=extend(Object,"Object",{})})();(function(){var ArrayProto=Array.prototype;var ObjProto=Object.prototype;var escapeMap={"&":"&",'"':""","'":"'","<":"<",">":">"};var escapeRegex=/[&"'<>]/g;var lookupEscape=function(ch){return escapeMap[ch]};var exports=modules["lib"]={};exports.withPrettyErrors=function(path,withInternals,func){try{return func()}catch(e){if(!e.Update){e=new exports.TemplateError(e)}e.Update(path);if(!withInternals){var old=e;e=new Error(old.message);e.name=old.name}throw e}};exports.TemplateError=function(message,lineno,colno){var err=this;if(message instanceof Error){err=message;message=message.name+": "+message.message}else{if(Error.captureStackTrace){Error.captureStackTrace(err)}}err.name="Template render error";err.message=message;err.lineno=lineno;err.colno=colno;err.firstUpdate=true;err.Update=function(path){var message="("+(path||"unknown path")+")";if(this.firstUpdate){if(this.lineno&&this.colno){message+=" [Line "+this.lineno+", Column "+this.colno+"]"}else if(this.lineno){message+=" [Line "+this.lineno+"]"}}message+="\n ";if(this.firstUpdate){message+=" "}this.message=message+(this.message||"");this.firstUpdate=false;return this};return err};exports.TemplateError.prototype=Error.prototype;exports.escape=function(val){return val.replace(escapeRegex,lookupEscape)};exports.isFunction=function(obj){return ObjProto.toString.call(obj)=="[object Function]"};exports.isArray=Array.isArray||function(obj){return ObjProto.toString.call(obj)=="[object Array]"};exports.isString=function(obj){return ObjProto.toString.call(obj)=="[object String]"};exports.isObject=function(obj){return ObjProto.toString.call(obj)=="[object Object]"};exports.groupBy=function(obj,val){var result={};var iterator=exports.isFunction(val)?val:function(obj){return obj[val]};for(var i=0;i>>0;if(len===0){return-1}var n=0;if(arguments.length>2){n=Number(arguments[2]);if(n!=n){n=0}else if(n!=0&&n!=Infinity&&n!=-Infinity){n=(n>0||-1)*Math.floor(Math.abs(n))}}if(n>=len){return-1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;kargNames.length){args=Array.prototype.slice.call(arguments,0,argNames.length);var vals=Array.prototype.slice.call(arguments,args.length,argCount);for(var i=0;i=width){return str}var spaces=width-str.length;var pre=lib.repeat(" ",spaces/2-spaces%2);var post=lib.repeat(" ",spaces/2);return r.copySafeness(str,pre+str+post)},"default":function(val,def){return val?val:def},dictsort:function(val,case_sensitive,by){if(!lib.isObject(val)){throw new lib.TemplateError("dictsort filter: val must be an object")}var array=[];for(var k in val){array.push([k,val[k]])}var si;if(by===undefined||by==="key"){si=0}else if(by==="value"){si=1}else{throw new lib.TemplateError("dictsort filter: You can only sort by either key or value")}array.sort(function(t1,t2){var a=t1[si];var b=t2[si];if(!case_sensitive){if(lib.isString(a)){a=a.toUpperCase()}if(lib.isString(b)){b=b.toUpperCase()}}return a>b?1:a==b?0:-1});return array},escape:function(str){if(typeof str=="string"||str instanceof r.SafeString){return lib.escape(str)}return str},safe:function(str){return r.markSafe(str)},first:function(arr){return arr[0]},groupby:function(arr,attr){return lib.groupBy(arr,attr)},indent:function(str,width,indentfirst){width=width||4;var res="";var lines=str.split("\n");var sp=lib.repeat(" ",width);for(var i=0;i=maxCount){break}last=res;res=res.replace(old,new_);count++}return r.copySafeness(str,res)},reverse:function(val){var arr;if(lib.isString(val)){arr=filters.list(val)}else{arr=lib.map(val,function(v){return v})}arr.reverse();if(lib.isString(val)){return r.copySafeness(val,arr.join(""))}return arr},round:function(val,precision,method){precision=precision||0;var factor=Math.pow(10,precision);var rounder;if(method=="ceil"){rounder=Math.ceil}else if(method=="floor"){rounder=Math.floor}else{rounder=Math.round}return rounder(val*factor)/factor},slice:function(arr,slices,fillWith){var sliceLength=Math.floor(arr.length/slices);var extra=arr.length%slices;var offset=0;var res=[];for(var i=0;i=extra){slice.push(fillWith)}res.push(slice)}return res},sort:function(arr,reverse,caseSens,attr){arr=lib.map(arr,function(v){return v});arr.sort(function(a,b){var x,y;if(attr){x=a[attr];y=b[attr]}else{x=a;y=b}if(!caseSens&&lib.isString(x)&&lib.isString(y)){x=x.toLowerCase();y=y.toLowerCase()}if(xy){return reverse?-1:1}else{return 0}});return arr},string:function(obj){return r.copySafeness(obj,obj)},title:function(str){var words=str.split(" ");for(var i=0;i"+possibleUrl.substr(0,length)+"";if(wwwRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";if(emailRE.test(possibleUrl))return''+possibleUrl+"";if(tldRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";return possibleUrl});return words.join(" ")},wordcount:function(str){var words=str?str.match(/\w+/g):null;return words?words.length:null},"float":function(val,def){var res=parseFloat(val);return isNaN(res)?def:res},"int":function(val,def){var res=parseInt(val,10);return isNaN(res)?def:res}};filters.d=filters["default"];filters.e=filters.escape;modules["filters"]=filters})();(function(){function cycler(items){var index=-1;var current=null;return{reset:function(){index=-1;current=null},next:function(){index++;if(index>=items.length){index=0}current=items[index];return current}}}function joiner(sep){sep=sep||",";var first=true;return function(){var val=first?"":sep;first=false;return val}}var globals={range:function(start,stop,step){if(!stop){stop=start;start=0;step=1}else if(!step){step=1}var arr=[];for(var i=start;i":">"};var escapeRegex=/[&"'<>]/g;var lookupEscape=function(ch){return escapeMap[ch]};var exports=modules["lib"]={};exports.withPrettyErrors=function(path,withInternals,func){try{return func()}catch(e){if(!e.Update){e=new exports.TemplateError(e)}e.Update(path);if(!withInternals){var old=e;e=new Error(old.message);e.name=old.name}throw e}};exports.TemplateError=function(message,lineno,colno){var err=this;if(message instanceof Error){err=message;message=message.name+": "+message.message}else{if(Error.captureStackTrace){Error.captureStackTrace(err)}}err.name="Template render error";err.message=message;err.lineno=lineno;err.colno=colno;err.firstUpdate=true;err.Update=function(path){var message="("+(path||"unknown path")+")";if(this.firstUpdate){if(this.lineno&&this.colno){message+=" [Line "+this.lineno+", Column "+this.colno+"]"}else if(this.lineno){message+=" [Line "+this.lineno+"]"}}message+="\n ";if(this.firstUpdate){message+=" "}this.message=message+(this.message||"");this.firstUpdate=false;return this};return err};exports.TemplateError.prototype=Error.prototype;exports.escape=function(val){return val.replace(escapeRegex,lookupEscape)};exports.isFunction=function(obj){return ObjProto.toString.call(obj)=="[object Function]"};exports.isArray=Array.isArray||function(obj){return ObjProto.toString.call(obj)=="[object Array]"};exports.isString=function(obj){return ObjProto.toString.call(obj)=="[object String]"};exports.isObject=function(obj){return ObjProto.toString.call(obj)=="[object Object]"};exports.groupBy=function(obj,val){var result={};var iterator=exports.isFunction(val)?val:function(obj){return obj[val]};for(var i=0;i>>0;fromIndex=+fromIndex||0;if(Math.abs(fromIndex)===Infinity){fromIndex=0}if(fromIndex<0){fromIndex+=length;if(fromIndex<0){fromIndex=0}}for(;fromIndexargNames.length){args=Array.prototype.slice.call(arguments,0,argNames.length);var vals=Array.prototype.slice.call(arguments,args.length,argCount);for(var i=0;i=width){return str}var spaces=width-str.length;var pre=lib.repeat(" ",spaces/2-spaces%2);var post=lib.repeat(" ",spaces/2);return r.copySafeness(str,pre+str+post)},"default":function(val,def){return val?val:def},dictsort:function(val,case_sensitive,by){if(!lib.isObject(val)){throw new lib.TemplateError("dictsort filter: val must be an object")}var array=[];for(var k in val){array.push([k,val[k]])}var si;if(by===undefined||by==="key"){si=0}else if(by==="value"){si=1}else{throw new lib.TemplateError("dictsort filter: You can only sort by either key or value")}array.sort(function(t1,t2){var a=t1[si];var b=t2[si];if(!case_sensitive){if(lib.isString(a)){a=a.toUpperCase()}if(lib.isString(b)){b=b.toUpperCase()}}return a>b?1:a==b?0:-1});return array},escape:function(str){if(typeof str=="string"||str instanceof r.SafeString){return lib.escape(str)}return str},safe:function(str){return r.markSafe(str)},first:function(arr){return arr[0]},groupby:function(arr,attr){return lib.groupBy(arr,attr)},indent:function(str,width,indentfirst){width=width||4;var res="";var lines=str.split("\n");var sp=lib.repeat(" ",width);for(var i=0;i=maxCount){break}last=res;res=res.replace(old,new_);count++}return r.copySafeness(str,res)},reverse:function(val){var arr;if(lib.isString(val)){arr=filters.list(val)}else{arr=lib.map(val,function(v){return v})}arr.reverse();if(lib.isString(val)){return r.copySafeness(val,arr.join(""))}return arr},round:function(val,precision,method){precision=precision||0;var factor=Math.pow(10,precision);var rounder;if(method=="ceil"){rounder=Math.ceil}else if(method=="floor"){rounder=Math.floor}else{rounder=Math.round}return rounder(val*factor)/factor},slice:function(arr,slices,fillWith){var sliceLength=Math.floor(arr.length/slices);var extra=arr.length%slices;var offset=0;var res=[];for(var i=0;i=extra){slice.push(fillWith)}res.push(slice)}return res},sort:function(arr,reverse,caseSens,attr){arr=lib.map(arr,function(v){return v});arr.sort(function(a,b){var x,y;if(attr){x=a[attr];y=b[attr]}else{x=a;y=b}if(!caseSens&&lib.isString(x)&&lib.isString(y)){x=x.toLowerCase();y=y.toLowerCase()}if(xy){return reverse?-1:1}else{return 0}});return arr},string:function(obj){return r.copySafeness(obj,obj)},title:function(str){var words=str.split(" ");for(var i=0;i"+possibleUrl.substr(0,length)+"";if(wwwRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";if(emailRE.test(possibleUrl))return''+possibleUrl+"";if(tldRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";return word});return words.join(" ")},wordcount:function(str){var words=str?str.match(/\w+/g):null;return words?words.length:null},"float":function(val,def){var res=parseFloat(val);return isNaN(res)?def:res},"int":function(val,def){var res=parseInt(val,10);return isNaN(res)?def:res}};filters.d=filters["default"];filters.e=filters.escape;modules["filters"]=filters})();(function(){function cycler(items){var index=-1;this.current=null;return{reset:function(){index=-1;this.current=null},next:function(){index++;if(index>=items.length){index=0}this.current=items[index];return this.current}}}function joiner(sep){sep=sep||",";var first=true;return function(){var val=first?"":sep;first=false;return val}}var globals={range:function(start,stop,step){if(!stop){stop=start;start=0;step=1}else if(!step){step=1}var arr=[];for(var i=start;i>> 0; - if (len === 0) { - return -1; +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill +exports.indexOf = Array.prototype.indexOf ? + function (arr, searchElement, fromIndex) { + return Array.prototype.indexOf.call(arr, searchElement, fromIndex); + } : + function (arr, searchElement, fromIndex) { + var length = this.length >>> 0; // Hack to convert object.length to a UInt32 + + fromIndex = +fromIndex || 0; + + if(Math.abs(fromIndex) === Infinity) { + fromIndex = 0; } - var n = 0; - if (arguments.length > 2) { - n = Number(arguments[2]); - if (n != n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n != 0 && n != Infinity && n != -Infinity) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); + + if(fromIndex < 0) { + fromIndex += length; + if (fromIndex < 0) { + fromIndex = 0; } } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; + + for(;fromIndex < length; fromIndex++) { + if (arr[fromIndex] === searchElement) { + return fromIndex; } } + return -1; }; -} if(!Array.prototype.map) { Array.prototype.map = function() { @@ -438,10 +436,11 @@ var LookupVal = Node.extend("LookupVal", { fields: ['target', 'val'] }); var If = Node.extend("If", { fields: ['cond', 'body', 'else_'] }); var IfAsync = If.extend("IfAsync"); var InlineIf = Node.extend("InlineIf", { fields: ['cond', 'body', 'else_'] }); -var For = Node.extend("For", { fields: ['arr', 'name', 'body'] }); +var For = Node.extend("For", { fields: ['arr', 'name', 'body', 'else_'] }); var AsyncEach = For.extend("AsyncEach"); var AsyncAll = For.extend("AsyncAll"); var Macro = Node.extend("Macro", { fields: ['name', 'args', 'body'] }); +var Caller = Macro.extend("Caller"); var Import = Node.extend("Import", { fields: ['template', 'target'] }); var FromImport = Node.extend("FromImport", { fields: ['template', 'names'], @@ -468,6 +467,7 @@ var Output = NodeList.extend("Output"); var TemplateData = Literal.extend("TemplateData"); var UnaryOp = Node.extend("UnaryOp", { fields: ['target'] }); var BinOp = Node.extend("BinOp", { fields: ['left', 'right'] }); +var In = BinOp.extend("In"); var Or = BinOp.extend("Or"); var And = BinOp.extend("And"); var Not = UnaryOp.extend("Not"); @@ -614,6 +614,7 @@ modules['nodes'] = { AsyncEach: AsyncEach, AsyncAll: AsyncAll, Macro: Macro, + Caller: Caller, Import: Import, FromImport: FromImport, FunCall: FunCall, @@ -627,6 +628,7 @@ modules['nodes'] = { Set: Set, LookupVal: LookupVal, BinOp: BinOp, + In: In, Or: Or, And: And, Not: Not, @@ -1025,6 +1027,7 @@ var TOKEN_FLOAT = "float"; var TOKEN_BOOLEAN = "boolean"; var TOKEN_SYMBOL = "symbol"; var TOKEN_SPECIAL = "special"; +var TOKEN_REGEX = "regex"; function token(type, value, lineno, colno) { return { @@ -1035,7 +1038,7 @@ function token(type, value, lineno, colno) { }; } -function Tokenizer(str) { +function Tokenizer(str, tags) { this.str = str; this.index = 0; this.len = str.length; @@ -1043,6 +1046,16 @@ function Tokenizer(str) { this.colno = 0; this.in_code = false; + + tags = tags || {}; + this.tags = { + BLOCK_START: tags.blockStart || BLOCK_START, + BLOCK_END: tags.blockEnd || BLOCK_END, + VARIABLE_START: tags.variableStart || VARIABLE_START, + VARIABLE_END: tags.variableEnd || VARIABLE_END, + COMMENT_START: tags.commentStart || COMMENT_START, + COMMENT_END: tags.commentEnd || COMMENT_END + }; } Tokenizer.prototype.nextToken = function() { @@ -1066,8 +1079,8 @@ Tokenizer.prototype.nextToken = function() { // We hit some whitespace return token(TOKEN_WHITESPACE, tok, lineno, colno); } - else if((tok = this._extractString(BLOCK_END)) || - (tok = this._extractString('-' + BLOCK_END))) { + else if((tok = this._extractString(this.tags.BLOCK_END)) || + (tok = this._extractString('-' + this.tags.BLOCK_END))) { // Special check for the block end tag // // It is a requirement that start and end tags are composed of @@ -1077,11 +1090,43 @@ Tokenizer.prototype.nextToken = function() { this.in_code = false; return token(TOKEN_BLOCK_END, tok, lineno, colno); } - else if((tok = this._extractString(VARIABLE_END))) { + else if((tok = this._extractString(this.tags.VARIABLE_END))) { // Special check for variable end tag (see above) this.in_code = false; return token(TOKEN_VARIABLE_END, tok, lineno, colno); } + else if (cur === 'r' && this.str.charAt(this.index + 1) === '/') { + // Skip past 'r/'. + this.forwardN(2); + + // Extract until the end of the regex -- / ends it, \/ does not. + var regexBody = ''; + while (!this.is_finished()) { + if (this.current() === '/' && this.previous() !== '\\') { + this.forward(); + break; + } else { + regexBody += this.current(); + this.forward(); + } + } + + // Check for flags. + // The possible flags are according to https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp) + var POSSIBLE_FLAGS = ['g', 'i', 'm', 'y']; + var regexFlags = ''; + while (!this.is_finished()) { + var isCurrentAFlag = POSSIBLE_FLAGS.indexOf(this.current()) !== -1; + if (isCurrentAFlag) { + regexFlags += this.current(); + this.forward(); + } else { + break; + } + } + + return token(TOKEN_REGEX, {body: regexBody, flags: regexFlags}, lineno, colno); + } else if(delimChars.indexOf(cur) != -1) { // We've hit a delimiter (a special char like a bracket) this.forward(); @@ -1089,7 +1134,7 @@ Tokenizer.prototype.nextToken = function() { var curComplex = cur + this.current(); var type; - if(complexOps.indexOf(curComplex) !== -1) { + if(lib.indexOf(complexOps, curComplex) !== -1) { this.forward(); cur = curComplex; } @@ -1139,21 +1184,21 @@ 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.charAt(0) + - VARIABLE_START.charAt(0) + - COMMENT_START.charAt(0) + - COMMENT_END.charAt(0)); + var beginChars = (this.tags.BLOCK_START.charAt(0) + + this.tags.VARIABLE_START.charAt(0) + + this.tags.COMMENT_START.charAt(0) + + this.tags.COMMENT_END.charAt(0)); var tok; if(this.is_finished()) { return null; } - else if((tok = this._extractString(BLOCK_START + '-')) || - (tok = this._extractString(BLOCK_START))) { + else if((tok = this._extractString(this.tags.BLOCK_START + '-')) || + (tok = this._extractString(this.tags.BLOCK_START))) { this.in_code = true; return token(TOKEN_BLOCK_START, tok, lineno, colno); } - else if((tok = this._extractString(VARIABLE_START))) { + else if((tok = this._extractString(this.tags.VARIABLE_START))) { this.in_code = true; return token(TOKEN_VARIABLE_START, tok, lineno, colno); } @@ -1162,9 +1207,9 @@ Tokenizer.prototype.nextToken = function() { var data; var in_comment = false; - if(this._matches(COMMENT_START)) { + if(this._matches(this.tags.COMMENT_START)) { in_comment = true; - tok = this._extractString(COMMENT_START); + tok = this._extractString(this.tags.COMMENT_START); } // Continually consume text, breaking on the tag delimiter @@ -1176,18 +1221,18 @@ Tokenizer.prototype.nextToken = function() { while((data = this._extractUntil(beginChars)) !== null) { tok += data; - if((this._matches(BLOCK_START) || - this._matches(VARIABLE_START) || - this._matches(COMMENT_START)) && + if((this._matches(this.tags.BLOCK_START) || + this._matches(this.tags.VARIABLE_START) || + this._matches(this.tags.COMMENT_START)) && !in_comment) { // If it is a start tag, stop looping break; } - else if(this._matches(COMMENT_END)) { + else if(this._matches(this.tags.COMMENT_END)) { if(!in_comment) { throw new Error("unexpected end of comment"); } - tok += this._extractString(COMMENT_END); + tok += this._extractString(this.tags.COMMENT_END); break; } else { @@ -1360,17 +1405,8 @@ Tokenizer.prototype.previous = function() { }; modules['lexer'] = { - lex: function(src) { - return new Tokenizer(src); - }, - - setTags: function(tags) { - BLOCK_START = tags.blockStart || BLOCK_START; - BLOCK_END = tags.blockEnd || BLOCK_END; - VARIABLE_START = tags.variableStart || VARIABLE_START; - VARIABLE_END = tags.variableEnd || VARIABLE_END; - COMMENT_START = tags.commentStart || COMMENT_START; - COMMENT_END = tags.commentEnd || COMMENT_END; + lex: function(src, tags) { + return new Tokenizer(src, tags); }, TOKEN_STRING: TOKEN_STRING, @@ -1395,7 +1431,8 @@ modules['lexer'] = { TOKEN_FLOAT: TOKEN_FLOAT, TOKEN_BOOLEAN: TOKEN_BOOLEAN, TOKEN_SYMBOL: TOKEN_SYMBOL, - TOKEN_SPECIAL: TOKEN_SPECIAL + TOKEN_SPECIAL: TOKEN_SPECIAL, + TOKEN_REGEX: TOKEN_REGEX }; })(); (function() { @@ -1582,7 +1619,13 @@ var Parser = Object.extend({ node.arr = this.parseExpression(); this.advanceAfterBlockEnd(forTok.value); - node.body = this.parseUntilBlocks(endBlock); + node.body = this.parseUntilBlocks(endBlock, 'else'); + + if(this.skipSymbol('else')) { + this.advanceAfterBlockEnd('else'); + node.else_ = this.parseUntilBlocks(endBlock); + } + this.advanceAfterBlockEnd(); return node; @@ -1608,6 +1651,46 @@ var Parser = Object.extend({ return node; }, + parseCall: function() { + // a call block is parsed as a normal FunCall, but with an added + // 'caller' kwarg which is a Caller node. + var callTok = this.peekToken(); + if(!this.skipSymbol('call')) { + this.fail("expected call"); + } + + var callerArgs = this.parseSignature(true) || new nodes.NodeList(); + var macroCall = this.parsePrimary(); + + this.advanceAfterBlockEnd(callTok.value); + var body = this.parseUntilBlocks('endcall'); + this.advanceAfterBlockEnd(); + + var callerName = new nodes.Symbol(callTok.lineno, + callTok.colno, + 'caller'); + var callerNode = new nodes.Caller(callTok.lineno, + callTok.colno, + callerName, + callerArgs, + body); + + // add the additional caller kwarg, adding kwargs if necessary + var args = macroCall.args.children; + if (!(args[args.length-1] instanceof nodes.KeywordArgs)) { + args.push(new nodes.KeywordArgs()); + } + var kwargs = args[args.length - 1]; + kwargs.addChild(new nodes.Pair(callTok.lineno, + callTok.colno, + callerName, + callerNode)); + + return new nodes.Output(callTok.lineno, + callTok.colno, + [macroCall]); + }, + parseImport: function() { var importTok = this.peekToken(); if(!this.skipSymbol('import')) { @@ -1833,7 +1916,7 @@ var Parser = Object.extend({ } if(this.breakOnBlocks && - this.breakOnBlocks.indexOf(tok.value) !== -1) { + lib.indexOf(this.breakOnBlocks, tok.value) !== -1) { return null; } @@ -1851,13 +1934,14 @@ var Parser = Object.extend({ case 'include': return this.parseInclude(); case 'set': return this.parseSet(); case 'macro': return this.parseMacro(); + case 'call': return this.parseCall(); case 'import': return this.parseImport(); case 'from': return this.parseFrom(); default: if (this.extensions.length) { for (var i = 0; i < this.extensions.length; i++) { var ext = this.extensions[i]; - if ((ext.tags || []).indexOf(tok.value) !== -1) { + if (lib.indexOf(ext.tags || [], tok.value) !== -1) { return ext.parse(this, nodes, lexer); } } @@ -1986,15 +2070,15 @@ var Parser = Object.extend({ }, parseInlineIf: function() { - var node = this.parseOr(); + var node = this.parseIn(); if(this.skipSymbol('if')) { - var cond_node = this.parseOr(); + var cond_node = this.parseIn(); var body_node = node; node = new nodes.InlineIf(node.lineno, node.colno); node.body = body_node; node.cond = cond_node; if(this.skipSymbol('else')) { - node.else_ = this.parseOr(); + node.else_ = this.parseIn(); } else { node.else_ = null; } @@ -2003,6 +2087,36 @@ var Parser = Object.extend({ return node; }, + parseIn: function() { + var node = this.parseOr(); + while(1) { + // check if the next token is 'not' + var tok = this.nextToken(); + if (!tok) { break; } + var invert = tok.type == lexer.TOKEN_SYMBOL && tok.value == 'not'; + // if it wasn't 'not', put it back + if (!invert) { this.pushToken(tok); } + if (this.skipSymbol('in')) { + var node2 = this.parseOr(); + node = new nodes.In(node.lineno, + node.colno, + node, + node2); + if (invert) { + node = new nodes.Not(node.lineno, + node.colno, + node); + } + } + else { + // if we'd found a 'not' but this wasn't an 'in', put back the 'not' + if (invert) { this.pushToken(tok); } + break; + } + } + return node; + }, + parseOr: function() { var node = this.parseAnd(); while(this.skipSymbol('or')) { @@ -2048,27 +2162,12 @@ var Parser = Object.extend({ if(!tok) { break; } - else if(compareOps.indexOf(tok.value) !== -1) { + else if(lib.indexOf(compareOps, tok.value) !== -1) { ops.push(new nodes.CompareOperand(tok.lineno, tok.colno, this.parseAdd(), tok.value)); } - else if(tok.type == lexer.TOKEN_SYMBOL && - tok.value == 'in') { - ops.push(new nodes.CompareOperand(tok.lineno, - tok.colno, - this.parseAdd(), - 'in')); - } - else if(tok.type == lexer.TOKEN_SYMBOL && - tok.value == 'not' && - this.skipSymbol('in')) { - ops.push(new nodes.CompareOperand(tok.lineno, - tok.colno, - this.parseAdd(), - 'notin')); - } else { this.pushToken(tok); break; @@ -2225,6 +2324,9 @@ var Parser = Object.extend({ tok.colno); } } + else if (tok.type == lexer.TOKEN_REGEX) { + val = new RegExp(tok.value.body, tok.value.flags); + } if(val !== null) { node = new nodes.Literal(tok.lineno, tok.colno, val); @@ -2489,14 +2591,13 @@ var Parser = Object.extend({ // console.log(util.inspect(t)); // } -// var p = new Parser(lexer.lex('hello {% foo %} {{ "hi" | bar }} {% endfoo %} end')); -// p.extensions = [new FooExtension()]; +// var p = new Parser(lexer.lex('{% if not x %}foo{% endif %}')); // var n = p.parseAsRoot(); // nodes.printNodes(n); modules['parser'] = { - parse: function(src, extensions) { - var p = new Parser(lexer.lex(src)); + parse: function(src, extensions, lexerTags) { + var p = new Parser(lexer.lex(src, lexerTags)); if (extensions !== undefined) { p.extensions = extensions; } @@ -2506,6 +2607,7 @@ modules['parser'] = { })(); (function() { var nodes = modules["nodes"]; +var lib = modules["lib"]; var sym = 0; function gensym() { @@ -2600,7 +2702,7 @@ function _liftFilters(node, asyncFilters, prop) { return node; } else if((node instanceof nodes.Filter && - asyncFilters.indexOf(node.name.value) !== -1) || + lib.indexOf(asyncFilters, node.name.value) !== -1) || node instanceof nodes.CallExtensionAsync) { var symbol = new nodes.Symbol(node.lineno, node.colno, @@ -2714,7 +2816,8 @@ function convertStatements(ast) { node.colno, node.arr, node.name, - node.body + node.body, + node.else_ ); } } @@ -2910,10 +3013,12 @@ var Compiler = Object.extend({ nodes.Array, nodes.Dict, nodes.FunCall, + nodes.Caller, nodes.Filter, nodes.LookupVal, nodes.Compare, nodes.InlineIf, + nodes.In, nodes.And, nodes.Or, nodes.Not, @@ -3104,6 +3209,14 @@ var Compiler = Object.extend({ this.emit(')'); }, + compileIn: function(node, frame) { + this.emit('('); + this.compile(node.right, frame); + this.emit('.indexOf('); + this.compile(node.left, frame); + this.emit(') !== -1)'); + }, + compileOr: binOpEmitter(' || '), compileAnd: binOpEmitter(' && '), compileAdd: binOpEmitter(' + '), @@ -3312,27 +3425,7 @@ var Compiler = Object.extend({ this.addScopeLevel(); }, - scanLoop: function(node) { - var loopUses = {}; - - node.iterFields(function(field) { - var lookups = field.findAll(nodes.LookupVal); - - lib.each(lookups, function(lookup) { - if (lookup.target instanceof nodes.Symbol && - lookup.target.value == 'loop' && - lookup.val instanceof nodes.Literal) { - loopUses[lookup.val.value] = true; - } - }); - }); - - return loopUses; - }, - - emitLoopBindings: function(node, loopUses, arr, i, len) { - len = len || arr + '.length'; - + emitLoopBindings: function(node, arr, i, len) { var bindings = { index: i + ' + 1', index0: i, @@ -3343,10 +3436,8 @@ var Compiler = Object.extend({ length: len }; - for(var name in bindings) { - if(name in loopUses) { - this.emitLine('frame.set("loop.' + name + '", ' + bindings[name] + ');'); - } + for (var name in bindings) { + this.emitLine('frame.set("loop.' + name + '", ' + bindings[name] + ');'); } }, @@ -3358,7 +3449,6 @@ var Compiler = Object.extend({ var i = this.tmpid(); var len = this.tmpid(); var arr = this.tmpid(); - var loopUses = this.scanLoop(node); frame = frame.push(); this.emitLine('frame = frame.push();'); @@ -3378,6 +3468,7 @@ var Compiler = Object.extend({ // body of the loop is duplicated for each condition, but // we are optimizing for speed over size. this.emitLine('if(runtime.isArray(' + arr + ')) {'); { + this.emitLine('var ' + len + ' = ' + arr + '.length;'); this.emitLine('for(' + i + '=0; ' + i + ' < ' + arr + '.length; ' + i + '++) {'); @@ -3390,7 +3481,7 @@ var Compiler = Object.extend({ frame.set(node.name.children[u].value, tid); } - this.emitLoopBindings(node, loopUses, arr, i); + this.emitLoopBindings(node, arr, i, len); this.withScopedSyntax(function() { this.compile(node.body, frame); }); @@ -3407,19 +3498,14 @@ var Compiler = Object.extend({ frame.set(val.value, v); this.emitLine(i + ' = -1;'); - - if(loopUses['revindex'] || loopUses['revindex0'] || - loopUses['last'] || loopUses['length']) { - this.emitLine('var ' + len + ' = runtime.keys(' + arr + ').length;'); - } - + this.emitLine('var ' + len + ' = runtime.keys(' + arr + ').length;'); this.emitLine('for(var ' + k + ' in ' + arr + ') {'); this.emitLine(i + '++;'); this.emitLine('var ' + v + ' = ' + arr + '[' + k + '];'); this.emitLine('frame.set("' + key.value + '", ' + k + ');'); this.emitLine('frame.set("' + val.value + '", ' + v + ');'); - this.emitLoopBindings(node, loopUses, arr, i, len); + this.emitLoopBindings(node, arr, i, len); this.withScopedSyntax(function() { this.compile(node.body, frame); }); @@ -3433,12 +3519,13 @@ var Compiler = Object.extend({ var v = this.tmpid(); frame.set(node.name.value, v); + this.emitLine('var ' + len + ' = ' + arr + '.length;'); this.emitLine('for(var ' + i + '=0; ' + i + ' < ' + arr + '.length; ' + i + '++) {'); this.emitLine('var ' + v + ' = ' + arr + '[' + i + '];'); this.emitLine('frame.set("' + node.name.value + '", ' + v + ');'); - this.emitLoopBindings(node, loopUses, arr, i); + this.emitLoopBindings(node, arr, i, len); this.withScopedSyntax(function() { this.compile(node.body, frame); @@ -3448,6 +3535,12 @@ var Compiler = Object.extend({ } this.emitLine('}'); + if (node.else_) { + this.emitLine('if (!' + len + ') {'); + this.compile(node.else_, frame); + this.emitLine('}'); + } + this.emitLine('frame = frame.pop();'); }, @@ -3459,7 +3552,6 @@ var Compiler = Object.extend({ var i = this.tmpid(); var len = this.tmpid(); var arr = this.tmpid(); - var loopUses = this.scanLoop(node); var asyncMethod = parallel ? 'asyncAll' : 'asyncEach'; frame = frame.push(); @@ -3492,7 +3584,7 @@ var Compiler = Object.extend({ frame.set(id, id); } - this.emitLoopBindings(node, loopUses, arr, i, len); + this.emitLoopBindings(node, arr, i, len); this.withScopedSyntax(function() { var buf; @@ -3517,6 +3609,12 @@ var Compiler = Object.extend({ this.emitLine(this.buffer + ' += ' + output + ';'); } + if (node.else_) { + this.emitLine('if (!' + arr + '.length) {'); + this.compile(node.else_, frame); + this.emitLine('}'); + } + this.emitLine('frame = frame.pop();'); }, @@ -3528,7 +3626,7 @@ var Compiler = Object.extend({ this._compileAsyncLoop(node, frame, true); }, - _emitMacroBegin: function(node, frame) { + _compileMacro: function(node, frame) { var args = []; var kwargs = null; var funcId = 'macro_' + this.tmpid(); @@ -3557,13 +3655,16 @@ var Compiler = Object.extend({ // arguments so support setting positional args with keywords // args and passing keyword args as positional args // (essentially default values). See runtime.js. + frame = frame.push(); this.emitLines( 'var ' + funcId + ' = runtime.makeMacro(', '[' + argNames.join(', ') + '], ', '[' + kwargNames.join(', ') + '], ', 'function (' + realNames.join(', ') + ') {', 'frame = frame.push();', - 'kwargs = kwargs || {};' + 'kwargs = kwargs || {};', + 'if (kwargs.hasOwnProperty("caller")) {', + 'frame.set("caller", kwargs.caller); }' ); // Expose the arguments to the template. Don't need to use @@ -3587,31 +3688,27 @@ var Compiler = Object.extend({ }, this); } - return funcId; - }, + var bufferId = this.tmpid(); + this.pushBufferId(bufferId); - _emitMacroEnd: function(bufferId) { + this.withScopedSyntax(function () { + this.compile(node.body, frame); + }); + + frame = frame.pop(); this.emitLine('frame = frame.pop();'); this.emitLine('return new runtime.SafeString(' + bufferId + ');'); this.emitLine('});'); + this.popBufferId(); + + return funcId; }, compileMacro: function(node, frame) { - frame = frame.push(); - var funcId = this._emitMacroBegin(node, frame); - var id = this.tmpid(); - this.pushBufferId(id); - - this.withScopedSyntax(function() { - this.compile(node.body, frame); - }); - - this._emitMacroEnd(id); - this.popBufferId(); + var funcId = this._compileMacro(node, frame); // Expose the macro to the templates var name = node.name.value; - frame = frame.pop(); frame.set(name, funcId); if(frame.parent) { @@ -3625,6 +3722,13 @@ var Compiler = Object.extend({ } }, + compileCaller: function(node, frame) { + // basically an anonymous "macro expression" + this.emit('(function (){'); + var funcId = this._compileMacro(node, frame); + this.emit('return ' + funcId + ';})()'); + }, + compileImport: function(node, frame) { var id = this.tmpid(); var target = node.target.value; @@ -3831,15 +3935,15 @@ var Compiler = Object.extend({ }); // var c = new Compiler(); -// var src = '{% macro foo() %}{% include "include.html" %}{% endmacro %} This is my template {{ foo() }}'; +// var src = '{% asyncEach i in arr %}{{ i }}{% else %}empty{% endeach %}'; // var ast = transformer.transform(parser.parse(src)); -//nodes.printNodes(ast); +// nodes.printNodes(ast); // c.compile(ast); // var tmpl = c.getCode(); // console.log(tmpl); modules['compiler'] = { - compile: function(src, asyncFilters, extensions, name) { + compile: function(src, asyncFilters, extensions, name, lexerTags) { var c = new Compiler(); // Run the extension preprocessors against the source. @@ -3851,7 +3955,7 @@ modules['compiler'] = { } } - c.compile(transformer.transform(parser.parse(src, extensions), + c.compile(transformer.transform(parser.parse(src, extensions, lexerTags), asyncFilters, name)); return c.getCode(); @@ -4050,6 +4154,10 @@ var filters = { }, replace: function(str, old, new_, maxCount) { + if (old instanceof RegExp) { + return str.replace(old, new_); + } + var res = str; var last = res; var count = 1; @@ -4247,8 +4355,10 @@ var filters = { }).map(function(word) { var matches = word.match(puncRE); + var possibleUrl = matches && matches[1] || word; + // url that starts with http or https if (httpHttpsRE.test(possibleUrl)) return '' + possibleUrl.substr(0, length) + ''; @@ -4265,7 +4375,7 @@ var filters = { if (tldRE.test(possibleUrl)) return '' + possibleUrl.substr(0, length) + ''; - return possibleUrl; + return word; }); @@ -4298,12 +4408,12 @@ modules['filters'] = filters; function cycler(items) { var index = -1; - var current = null; + this.current = null; return { reset: function() { index = -1; - current = null; + this.current = null; }, next: function() { @@ -4312,9 +4422,9 @@ function cycler(items) { index = 0; } - current = items[index]; - return current; - } + this.current = items[index]; + return this.current; + }, }; } @@ -4486,6 +4596,7 @@ var Environment = Obj.extend({ // defaults to false opts = opts || {}; this.dev = !!opts.dev; + this.lexerTags = opts.tags; // The autoescape flag sets global autoescaping. If true, // every string variable will be escaped by default. @@ -4512,10 +4623,6 @@ var Environment = Obj.extend({ this.extensions = {}; this.extensionsList = []; - if(opts.tags) { - lexer.setTags(opts.tags); - } - for(var name in builtin_filters) { this.addFilter(name, builtin_filters[name]); } @@ -4746,7 +4853,7 @@ var Context = Obj.extend({ }, getSuper: function(env, name, block, frame, runtime, cb) { - var idx = (this.blocks[name] || []).indexOf(block); + var idx = lib.indexOf(this.blocks[name] || [], block); var blk = this.blocks[name][idx + 1]; var context = this; @@ -4860,7 +4967,8 @@ var Template = Obj.extend({ var source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, - this.path); + this.path, + this.env.lexerTags); var func = new Function(source); props = func(); } diff --git a/browser/nunjucks.min.js b/browser/nunjucks.min.js index 7774ddc4..b976bc3d 100644 --- a/browser/nunjucks.min.js +++ b/browser/nunjucks.min.js @@ -1,5 +1,5 @@ -// Browser bundle of nunjucks 1.0.7 +// Browser bundle of nunjucks 1.1.0 -(function(){var modules={};(function(){function extend(cls,name,props){var F=function(){};F.prototype=cls.prototype;var prototype=new F;var fnTest=/xyz/.test(function(){xyz})?/\bparent\b/:/.*/;props=props||{};for(var k in props){var src=props[k];var parent=prototype[k];if(typeof parent=="function"&&typeof src=="function"&&fnTest.test(src)){prototype[k]=function(src,parent){return function(){var tmp=this.parent;this.parent=parent;var res=src.apply(this,arguments);this.parent=tmp;return res}}(src,parent)}else{prototype[k]=src}}prototype.typename=name;var new_cls=function(){if(prototype.init){prototype.init.apply(this,arguments)}};new_cls.prototype=prototype;new_cls.prototype.constructor=new_cls;new_cls.extend=function(name,props){if(typeof name=="object"){props=name;name="anonymous"}return extend(new_cls,name,props)};return new_cls}modules["object"]=extend(Object,"Object",{})})();(function(){var ArrayProto=Array.prototype;var ObjProto=Object.prototype;var escapeMap={"&":"&",'"':""","'":"'","<":"<",">":">"};var escapeRegex=/[&"'<>]/g;var lookupEscape=function(ch){return escapeMap[ch]};var exports=modules["lib"]={};exports.withPrettyErrors=function(path,withInternals,func){try{return func()}catch(e){if(!e.Update){e=new exports.TemplateError(e)}e.Update(path);if(!withInternals){var old=e;e=new Error(old.message);e.name=old.name}throw e}};exports.TemplateError=function(message,lineno,colno){var err=this;if(message instanceof Error){err=message;message=message.name+": "+message.message}else{if(Error.captureStackTrace){Error.captureStackTrace(err)}}err.name="Template render error";err.message=message;err.lineno=lineno;err.colno=colno;err.firstUpdate=true;err.Update=function(path){var message="("+(path||"unknown path")+")";if(this.firstUpdate){if(this.lineno&&this.colno){message+=" [Line "+this.lineno+", Column "+this.colno+"]"}else if(this.lineno){message+=" [Line "+this.lineno+"]"}}message+="\n ";if(this.firstUpdate){message+=" "}this.message=message+(this.message||"");this.firstUpdate=false;return this};return err};exports.TemplateError.prototype=Error.prototype;exports.escape=function(val){return val.replace(escapeRegex,lookupEscape)};exports.isFunction=function(obj){return ObjProto.toString.call(obj)=="[object Function]"};exports.isArray=Array.isArray||function(obj){return ObjProto.toString.call(obj)=="[object Array]"};exports.isString=function(obj){return ObjProto.toString.call(obj)=="[object String]"};exports.isObject=function(obj){return ObjProto.toString.call(obj)=="[object Object]"};exports.groupBy=function(obj,val){var result={};var iterator=exports.isFunction(val)?val:function(obj){return obj[val]};for(var i=0;i>>0;if(len===0){return-1}var n=0;if(arguments.length>2){n=Number(arguments[2]);if(n!=n){n=0}else if(n!=0&&n!=Infinity&&n!=-Infinity){n=(n>0||-1)*Math.floor(Math.abs(n))}}if(n>=len){return-1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;k0||!inline){for(var j=0;jargNames.length){args=Array.prototype.slice.call(arguments,0,argNames.length);var vals=Array.prototype.slice.call(arguments,args.length,argCount);for(var i=0;i=","//","**"];var curComplex=cur+this.current();var type;if(complexOps.indexOf(curComplex)!==-1){this.forward();cur=curComplex}switch(cur){case"(":type=TOKEN_LEFT_PAREN;break;case")":type=TOKEN_RIGHT_PAREN;break;case"[":type=TOKEN_LEFT_BRACKET;break;case"]":type=TOKEN_RIGHT_BRACKET;break;case"{":type=TOKEN_LEFT_CURLY;break;case"}":type=TOKEN_RIGHT_CURLY;break;case",":type=TOKEN_COMMA;break;case":":type=TOKEN_COLON;break;case"|":type=TOKEN_PIPE;break;default:type=TOKEN_OPERATOR}return token(type,cur,lineno,colno)}else{tok=this._extractUntil(whitespaceChars+delimChars);if(tok.match(/^[-+]?[0-9]+$/)){if(this.current()=="."){this.forward();var dec=this._extract(intChars);return token(TOKEN_FLOAT,tok+"."+dec,lineno,colno)}else{return token(TOKEN_INT,tok,lineno,colno)}}else if(tok.match(/^(true|false)$/)){return token(TOKEN_BOOLEAN,tok,lineno,colno)}else if(tok){return token(TOKEN_SYMBOL,tok,lineno,colno)}else{throw new Error("Unexpected value while parsing: "+tok)}}}else{var beginChars=BLOCK_START.charAt(0)+VARIABLE_START.charAt(0)+COMMENT_START.charAt(0)+COMMENT_END.charAt(0);var tok;if(this.is_finished()){return null}else if((tok=this._extractString(BLOCK_START+"-"))||(tok=this._extractString(BLOCK_START))){this.in_code=true;return token(TOKEN_BLOCK_START,tok,lineno,colno)}else if(tok=this._extractString(VARIABLE_START)){this.in_code=true;return token(TOKEN_VARIABLE_START,tok,lineno,colno)}else{tok="";var data;var in_comment=false;if(this._matches(COMMENT_START)){in_comment=true;tok=this._extractString(COMMENT_START)}while((data=this._extractUntil(beginChars))!==null){tok+=data;if((this._matches(BLOCK_START)||this._matches(VARIABLE_START)||this._matches(COMMENT_START))&&!in_comment){break}else if(this._matches(COMMENT_END)){if(!in_comment){throw new Error("unexpected end of comment")}tok+=this._extractString(COMMENT_END);break}else{tok+=this.current();this.forward()}}if(data===null&&in_comment){throw new Error("expected end of comment, got end of file")}return token(in_comment?TOKEN_COMMENT:TOKEN_DATA,tok,lineno,colno)}}throw new Error("Could not parse text")};Tokenizer.prototype.parseString=function(delimiter){this.forward();var lineno=this.lineno;var colno=this.colno;var str="";while(!this.is_finished()&&this.current()!=delimiter){var cur=this.current();if(cur=="\\"){this.forward();switch(this.current()){case"n":str+="\n";break;case"t":str+=" ";break;case"r":str+="\r";break;default:str+=this.current()}this.forward()}else{str+=cur;this.forward()}}this.forward();return str};Tokenizer.prototype._matches=function(str){if(this.index+str.length>this.length){return null}var m=this.str.slice(this.index,this.index+str.length);return m==str};Tokenizer.prototype._extractString=function(str){if(this._matches(str)){this.index+=str.length;return str}return null};Tokenizer.prototype._extractUntil=function(charString){return this._extractMatching(true,charString||"")};Tokenizer.prototype._extract=function(charString){return this._extractMatching(false,charString)};Tokenizer.prototype._extractMatching=function(breakOnMatch,charString){if(this.is_finished()){return null}var first=charString.indexOf(this.current());if(breakOnMatch&&first==-1||!breakOnMatch&&first!=-1){var t=this.current();this.forward();var idx=charString.indexOf(this.current());while((breakOnMatch&&idx==-1||!breakOnMatch&&idx!=-1)&&!this.is_finished()){t+=this.current();this.forward();idx=charString.indexOf(this.current())}return t}return""};Tokenizer.prototype.is_finished=function(){return this.index>=this.len};Tokenizer.prototype.forwardN=function(n){for(var i=0;i0&&!this.skip(lexer.TOKEN_COMMA)){this.fail("parseFrom: expected comma",fromTok.lineno,fromTok.colno)}var name=this.parsePrimary();if(name.value.charAt(0)=="_"){this.fail("parseFrom: 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")){this.fail("parseBlock: expected block",tag.lineno,tag.colno)}var node=new nodes.Block(tag.lineno,tag.colno);node.name=this.parsePrimary();if(!(node.name instanceof nodes.Symbol)){this.fail("parseBlock: variable name expected",tag.lineno,tag.colno)}this.advanceAfterBlockEnd(tag.value);node.body=this.parseUntilBlocks("endblock");if(!this.peekToken()){this.fail("parseBlock: expected endblock, got end of file")}this.advanceAfterBlockEnd();return node},parseTemplateRef:function(tagName,nodeType){var tag=this.peekToken();if(!this.skipSymbol(tagName)){this.fail("parseTemplateRef: expected "+tagName)}var node=new nodeType(tag.lineno,tag.colno);node.template=this.parseExpression();this.advanceAfterBlockEnd(tag.value);return node},parseExtends:function(){return this.parseTemplateRef("extends",nodes.Extends)},parseInclude:function(){return this.parseTemplateRef("include",nodes.Include)},parseIf:function(){var tag=this.peekToken();var node;if(this.skipSymbol("if")||this.skipSymbol("elif")){node=new nodes.If(tag.lineno,tag.colno)}else if(this.skipSymbol("ifAsync")){node=new nodes.IfAsync(tag.lineno,tag.colno)}else{this.fail("parseIf: expected if or elif",tag.lineno,tag.colno)}node.cond=this.parseExpression();this.advanceAfterBlockEnd(tag.value);node.body=this.parseUntilBlocks("elif","else","endif");var tok=this.peekToken();switch(tok&&tok.value){case"elif":node.else_=this.parseIf();break;case"else":this.advanceAfterBlockEnd();node.else_=this.parseUntilBlocks("endif");this.advanceAfterBlockEnd();break;case"endif":node.else_=null;this.advanceAfterBlockEnd();break;default:this.fail("parseIf: expected endif, else, or endif, "+"got end of file")}return node},parseSet:function(){var tag=this.peekToken();if(!this.skipSymbol("set")){this.fail("parseSet: expected set",tag.lineno,tag.colno)}var node=new nodes.Set(tag.lineno,tag.colno,[]);var target;while(target=this.parsePrimary()){node.targets.push(target);if(!this.skip(lexer.TOKEN_COMMA)){break}}if(!this.skipValue(lexer.TOKEN_OPERATOR,"=")){this.fail("parseSet: expected = in set tag",tag.lineno,tag.colno)}node.value=this.parseExpression();this.advanceAfterBlockEnd(tag.value);return node},parseStatement:function(){var tok=this.peekToken();var node;if(tok.type!=lexer.TOKEN_SYMBOL){this.fail("tag name expected",tok.lineno,tok.colno)}if(this.breakOnBlocks&&this.breakOnBlocks.indexOf(tok.value)!==-1){return null}switch(tok.value){case"raw":return this.parseRaw();case"if":case"ifAsync":return this.parseIf();case"for":case"asyncEach":case"asyncAll":return this.parseFor();case"block":return this.parseBlock();case"extends":return this.parseExtends();case"include":return this.parseInclude();case"set":return this.parseSet();case"macro":return this.parseMacro();case"import":return this.parseImport();case"from":return this.parseFrom();default:if(this.extensions.length){for(var i=0;i1){this.fail("invalid index")}node=new nodes.LookupVal(tok.lineno,tok.colno,node,lookup.children[0])}else if(tok.type==lexer.TOKEN_OPERATOR&&tok.value=="."){this.nextToken();var val=this.nextToken();if(val.type!=lexer.TOKEN_SYMBOL){this.fail("expected name as lookup value, got "+val.value,val.lineno,val.colno)}var lookup=new nodes.Literal(val.lineno,val.colno,val.value);node=new nodes.LookupVal(tok.lineno,tok.colno,node,lookup)}else{break}tok=this.peekToken()}return node},parseExpression:function(){var node=this.parseInlineIf();return node},parseInlineIf:function(){var node=this.parseOr(); -if(this.skipSymbol("if")){var cond_node=this.parseOr();var body_node=node;node=new nodes.InlineIf(node.lineno,node.colno);node.body=body_node;node.cond=cond_node;if(this.skipSymbol("else")){node.else_=this.parseOr()}else{node.else_=null}}return node},parseOr:function(){var node=this.parseAnd();while(this.skipSymbol("or")){var node2=this.parseAnd();node=new nodes.Or(node.lineno,node.colno,node,node2)}return node},parseAnd:function(){var node=this.parseNot();while(this.skipSymbol("and")){var node2=this.parseNot();node=new nodes.And(node.lineno,node.colno,node,node2)}return node},parseNot:function(){var tok=this.peekToken();if(this.skipSymbol("not")){return new nodes.Not(tok.lineno,tok.colno,this.parseNot())}return this.parseCompare()},parseCompare:function(){var compareOps=["==","!=","<",">","<=",">="];var expr=this.parseAdd();var ops=[];while(1){var tok=this.nextToken();if(!tok){break}else if(compareOps.indexOf(tok.value)!==-1){ops.push(new nodes.CompareOperand(tok.lineno,tok.colno,this.parseAdd(),tok.value))}else if(tok.type==lexer.TOKEN_SYMBOL&&tok.value=="in"){ops.push(new nodes.CompareOperand(tok.lineno,tok.colno,this.parseAdd(),"in"))}else if(tok.type==lexer.TOKEN_SYMBOL&&tok.value=="not"&&this.skipSymbol("in")){ops.push(new nodes.CompareOperand(tok.lineno,tok.colno,this.parseAdd(),"notin"))}else{this.pushToken(tok);break}}if(ops.length){return new nodes.Compare(ops[0].lineno,ops[0].colno,expr,ops)}else{return expr}},parseAdd:function(){var node=this.parseSub();while(this.skipValue(lexer.TOKEN_OPERATOR,"+")){var node2=this.parseSub();node=new nodes.Add(node.lineno,node.colno,node,node2)}return node},parseSub:function(){var node=this.parseMul();while(this.skipValue(lexer.TOKEN_OPERATOR,"-")){var node2=this.parseMul();node=new nodes.Sub(node.lineno,node.colno,node,node2)}return node},parseMul:function(){var node=this.parseDiv();while(this.skipValue(lexer.TOKEN_OPERATOR,"*")){var node2=this.parseDiv();node=new nodes.Mul(node.lineno,node.colno,node,node2)}return node},parseDiv:function(){var node=this.parseFloorDiv();while(this.skipValue(lexer.TOKEN_OPERATOR,"/")){var node2=this.parseFloorDiv();node=new nodes.Div(node.lineno,node.colno,node,node2)}return node},parseFloorDiv:function(){var node=this.parseMod();while(this.skipValue(lexer.TOKEN_OPERATOR,"//")){var node2=this.parseMod();node=new nodes.FloorDiv(node.lineno,node.colno,node,node2)}return node},parseMod:function(){var node=this.parsePow();while(this.skipValue(lexer.TOKEN_OPERATOR,"%")){var node2=this.parsePow();node=new nodes.Mod(node.lineno,node.colno,node,node2)}return node},parsePow:function(){var node=this.parseUnary();while(this.skipValue(lexer.TOKEN_OPERATOR,"**")){var node2=this.parseUnary();node=new nodes.Pow(node.lineno,node.colno,node,node2)}return node},parseUnary:function(noFilters){var tok=this.peekToken();var node;if(this.skipValue(lexer.TOKEN_OPERATOR,"-")){node=new nodes.Neg(tok.lineno,tok.colno,this.parseUnary(true))}else if(this.skipValue(lexer.TOKEN_OPERATOR,"+")){node=new nodes.Pos(tok.lineno,tok.colno,this.parseUnary(true))}else{node=this.parsePrimary()}if(!noFilters){node=this.parseFilter(node)}return node},parsePrimary:function(noPostfix){var tok=this.nextToken();var val=null;var node=null;if(!tok){this.fail("expected expression, got end of file")}else if(tok.type==lexer.TOKEN_STRING){val=tok.value}else if(tok.type==lexer.TOKEN_INT){val=parseInt(tok.value,10)}else if(tok.type==lexer.TOKEN_FLOAT){val=parseFloat(tok.value)}else if(tok.type==lexer.TOKEN_BOOLEAN){if(tok.value=="true"){val=true}else if(tok.value=="false"){val=false}else{this.fail("invalid boolean: "+tok.val,tok.lineno,tok.colno)}}if(val!==null){node=new nodes.Literal(tok.lineno,tok.colno,val)}else if(tok.type==lexer.TOKEN_SYMBOL){node=new nodes.Symbol(tok.lineno,tok.colno,tok.value);if(!noPostfix){node=this.parsePostfix(node)}}else{this.pushToken(tok);node=this.parseAggregate()}if(node){return node}else{this.fail("unexpected token: "+tok.value,tok.lineno,tok.colno)}},parseFilter:function(node){while(this.skip(lexer.TOKEN_PIPE)){var tok=this.expect(lexer.TOKEN_SYMBOL);var name=tok.value;while(this.skipValue(lexer.TOKEN_OPERATOR,".")){name+="."+this.expect(lexer.TOKEN_SYMBOL).value}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){var call=this.parsePostfix(node);node.args.children=node.args.children.concat(call.args.children)}}return node},parseAggregate:function(){var tok=this.nextToken();var node;switch(tok.type){case lexer.TOKEN_LEFT_PAREN:node=new nodes.Group(tok.lineno,tok.colno);break;case lexer.TOKEN_LEFT_BRACKET:node=new nodes.Array(tok.lineno,tok.colno);break;case lexer.TOKEN_LEFT_CURLY:node=new nodes.Dict(tok.lineno,tok.colno);break;default:return null}while(1){var type=this.peekToken().type;if(type==lexer.TOKEN_RIGHT_PAREN||type==lexer.TOKEN_RIGHT_BRACKET||type==lexer.TOKEN_RIGHT_CURLY){this.nextToken();break}if(node.children.length>0){if(!this.skip(lexer.TOKEN_COMMA)){this.fail("parseAggregate: expected comma after expression",tok.lineno,tok.colno)}}if(node instanceof nodes.Dict){var key=this.parsePrimary();if(!this.skip(lexer.TOKEN_COLON)){this.fail("parseAggregate: expected colon after dict key",tok.lineno,tok.colno)}var value=this.parseExpression();node.addChild(new nodes.Pair(key.lineno,key.colno,key,value))}else{var expr=this.parseExpression();node.addChild(expr)}}return node},parseSignature:function(tolerant,noParens){var tok=this.peekToken();if(!noParens&&tok.type!=lexer.TOKEN_LEFT_PAREN){if(tolerant){return null}else{this.fail("expected arguments",tok.lineno,tok.colno)}}if(tok.type==lexer.TOKEN_LEFT_PAREN){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){tok=this.peekToken();if(!noParens&&tok.type==lexer.TOKEN_RIGHT_PAREN){this.nextToken();break}else if(noParens&&tok.type==lexer.TOKEN_BLOCK_END){break}if(checkComma&&!this.skip(lexer.TOKEN_COMMA)){this.fail("parseSignature: expected comma after expression",tok.lineno,tok.colno)}else{var arg=this.parseExpression();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(){var prev=this.breakOnBlocks;this.breakOnBlocks=lib.toArray(arguments);var ret=this.parse();this.breakOnBlocks=prev;return ret},parseNodes:function(){var tok;var buf=[];while(tok=this.nextToken()){if(tok.type==lexer.TOKEN_DATA){var data=tok.value;var nextToken=this.peekToken();var nextVal=nextToken&&nextToken.value;if(this.dropLeadingWhitespace){data=data.replace(/^\s*/,"");this.dropLeadingWhitespace=false}if(nextToken&&nextToken.type==lexer.TOKEN_BLOCK_START&&nextVal.charAt(nextVal.length-1)=="-"){data=data.replace(/\s*$/,"")}buf.push(new nodes.Output(tok.lineno,tok.colno,[new nodes.TemplateData(tok.lineno,tok.colno,data)]))}else if(tok.type==lexer.TOKEN_BLOCK_START){var n=this.parseStatement();if(!n){break}buf.push(n)}else if(tok.type==lexer.TOKEN_VARIABLE_START){var e=this.parseExpression();this.advanceAfterVariableEnd();buf.push(new nodes.Output(tok.lineno,tok.colno,[e]))}else if(tok.type!=lexer.TOKEN_COMMENT){this.fail("Unexpected token at top-level: "+tok.type,tok.lineno,tok.colno)}}return buf},parse:function(){return new nodes.NodeList(0,0,this.parseNodes())},parseAsRoot:function(){return new nodes.Root(0,0,this.parseNodes())}});modules["parser"]={parse:function(src,extensions){var p=new Parser(lexer.lex(src));if(extensions!==undefined){p.extensions=extensions}return p.parseAsRoot()}}})();(function(){var nodes=modules["nodes"];var sym=0;function gensym(){return"hole_"+sym++}function mapCOW(arr,func){var res=null;for(var i=0;i":">","<=":"<=",">=":">="};function binOpEmitter(str){return function(node,frame){this.compile(node.left,frame);this.emit(str);this.compile(node.right,frame)}}function quotedArray(arr){return"["+lib.map(arr,function(x){return'"'+x+'"'})+"]"}var Compiler=Object.extend({init:function(){this.codebuf=[];this.lastId=0;this.buffer=null;this.bufferStack=[];this.isChild=false;this.scopeClosers=""},fail:function(msg,lineno,colno){if(lineno!==undefined)lineno+=1;if(colno!==undefined)colno+=1;throw new lib.TemplateError(msg,lineno,colno)},pushBufferId:function(id){this.bufferStack.push(this.buffer);this.buffer=id;this.emit("var "+this.buffer+' = "";')},popBufferId:function(){this.buffer=this.bufferStack.pop()},emit:function(code){this.codebuf.push(code)},emitLine:function(code){this.emit(code+"\n")},emitLines:function(){lib.each(lib.toArray(arguments),function(line){this.emitLine(line)},this)},emitFuncBegin:function(name){this.buffer="output";this.scopeClosers="";this.emitLine("function "+name+"(env, context, frame, runtime, cb) {");this.emitLine("var lineno = null;");this.emitLine("var colno = null;");this.emitLine("var "+this.buffer+' = "";');this.emitLine("try {")},emitFuncEnd:function(noReturn){if(!noReturn){this.emitLine("cb(null, "+this.buffer+");")}this.closeScopeLevels();this.emitLine("} catch (e) {");this.emitLine(" cb(runtime.handleError(e, lineno, colno));");this.emitLine("}");this.emitLine("}");this.buffer=null},addScopeLevel:function(){this.scopeClosers+="})"},closeScopeLevels:function(){this.emitLine(this.scopeClosers+";");this.scopeClosers=""},withScopedSyntax:function(func){var scopeClosers=this.scopeClosers;this.scopeClosers="";func.call(this);this.closeScopeLevels();this.scopeClosers=scopeClosers},makeCallback:function(res){var err=this.tmpid();return"function("+err+(res?","+res:"")+") {\n"+"if("+err+") { cb("+err+"); return; }"},tmpid:function(){this.lastId++;return"t_"+this.lastId},_bufferAppend:function(func){this.emit(this.buffer+" += runtime.suppressValue(");func.call(this);this.emit(", env.autoesc);\n")},_compileChildren:function(node,frame){var children=node.children;for(var i=0,l=children.length;i0){this.emit(",")}this.compile(node.children[i],frame)}if(endChar){this.emit(endChar)}},_compileExpression:function(node,frame){this.assertType(node,nodes.Literal,nodes.Symbol,nodes.Group,nodes.Array,nodes.Dict,nodes.FunCall,nodes.Filter,nodes.LookupVal,nodes.Compare,nodes.InlineIf,nodes.And,nodes.Or,nodes.Not,nodes.Add,nodes.Sub,nodes.Mul,nodes.Div,nodes.FloorDiv,nodes.Mod,nodes.Pow,nodes.Neg,nodes.Pos,nodes.Compare,nodes.NodeList);this.compile(node,frame)},assertType:function(node){var types=lib.toArray(arguments).slice(1);var success=false;for(var i=0;i0){this.emit(",")}if(arg){var id=this.tmpid();this.emitLine("function(cb) {");this.emitLine("if(!cb) { cb = function(err) { if(err) { throw err; }}}");this.pushBufferId(id);this.withScopedSyntax(function(){this.compile(arg,frame);this.emitLine("cb(null, "+id+");")});this.popBufferId();this.emitLine("return "+id+";");this.emitLine("}")}else{this.emit("null")}},this)}if(async){var res=this.tmpid();this.emitLine(", "+this.makeCallback(res));this.emitLine(this.buffer+" += runtime.suppressValue("+res+", "+autoescape+" && env.autoesc);");this.addScopeLevel()}else{this.emit(")");this.emit(", "+autoescape+" && env.autoesc);\n")}},compileCallExtensionAsync:function(node,frame){this.compileCallExtension(node,frame,true)},compileNodeList:function(node,frame){this._compileChildren(node,frame)},compileLiteral:function(node,frame){if(typeof node.value=="string"){var val=node.value.replace(/\\/g,"\\\\");val=val.replace(/"/g,'\\"');val=val.replace(/\n/g,"\\n");val=val.replace(/\r/g,"\\r");val=val.replace(/\t/g,"\\t");this.emit('"'+val+'"')}else{this.emit(node.value.toString())}},compileSymbol:function(node,frame){var name=node.value;var v;if(v=frame.lookup(name)){this.emit(v)}else{this.emit("runtime.contextOrFrameLookup("+'context, frame, "'+name+'")')}},compileGroup:function(node,frame){this._compileAggregate(node,frame,"(",")")},compileArray:function(node,frame){this._compileAggregate(node,frame,"[","]")},compileDict:function(node,frame){this._compileAggregate(node,frame,"{","}")},compilePair:function(node,frame){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 key.value=="string")){this.fail("compilePair: Dict keys must be strings or names",key.lineno,key.colno)}this.compile(key,frame);this.emit(": ");this._compileExpression(val,frame)},compileInlineIf:function(node,frame){this.emit("(");this.compile(node.cond,frame);this.emit("?");this.compile(node.body,frame);this.emit(":");if(node.else_!==null)this.compile(node.else_,frame);else this.emit('""');this.emit(")")},compileOr:binOpEmitter(" || "),compileAnd:binOpEmitter(" && "),compileAdd:binOpEmitter(" + "),compileSub:binOpEmitter(" - "),compileMul:binOpEmitter(" * "),compileDiv:binOpEmitter(" / "),compileMod:binOpEmitter(" % "),compileNot:function(node,frame){this.emit("!");this.compile(node.target,frame)},compileFloorDiv:function(node,frame){this.emit("Math.floor(");this.compile(node.left,frame);this.emit(" / ");this.compile(node.right,frame);this.emit(")")},compilePow:function(node,frame){this.emit("Math.pow(");this.compile(node.left,frame);this.emit(", ");this.compile(node.right,frame);this.emit(")")},compileNeg:function(node,frame){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=width){return str}var spaces=width-str.length;var pre=lib.repeat(" ",spaces/2-spaces%2);var post=lib.repeat(" ",spaces/2);return r.copySafeness(str,pre+str+post)},"default":function(val,def){return val?val:def},dictsort:function(val,case_sensitive,by){if(!lib.isObject(val)){throw new lib.TemplateError("dictsort filter: val must be an object")}var array=[];for(var k in val){array.push([k,val[k]])}var si;if(by===undefined||by==="key"){si=0}else if(by==="value"){si=1}else{throw new lib.TemplateError("dictsort filter: You can only sort by either key or value")}array.sort(function(t1,t2){var a=t1[si];var b=t2[si];if(!case_sensitive){if(lib.isString(a)){a=a.toUpperCase()}if(lib.isString(b)){b=b.toUpperCase()}}return a>b?1:a==b?0:-1});return array},escape:function(str){if(typeof str=="string"||str instanceof r.SafeString){return lib.escape(str)}return str},safe:function(str){return r.markSafe(str)},first:function(arr){return arr[0]},groupby:function(arr,attr){return lib.groupBy(arr,attr)},indent:function(str,width,indentfirst){width=width||4;var res="";var lines=str.split("\n");var sp=lib.repeat(" ",width);for(var i=0;i=maxCount){break}last=res;res=res.replace(old,new_);count++}return r.copySafeness(str,res)},reverse:function(val){var arr;if(lib.isString(val)){arr=filters.list(val)}else{arr=lib.map(val,function(v){return v})}arr.reverse();if(lib.isString(val)){return r.copySafeness(val,arr.join(""))}return arr},round:function(val,precision,method){precision=precision||0;var factor=Math.pow(10,precision);var rounder;if(method=="ceil"){rounder=Math.ceil}else if(method=="floor"){rounder=Math.floor}else{rounder=Math.round}return rounder(val*factor)/factor},slice:function(arr,slices,fillWith){var sliceLength=Math.floor(arr.length/slices);var extra=arr.length%slices;var offset=0;var res=[];for(var i=0;i=extra){slice.push(fillWith)}res.push(slice)}return res},sort:function(arr,reverse,caseSens,attr){arr=lib.map(arr,function(v){return v});arr.sort(function(a,b){var x,y;if(attr){x=a[attr];y=b[attr]}else{x=a;y=b}if(!caseSens&&lib.isString(x)&&lib.isString(y)){x=x.toLowerCase();y=y.toLowerCase()}if(xy){return reverse?-1:1}else{return 0}});return arr},string:function(obj){return r.copySafeness(obj,obj)},title:function(str){var words=str.split(" ");for(var i=0;i"+possibleUrl.substr(0,length)+"";if(wwwRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";if(emailRE.test(possibleUrl))return''+possibleUrl+"";if(tldRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";return possibleUrl});return words.join(" ")},wordcount:function(str){var words=str?str.match(/\w+/g):null;return words?words.length:null},"float":function(val,def){var res=parseFloat(val);return isNaN(res)?def:res},"int":function(val,def){var res=parseInt(val,10);return isNaN(res)?def:res}};filters.d=filters["default"];filters.e=filters.escape;modules["filters"]=filters})();(function(){function cycler(items){var index=-1;var current=null;return{reset:function(){index=-1;current=null},next:function(){index++;if(index>=items.length){index=0}current=items[index];return current}}}function joiner(sep){sep=sep||",";var first=true;return function(){var val=first?"":sep;first=false;return val}}var globals={range:function(start,stop,step){if(!stop){stop=start;start=0;step=1}else if(!step){step=1}var arr=[];for(var i=start;i":">"};var escapeRegex=/[&"'<>]/g;var lookupEscape=function(ch){return escapeMap[ch]};var exports=modules["lib"]={};exports.withPrettyErrors=function(path,withInternals,func){try{return func()}catch(e){if(!e.Update){e=new exports.TemplateError(e)}e.Update(path);if(!withInternals){var old=e;e=new Error(old.message);e.name=old.name}throw e}};exports.TemplateError=function(message,lineno,colno){var err=this;if(message instanceof Error){err=message;message=message.name+": "+message.message}else{if(Error.captureStackTrace){Error.captureStackTrace(err)}}err.name="Template render error";err.message=message;err.lineno=lineno;err.colno=colno;err.firstUpdate=true;err.Update=function(path){var message="("+(path||"unknown path")+")";if(this.firstUpdate){if(this.lineno&&this.colno){message+=" [Line "+this.lineno+", Column "+this.colno+"]"}else if(this.lineno){message+=" [Line "+this.lineno+"]"}}message+="\n ";if(this.firstUpdate){message+=" "}this.message=message+(this.message||"");this.firstUpdate=false;return this};return err};exports.TemplateError.prototype=Error.prototype;exports.escape=function(val){return val.replace(escapeRegex,lookupEscape)};exports.isFunction=function(obj){return ObjProto.toString.call(obj)=="[object Function]"};exports.isArray=Array.isArray||function(obj){return ObjProto.toString.call(obj)=="[object Array]"};exports.isString=function(obj){return ObjProto.toString.call(obj)=="[object String]"};exports.isObject=function(obj){return ObjProto.toString.call(obj)=="[object Object]"};exports.groupBy=function(obj,val){var result={};var iterator=exports.isFunction(val)?val:function(obj){return obj[val]};for(var i=0;i>>0;fromIndex=+fromIndex||0;if(Math.abs(fromIndex)===Infinity){fromIndex=0}if(fromIndex<0){fromIndex+=length;if(fromIndex<0){fromIndex=0}}for(;fromIndex0||!inline){for(var j=0;jargNames.length){args=Array.prototype.slice.call(arguments,0,argNames.length);var vals=Array.prototype.slice.call(arguments,args.length,argCount);for(var i=0;i=","//","**"];var curComplex=cur+this.current();var type;if(lib.indexOf(complexOps,curComplex)!==-1){this.forward();cur=curComplex}switch(cur){case"(":type=TOKEN_LEFT_PAREN;break;case")":type=TOKEN_RIGHT_PAREN;break;case"[":type=TOKEN_LEFT_BRACKET;break;case"]":type=TOKEN_RIGHT_BRACKET;break;case"{":type=TOKEN_LEFT_CURLY;break;case"}":type=TOKEN_RIGHT_CURLY;break;case",":type=TOKEN_COMMA;break;case":":type=TOKEN_COLON;break;case"|":type=TOKEN_PIPE;break;default:type=TOKEN_OPERATOR}return token(type,cur,lineno,colno)}else{tok=this._extractUntil(whitespaceChars+delimChars);if(tok.match(/^[-+]?[0-9]+$/)){if(this.current()=="."){this.forward();var dec=this._extract(intChars);return token(TOKEN_FLOAT,tok+"."+dec,lineno,colno)}else{return token(TOKEN_INT,tok,lineno,colno)}}else if(tok.match(/^(true|false)$/)){return token(TOKEN_BOOLEAN,tok,lineno,colno)}else if(tok){return token(TOKEN_SYMBOL,tok,lineno,colno)}else{throw new Error("Unexpected value while parsing: "+tok)}}}else{var beginChars=this.tags.BLOCK_START.charAt(0)+this.tags.VARIABLE_START.charAt(0)+this.tags.COMMENT_START.charAt(0)+this.tags.COMMENT_END.charAt(0);var tok;if(this.is_finished()){return null}else if((tok=this._extractString(this.tags.BLOCK_START+"-"))||(tok=this._extractString(this.tags.BLOCK_START))){this.in_code=true;return token(TOKEN_BLOCK_START,tok,lineno,colno)}else if(tok=this._extractString(this.tags.VARIABLE_START)){this.in_code=true;return token(TOKEN_VARIABLE_START,tok,lineno,colno)}else{tok="";var data;var in_comment=false;if(this._matches(this.tags.COMMENT_START)){in_comment=true;tok=this._extractString(this.tags.COMMENT_START)}while((data=this._extractUntil(beginChars))!==null){tok+=data;if((this._matches(this.tags.BLOCK_START)||this._matches(this.tags.VARIABLE_START)||this._matches(this.tags.COMMENT_START))&&!in_comment){break}else if(this._matches(this.tags.COMMENT_END)){if(!in_comment){throw new Error("unexpected end of comment")}tok+=this._extractString(this.tags.COMMENT_END);break}else{tok+=this.current();this.forward()}}if(data===null&&in_comment){throw new Error("expected end of comment, got end of file")}return token(in_comment?TOKEN_COMMENT:TOKEN_DATA,tok,lineno,colno)}}throw new Error("Could not parse text")};Tokenizer.prototype.parseString=function(delimiter){this.forward();var lineno=this.lineno;var colno=this.colno;var str="";while(!this.is_finished()&&this.current()!=delimiter){var cur=this.current();if(cur=="\\"){this.forward();switch(this.current()){case"n":str+="\n";break;case"t":str+=" ";break;case"r":str+="\r";break;default:str+=this.current()}this.forward()}else{str+=cur;this.forward()}}this.forward();return str};Tokenizer.prototype._matches=function(str){if(this.index+str.length>this.length){return null}var m=this.str.slice(this.index,this.index+str.length);return m==str};Tokenizer.prototype._extractString=function(str){if(this._matches(str)){this.index+=str.length;return str}return null};Tokenizer.prototype._extractUntil=function(charString){return this._extractMatching(true,charString||"")};Tokenizer.prototype._extract=function(charString){return this._extractMatching(false,charString)};Tokenizer.prototype._extractMatching=function(breakOnMatch,charString){if(this.is_finished()){return null}var first=charString.indexOf(this.current());if(breakOnMatch&&first==-1||!breakOnMatch&&first!=-1){var t=this.current();this.forward();var idx=charString.indexOf(this.current());while((breakOnMatch&&idx==-1||!breakOnMatch&&idx!=-1)&&!this.is_finished()){t+=this.current();this.forward();idx=charString.indexOf(this.current())}return t}return""};Tokenizer.prototype.is_finished=function(){return this.index>=this.len};Tokenizer.prototype.forwardN=function(n){for(var i=0;i0&&!this.skip(lexer.TOKEN_COMMA)){this.fail("parseFrom: expected comma",fromTok.lineno,fromTok.colno)}var name=this.parsePrimary();if(name.value.charAt(0)=="_"){this.fail("parseFrom: 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")){this.fail("parseBlock: expected block",tag.lineno,tag.colno)}var node=new nodes.Block(tag.lineno,tag.colno);node.name=this.parsePrimary();if(!(node.name instanceof nodes.Symbol)){this.fail("parseBlock: variable name expected",tag.lineno,tag.colno)}this.advanceAfterBlockEnd(tag.value);node.body=this.parseUntilBlocks("endblock");if(!this.peekToken()){this.fail("parseBlock: expected endblock, got end of file")}this.advanceAfterBlockEnd();return node},parseTemplateRef:function(tagName,nodeType){var tag=this.peekToken();if(!this.skipSymbol(tagName)){this.fail("parseTemplateRef: expected "+tagName)}var node=new nodeType(tag.lineno,tag.colno);node.template=this.parseExpression();this.advanceAfterBlockEnd(tag.value);return node},parseExtends:function(){return this.parseTemplateRef("extends",nodes.Extends)},parseInclude:function(){return this.parseTemplateRef("include",nodes.Include)},parseIf:function(){var tag=this.peekToken();var node;if(this.skipSymbol("if")||this.skipSymbol("elif")){node=new nodes.If(tag.lineno,tag.colno)}else if(this.skipSymbol("ifAsync")){node=new nodes.IfAsync(tag.lineno,tag.colno)}else{this.fail("parseIf: expected if or elif",tag.lineno,tag.colno)}node.cond=this.parseExpression();this.advanceAfterBlockEnd(tag.value);node.body=this.parseUntilBlocks("elif","else","endif");var tok=this.peekToken();switch(tok&&tok.value){case"elif":node.else_=this.parseIf();break;case"else":this.advanceAfterBlockEnd();node.else_=this.parseUntilBlocks("endif");this.advanceAfterBlockEnd();break;case"endif":node.else_=null;this.advanceAfterBlockEnd();break;default:this.fail("parseIf: expected endif, else, or endif, "+"got end of file")}return node},parseSet:function(){var tag=this.peekToken();if(!this.skipSymbol("set")){this.fail("parseSet: expected set",tag.lineno,tag.colno)}var node=new nodes.Set(tag.lineno,tag.colno,[]);var target;while(target=this.parsePrimary()){node.targets.push(target);if(!this.skip(lexer.TOKEN_COMMA)){break}}if(!this.skipValue(lexer.TOKEN_OPERATOR,"=")){this.fail("parseSet: expected = in set tag",tag.lineno,tag.colno)}node.value=this.parseExpression();this.advanceAfterBlockEnd(tag.value);return node},parseStatement:function(){var tok=this.peekToken();var node;if(tok.type!=lexer.TOKEN_SYMBOL){this.fail("tag name expected",tok.lineno,tok.colno)}if(this.breakOnBlocks&&lib.indexOf(this.breakOnBlocks,tok.value)!==-1){return null}switch(tok.value){case"raw":return this.parseRaw();case"if":case"ifAsync":return this.parseIf();case"for":case"asyncEach":case"asyncAll":return this.parseFor();case"block":return this.parseBlock();case"extends":return this.parseExtends();case"include":return this.parseInclude();case"set":return this.parseSet();case"macro":return this.parseMacro();case"call":return this.parseCall();case"import":return this.parseImport();case"from":return this.parseFrom();default:if(this.extensions.length){for(var i=0;i1){this.fail("invalid index")}node=new nodes.LookupVal(tok.lineno,tok.colno,node,lookup.children[0])}else if(tok.type==lexer.TOKEN_OPERATOR&&tok.value=="."){this.nextToken();var val=this.nextToken();if(val.type!=lexer.TOKEN_SYMBOL){this.fail("expected name as lookup value, got "+val.value,val.lineno,val.colno)}var lookup=new nodes.Literal(val.lineno,val.colno,val.value);node=new nodes.LookupVal(tok.lineno,tok.colno,node,lookup)}else{break}tok=this.peekToken()}return node},parseExpression:function(){var node=this.parseInlineIf();return node},parseInlineIf:function(){var node=this.parseIn();if(this.skipSymbol("if")){var cond_node=this.parseIn();var body_node=node;node=new nodes.InlineIf(node.lineno,node.colno);node.body=body_node;node.cond=cond_node;if(this.skipSymbol("else")){node.else_=this.parseIn()}else{node.else_=null}}return node},parseIn:function(){var node=this.parseOr();while(1){var tok=this.nextToken();if(!tok){break}var invert=tok.type==lexer.TOKEN_SYMBOL&&tok.value=="not";if(!invert){this.pushToken(tok)}if(this.skipSymbol("in")){var node2=this.parseOr();node=new nodes.In(node.lineno,node.colno,node,node2);if(invert){node=new nodes.Not(node.lineno,node.colno,node)}}else{if(invert){this.pushToken(tok)}break}}return node},parseOr:function(){var node=this.parseAnd();while(this.skipSymbol("or")){var node2=this.parseAnd();node=new nodes.Or(node.lineno,node.colno,node,node2)}return node},parseAnd:function(){var node=this.parseNot();while(this.skipSymbol("and")){var node2=this.parseNot();node=new nodes.And(node.lineno,node.colno,node,node2)}return node},parseNot:function(){var tok=this.peekToken();if(this.skipSymbol("not")){return new nodes.Not(tok.lineno,tok.colno,this.parseNot())}return this.parseCompare()},parseCompare:function(){var compareOps=["==","!=","<",">","<=",">="];var expr=this.parseAdd();var ops=[];while(1){var tok=this.nextToken();if(!tok){break}else if(lib.indexOf(compareOps,tok.value)!==-1){ops.push(new nodes.CompareOperand(tok.lineno,tok.colno,this.parseAdd(),tok.value))}else{this.pushToken(tok);break}}if(ops.length){return new nodes.Compare(ops[0].lineno,ops[0].colno,expr,ops)}else{return expr}},parseAdd:function(){var node=this.parseSub();while(this.skipValue(lexer.TOKEN_OPERATOR,"+")){var node2=this.parseSub();node=new nodes.Add(node.lineno,node.colno,node,node2)}return node},parseSub:function(){var node=this.parseMul();while(this.skipValue(lexer.TOKEN_OPERATOR,"-")){var node2=this.parseMul();node=new nodes.Sub(node.lineno,node.colno,node,node2)}return node},parseMul:function(){var node=this.parseDiv();while(this.skipValue(lexer.TOKEN_OPERATOR,"*")){var node2=this.parseDiv();node=new nodes.Mul(node.lineno,node.colno,node,node2)}return node},parseDiv:function(){var node=this.parseFloorDiv();while(this.skipValue(lexer.TOKEN_OPERATOR,"/")){var node2=this.parseFloorDiv();node=new nodes.Div(node.lineno,node.colno,node,node2)}return node},parseFloorDiv:function(){var node=this.parseMod();while(this.skipValue(lexer.TOKEN_OPERATOR,"//")){var node2=this.parseMod();node=new nodes.FloorDiv(node.lineno,node.colno,node,node2)}return node},parseMod:function(){var node=this.parsePow();while(this.skipValue(lexer.TOKEN_OPERATOR,"%")){var node2=this.parsePow();node=new nodes.Mod(node.lineno,node.colno,node,node2)}return node},parsePow:function(){var node=this.parseUnary();while(this.skipValue(lexer.TOKEN_OPERATOR,"**")){var node2=this.parseUnary();node=new nodes.Pow(node.lineno,node.colno,node,node2)}return node},parseUnary:function(noFilters){var tok=this.peekToken();var node;if(this.skipValue(lexer.TOKEN_OPERATOR,"-")){node=new nodes.Neg(tok.lineno,tok.colno,this.parseUnary(true))}else if(this.skipValue(lexer.TOKEN_OPERATOR,"+")){node=new nodes.Pos(tok.lineno,tok.colno,this.parseUnary(true))}else{node=this.parsePrimary()}if(!noFilters){node=this.parseFilter(node)}return node},parsePrimary:function(noPostfix){var tok=this.nextToken();var val=null;var node=null;if(!tok){this.fail("expected expression, got end of file")}else if(tok.type==lexer.TOKEN_STRING){val=tok.value}else if(tok.type==lexer.TOKEN_INT){val=parseInt(tok.value,10)}else if(tok.type==lexer.TOKEN_FLOAT){val=parseFloat(tok.value)}else if(tok.type==lexer.TOKEN_BOOLEAN){if(tok.value=="true"){val=true}else if(tok.value=="false"){val=false}else{this.fail("invalid boolean: "+tok.val,tok.lineno,tok.colno)}}else if(tok.type==lexer.TOKEN_REGEX){val=new RegExp(tok.value.body,tok.value.flags)}if(val!==null){node=new nodes.Literal(tok.lineno,tok.colno,val)}else if(tok.type==lexer.TOKEN_SYMBOL){node=new nodes.Symbol(tok.lineno,tok.colno,tok.value);if(!noPostfix){node=this.parsePostfix(node)}}else{this.pushToken(tok);node=this.parseAggregate()}if(node){return node}else{this.fail("unexpected token: "+tok.value,tok.lineno,tok.colno)}},parseFilter:function(node){while(this.skip(lexer.TOKEN_PIPE)){var tok=this.expect(lexer.TOKEN_SYMBOL);var name=tok.value;while(this.skipValue(lexer.TOKEN_OPERATOR,".")){name+="."+this.expect(lexer.TOKEN_SYMBOL).value}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){var call=this.parsePostfix(node);node.args.children=node.args.children.concat(call.args.children)}}return node},parseAggregate:function(){var tok=this.nextToken();var node;switch(tok.type){case lexer.TOKEN_LEFT_PAREN:node=new nodes.Group(tok.lineno,tok.colno);break;case lexer.TOKEN_LEFT_BRACKET:node=new nodes.Array(tok.lineno,tok.colno);break;case lexer.TOKEN_LEFT_CURLY:node=new nodes.Dict(tok.lineno,tok.colno);break;default:return null}while(1){var type=this.peekToken().type;if(type==lexer.TOKEN_RIGHT_PAREN||type==lexer.TOKEN_RIGHT_BRACKET||type==lexer.TOKEN_RIGHT_CURLY){this.nextToken();break}if(node.children.length>0){if(!this.skip(lexer.TOKEN_COMMA)){this.fail("parseAggregate: expected comma after expression",tok.lineno,tok.colno)}}if(node instanceof nodes.Dict){var key=this.parsePrimary();if(!this.skip(lexer.TOKEN_COLON)){this.fail("parseAggregate: expected colon after dict key",tok.lineno,tok.colno)}var value=this.parseExpression();node.addChild(new nodes.Pair(key.lineno,key.colno,key,value))}else{var expr=this.parseExpression();node.addChild(expr)}}return node},parseSignature:function(tolerant,noParens){var tok=this.peekToken();if(!noParens&&tok.type!=lexer.TOKEN_LEFT_PAREN){if(tolerant){return null}else{this.fail("expected arguments",tok.lineno,tok.colno)}}if(tok.type==lexer.TOKEN_LEFT_PAREN){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){tok=this.peekToken();if(!noParens&&tok.type==lexer.TOKEN_RIGHT_PAREN){this.nextToken();break}else if(noParens&&tok.type==lexer.TOKEN_BLOCK_END){break}if(checkComma&&!this.skip(lexer.TOKEN_COMMA)){this.fail("parseSignature: expected comma after expression",tok.lineno,tok.colno)}else{var arg=this.parseExpression();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(){var prev=this.breakOnBlocks;this.breakOnBlocks=lib.toArray(arguments);var ret=this.parse();this.breakOnBlocks=prev;return ret},parseNodes:function(){var tok;var buf=[];while(tok=this.nextToken()){if(tok.type==lexer.TOKEN_DATA){var data=tok.value;var nextToken=this.peekToken();var nextVal=nextToken&&nextToken.value;if(this.dropLeadingWhitespace){data=data.replace(/^\s*/,"");this.dropLeadingWhitespace=false}if(nextToken&&nextToken.type==lexer.TOKEN_BLOCK_START&&nextVal.charAt(nextVal.length-1)=="-"){data=data.replace(/\s*$/,"")}buf.push(new nodes.Output(tok.lineno,tok.colno,[new nodes.TemplateData(tok.lineno,tok.colno,data)]))}else if(tok.type==lexer.TOKEN_BLOCK_START){var n=this.parseStatement();if(!n){break}buf.push(n)}else if(tok.type==lexer.TOKEN_VARIABLE_START){var e=this.parseExpression();this.advanceAfterVariableEnd();buf.push(new nodes.Output(tok.lineno,tok.colno,[e]))}else if(tok.type!=lexer.TOKEN_COMMENT){this.fail("Unexpected token at top-level: "+tok.type,tok.lineno,tok.colno)}}return buf},parse:function(){return new nodes.NodeList(0,0,this.parseNodes())},parseAsRoot:function(){return new nodes.Root(0,0,this.parseNodes())}});modules["parser"]={parse:function(src,extensions,lexerTags){var p=new Parser(lexer.lex(src,lexerTags));if(extensions!==undefined){p.extensions=extensions}return p.parseAsRoot()}}})();(function(){var nodes=modules["nodes"];var lib=modules["lib"];var sym=0;function gensym(){return"hole_"+sym++}function mapCOW(arr,func){var res=null;for(var i=0;i":">","<=":"<=",">=":">="};function binOpEmitter(str){return function(node,frame){this.compile(node.left,frame);this.emit(str);this.compile(node.right,frame)}}function quotedArray(arr){return"["+lib.map(arr,function(x){return'"'+x+'"'})+"]"}var Compiler=Object.extend({init:function(){this.codebuf=[];this.lastId=0;this.buffer=null;this.bufferStack=[];this.isChild=false;this.scopeClosers=""},fail:function(msg,lineno,colno){if(lineno!==undefined)lineno+=1;if(colno!==undefined)colno+=1;throw new lib.TemplateError(msg,lineno,colno)},pushBufferId:function(id){this.bufferStack.push(this.buffer);this.buffer=id;this.emit("var "+this.buffer+' = "";')},popBufferId:function(){this.buffer=this.bufferStack.pop()},emit:function(code){this.codebuf.push(code)},emitLine:function(code){this.emit(code+"\n")},emitLines:function(){lib.each(lib.toArray(arguments),function(line){this.emitLine(line)},this)},emitFuncBegin:function(name){this.buffer="output";this.scopeClosers="";this.emitLine("function "+name+"(env, context, frame, runtime, cb) {");this.emitLine("var lineno = null;");this.emitLine("var colno = null;");this.emitLine("var "+this.buffer+' = "";');this.emitLine("try {")},emitFuncEnd:function(noReturn){if(!noReturn){this.emitLine("cb(null, "+this.buffer+");")}this.closeScopeLevels();this.emitLine("} catch (e) {");this.emitLine(" cb(runtime.handleError(e, lineno, colno));");this.emitLine("}");this.emitLine("}");this.buffer=null},addScopeLevel:function(){this.scopeClosers+="})"},closeScopeLevels:function(){this.emitLine(this.scopeClosers+";");this.scopeClosers=""},withScopedSyntax:function(func){var scopeClosers=this.scopeClosers;this.scopeClosers="";func.call(this);this.closeScopeLevels();this.scopeClosers=scopeClosers},makeCallback:function(res){var err=this.tmpid();return"function("+err+(res?","+res:"")+") {\n"+"if("+err+") { cb("+err+"); return; }"},tmpid:function(){this.lastId++;return"t_"+this.lastId},_bufferAppend:function(func){this.emit(this.buffer+" += runtime.suppressValue(");func.call(this);this.emit(", env.autoesc);\n")},_compileChildren:function(node,frame){var children=node.children;for(var i=0,l=children.length;i0){this.emit(",")}this.compile(node.children[i],frame)}if(endChar){this.emit(endChar)}},_compileExpression:function(node,frame){this.assertType(node,nodes.Literal,nodes.Symbol,nodes.Group,nodes.Array,nodes.Dict,nodes.FunCall,nodes.Caller,nodes.Filter,nodes.LookupVal,nodes.Compare,nodes.InlineIf,nodes.In,nodes.And,nodes.Or,nodes.Not,nodes.Add,nodes.Sub,nodes.Mul,nodes.Div,nodes.FloorDiv,nodes.Mod,nodes.Pow,nodes.Neg,nodes.Pos,nodes.Compare,nodes.NodeList);this.compile(node,frame)},assertType:function(node){var types=lib.toArray(arguments).slice(1);var success=false;for(var i=0;i0){this.emit(",")}if(arg){var id=this.tmpid();this.emitLine("function(cb) {");this.emitLine("if(!cb) { cb = function(err) { if(err) { throw err; }}}");this.pushBufferId(id);this.withScopedSyntax(function(){this.compile(arg,frame);this.emitLine("cb(null, "+id+");")});this.popBufferId();this.emitLine("return "+id+";");this.emitLine("}")}else{this.emit("null")}},this)}if(async){var res=this.tmpid();this.emitLine(", "+this.makeCallback(res));this.emitLine(this.buffer+" += runtime.suppressValue("+res+", "+autoescape+" && env.autoesc);");this.addScopeLevel()}else{this.emit(")");this.emit(", "+autoescape+" && env.autoesc);\n")}},compileCallExtensionAsync:function(node,frame){this.compileCallExtension(node,frame,true)},compileNodeList:function(node,frame){this._compileChildren(node,frame)},compileLiteral:function(node,frame){if(typeof node.value=="string"){var val=node.value.replace(/\\/g,"\\\\");val=val.replace(/"/g,'\\"');val=val.replace(/\n/g,"\\n");val=val.replace(/\r/g,"\\r");val=val.replace(/\t/g,"\\t");this.emit('"'+val+'"')}else{this.emit(node.value.toString())}},compileSymbol:function(node,frame){var name=node.value;var v;if(v=frame.lookup(name)){this.emit(v)}else{this.emit("runtime.contextOrFrameLookup("+'context, frame, "'+name+'")')}},compileGroup:function(node,frame){this._compileAggregate(node,frame,"(",")")},compileArray:function(node,frame){this._compileAggregate(node,frame,"[","]")},compileDict:function(node,frame){this._compileAggregate(node,frame,"{","}")},compilePair:function(node,frame){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 key.value=="string")){this.fail("compilePair: Dict keys must be strings or names",key.lineno,key.colno)}this.compile(key,frame);this.emit(": ");this._compileExpression(val,frame)},compileInlineIf:function(node,frame){this.emit("(");this.compile(node.cond,frame);this.emit("?");this.compile(node.body,frame);this.emit(":");if(node.else_!==null)this.compile(node.else_,frame);else this.emit('""');this.emit(")")},compileIn:function(node,frame){this.emit("(");this.compile(node.right,frame);this.emit(".indexOf(");this.compile(node.left,frame);this.emit(") !== -1)")},compileOr:binOpEmitter(" || "),compileAnd:binOpEmitter(" && "),compileAdd:binOpEmitter(" + "),compileSub:binOpEmitter(" - "),compileMul:binOpEmitter(" * "),compileDiv:binOpEmitter(" / "),compileMod:binOpEmitter(" % "),compileNot:function(node,frame){this.emit("!");this.compile(node.target,frame)},compileFloorDiv:function(node,frame){this.emit("Math.floor(");this.compile(node.left,frame);this.emit(" / ");this.compile(node.right,frame);this.emit(")")},compilePow:function(node,frame){this.emit("Math.pow(");this.compile(node.left,frame);this.emit(", ");this.compile(node.right,frame);this.emit(")")},compileNeg:function(node,frame){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=width){return str}var spaces=width-str.length;var pre=lib.repeat(" ",spaces/2-spaces%2);var post=lib.repeat(" ",spaces/2);return r.copySafeness(str,pre+str+post)},"default":function(val,def){return val?val:def},dictsort:function(val,case_sensitive,by){if(!lib.isObject(val)){throw new lib.TemplateError("dictsort filter: val must be an object")}var array=[];for(var k in val){array.push([k,val[k]])}var si;if(by===undefined||by==="key"){si=0}else if(by==="value"){si=1}else{throw new lib.TemplateError("dictsort filter: You can only sort by either key or value")}array.sort(function(t1,t2){var a=t1[si];var b=t2[si];if(!case_sensitive){if(lib.isString(a)){a=a.toUpperCase()}if(lib.isString(b)){b=b.toUpperCase()}}return a>b?1:a==b?0:-1});return array},escape:function(str){if(typeof str=="string"||str instanceof r.SafeString){return lib.escape(str)}return str},safe:function(str){return r.markSafe(str)},first:function(arr){return arr[0]},groupby:function(arr,attr){return lib.groupBy(arr,attr)},indent:function(str,width,indentfirst){width=width||4;var res="";var lines=str.split("\n");var sp=lib.repeat(" ",width);for(var i=0;i=maxCount){break}last=res;res=res.replace(old,new_);count++}return r.copySafeness(str,res)},reverse:function(val){var arr;if(lib.isString(val)){arr=filters.list(val)}else{arr=lib.map(val,function(v){return v})}arr.reverse();if(lib.isString(val)){return r.copySafeness(val,arr.join(""))}return arr},round:function(val,precision,method){precision=precision||0;var factor=Math.pow(10,precision);var rounder;if(method=="ceil"){rounder=Math.ceil}else if(method=="floor"){rounder=Math.floor}else{rounder=Math.round}return rounder(val*factor)/factor},slice:function(arr,slices,fillWith){var sliceLength=Math.floor(arr.length/slices);var extra=arr.length%slices;var offset=0;var res=[];for(var i=0;i=extra){slice.push(fillWith)}res.push(slice)}return res},sort:function(arr,reverse,caseSens,attr){arr=lib.map(arr,function(v){return v});arr.sort(function(a,b){var x,y;if(attr){x=a[attr];y=b[attr]}else{x=a;y=b}if(!caseSens&&lib.isString(x)&&lib.isString(y)){x=x.toLowerCase();y=y.toLowerCase()}if(xy){return reverse?-1:1}else{return 0}});return arr},string:function(obj){return r.copySafeness(obj,obj)},title:function(str){var words=str.split(" ");for(var i=0;i"+possibleUrl.substr(0,length)+"";if(wwwRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";if(emailRE.test(possibleUrl))return''+possibleUrl+"";if(tldRE.test(possibleUrl))return'"+possibleUrl.substr(0,length)+"";return word});return words.join(" ")},wordcount:function(str){var words=str?str.match(/\w+/g):null;return words?words.length:null},"float":function(val,def){var res=parseFloat(val);return isNaN(res)?def:res},"int":function(val,def){var res=parseInt(val,10);return isNaN(res)?def:res}};filters.d=filters["default"];filters.e=filters.escape;modules["filters"]=filters})();(function(){function cycler(items){var index=-1;this.current=null;return{reset:function(){index=-1;this.current=null},next:function(){index++;if(index>=items.length){index=0}this.current=items[index];return this.current}}}function joiner(sep){sep=sep||",";var first=true;return function(){var val=first?"":sep;first=false;return val}}var globals={range:function(start,stop,step){if(!stop){stop=start;start=0;step=1}else if(!step){step=1}var arr=[];for(var i=start;i", "dependencies": { "optimist": "*",