Comparing version 0.1.8 to 0.1.9
@@ -1,1 +0,1 @@ | ||
(function(){var a={};(function(){function b(a,c,d){var e=Object.create(a.prototype),f=/xyz/.test(function(){xyz})?/\bparent\b/:/.*/;d=d||{};for(var g in d){var h=d[g],i=e[g];typeof i=="function"&&typeof h=="function"&&f.test(h)?e[g]=function(a,b){return function(){var c=this.parent;this.parent=b;var d=a.apply(this,arguments);return this.parent=c,d}}(h,i):e[g]=h}e.typename=c;var j=function(){e.init&&e.init.apply(this,arguments)};return j.prototype=e,j.prototype.constructor=j,j.extend=function(a,c){return typeof a=="object"&&(c=a,a="anonymous"),b(j,a,c)},j}a.object=b(Object,"Object",{})})(),function(){var b=Array.prototype,c=Object.prototype,d=a.lib={};d.TemplateError=function(a,b,c){var d=this;return a instanceof Error?(d=a,a=a.name+": "+a.message):Error.captureStackTrace(d),d.name="Template render error",d.message=a,d.lineno=b,d.colno=c,d.firstUpdate=!0,d.Update=function(a){var b="("+(a||"unknown path")+")";return this.firstUpdate&&this.lineno&&this.colno&&(b+=" [Line "+this.lineno+", Column "+this.colno+"]"),b+="\n ",this.firstUpdate&&(b+=" "),this.message=b+(this.message||""),this.firstUpdate=!1,this},d},d.TemplateError.prototype=Error.prototype,d.isFunction=function(a){return c.toString.call(a)=="[object Function]"},d.isArray=Array.isArray||function(a){return c.toString.call(a)=="[object Array]"},d.isString=function(a){return c.toString.call(a)=="[object String]"},d.isObject=function(a){return a===Object(a)},d.groupBy=function(a,b){var c={},e=d.isFunction(b)?b:function(a){return a[b]};for(var f=0;f<a.length;f++){var g=a[f],h=e(g,f);(c[h]||(c[h]=[])).push(g)}return c},d.toArray=function(a){return Array.prototype.slice.call(a)},d.without=function(a){var b=[];if(!a)return b;var c=-1,e=a.length,f=d.toArray(arguments).slice(1);while(++c<e)f.indexOf(a[c])===-1&&b.push(a[c]);return b},d.extend=function(a,b){for(var c in b)a[c]=b[c];return a},d.repeat=function(a,b){var c="";for(var d=0;d<b;d++)c+=a;return c},d.each=function(a,c,d){if(a==null)return;if(b.each&&a.each==b.each)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++)c.call(d,a[e],e,a)},d.map=function(a,c){var d=[];if(a==null)return d;if(b.map&&a.map===b.map)return a.map(c);for(var e=0;e<a.length;e++)d[d.length]=c(a[e],e);return a.length===+a.length&&(d.length=a.length),d}}(),function(){var b=a.lib,c={abs:function(a){return Math.abs(a)},batch:function(a,b,c){var d=[],e=[];for(var f=0;f<a.length;f++)f%b===0&&e.length&&(d.push(e),e=[]),e.push(a[f]);if(e.length){if(c)for(var f=e.length;f<b;f++)e.push(c);d.push(e)}return d},capitalize:function(a){return a=a.toLowerCase(),a[0].toUpperCase()+a.slice(1)},center:function(a,c){c=c||80;if(a.length>=c)return a;var d=c-a.length,e=b.repeat(" ",d/2-d%2),f=b.repeat(" ",d/2);return e+a+f},"default":function(a,b){return a?a:b},escape:function(a){return a.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">")},first:function(a){return a[0]},groupby:function(a,c){return b.groupBy(a,c)},indent:function(a,c,d){c=c||4;var e="",f=a.split("\n"),g=b.repeat(" ",c);for(var h=0;h<f.length;h++)h==0&&!d?e+=f[h]+"\n":e+=g+f[h]+"\n";return e},join:function(a,c,d){return c=c||"",d&&(a=b.map(a,function(a){return a[d]})),a.join(c)},last:function(a){return a[a.length-1]},length:function(a){return a.length},list:function(a){if(b.isString(a))return a.split("");if(b.isObject(a)){var c=[];if(Object.keys)c=Object.keys(a);else for(var d in a)c.push(d);return b.map(c,function(b){return{key:b,value:a[b]}})}throw new b.TemplateError("list filter: type not iterable")},lower:function(a){return a.toLowerCase()},random:function(a){var b=Math.floor(Math.random()*a.length);return b==a.length&&b--,a[b]},replace:function(a,b,c,d){var e=a,f=e,g=1;e=e.replace(b,c);while(f!=e){if(g>=d)break;f=e,e=e.replace(b,c),g++}return e},reverse:function(a){var d;return b.isString(a)?d=c.list(a):d=b.map(a,function(a){return a}),d.reverse(),b.isString(a)?d.join(""):d},round:function(a,b,c){b=b||0;var d=Math.pow(10,b),e;return c=="ceil"?e=Math.ceil:c=="floor"?e=Math.floor:e=Math.round,e(a*d)/d},slice:function(a,b,c){var d=Math.floor(a.length/b),e=a.length%b,f=0,g=[];for(var h=0;h<b;h++){var i=f+h*d;h<e&&f++;var j=f+(h+1)*d,k=a.slice(i,j);c&&h>=e&&k.push(c),g.push(k)}return g},sort:function(a,c,d,e){return a=b.map(a,function(a){return a}),a.sort(function(a,f){var g,h;return e?(g=a[e],h=f[e]):(g=a,h=f),!d&&b.isString(g)&&b.isString(h)&&(g=g.toLowerCase(),h=h.toLowerCase()),g<h?c?1:-1:g>h?c?-1:1:0}),a},string:function(a){return a.toString()},title:function(a){return a.toUpperCase()},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},upper:function(a){return a.toUpperCase()},wordcount:function(a){return a.match(/\w+/g).length},"float":function(a,b){return parseFloat(a)||b},"int":function(a,b){return parseInt(a)||b}};c.d=c.default,c.e=c.escape,a.filters=c}(),function(){function d(a,b,c){return function(){var d=g(arguments),e,h=f(arguments);if(d>a.length){e=Array.prototype.slice.call(arguments,0,a.length);var i=Array.prototype.slice.call(arguments,e.length,d);for(var j=0;j<i.length;j++)j<b.length&&(h[b[j]]=i[j]);e.push(h)}else if(d<a.length){e=Array.prototype.slice.call(arguments,0,d);for(var j=d;j<a.length;j++){var k=a[j];e.push(h[k]),delete h[k]}e.push(h)}else e=arguments;return c.apply(this,e)}}function e(a){return a.__keywords=!0,a}function f(a){return a.length&&a[a.length-1].__keywords?a[a.length-1]:{}}function g(a){return a.length===0?0:a[a.length-1].__keywords?a.length-1:a.length}function h(a){return a!==undefined&&a!==null?a:""}function i(a,b){return a=a||{},b=a[b],typeof b=="function"?function(){return h(b.apply(a,arguments))}:h(b)}function j(a,b,c){var d=a.lookup(c);return d!==undefined&&d!==null?d:b.lookup(c)}var b=a.object,c=b.extend({init:function(a){this.variables={},this.parent=a},set:function(a,b){var c=a.split("."),d=this.variables;for(var e=0;e<c.length-1;e++){var f=c[e];d[f]||(d[f]={}),d=d[f]}d[c[c.length-1]]=b},lookup:function(a){var b=this.parent,c=this.variables[a];return c!==undefined&&c!==null?c:b&&b.lookup(a)},push:function(){return new c(this)},pop:function(){return this.parent}});a.runtime={Frame:c,makeMacro:d,makeKeywordArgs:e,numArgs:g,suppressValue:h,suppressLookupValue:i,contextOrFrameLookup:j}}(),function(){var b=a.lib,c=a.object,d=a.lexer,e=a.compiler,f=a.filters,g=a.loaders,h=a.runtime,i=h.Frame,j=c.extend({init:function(a,c,e){this.dev=e,a?this.loaders=b.isArray(a)?a:[a]:g.FileSystemLoader?this.loaders=[new g.FileSystemLoader]:this.loaders=[new g.HttpLoader("/views")],c&&d.setTags(c),this.filters=f,this.cache={}},tryTemplate:function(a,c){try{return c()}catch(d){d.Update||(d=b.TemplateError(d)),d.Update(a);if(!this.dev){var e=d;d=new Error(e.message),d.name=e.name}throw d}},addFilter:function(a,b){this.filters[a]=b},getFilter:function(a){if(!this.filters[a])throw new Error("filter not found: "+a);return this.filters[a]},getTemplate:function(a,b){var c=null,d=this.cache[a],e;if(typeof a!="string")throw new Error("template names must be a string: "+a);if(!d||!d.isUpToDate()){for(var f=0;f<this.loaders.length;f++)if(c=this.loaders[f].getSource(a))break;if(!c)throw new Error("template not found: "+a);this.cache[a]=new l(c.src,this,c.path,c.upToDate,b)}return this.cache[a]},registerPrecompiled:function(a){for(var b in a)this.cache[b]=new l({type:"code",obj:a[b]},this,b,function(){return!0},!0)},express:function(c){var d=this;if(c.render)c.render=function(a,c,e){var f={};b.isFunction(c)&&(e=c,c={}),f=b.extend(f,this.locals),c._locals&&(f=b.extend(f,c._locals)),f=b.extend(f,c);var g=d.render(a,f);e(null,g)};else{var e=a.http,f=e.ServerResponse.prototype;f._render=function(a,c,e){var f=this.app,g={};this._locals&&(g=b.extend(g,this._locals)),c&&(g=b.extend(g,c),c.locals&&(g=b.extend(g,c.locals))),g=b.extend(g,f._locals);var h=d.render(a,g);e?e(null,h):this.send(h)}}},render:function(a,b){return this.getTemplate(a).render(b)}}),k=c.extend({init:function(a,b){this.ctx=a,this.blocks={},this.exported=[];for(var c in b)this.addBlock(c,b[c])},lookup:function(a){return this.ctx[a]},setVariable:function(a,b){this.ctx[a]=b},getVariables:function(){return this.ctx},addBlock:function(a,b){this.blocks[a]=this.blocks[a]||[],this.blocks[a].push(b)},getBlock:function(a){if(!this.blocks[a])throw new Error('unknown block "'+a+'"');return this.blocks[a][0]},getSuper:function(a,b,c){var d=(this.blocks[b]||[]).indexOf(c),e=this.blocks[b][d+1],f=this;return function(){if(d==-1||!e)throw new Error('no super block available for "'+b+'"');return e(a,f)}},addExport:function(a){this.exported.push(a)},getExported:function(){var a={};for(var b=0;b<this.exported.length;b++){var c=this.exported[b];a[c]=this.ctx[c]}return a}}),l=c.extend({init:function(a,c,d,e,f){this.env=c||new j;if(b.isObject(a))switch(a.type){case"code":this.tmplProps=a.obj;break;case"string":this.tmplStr=a.obj}else{if(!b.isString(a))throw new Error("src must be a string or an object describing the source");this.tmplStr=a}this.path=d,this.upToDate=e||function(){return!1};if(f){var g=this;this.env.tryTemplate(this.path,function(){g._compile()}),g=null}else this.compiled=!1},render:function(a,b){var c=this,d=function(){c.compiled||c._compile();var d=new k(a||{},c.blocks);return c.rootRenderFunc(c.env,d,b||new i,h)};return this.env.tryTemplate(this.path,d)},isUpToDate:function(){return this.upToDate()},getExported:function(){this.compiled||this._compile();var a=new k({},this.blocks);return this.rootRenderFunc(this.env,a,new i,h),a.getExported()},_compile:function(){var a;if(this.tmplProps)a=this.tmplProps;else{var b=new Function(e.compile(this.tmplStr,this.env));a=b()}this.blocks=this._getBlocks(a),this.rootRenderFunc=a.root,this.compiled=!0},_getBlocks:function(a){var b={};for(var c in a)c.slice(0,2)=="b_"&&(b[c.slice(2)]=a[c]);return b}});a.environment={Environment:j,Template:l}}();var b=a.environment,c=a.compiler,d=a.parser,e=a.lexer,f=a.loaders;window.nunjucks={},window.nunjucks.Environment=b.Environment,window.nunjucks.Template=b.Template,f&&(f.FileSystemLoader?window.nunjucks.FileSystemLoader=f.FileSystemLoader:window.nunjucks.HttpLoader=f.HttpLoader),window.nunjucks.compiler=c,window.nunjucks.parser=d,window.nunjucks.lexer=e,window.nunjucks.require=function(b){return a[b]}})(); | ||
(function(){var modules={};(function(){function extend(cls,name,props){var prototype=Object.create(cls.prototype);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 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(/[&"'<>]/g,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 obj===Object(obj)};exports.groupBy=function(obj,val){var result={};var iterator=exports.isFunction(val)?val:function(obj){return obj[val]};for(var i=0;i<obj.length;i++){var value=obj[i];var key=iterator(value,i);(result[key]||(result[key]=[])).push(value)}return result};exports.toArray=function(obj){return Array.prototype.slice.call(obj)};exports.without=function(array){var result=[];if(!array){return result}var index=-1,length=array.length,contains=exports.toArray(arguments).slice(1);while(++index<length){if(contains.indexOf(array[index])===-1){result.push(array[index])}}return result};exports.extend=function(obj,obj2){for(var k in obj2){obj[k]=obj2[k]}return obj};exports.repeat=function(char_,n){var str="";for(var i=0;i<n;i++){str+=char_}return str};exports.each=function(obj,func,context){if(obj==null){return}if(ArrayProto.each&&obj.each==ArrayProto.each){obj.forEach(func,context)}else if(obj.length===+obj.length){for(var i=0,l=obj.length;i<l;i++){func.call(context,obj[i],i,obj)}}};exports.map=function(obj,func){var results=[];if(obj==null){return results}if(ArrayProto.map&&obj.map===ArrayProto.map){return obj.map(func)}for(var i=0;i<obj.length;i++){results[results.length]=func(obj[i],i)}if(obj.length===+obj.length){results.length=obj.length}return results}})();(function(){var lib=modules["lib"];var Object=modules["object"];var Frame=Object.extend({init:function(parent){this.variables={};this.parent=parent},set:function(name,val){var parts=name.split(".");var obj=this.variables;for(var i=0;i<parts.length-1;i++){var id=parts[i];if(!obj[id]){obj[id]={}}obj=obj[id]}obj[parts[parts.length-1]]=val},get:function(name){var val=this.variables[name];if(val!==undefined&&val!==null){return val}return null},lookup:function(name){var p=this.parent;var val=this.variables[name];if(val!==undefined&&val!==null){return val}return p&&p.lookup(name)},push:function(){return new Frame(this)},pop:function(){return this.parent}});function makeMacro(argNames,kwargNames,func){return function(){var argCount=numArgs(arguments);var args;var kwargs=getKeywordArgs(arguments);if(argCount>argNames.length){args=Array.prototype.slice.call(arguments,0,argNames.length);var vals=Array.prototype.slice.call(arguments,args.length,argCount);for(var i=0;i<vals.length;i++){if(i<kwargNames.length){kwargs[kwargNames[i]]=vals[i]}}args.push(kwargs)}else if(argCount<argNames.length){args=Array.prototype.slice.call(arguments,0,argCount);for(var i=argCount;i<argNames.length;i++){var arg=argNames[i];args.push(kwargs[arg]);delete kwargs[arg]}args.push(kwargs)}else{args=arguments}return func.apply(this,args)}}function makeKeywordArgs(obj){obj.__keywords=true;return obj}function getKeywordArgs(args){var len=args.length;if(len){var lastArg=args[len-1];if(lastArg&&lastArg.hasOwnProperty("__keywords")){return lastArg}}return{}}function numArgs(args){var len=args.length;if(len===0){return 0}var lastArg=args[len-1];if(lastArg&&lastArg.hasOwnProperty("__keywords")){return len-1}else{return len}}function SafeString(val){if(typeof val!="string"){return val}this.toString=function(){return val};this.length=val.length;var methods=["charAt","charCodeAt","concat","contains","endsWith","fromCharCode","indexOf","lastIndexOf","length","localeCompare","match","quote","replace","search","slice","split","startsWith","substr","substring","toLocaleLowerCase","toLocaleUpperCase","toLowerCase","toUpperCase","trim","trimLeft","trimRight"];for(var i=0;i<methods.length;i++){this[methods[i]]=proxyStr(val[methods[i]])}}function copySafeness(dest,target){if(dest instanceof SafeString){return new SafeString(target)}return target.toString()}function proxyStr(func){return function(){var ret=func.apply(this,arguments);if(typeof ret=="string"){return new SafeString(ret)}return ret}}function suppressValue(val,autoescape){val=val!==undefined&&val!==null?val:"";if(autoescape&&typeof val==="string"){val=lib.escape(val)}return val}function memberLookup(obj,val){obj=obj||{};if(typeof obj[val]==="function"){return function(){return obj[val].apply(obj,arguments)}}return obj[val]}function callWrap(obj,name,args){if(!obj){throw new Error("Unable to call `"+name+"`, which is undefined or falsey")}else if(typeof obj!=="function"){throw new Error("Unable to call `"+name+"`, which is not a function")}return obj.apply(this,args)}function contextOrFrameLookup(context,frame,name){var val=context.lookup(name);return val!==undefined&&val!==null?val:frame.lookup(name)}function handleError(error,lineno,colno){if(error.lineno){throw error}else{throw new lib.TemplateError(error,lineno,colno)}}modules["runtime"]={Frame:Frame,makeMacro:makeMacro,makeKeywordArgs:makeKeywordArgs,numArgs:numArgs,suppressValue:suppressValue,memberLookup:memberLookup,contextOrFrameLookup:contextOrFrameLookup,callWrap:callWrap,handleError:handleError,isArray:lib.isArray,SafeString:SafeString,copySafeness:copySafeness}})();(function(){var lib=modules["lib"];var r=modules["runtime"];var filters={abs:function(n){return Math.abs(n)},batch:function(arr,linecount,fill_with){var res=[];var tmp=[];for(var i=0;i<arr.length;i++){if(i%linecount===0&&tmp.length){res.push(tmp);tmp=[]}tmp.push(arr[i])}if(tmp.length){if(fill_with){for(var i=tmp.length;i<linecount;i++){tmp.push(fill_with)}}res.push(tmp)}return res},capitalize:function(str){var ret=str.toLowerCase();return r.copySafeness(str,ret[0].toUpperCase()+ret.slice(1))},center:function(str,width){width=width||80;if(str.length>=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 new r.SafeString(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<lines.length;i++){if(i==0&&!indentfirst){res+=lines[i]+"\n"}else{res+=sp+lines[i]+"\n"}}return r.copySafeness(str,res)},join:function(arr,del,attr){del=del||"";if(attr){arr=lib.map(arr,function(v){return v[attr]})}return arr.join(del)},last:function(arr){return arr[arr.length-1]},length:function(arr){return arr.length},list:function(val){if(lib.isString(val)){return val.split("")}else if(lib.isObject(val)){var keys=[];if(Object.keys){keys=Object.keys(val)}else{for(var k in val){keys.push(k)}}return lib.map(keys,function(k){return{key:k,value:val[k]}})}else{throw new lib.TemplateError("list filter: type not iterable")}},lower:function(str){return str.toLowerCase()},random:function(arr){var i=Math.floor(Math.random()*arr.length);if(i==arr.length){i--}return arr[i]},replace:function(str,old,new_,maxCount){var res=str;var last=res;var count=1;res=res.replace(old,new_);while(last!=res){if(count>=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<slices;i++){var start=offset+i*sliceLength;if(i<extra){offset++}var end=offset+(i+1)*sliceLength;var slice=arr.slice(start,end);if(fillWith&&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(x<y){return reverse?1:-1}else if(x>y){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<words.length;i++){words[i]=filters.capitalize(words[i])}return r.copySafeness(str,words.join(" "))},trim:function(str){return r.copySafeness(str,str.replace(/^\s*|\s*$/g,""))},truncate:function(input,length,killwords,end){var orig=input;length=length||255;if(input.length<=length)return input;if(killwords){input=input.substring(0,length)}else{var idx=input.lastIndexOf(" ",length);if(idx===-1){idx=length}input=input.substring(0,idx)}input+=end!==undefined&&end!==null?end:"...";return r.copySafeness(orig,input)},upper:function(str){return str.toUpperCase()},wordcount:function(str){return str.match(/\w+/g).length},"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<stop;i+=step){arr.push(i)}return arr},cycler:function(){return cycler(Array.prototype.slice.call(arguments))},joiner:function(sep){return joiner(sep)}};modules["globals"]=globals})();(function(){var lib=modules["lib"];var Object=modules["object"];var lexer=modules["lexer"];var compiler=modules["compiler"];var builtin_filters=modules["filters"];var builtin_loaders=modules["loaders"];var runtime=modules["runtime"];var globals=modules["globals"];var Frame=runtime.Frame;var Environment=Object.extend({init:function(loaders,opts){opts=opts||{};this.dev=!!opts.dev;this.autoesc=!!opts.autoescape;if(!loaders){if(builtin_loaders.FileSystemLoader){this.loaders=[new builtin_loaders.FileSystemLoader]}else{this.loaders=[new builtin_loaders.HttpLoader("/views")]}}else{this.loaders=lib.isArray(loaders)?loaders:[loaders]}if(opts.tags){lexer.setTags(opts.tags)}this.filters=builtin_filters;this.cache={};this.extensions={};this.extensionsList=[]},addExtension:function(name,extension){extension._name=name;this.extensions[name]=extension;this.extensionsList.push(extension)},getExtension:function(name){return this.extensions[name]},addFilter:function(name,func){this.filters[name]=func},getFilter:function(name){if(!this.filters[name]){throw new Error("filter not found: "+name)}return this.filters[name]},getTemplate:function(name,eagerCompile){if(name&&name.raw){name=name.raw}var info=null;var tmpl=this.cache[name];var upToDate;if(typeof name!=="string"){throw new Error("template names must be a string: "+name)}if(!tmpl||!tmpl.isUpToDate()){for(var i=0;i<this.loaders.length;i++){if(info=this.loaders[i].getSource(name)){break}}if(!info){throw new Error("template not found: "+name)}this.cache[name]=new Template(info.src,this,info.path,info.upToDate,eagerCompile)}return this.cache[name]},registerPrecompiled:function(templates){for(var name in templates){this.cache[name]=new Template({type:"code",obj:templates[name]},this,name,function(){return true},true)}},express:function(app){var env=this;if(app.render){app.render=function(name,ctx,k){var context={};if(lib.isFunction(ctx)){k=ctx;ctx={}}context=lib.extend(context,this.locals);if(ctx._locals){context=lib.extend(context,ctx._locals)}context=lib.extend(context,ctx);var res=env.render(name,context);k(null,res)}}else{var http=modules["http"];var res=http.ServerResponse.prototype;res._render=function(name,ctx,k){var app=this.app;var context={};if(this._locals){context=lib.extend(context,this._locals)}if(ctx){context=lib.extend(context,ctx);if(ctx.locals){context=lib.extend(context,ctx.locals)}}context=lib.extend(context,app._locals);var str=env.render(name,context);if(k){k(null,str)}else{this.send(str)}}}},render:function(name,ctx){return this.getTemplate(name).render(ctx)}});var Context=Object.extend({init:function(ctx,blocks){this.ctx=ctx;this.blocks={};this.exported=[];for(var name in blocks){this.addBlock(name,blocks[name])}},lookup:function(name){if(name in globals&&!(name in this.ctx)){return globals[name]}else{return this.ctx[name]}},setVariable:function(name,val){this.ctx[name]=val},getVariables:function(){return this.ctx},addBlock:function(name,block){this.blocks[name]=this.blocks[name]||[];this.blocks[name].push(block)},getBlock:function(name){if(!this.blocks[name]){throw new Error('unknown block "'+name+'"')}return this.blocks[name][0]},getSuper:function(env,name,block,frame,runtime){var idx=(this.blocks[name]||[]).indexOf(block);var blk=this.blocks[name][idx+1];var context=this;return function(){if(idx==-1||!blk){throw new Error('no super block available for "'+name+'"')}return blk(env,context,frame,runtime)}},addExport:function(name){this.exported.push(name)},getExported:function(){var exported={};for(var i=0;i<this.exported.length;i++){var name=this.exported[i];exported[name]=this.ctx[name]}return exported}});var Template=Object.extend({init:function(src,env,path,upToDate,eagerCompile){this.env=env||new Environment;if(lib.isObject(src)){switch(src.type){case"code":this.tmplProps=src.obj;break;case"string":this.tmplStr=src.obj;break}}else if(lib.isString(src)){this.tmplStr=src}else{throw new Error("src must be a string or an object describing "+"the source")}this.path=path;this.upToDate=upToDate||function(){return false};if(eagerCompile){var _this=this;lib.withPrettyErrors(this.path,this.env.dev,function(){_this._compile()})}else{this.compiled=false}},render:function(ctx,frame){var self=this;var render=function(){if(!self.compiled){self._compile()}var context=new Context(ctx||{},self.blocks);return self.rootRenderFunc(self.env,context,frame||new Frame,runtime)};return lib.withPrettyErrors(this.path,this.env.dev,render)},isUpToDate:function(){return this.upToDate()},getExported:function(){if(!this.compiled){this._compile()}var context=new Context({},this.blocks);this.rootRenderFunc(this.env,context,new Frame,runtime);return context.getExported()},_compile:function(){var props;if(this.tmplProps){props=this.tmplProps}else{var source=compiler.compile(this.tmplStr,this.env.extensionsList,this.path);var func=new Function(source);props=func()}this.blocks=this._getBlocks(props);this.rootRenderFunc=props.root;this.compiled=true},_getBlocks:function(props){var blocks={};for(var k in props){if(k.slice(0,2)=="b_"){blocks[k.slice(2)]=props[k]}}return blocks}});modules["environment"]={Environment:Environment,Template:Template}})();var nunjucks;var env=modules["environment"];var compiler=modules["compiler"];var parser=modules["parser"];var lexer=modules["lexer"];var runtime=modules["runtime"];var loaders=modules["loaders"];nunjucks={};nunjucks.Environment=env.Environment;nunjucks.Template=env.Template;if(loaders){if(loaders.FileSystemLoader){nunjucks.FileSystemLoader=loaders.FileSystemLoader}else{nunjucks.HttpLoader=loaders.HttpLoader}}nunjucks.compiler=compiler;nunjucks.parser=parser;nunjucks.lexer=lexer;nunjucks.runtime=runtime;nunjucks.require=function(name){return modules[name]};if(typeof define==="function"&&define.amd){define(function(){return nunjucks})}else{window.nunjucks=nunjucks}})(); |
@@ -66,21 +66,56 @@ (function() { | ||
var escapeMap = { | ||
'&': '&', | ||
'"': '"', | ||
"'": ''', | ||
"<": '<', | ||
">": '>' | ||
}; | ||
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) { | ||
// not one of ours, cast it | ||
e = new exports.TemplateError(e); | ||
} | ||
e.Update(path); | ||
// Unless they marked the dev flag, show them a trace from here | ||
if (!withInternals) { | ||
var old = e; | ||
e = new Error(old.message); | ||
e.name = old.name; | ||
} | ||
throw e; | ||
} | ||
} | ||
exports.TemplateError = function(message, lineno, colno) { | ||
var self = this; | ||
var err = this; | ||
if (message instanceof Error) { // for casting regular js errors | ||
self = message; | ||
err = message; | ||
message = message.name + ": " + message.message; | ||
} else { | ||
Error.captureStackTrace(self); | ||
if(Error.captureStackTrace) { | ||
Error.captureStackTrace(err); | ||
} | ||
} | ||
self.name = "Template render error"; | ||
self.message = message; | ||
self.lineno = lineno; | ||
self.colno = colno; | ||
self.firstUpdate = true; | ||
err.name = "Template render error"; | ||
err.message = message; | ||
err.lineno = lineno; | ||
err.colno = colno; | ||
err.firstUpdate = true; | ||
self.Update = function(path) { | ||
err.Update = function(path) { | ||
var message = "(" + (path || "unknown path") + ")"; | ||
@@ -90,4 +125,9 @@ | ||
// where error occurred | ||
if (this.firstUpdate && this.lineno && this.colno) { | ||
message += ' [Line ' + this.lineno + ', Column ' + this.colno + ']'; | ||
if (this.firstUpdate) { | ||
if(this.lineno && this.colno) { | ||
message += ' [Line ' + this.lineno + ', Column ' + this.colno + ']'; | ||
} | ||
else if(this.lineno) { | ||
message += ' [Line ' + this.lineno + ']'; | ||
} | ||
} | ||
@@ -104,6 +144,11 @@ | ||
}; | ||
return self; | ||
return err; | ||
}; | ||
exports.TemplateError.prototype = Error.prototype; | ||
exports.escape = function(val) { | ||
return val.replace(/[&"'<>]/g, lookupEscape); | ||
}; | ||
@@ -177,3 +222,3 @@ exports.isFunction = function(obj) { | ||
} | ||
if(ArrayProto.each && obj.each == ArrayProto.each) { | ||
@@ -198,3 +243,3 @@ obj.forEach(func, context); | ||
} | ||
for(var i=0; i<obj.length; i++) { | ||
@@ -214,3 +259,246 @@ results[results.length] = func(obj[i], i); | ||
var lib = modules["lib"]; | ||
var Object = modules["object"]; | ||
// Frames keep track of scoping both at compile-time and run-time so | ||
// we know how to access variables. Block tags can introduce special | ||
// variables, for example. | ||
var Frame = Object.extend({ | ||
init: function(parent) { | ||
this.variables = {}; | ||
this.parent = parent; | ||
}, | ||
set: function(name, val) { | ||
// Allow variables with dots by automatically creating the | ||
// nested structure | ||
var parts = name.split('.'); | ||
var obj = this.variables; | ||
for(var i=0; i<parts.length - 1; i++) { | ||
var id = parts[i]; | ||
if(!obj[id]) { | ||
obj[id] = {}; | ||
} | ||
obj = obj[id]; | ||
} | ||
obj[parts[parts.length - 1]] = val; | ||
}, | ||
get: function(name) { | ||
var val = this.variables[name]; | ||
if(val !== undefined && val !== null) { | ||
return val; | ||
} | ||
return null; | ||
}, | ||
lookup: function(name) { | ||
var p = this.parent; | ||
var val = this.variables[name]; | ||
if(val !== undefined && val !== null) { | ||
return val; | ||
} | ||
return p && p.lookup(name); | ||
}, | ||
push: function() { | ||
return new Frame(this); | ||
}, | ||
pop: function() { | ||
return this.parent; | ||
} | ||
}); | ||
function makeMacro(argNames, kwargNames, func) { | ||
return function() { | ||
var argCount = numArgs(arguments); | ||
var args; | ||
var kwargs = getKeywordArgs(arguments); | ||
if(argCount > argNames.length) { | ||
args = Array.prototype.slice.call(arguments, 0, argNames.length); | ||
// Positional arguments that should be passed in as | ||
// keyword arguments (essentially default values) | ||
var vals = Array.prototype.slice.call(arguments, args.length, argCount); | ||
for(var i=0; i<vals.length; i++) { | ||
if(i < kwargNames.length) { | ||
kwargs[kwargNames[i]] = vals[i]; | ||
} | ||
} | ||
args.push(kwargs); | ||
} | ||
else if(argCount < argNames.length) { | ||
args = Array.prototype.slice.call(arguments, 0, argCount); | ||
for(var i=argCount; i<argNames.length; i++) { | ||
var arg = argNames[i]; | ||
// Keyword arguments that should be passed as | ||
// positional arguments, i.e. the caller explicitly | ||
// used the name of a positional arg | ||
args.push(kwargs[arg]); | ||
delete kwargs[arg]; | ||
} | ||
args.push(kwargs); | ||
} | ||
else { | ||
args = arguments; | ||
} | ||
return func.apply(this, args); | ||
}; | ||
} | ||
function makeKeywordArgs(obj) { | ||
obj.__keywords = true; | ||
return obj; | ||
} | ||
function getKeywordArgs(args) { | ||
var len = args.length; | ||
if(len) { | ||
var lastArg = args[len - 1]; | ||
if(lastArg && lastArg.hasOwnProperty('__keywords')) { | ||
return lastArg; | ||
} | ||
} | ||
return {}; | ||
} | ||
function numArgs(args) { | ||
var len = args.length; | ||
if(len === 0) { | ||
return 0; | ||
} | ||
var lastArg = args[len - 1]; | ||
if(lastArg && lastArg.hasOwnProperty('__keywords')) { | ||
return len - 1; | ||
} | ||
else { | ||
return len; | ||
} | ||
} | ||
// A SafeString object indicates that the string should not be | ||
// autoescaped. This happens magically because autoescaping only | ||
// occurs on primitive string objects. | ||
function SafeString(val) { | ||
if(typeof val != 'string') { | ||
return val; | ||
} | ||
this.toString = function() { | ||
return val; | ||
}; | ||
this.length = val.length; | ||
var methods = [ | ||
'charAt', 'charCodeAt', 'concat', 'contains', | ||
'endsWith', 'fromCharCode', 'indexOf', 'lastIndexOf', | ||
'length', 'localeCompare', 'match', 'quote', 'replace', | ||
'search', 'slice', 'split', 'startsWith', 'substr', | ||
'substring', 'toLocaleLowerCase', 'toLocaleUpperCase', | ||
'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight' | ||
]; | ||
for(var i=0; i<methods.length; i++) { | ||
this[methods[i]] = proxyStr(val[methods[i]]); | ||
} | ||
} | ||
function copySafeness(dest, target) { | ||
if(dest instanceof SafeString) { | ||
return new SafeString(target); | ||
} | ||
return target.toString(); | ||
} | ||
function proxyStr(func) { | ||
return function() { | ||
var ret = func.apply(this, arguments); | ||
if(typeof ret == 'string') { | ||
return new SafeString(ret); | ||
} | ||
return ret; | ||
}; | ||
} | ||
function suppressValue(val, autoescape) { | ||
val = (val !== undefined && val !== null) ? val : ""; | ||
if(autoescape && typeof val === "string") { | ||
val = lib.escape(val); | ||
} | ||
return val; | ||
} | ||
function memberLookup(obj, val) { | ||
obj = obj || {}; | ||
if(typeof obj[val] === 'function') { | ||
return function() { | ||
return obj[val].apply(obj, arguments); | ||
}; | ||
} | ||
return obj[val]; | ||
} | ||
function callWrap(obj, name, args) { | ||
if(!obj) { | ||
throw new Error('Unable to call `' + name + '`, which is undefined or falsey'); | ||
} | ||
else if(typeof obj !== 'function') { | ||
throw new Error('Unable to call `' + name + '`, which is not a function'); | ||
} | ||
return obj.apply(this, args); | ||
} | ||
function contextOrFrameLookup(context, frame, name) { | ||
var val = context.lookup(name); | ||
return (val !== undefined && val !== null) ? | ||
val : | ||
frame.lookup(name); | ||
} | ||
function handleError(error, lineno, colno) { | ||
if(error.lineno) { | ||
throw error; | ||
} | ||
else { | ||
throw new lib.TemplateError(error, lineno, colno); | ||
} | ||
} | ||
modules['runtime'] = { | ||
Frame: Frame, | ||
makeMacro: makeMacro, | ||
makeKeywordArgs: makeKeywordArgs, | ||
numArgs: numArgs, | ||
suppressValue: suppressValue, | ||
memberLookup: memberLookup, | ||
contextOrFrameLookup: contextOrFrameLookup, | ||
callWrap: callWrap, | ||
handleError: handleError, | ||
isArray: lib.isArray, | ||
SafeString: SafeString, | ||
copySafeness: copySafeness | ||
}; | ||
})(); | ||
(function() { | ||
var lib = modules["lib"]; | ||
var r = modules["runtime"]; | ||
var filters = { | ||
@@ -248,4 +536,4 @@ abs: function(n) { | ||
capitalize: function(str) { | ||
str = str.toLowerCase(); | ||
return str[0].toUpperCase() + str.slice(1); | ||
var ret = str.toLowerCase(); | ||
return r.copySafeness(str, ret[0].toUpperCase() + ret.slice(1)); | ||
}, | ||
@@ -263,17 +551,61 @@ | ||
var post = lib.repeat(" ", spaces/2); | ||
return pre + str + post; | ||
return r.copySafeness(str, pre + str + post); | ||
}, | ||
default: function(val, def) { | ||
'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) { | ||
// deliberately include properties from the object's prototype | ||
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) { | ||
return str.replace(/&/g, '&') | ||
.replace(/"/g, '"') | ||
.replace(/'/g, ''') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>'); | ||
if(typeof str == 'string' || | ||
str instanceof r.SafeString) { | ||
return lib.escape(str); | ||
} | ||
return str; | ||
}, | ||
safe: function(str) { | ||
return new r.SafeString(str); | ||
}, | ||
first: function(arr) { | ||
@@ -302,3 +634,3 @@ return arr[0]; | ||
return res; | ||
return r.copySafeness(str, res); | ||
}, | ||
@@ -381,3 +713,3 @@ | ||
return res; | ||
return r.copySafeness(str, res); | ||
}, | ||
@@ -398,3 +730,3 @@ | ||
if(lib.isString(val)) { | ||
return arr.join(''); | ||
return r.copySafeness(val, arr.join('')); | ||
} | ||
@@ -481,13 +813,39 @@ return arr; | ||
string: function(obj) { | ||
return obj.toString(); | ||
return r.copySafeness(obj, obj); | ||
}, | ||
title: function(str) { | ||
return str.toUpperCase(); | ||
var words = str.split(' '); | ||
for(var i = 0; i < words.length; i++) { | ||
words[i] = filters.capitalize(words[i]); | ||
} | ||
return r.copySafeness(str, words.join(' ')); | ||
}, | ||
trim: function(str) { | ||
return str.replace(/^\s*|\s*$/g, ''); | ||
return r.copySafeness(str, str.replace(/^\s*|\s*$/g, '')); | ||
}, | ||
truncate: function(input, length, killwords, end) { | ||
var orig = input; | ||
length = length || 255; | ||
if (input.length <= length) | ||
return input; | ||
if (killwords) { | ||
input = input.substring(0, length); | ||
} else { | ||
var idx = input.lastIndexOf(' ', length); | ||
if(idx === -1) { | ||
idx = length; | ||
} | ||
input = input.substring(0, idx); | ||
} | ||
input += (end !== undefined && end !== null) ? end : '...'; | ||
return r.copySafeness(orig, input); | ||
}, | ||
upper: function(str) { | ||
@@ -501,8 +859,10 @@ return str.toUpperCase(); | ||
float: function(val, def) { | ||
return parseFloat(val) || def; | ||
'float': function(val, def) { | ||
var res = parseFloat(val); | ||
return isNaN(res) ? def : res; | ||
}, | ||
int: function(val, def) { | ||
return parseInt(val) || def; | ||
'int': function(val, def) { | ||
var res = parseInt(val, 10); | ||
return isNaN(res) ? def : res; | ||
} | ||
@@ -512,3 +872,3 @@ }; | ||
// Aliases | ||
filters.d = filters.default; | ||
filters.d = filters['default']; | ||
filters.e = filters.escape; | ||
@@ -520,150 +880,67 @@ | ||
var Object = modules["object"]; | ||
function cycler(items) { | ||
var index = -1; | ||
var current = null; | ||
// Frames keep track of scoping both at compile-time and run-time so | ||
// we know how to access variables. Block tags can introduce special | ||
// variables, for example. | ||
var Frame = Object.extend({ | ||
init: function(parent) { | ||
this.variables = {}; | ||
this.parent = parent; | ||
}, | ||
return { | ||
reset: function() { | ||
index = -1; | ||
current = null; | ||
}, | ||
set: function(name, val) { | ||
// Allow variables with dots by automatically creating the | ||
// nested structure | ||
var parts = name.split('.'); | ||
var obj = this.variables; | ||
for(var i=0; i<parts.length - 1; i++) { | ||
var id = parts[i]; | ||
if(!obj[id]) { | ||
obj[id] = {}; | ||
next: function() { | ||
index++; | ||
if(index >= items.length) { | ||
index = 0; | ||
} | ||
obj = obj[id]; | ||
} | ||
obj[parts[parts.length - 1]] = val; | ||
}, | ||
lookup: function(name) { | ||
var p = this.parent; | ||
var val = this.variables[name]; | ||
if(val !== undefined && val !== null) { | ||
return val; | ||
current = items[index]; | ||
return current; | ||
} | ||
return p && p.lookup(name); | ||
}, | ||
}; | ||
push: function() { | ||
return new Frame(this); | ||
}, | ||
} | ||
pop: function() { | ||
return this.parent; | ||
} | ||
}); | ||
function joiner(sep) { | ||
sep = sep || ','; | ||
var first = true; | ||
function makeMacro(argNames, kwargNames, func) { | ||
return function() { | ||
var argCount = numArgs(arguments); | ||
var args; | ||
var kwargs = getKeywordArgs(arguments); | ||
var val = first ? '' : sep; | ||
first = false; | ||
return val; | ||
}; | ||
} | ||
if(argCount > argNames.length) { | ||
args = Array.prototype.slice.call(arguments, 0, argNames.length); | ||
// Positional arguments that should be passed in as | ||
// keyword arguments (essentially default values) | ||
var vals = Array.prototype.slice.call(arguments, args.length, argCount); | ||
for(var i=0; i<vals.length; i++) { | ||
if(i < kwargNames.length) { | ||
kwargs[kwargNames[i]] = vals[i]; | ||
} | ||
} | ||
args.push(kwargs); | ||
var globals = { | ||
range: function(start, stop, step) { | ||
if(!stop) { | ||
stop = start; | ||
start = 0; | ||
step = 1; | ||
} | ||
else if(argCount < argNames.length) { | ||
args = Array.prototype.slice.call(arguments, 0, argCount); | ||
else if(!step) { | ||
step = 1; | ||
} | ||
for(var i=argCount; i<argNames.length; i++) { | ||
var arg = argNames[i]; | ||
// Keyword arguments that should be passed as | ||
// positional arguments, i.e. the caller explicitly | ||
// used the name of a positional arg | ||
args.push(kwargs[arg]); | ||
delete kwargs[arg]; | ||
} | ||
args.push(kwargs); | ||
var arr = []; | ||
for(var i=start; i<stop; i+=step) { | ||
arr.push(i); | ||
} | ||
else { | ||
args = arguments; | ||
} | ||
return arr; | ||
}, | ||
return func.apply(this, args); | ||
}; | ||
} | ||
// lipsum: function(n, html, min, max) { | ||
// }, | ||
function makeKeywordArgs(obj) { | ||
obj.__keywords = true; | ||
return obj; | ||
} | ||
cycler: function() { | ||
return cycler(Array.prototype.slice.call(arguments)); | ||
}, | ||
function getKeywordArgs(args) { | ||
if(args.length && args[args.length - 1].__keywords) { | ||
return args[args.length - 1]; | ||
joiner: function(sep) { | ||
return joiner(sep); | ||
} | ||
return {}; | ||
} | ||
function numArgs(args) { | ||
if(args.length === 0) { | ||
return 0; | ||
} | ||
else if(args[args.length - 1].__keywords) { | ||
return args.length - 1; | ||
} | ||
else { | ||
return args.length; | ||
} | ||
} | ||
function suppressValue(val) { | ||
return (val !== undefined && val !== null) ? val : ""; | ||
} | ||
function suppressLookupValue(obj, val) { | ||
obj = obj || {}; | ||
val = obj[val]; | ||
if(typeof val === 'function') { | ||
return function() { | ||
return suppressValue(val.apply(obj, arguments)); | ||
}; | ||
} | ||
else { | ||
return suppressValue(val); | ||
} | ||
} | ||
function contextOrFrameLookup(context, frame, name) { | ||
var val = context.lookup(name); | ||
return (val !== undefined && val !== null) ? | ||
val : | ||
frame.lookup(name); | ||
} | ||
modules['runtime'] = { | ||
Frame: Frame, | ||
makeMacro: makeMacro, | ||
makeKeywordArgs: makeKeywordArgs, | ||
numArgs: numArgs, | ||
suppressValue: suppressValue, | ||
suppressLookupValue: suppressLookupValue, | ||
contextOrFrameLookup: contextOrFrameLookup | ||
}; | ||
modules['globals'] = globals; | ||
})(); | ||
@@ -678,6 +955,7 @@ (function() { | ||
var runtime = modules["runtime"]; | ||
var globals = modules["globals"]; | ||
var Frame = runtime.Frame; | ||
var Environment = Object.extend({ | ||
init: function(loaders, tags, dev) { | ||
init: function(loaders, opts) { | ||
// The dev flag determines the trace that'll be shown on errors. | ||
@@ -688,4 +966,12 @@ // If set to true, returns the full trace from the error point, | ||
// the library) | ||
this.dev = dev; | ||
// defaults to false | ||
opts = opts || {}; | ||
this.dev = !!opts.dev; | ||
// The autoescape flag sets global autoescaping. If true, | ||
// every string variable will be escaped by default. | ||
// If false, strings can be manually escaped using the `escape` filter. | ||
// defaults to false | ||
this.autoesc = !!opts.autoescape; | ||
if(!loaders) { | ||
@@ -704,4 +990,4 @@ // The filesystem loader is only available client-side | ||
if(tags) { | ||
lexer.setTags(tags); | ||
if(opts.tags) { | ||
lexer.setTags(opts.tags); | ||
} | ||
@@ -711,23 +997,14 @@ | ||
this.cache = {}; | ||
this.extensions = {}; | ||
this.extensionsList = []; | ||
}, | ||
tryTemplate: function(path, func) { | ||
try { | ||
return func(); | ||
} catch (e) { | ||
if (!e.Update) { | ||
// not one of ours, cast it | ||
e = lib.TemplateError(e); | ||
} | ||
e.Update(path); | ||
addExtension: function(name, extension) { | ||
extension._name = name; | ||
this.extensions[name] = extension; | ||
this.extensionsList.push(extension); | ||
}, | ||
// Unless they marked the dev flag, show them a trace from here | ||
if (!this.dev) { | ||
var old = e; | ||
e = new Error(old.message); | ||
e.name = old.name; | ||
} | ||
throw e; | ||
} | ||
getExtension: function(name) { | ||
return this.extensions[name]; | ||
}, | ||
@@ -747,2 +1024,6 @@ | ||
getTemplate: function(name, eagerCompile) { | ||
if (name && name.raw) { | ||
// this fixes autoescape for templates referenced in symbols | ||
name = name.raw; | ||
} | ||
var info = null; | ||
@@ -864,3 +1145,10 @@ var tmpl = this.cache[name]; | ||
lookup: function(name) { | ||
return this.ctx[name]; | ||
// This is one of the most called functions, so optimize for | ||
// the typical case where the name isn't in the globals | ||
if(name in globals && !(name in this.ctx)) { | ||
return globals[name]; | ||
} | ||
else { | ||
return this.ctx[name]; | ||
} | ||
}, | ||
@@ -889,3 +1177,3 @@ | ||
getSuper: function(env, name, block) { | ||
getSuper: function(env, name, block, frame, runtime) { | ||
var idx = (this.blocks[name] || []).indexOf(block); | ||
@@ -900,3 +1188,3 @@ var blk = this.blocks[name][idx + 1]; | ||
return blk(env, context); | ||
return blk(env, context, frame, runtime); | ||
}; | ||
@@ -941,5 +1229,6 @@ }, | ||
if(eagerCompile) { | ||
var self = this; | ||
this.env.tryTemplate(this.path, function() { self._compile(); }); | ||
self = null; | ||
var _this = this; | ||
lib.withPrettyErrors(this.path, | ||
this.env.dev, | ||
function() { _this._compile(); }); | ||
} | ||
@@ -966,3 +1255,4 @@ else { | ||
}; | ||
return this.env.tryTemplate(this.path, render); | ||
return lib.withPrettyErrors(this.path, this.env.dev, render); | ||
}, | ||
@@ -995,3 +1285,4 @@ | ||
else { | ||
var func = new Function(compiler.compile(this.tmplStr, this.env)); | ||
var source = compiler.compile(this.tmplStr, this.env.extensionsList, this.path); | ||
var func = new Function(source); | ||
props = func(); | ||
@@ -1020,6 +1311,10 @@ } | ||
// var src = fs.readFileSync('test.html', 'utf-8'); | ||
// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}'; | ||
// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}'; | ||
// var src = '{% macro foo(x) %}{{ x }}{% endmacro %}{{ foo("<>") }}'; | ||
// var env = new Environment(null, { autoescape: true, dev: true }); | ||
// var env = new Environment(); | ||
// env.addFilter('bar', function(x) { | ||
// return runtime.copySafeness(x, x.substring(3, 1) + x.substring(0, 2)); | ||
// }); | ||
// //env.addExtension('testExtension', new testExtension()); | ||
// console.log(compiler.compile(src)); | ||
@@ -1029,3 +1324,3 @@ | ||
// console.log("OUTPUT ---"); | ||
// console.log(tmpl.render({ username: "James" })); | ||
// console.log(tmpl.render({ bar: '<>&' })); | ||
@@ -1037,2 +1332,3 @@ modules['environment'] = { | ||
})(); | ||
var nunjucks; | ||
@@ -1043,7 +1339,8 @@ var env = modules["environment"]; | ||
var lexer = modules["lexer"]; | ||
var runtime = modules["runtime"]; | ||
var loaders = modules["loaders"]; | ||
window.nunjucks = {}; | ||
window.nunjucks.Environment = env.Environment; | ||
window.nunjucks.Template = env.Template; | ||
nunjucks = {}; | ||
nunjucks.Environment = env.Environment; | ||
nunjucks.Template = env.Template; | ||
@@ -1053,16 +1350,23 @@ // loaders is not available when using precompiled templates | ||
if(loaders.FileSystemLoader) { | ||
window.nunjucks.FileSystemLoader = loaders.FileSystemLoader; | ||
nunjucks.FileSystemLoader = loaders.FileSystemLoader; | ||
} | ||
else { | ||
window.nunjucks.HttpLoader = loaders.HttpLoader; | ||
nunjucks.HttpLoader = loaders.HttpLoader; | ||
} | ||
} | ||
window.nunjucks.compiler = compiler; | ||
window.nunjucks.parser = parser; | ||
window.nunjucks.lexer = lexer; | ||
nunjucks.compiler = compiler; | ||
nunjucks.parser = parser; | ||
nunjucks.lexer = lexer; | ||
nunjucks.runtime = runtime; | ||
window.nunjucks.require = | ||
function(name) { return modules[name]; }; | ||
nunjucks.require = function(name) { return modules[name]; }; | ||
if(typeof define === 'function' && define.amd) { | ||
define(function() { return nunjucks; }); | ||
} | ||
else { | ||
window.nunjucks = nunjucks; | ||
} | ||
})(); |
@@ -6,2 +6,3 @@ | ||
var lexer = require('./src/lexer'); | ||
var runtime = require('./src/runtime'); | ||
var loaders = require('./src/loaders'); | ||
@@ -25,2 +26,3 @@ | ||
module.exports.parser = parser; | ||
module.exports.lexer = lexer; | ||
module.exports.lexer = lexer; | ||
module.exports.runtime = runtime; |
{ | ||
"name": "nunjucks", | ||
"description": "A jinja inspired templating engine", | ||
"version": "0.1.8", | ||
"version": "0.1.9", | ||
"author": { | ||
@@ -10,4 +10,5 @@ "name": "James Long", | ||
"devDependencies": { | ||
"should": "*", | ||
"mocha": "*" | ||
"expect.js": "*", | ||
"mocha": "*", | ||
"uglify-js": "*" | ||
}, | ||
@@ -14,0 +15,0 @@ "engines": { |
@@ -23,3 +23,3 @@ | ||
The current version is v0.1.7 ([changelog](http://nunjucks.tumblr.com/post/37795552313/v0-1-7-helpful-errors-many-bug-fixes)). | ||
The current version is v0.1.9 ([changelog](https://github.com/jlongster/nunjucks/blob/master/CHANGELOG.md)) | ||
@@ -30,6 +30,10 @@ ## Documentation | ||
Run all the tests at http://jlongster.github.io/nunjucks/tests/browser/. | ||
## Mailing List | ||
Join our mailing list and get help with and issues you have: https://groups.google.com/forum/?fromgroups#!forum/nunjucks | ||
## Contributors | ||
* James Long (owner) | ||
* Brent Hagany | ||
* Thomas Hunkapiller | ||
https://github.com/jlongster/nunjucks/graphs/contributors |
@@ -1,2 +0,1 @@ | ||
var lib = require('./lib'); | ||
@@ -36,8 +35,12 @@ var parser = require('./parser'); | ||
var Compiler = Object.extend({ | ||
init: function() { | ||
init: function(extensions) { | ||
this.codebuf = []; | ||
this.lastId = 0; | ||
this.buffer = null; | ||
this.bufferStack = []; | ||
this.isChild = false; | ||
this.extensions = extensions || []; | ||
}, | ||
fail: function (msg, lineno, colno) { | ||
@@ -50,2 +53,12 @@ if (lineno !== undefined) lineno += 1; | ||
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) { | ||
@@ -91,6 +104,13 @@ this.codebuf.push(code); | ||
_bufferAppend: function(func) { | ||
this.emit(this.buffer + ' += runtime.suppressValue('); | ||
func.call(this); | ||
this.emit(', env.autoesc);\n'); | ||
}, | ||
_compileChildren: function(node, frame) { | ||
lib.each(node.children, function(n) { | ||
this.compile(n, frame); | ||
}, this); | ||
var children = node.children; | ||
for(var i=0, l=children.length; i<l; i++) { | ||
this.compile(children[i], frame); | ||
} | ||
}, | ||
@@ -126,2 +146,3 @@ | ||
nodes.Compare, | ||
nodes.InlineIf, | ||
nodes.And, | ||
@@ -161,2 +182,60 @@ nodes.Or, | ||
compileCallExtension: function(node, frame) { | ||
var name = node.extName; | ||
var args = node.args; | ||
var contentArgs = node.contentArgs; | ||
var transformedArgs = []; | ||
this.emit(this.buffer + ' += runtime.suppressValue('); | ||
this.emit('env.getExtension("' + node.extName + '")["' + node.prop + '"]('); | ||
this.emit('context'); | ||
if(args || contentArgs) { | ||
this.emit(','); | ||
} | ||
if(args) { | ||
if(!(args instanceof nodes.NodeList)) { | ||
this.fail('compileCallExtension: arguments must be a NodeList, ' + | ||
'use `parser.parseSignature`'); | ||
} | ||
lib.each(args.children, function(arg, i) { | ||
// Tag arguments are passed normally to the call. Note | ||
// that keyword arguments are turned into a single js | ||
// object as the last argument, if they exist. | ||
this._compileExpression(arg, frame); | ||
if(i != args.children.length || contentArgs) { | ||
this.emit(','); | ||
} | ||
}, this); | ||
} | ||
if(contentArgs) { | ||
lib.each(contentArgs, function(arg, i) { | ||
if(i > 0) { | ||
this.emit(','); | ||
} | ||
if(arg) { | ||
var id = this.tmpid(); | ||
this.emit('function() {'); | ||
this.pushBufferId(id); | ||
this.compile(arg, frame); | ||
this.popBufferId(); | ||
this.emitLine('return ' + id + ';\n' + | ||
'}'); | ||
} | ||
else { | ||
this.emit('null'); | ||
} | ||
}, this); | ||
} | ||
this.emit(')'); | ||
this.emit(', env.autoesc);\n'); | ||
}, | ||
compileNodeList: function(node, frame) { | ||
@@ -224,2 +303,15 @@ this._compileChildren(node, 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(' || '), | ||
@@ -275,9 +367,25 @@ compileAnd: binOpEmitter(' && '), | ||
compileLookupVal: function(node, frame) { | ||
this.emit('runtime.suppressLookupValue(('); | ||
this.emit('runtime.memberLookup(('); | ||
this._compileExpression(node.target, frame); | ||
this.emit('),'); | ||
this._compileExpression(node.val, frame); | ||
this.emit(')'); | ||
this.emit(', env.autoesc)'); | ||
}, | ||
_getNodeName: function(node) { | ||
switch (node.typename) { | ||
case 'Symbol': | ||
return node.value; | ||
case 'FunCall': | ||
return 'the return value of (' + this._getNodeName(node.name) + ')'; | ||
case 'LookupVal': | ||
return this._getNodeName(node.target) + '["' + | ||
this._getNodeName(node.val) + '"]'; | ||
case 'Literal': | ||
return node.value.toString().substr(0, 10); | ||
default: | ||
return '--expression--'; | ||
} | ||
}, | ||
compileFunCall: function(node, frame) { | ||
@@ -291,8 +399,12 @@ // Keep track of line/col info at runtime by settings | ||
this.emit('('); | ||
this.emit('runtime.callWrap('); | ||
// Compile it as normal. | ||
this._compileExpression(node.name, frame); | ||
this.emit(')'); | ||
this._compileAggregate(node.args, frame, '(', ')'); | ||
// Output the name of what we're calling so we can get friendly errors | ||
// if the lookup fails. | ||
this.emit(', "' + this._getNodeName(node.name).replace(/"/g, '\\"') + '", '); | ||
this._compileAggregate(node.args, frame, '[', '])'); | ||
this.emit(')'); | ||
@@ -322,14 +434,34 @@ }, | ||
compileSet: function(node, frame) { | ||
var id = this.tmpid(); | ||
var ids = []; | ||
this.emit('var ' + id + ' = '); | ||
// Lookup the variable names for each identifier and create | ||
// new ones if necessary | ||
lib.each(node.targets, function(target) { | ||
var name = target.value; | ||
var id = frame.get(name); | ||
if (id === null) { | ||
id = this.tmpid(); | ||
frame.set(name, id); | ||
// Note: This relies on js allowing scope across | ||
// blocks, in case this is created inside an `if` | ||
this.emitLine('var ' + id + ';'); | ||
} | ||
ids.push(id); | ||
}, this); | ||
this.emit(ids.join(' = ') + ' = '); | ||
this._compileExpression(node.value, frame); | ||
this.emitLine(';'); | ||
for(var i=0; i<node.targets.length; i++) { | ||
var name = node.targets[i].value; | ||
frame.set(name, id); | ||
lib.each(node.targets, function(target, i) { | ||
var id = ids[i]; | ||
var name = target.value; | ||
this.emitLine('frame.set("' + name + '", ' + id + ');'); | ||
// We are running this for every var, but it's very | ||
// uncommon to assign to multiple vars anyway | ||
this.emitLine('if(!frame.parent) {'); | ||
@@ -341,3 +473,3 @@ this.emitLine('context.setVariable("' + name + '", ' + id + ');'); | ||
this.emitLine('}'); | ||
} | ||
}, this); | ||
}, | ||
@@ -370,10 +502,25 @@ | ||
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; | ||
} | ||
}); | ||
}); | ||
this.emit('if(' + arr + ' !== undefined) {'); | ||
if(node.name instanceof nodes.Array) { | ||
// key/value iteration. the user could have passed a dict | ||
// key/value iteration. the user could have passed a dict | ||
// amd two elements to be unpacked - "for k,v in { a: b }" | ||
// or they could have passed an array of arrays - | ||
// for a,b,c in [[a,b,c],[c,d,e]] where the number of | ||
// or they could have passed an array of arrays - | ||
// for a,b,c in [[a,b,c],[c,d,e]] where the number of | ||
// elements to be unpacked is variable. | ||
// | ||
// we cant known in advance which has been passed so we | ||
// we cant known in advance which has been passed so we | ||
// have to emit code that handles both cases | ||
@@ -386,3 +533,3 @@ this.emitLine('var ' + i + ';'); | ||
// array of tuples | ||
this.emitLine('for (' + i + '=0; ' + i + ' < ' + arr + '.length; ' | ||
this.emitLine('for (' + i + '=0; ' + i + ' < ' + arr + '.length; ' | ||
+ i + '++) {'); | ||
@@ -394,3 +541,3 @@ | ||
this.emitLine('var ' + tid + ' = ' + arr + '[' + i + '][' + u + ']'); | ||
this.emitLine('frame.set("' + node.name.children[u].value | ||
this.emitLine('frame.set("' + node.name.children[u].value | ||
+ '", ' + arr + '[' + i + '][' + u + ']' + ');'); | ||
@@ -400,5 +547,11 @@ frame.set(node.name.children[u].value, tid); | ||
this.emitLine('frame.set("loop.index", ' + i + ' + 1);'); | ||
this.emitLine('frame.set("loop.index0", ' + i + ');'); | ||
this.emitLine('frame.set("loop.first", ' + i + ' === 0);'); | ||
if ('index' in loopUses) { | ||
this.emitLine('frame.set("loop.index", ' + i + ' + 1);'); | ||
} | ||
if ('index0' in loopUses) { | ||
this.emitLine('frame.set("loop.index0", ' + i + ');'); | ||
} | ||
if ('first' in loopUses) { | ||
this.emitLine('frame.set("loop.first", ' + i + ' === 0);'); | ||
} | ||
@@ -427,8 +580,14 @@ this.compile(node.body, frame); | ||
this.emitLine('frame.set("' + val.value + '", ' + v + ');'); | ||
this.emitLine('frame.set("loop.index", ' + i + ' + 1);'); | ||
this.emitLine('frame.set("loop.index0", ' + i + ');'); | ||
this.emitLine('frame.set("loop.first", ' + i + ' === 0);'); | ||
if ('index' in loopUses) { | ||
this.emitLine('frame.set("loop.index", ' + i + ' + 1);'); | ||
} | ||
if ('index0' in loopUses) { | ||
this.emitLine('frame.set("loop.index0", ' + i + ');'); | ||
} | ||
if ('first' in loopUses) { | ||
this.emitLine('frame.set("loop.first", ' + i + ' === 0);'); | ||
} | ||
this.compile(node.body, frame); | ||
this.emitLine('}'); // end for | ||
this.emitLine('}'); // end for | ||
@@ -447,16 +606,29 @@ this.emitLine('}'); // end if | ||
'", ' + v + ');'); | ||
this.emitLine('frame.set("loop.index", ' + i + ' + 1);'); | ||
this.emitLine('frame.set("loop.index0", ' + i + ');'); | ||
this.emitLine('frame.set("loop.revindex", ' + arr + '.length - ' + i + ');'); | ||
this.emitLine('frame.set("loop.revindex0", ' + arr + '.length - ' + i + ' - 1);'); | ||
this.emitLine('frame.set("loop.first", ' + i + ' === 0);'); | ||
this.emitLine('frame.set("loop.last", ' + i + ' === ' + arr + '.length - 1);'); | ||
this.emitLine('frame.set("loop.length", ' + arr + '.length);'); | ||
if ('index' in loopUses) { | ||
this.emitLine('frame.set("loop.index", ' + i + ' + 1);'); | ||
} | ||
if ('index0' in loopUses) { | ||
this.emitLine('frame.set("loop.index0", ' + i + ');'); | ||
} | ||
if ('revindex' in loopUses) { | ||
this.emitLine('frame.set("loop.revindex", ' + arr + '.length - ' + i + ');'); | ||
} | ||
if ('revindex0' in loopUses) { | ||
this.emitLine('frame.set("loop.revindex0", ' + arr + '.length - ' + i + ' - 1);'); | ||
} | ||
if ('first' in loopUses) { | ||
this.emitLine('frame.set("loop.first", ' + i + ' === 0);'); | ||
} | ||
if ('last' in loopUses) { | ||
this.emitLine('frame.set("loop.last", ' + i + ' === ' + arr + '.length - 1);'); | ||
} | ||
if ('length' in loopUses) { | ||
this.emitLine('frame.set("loop.length", ' + arr + '.length);'); | ||
} | ||
this.compile(node.body, frame); | ||
this.emitLine('}'); | ||
} | ||
this.emit('}'); | ||
this.emitLine('frame = frame.pop();'); | ||
@@ -529,3 +701,3 @@ }, | ||
this.emitLine('frame = frame.pop();'); | ||
this.emitLine('return ' + this.buffer + ';'); | ||
this.emitLine('return new runtime.SafeString(' + this.buffer + ');'); | ||
this.emitLine('});'); | ||
@@ -619,4 +791,6 @@ }, | ||
compileBlock: function(node, frame) { | ||
this.emitLine(this.buffer + ' += context.getBlock("' + | ||
node.name.value + '")(env, context, frame, runtime);'); | ||
if(!this.isChild) { | ||
this.emitLine(this.buffer + ' += context.getBlock("' + | ||
node.name.value + '")(env, context, frame, runtime);'); | ||
} | ||
}, | ||
@@ -659,5 +833,19 @@ | ||
compileOutput: function(node, frame) { | ||
this.emit(this.buffer + ' += runtime.suppressValue('); | ||
this._compileChildren(node, frame); | ||
this.emit(');\n'); | ||
var children = node.children; | ||
for(var i=0, l=children.length; i<l; i++) { | ||
// TemplateData is a special case because it is never | ||
// autoescaped, so simply output it for optimization | ||
if(children[i] instanceof nodes.TemplateData) { | ||
if(children[i].value) { | ||
this.emit(this.buffer + ' += '); | ||
this.compileLiteral(children[i], frame); | ||
this.emitLine(';'); | ||
} | ||
} | ||
else { | ||
this.emit(this.buffer + ' += runtime.suppressValue('); | ||
this.compile(children[i], frame); | ||
this.emit(', env.autoesc);\n'); | ||
} | ||
} | ||
}, | ||
@@ -680,2 +868,5 @@ | ||
// When compiling the blocks, they should all act as top-level code | ||
this.isChild = false; | ||
var blocks = node.findAll(nodes.Block); | ||
@@ -726,6 +917,6 @@ for(var i=0; i<blocks.length; i++) { | ||
// var fs = require("fs"); | ||
//var src = '{{ foo({a:1}) }} {% block content %}foo{% endblock %}'; | ||
// var c = new Compiler(); | ||
// //var src = '{{ foo({a:1}) }}'; | ||
// var src = '{% extends "base.html" %}' + | ||
// '{% block block1 %}{{ super() }}BAR{% endblock %}'; | ||
// var src = '{% extends "b.html" %}{% block block1 %}{% block nested %}BAR{% endblock %}{% endblock %}'; | ||
//var extensions = [new testExtension()]; | ||
@@ -737,9 +928,18 @@ // var ns = parser.parse(src); | ||
// var tmpl = c.getCode(); | ||
// console.log(tmpl); | ||
module.exports = { | ||
compile: function(src) { | ||
var c = new Compiler(); | ||
c.compile(parser.parse(src)); | ||
compile: function(src, extensions, name) { | ||
var c = new Compiler(extensions); | ||
// Run the extension preprocessors against the source. | ||
if (extensions && extensions.length) { | ||
for (var i = 0; i < extensions.length; i++) { | ||
if ('preprocess' in extensions[i]) { | ||
src = extensions[i].preprocess(src, name); | ||
} | ||
} | ||
} | ||
c.compile(parser.parse(src, extensions)); | ||
return c.getCode(); | ||
@@ -746,0 +946,0 @@ }, |
@@ -8,6 +8,7 @@ var lib = require('./lib'); | ||
var runtime = require('./runtime'); | ||
var globals = require('./globals'); | ||
var Frame = runtime.Frame; | ||
var Environment = Object.extend({ | ||
init: function(loaders, tags, dev) { | ||
init: function(loaders, opts) { | ||
// The dev flag determines the trace that'll be shown on errors. | ||
@@ -18,4 +19,12 @@ // If set to true, returns the full trace from the error point, | ||
// the library) | ||
this.dev = dev; | ||
// defaults to false | ||
opts = opts || {}; | ||
this.dev = !!opts.dev; | ||
// The autoescape flag sets global autoescaping. If true, | ||
// every string variable will be escaped by default. | ||
// If false, strings can be manually escaped using the `escape` filter. | ||
// defaults to false | ||
this.autoesc = !!opts.autoescape; | ||
if(!loaders) { | ||
@@ -34,4 +43,4 @@ // The filesystem loader is only available client-side | ||
if(tags) { | ||
lexer.setTags(tags); | ||
if(opts.tags) { | ||
lexer.setTags(opts.tags); | ||
} | ||
@@ -41,4 +50,16 @@ | ||
this.cache = {}; | ||
this.extensions = {}; | ||
this.extensionsList = []; | ||
}, | ||
addExtension: function(name, extension) { | ||
extension._name = name; | ||
this.extensions[name] = extension; | ||
this.extensionsList.push(extension); | ||
}, | ||
getExtension: function(name) { | ||
return this.extensions[name]; | ||
}, | ||
addFilter: function(name, func) { | ||
@@ -56,2 +77,6 @@ this.filters[name] = func; | ||
getTemplate: function(name, eagerCompile) { | ||
if (name && name.raw) { | ||
// this fixes autoescape for templates referenced in symbols | ||
name = name.raw; | ||
} | ||
var info = null; | ||
@@ -173,3 +198,10 @@ var tmpl = this.cache[name]; | ||
lookup: function(name) { | ||
return this.ctx[name]; | ||
// This is one of the most called functions, so optimize for | ||
// the typical case where the name isn't in the globals | ||
if(name in globals && !(name in this.ctx)) { | ||
return globals[name]; | ||
} | ||
else { | ||
return this.ctx[name]; | ||
} | ||
}, | ||
@@ -302,3 +334,4 @@ | ||
else { | ||
var func = new Function(compiler.compile(this.tmplStr, this.env)); | ||
var source = compiler.compile(this.tmplStr, this.env.extensionsList, this.path); | ||
var func = new Function(source); | ||
props = func(); | ||
@@ -327,6 +360,10 @@ } | ||
// var src = fs.readFileSync('test.html', 'utf-8'); | ||
// //var src = '{% macro foo(x, y, z=3) %}h{% endmacro %}'; | ||
// //var src = '{% macro foo() %}{{ h }}{% endmacro %} {{ foo() }}'; | ||
// var src = '{% macro foo(x) %}{{ x }}{% endmacro %}{{ foo("<>") }}'; | ||
// var env = new Environment(null, { autoescape: true, dev: true }); | ||
// var env = new Environment(); | ||
// env.addFilter('bar', function(x) { | ||
// return runtime.copySafeness(x, x.substring(3, 1) + x.substring(0, 2)); | ||
// }); | ||
// //env.addExtension('testExtension', new testExtension()); | ||
// console.log(compiler.compile(src)); | ||
@@ -336,3 +373,3 @@ | ||
// console.log("OUTPUT ---"); | ||
// console.log(tmpl.render({ username: "James" })); | ||
// console.log(tmpl.render({ bar: '<>&' })); | ||
@@ -339,0 +376,0 @@ module.exports = { |
var lib = require('./lib'); | ||
var r = require('./runtime'); | ||
@@ -36,4 +37,4 @@ var filters = { | ||
capitalize: function(str) { | ||
str = str.toLowerCase(); | ||
return str[0].toUpperCase() + str.slice(1); | ||
var ret = str.toLowerCase(); | ||
return r.copySafeness(str, ret[0].toUpperCase() + ret.slice(1)); | ||
}, | ||
@@ -51,3 +52,3 @@ | ||
var post = lib.repeat(" ", spaces/2); | ||
return pre + str + post; | ||
return r.copySafeness(str, pre + str + post); | ||
}, | ||
@@ -98,11 +99,15 @@ | ||
}, | ||
escape: function(str) { | ||
return str.replace(/&/g, '&') | ||
.replace(/"/g, '"') | ||
.replace(/'/g, ''') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>'); | ||
if(typeof str == 'string' || | ||
str instanceof r.SafeString) { | ||
return lib.escape(str); | ||
} | ||
return str; | ||
}, | ||
safe: function(str) { | ||
return new r.SafeString(str); | ||
}, | ||
first: function(arr) { | ||
@@ -131,3 +136,3 @@ return arr[0]; | ||
return res; | ||
return r.copySafeness(str, res); | ||
}, | ||
@@ -210,3 +215,3 @@ | ||
return res; | ||
return r.copySafeness(str, res); | ||
}, | ||
@@ -227,3 +232,3 @@ | ||
if(lib.isString(val)) { | ||
return arr.join(''); | ||
return r.copySafeness(val, arr.join('')); | ||
} | ||
@@ -310,13 +315,39 @@ return arr; | ||
string: function(obj) { | ||
return obj.toString(); | ||
return r.copySafeness(obj, obj); | ||
}, | ||
title: function(str) { | ||
return str.toUpperCase(); | ||
var words = str.split(' '); | ||
for(var i = 0; i < words.length; i++) { | ||
words[i] = filters.capitalize(words[i]); | ||
} | ||
return r.copySafeness(str, words.join(' ')); | ||
}, | ||
trim: function(str) { | ||
return str.replace(/^\s*|\s*$/g, ''); | ||
return r.copySafeness(str, str.replace(/^\s*|\s*$/g, '')); | ||
}, | ||
truncate: function(input, length, killwords, end) { | ||
var orig = input; | ||
length = length || 255; | ||
if (input.length <= length) | ||
return input; | ||
if (killwords) { | ||
input = input.substring(0, length); | ||
} else { | ||
var idx = input.lastIndexOf(' ', length); | ||
if(idx === -1) { | ||
idx = length; | ||
} | ||
input = input.substring(0, idx); | ||
} | ||
input += (end !== undefined && end !== null) ? end : '...'; | ||
return r.copySafeness(orig, input); | ||
}, | ||
upper: function(str) { | ||
@@ -323,0 +354,0 @@ return str.toUpperCase(); |
var ArrayProto = Array.prototype; | ||
var ObjProto = Object.prototype; | ||
var escapeMap = { | ||
'&': '&', | ||
'"': '"', | ||
"'": ''', | ||
"<": '<', | ||
">": '>' | ||
}; | ||
var lookupEscape = function(ch) { | ||
return escapeMap[ch]; | ||
}; | ||
var exports = module.exports = {}; | ||
@@ -34,3 +46,5 @@ | ||
} else { | ||
Error.captureStackTrace(err); | ||
if(Error.captureStackTrace) { | ||
Error.captureStackTrace(err); | ||
} | ||
} | ||
@@ -73,2 +87,6 @@ | ||
exports.escape = function(val) { | ||
return val.replace(/[&"'<>]/g, lookupEscape); | ||
}; | ||
exports.isFunction = function(obj) { | ||
@@ -141,3 +159,3 @@ return ObjProto.toString.call(obj) == '[object Function]'; | ||
} | ||
if(ArrayProto.each && obj.each == ArrayProto.each) { | ||
@@ -162,3 +180,3 @@ obj.forEach(func, context); | ||
} | ||
for(var i=0; i<obj.length; i++) { | ||
@@ -165,0 +183,0 @@ results[results.length] = func(obj[i], i); |
@@ -1,2 +0,1 @@ | ||
var util = require('util'); | ||
@@ -6,11 +5,24 @@ var lib = require('./lib'); | ||
function traverseAndCheck(obj, type, results) { | ||
if(obj instanceof type) { | ||
results.push(obj); | ||
} | ||
if(obj instanceof Node) { | ||
obj.findAll(type, results); | ||
} | ||
} | ||
var Node = Object.extend("Node", { | ||
init: function(lineno, colno) { | ||
var args = lib.toArray(arguments).slice(2); | ||
this.lineno = lineno; | ||
this.colno = colno; | ||
lib.each(this.fields, function(field, i) { | ||
var val = args[i]; | ||
var fields = this.fields; | ||
for(var i=0, l=fields.length; i<l; i++) { | ||
var field = fields[i]; | ||
// The first two args are line/col numbers, so offset by 2 | ||
var val = arguments[i + 2]; | ||
// Fields should never be undefined, but null. It makes | ||
@@ -23,31 +35,24 @@ // testing easier to normalize values. | ||
this[field] = val; | ||
}, this); | ||
} | ||
}, | ||
findAll: function(type) { | ||
var res = []; | ||
findAll: function(type, results) { | ||
results = results || []; | ||
function check(obj) { | ||
if(obj instanceof type) { | ||
res.push(obj); | ||
if(this instanceof NodeList) { | ||
var children = this.children; | ||
for(var i=0, l=children.length; i<l; i++) { | ||
traverseAndCheck(children[i], type, results); | ||
} | ||
if(obj instanceof Node) { | ||
res = res.concat(obj.findAll(type)); | ||
} | ||
} | ||
else { | ||
var fields = this.fields; | ||
if(this instanceof NodeList) { | ||
lib.each(this.children, function(node) { | ||
check(node); | ||
}, this); | ||
for(var i=0, l=fields.length; i<l; i++) { | ||
traverseAndCheck(this[fields[i]], type, results); | ||
} | ||
} | ||
else { | ||
lib.each(this.fields, function(field) { | ||
var obj = this[field]; | ||
check(obj); | ||
}, this); | ||
} | ||
return res; | ||
return results; | ||
}, | ||
@@ -87,2 +92,3 @@ | ||
var If = Node.extend("If", { fields: ['cond', 'body', 'else_'] }); | ||
var InlineIf = Node.extend("InlineIf", { fields: ['cond', 'body', 'else_'] }); | ||
var For = Node.extend("For", { fields: ['arr', 'name', 'body'] }); | ||
@@ -129,2 +135,21 @@ var Macro = Node.extend("Macro", { fields: ['name', 'args', 'body'] }); | ||
var CustomTag = Node.extend("CustomTag", { | ||
init: function(lineno, colno, name) { | ||
this.lineno = lineno; | ||
this.colno = colno; | ||
this.name = name; | ||
} | ||
}); | ||
var CallExtension = Node.extend("CallExtension", { | ||
fields: ['extName', 'prop', 'args', 'contentArgs'], | ||
init: function(ext, prop, args, contentArgs) { | ||
this.extName = ext._name; | ||
this.prop = prop; | ||
this.args = args; | ||
this.contentArgs = contentArgs; | ||
} | ||
}); | ||
// Print the AST in a nicely formatted tree format for debuggin | ||
@@ -191,3 +216,3 @@ function printNodes(node, indent) { | ||
} | ||
} | ||
@@ -218,2 +243,3 @@ } | ||
If: If, | ||
InlineIf: InlineIf, | ||
For: For, | ||
@@ -247,3 +273,6 @@ Macro: Macro, | ||
//CustomTag: CustomTag, | ||
CallExtension: CallExtension, | ||
printNodes: printNodes | ||
}; | ||
}; |
@@ -1,2 +0,1 @@ | ||
var lexer = require('./lexer'); | ||
@@ -13,2 +12,4 @@ var nodes = require('./nodes'); | ||
this.dropLeadingWhitespace = false; | ||
this.extensions = []; | ||
}, | ||
@@ -208,3 +209,3 @@ | ||
if(!this.skipSymbol('as')) { | ||
throw new Error('parseImport: expected "as" keyword', | ||
this.fail('parseImport: expected "as" keyword', | ||
importTok.lineno, | ||
@@ -237,3 +238,3 @@ importTok.colno); | ||
if(!this.skipSymbol('import')) { | ||
throw new Error("parseFrom: expected import", | ||
this.fail("parseFrom: expected import", | ||
fromTok.lineno, | ||
@@ -266,3 +267,3 @@ fromTok.colno); | ||
if(names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) { | ||
throw new Error('parseFrom: expected comma', | ||
this.fail('parseFrom: expected comma', | ||
fromTok.lineno, | ||
@@ -424,13 +425,22 @@ fromTok.colno); | ||
switch(tok.value) { | ||
case 'raw': node = this.parseRaw(); break; | ||
case 'if': node = this.parseIf(); break; | ||
case 'for': node = this.parseFor(); break; | ||
case 'block': node = this.parseBlock(); break; | ||
case 'extends': node = this.parseExtends(); break; | ||
case 'include': node = this.parseInclude(); break; | ||
case 'set': node = this.parseSet(); break; | ||
case 'macro': node = this.parseMacro(); break; | ||
case 'import': node = this.parseImport(); break; | ||
case 'from': node = this.parseFrom(); break; | ||
default: this.fail('unknown block tag: ' + tok.value, tok.lineno, tok.colno); | ||
case 'raw': return this.parseRaw(); | ||
case 'if': return this.parseIf(); | ||
case 'for': 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; i < this.extensions.length; i++) { | ||
var ext = this.extensions[i]; | ||
if ((ext.tags || []).indexOf(tok.value) > -1) { | ||
return ext.parse(this, nodes, lexer); | ||
} | ||
} | ||
} | ||
this.fail('unknown block tag: ' + tok.value, tok.lineno, tok.colno); | ||
} | ||
@@ -551,3 +561,21 @@ | ||
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; | ||
@@ -864,3 +892,5 @@ }, | ||
if(!this.skip(lexer.TOKEN_COMMA)) { | ||
throw new Error("parseAggregate: expected comma after expression"); | ||
this.fail("parseAggregate: expected comma after expression", | ||
tok.lineno, | ||
tok.colno); | ||
} | ||
@@ -876,3 +906,5 @@ } | ||
if(!this.skip(lexer.TOKEN_COLON)) { | ||
throw new Error("parseAggregate: expected colon after dict key"); | ||
this.fail("parseAggregate: expected colon after dict key", | ||
tok.lineno, | ||
tok.colno); | ||
} | ||
@@ -897,4 +929,17 @@ | ||
parseSignature: function() { | ||
var tok = this.nextToken(); | ||
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); | ||
@@ -906,10 +951,15 @@ var kwargs = new nodes.KeywordArgs(tok.lineno, tok.colno); | ||
while(1) { | ||
var type = this.peekToken().type; | ||
if(type == lexer.TOKEN_RIGHT_PAREN) { | ||
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)) { | ||
throw new Error("parseSignature: expected comma after expression"); | ||
this.fail("parseSignature: expected comma after expression", | ||
tok.lineno, | ||
tok.colno); | ||
} | ||
@@ -999,4 +1049,4 @@ else { | ||
// Ignore comments, otherwise this should be an error | ||
throw new Error("Unexpected token at top-level: " + | ||
tok.type); | ||
this.fail("Unexpected token at top-level: " + | ||
tok.type, tok.lineno, tok.colno); | ||
} | ||
@@ -1025,3 +1075,3 @@ } | ||
// var p = new Parser(lexer.lex('{% from x import y -%}\n hi \n')); | ||
// var p = new Parser(lexer.lex('{% macro foo(x) %}{{ x }}{% endmacro %}{{ foo(5) }}')); | ||
// var n = p.parse(); | ||
@@ -1031,6 +1081,9 @@ // nodes.printNodes(n); | ||
module.exports = { | ||
parse: function(src) { | ||
parse: function(src, extensions) { | ||
var p = new Parser(lexer.lex(src)); | ||
if (extensions !== undefined) { | ||
p.extensions = extensions; | ||
} | ||
return p.parseAsRoot(); | ||
} | ||
}; |
@@ -0,1 +1,2 @@ | ||
var lib = require('./lib'); | ||
@@ -31,2 +32,10 @@ var Object = require('./object'); | ||
get: function(name) { | ||
var val = this.variables[name]; | ||
if(val !== undefined && val !== null) { | ||
return val; | ||
} | ||
return null; | ||
}, | ||
lookup: function(name) { | ||
@@ -99,4 +108,8 @@ var p = this.parent; | ||
function getKeywordArgs(args) { | ||
if(args.length && args[args.length - 1].__keywords) { | ||
return args[args.length - 1]; | ||
var len = args.length; | ||
if(len) { | ||
var lastArg = args[len - 1]; | ||
if(lastArg && lastArg.hasOwnProperty('__keywords')) { | ||
return lastArg; | ||
} | ||
} | ||
@@ -107,29 +120,93 @@ return {}; | ||
function numArgs(args) { | ||
if(args.length === 0) { | ||
var len = args.length; | ||
if(len === 0) { | ||
return 0; | ||
} | ||
else if(args[args.length - 1].__keywords) { | ||
return args.length - 1; | ||
var lastArg = args[len - 1]; | ||
if(lastArg && lastArg.hasOwnProperty('__keywords')) { | ||
return len - 1; | ||
} | ||
else { | ||
return args.length; | ||
return len; | ||
} | ||
} | ||
function suppressValue(val) { | ||
return (val !== undefined && val !== null) ? val : ""; | ||
// A SafeString object indicates that the string should not be | ||
// autoescaped. This happens magically because autoescaping only | ||
// occurs on primitive string objects. | ||
function SafeString(val) { | ||
if(typeof val != 'string') { | ||
return val; | ||
} | ||
this.toString = function() { | ||
return val; | ||
}; | ||
this.length = val.length; | ||
var methods = [ | ||
'charAt', 'charCodeAt', 'concat', 'contains', | ||
'endsWith', 'fromCharCode', 'indexOf', 'lastIndexOf', | ||
'length', 'localeCompare', 'match', 'quote', 'replace', | ||
'search', 'slice', 'split', 'startsWith', 'substr', | ||
'substring', 'toLocaleLowerCase', 'toLocaleUpperCase', | ||
'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight' | ||
]; | ||
for(var i=0; i<methods.length; i++) { | ||
this[methods[i]] = proxyStr(val[methods[i]]); | ||
} | ||
} | ||
function suppressLookupValue(obj, val) { | ||
function copySafeness(dest, target) { | ||
if(dest instanceof SafeString) { | ||
return new SafeString(target); | ||
} | ||
return target.toString(); | ||
} | ||
function proxyStr(func) { | ||
return function() { | ||
var ret = func.apply(this, arguments); | ||
if(typeof ret == 'string') { | ||
return new SafeString(ret); | ||
} | ||
return ret; | ||
}; | ||
} | ||
function suppressValue(val, autoescape) { | ||
val = (val !== undefined && val !== null) ? val : ""; | ||
if(autoescape && typeof val === "string") { | ||
val = lib.escape(val); | ||
} | ||
return val; | ||
} | ||
function memberLookup(obj, val) { | ||
obj = obj || {}; | ||
val = obj[val]; | ||
if(typeof val === 'function') { | ||
if(typeof obj[val] === 'function') { | ||
return function() { | ||
return suppressValue(val.apply(obj, arguments)); | ||
return obj[val].apply(obj, arguments); | ||
}; | ||
} | ||
else { | ||
return suppressValue(val); | ||
return obj[val]; | ||
} | ||
function callWrap(obj, name, args) { | ||
if(!obj) { | ||
throw new Error('Unable to call `' + name + '`, which is undefined or falsey'); | ||
} | ||
else if(typeof obj !== 'function') { | ||
throw new Error('Unable to call `' + name + '`, which is not a function'); | ||
} | ||
return obj.apply(this, args); | ||
} | ||
@@ -159,6 +236,9 @@ | ||
suppressValue: suppressValue, | ||
suppressLookupValue: suppressLookupValue, | ||
memberLookup: memberLookup, | ||
contextOrFrameLookup: contextOrFrameLookup, | ||
callWrap: callWrap, | ||
handleError: handleError, | ||
isArray: lib.isArray | ||
isArray: lib.isArray, | ||
SafeString: SafeString, | ||
copySafeness: copySafeness | ||
}; |
@@ -36,2 +36,4 @@ | ||
url += (url.indexOf('?') === -1 ? '?' : '&') + 's=' + Date.now(); | ||
// Synchronous because this API shouldn't be used in | ||
@@ -38,0 +40,0 @@ // production (pre-load compiled templates instead) |
@@ -1,347 +0,604 @@ | ||
var should = require('should'); | ||
var render = require('./util').render; | ||
(function() { | ||
var expect, render; | ||
describe('compiler', function() { | ||
it('should compile templates', function() { | ||
var s = render('Hello world'); | ||
s.should.equal('Hello world'); | ||
if(typeof require != 'undefined') { | ||
expect = require('expect.js'); | ||
render = require('./util').render; | ||
} | ||
else { | ||
expect = window.expect; | ||
render = window.render; | ||
} | ||
s = render('Hello world, {{ name }}', | ||
{ name: 'James' }); | ||
s.should.equal('Hello world, James'); | ||
describe('compiler', function() { | ||
it('should compile templates', function() { | ||
var s = render('Hello world'); | ||
expect(s).to.equal('Hello world'); | ||
s = render('Hello world, {{name}}{{suffix}}, how are you', | ||
{ name: 'James', | ||
suffix: ' Long'}); | ||
s.should.equal('Hello world, James Long, how are you'); | ||
}); | ||
s = render('Hello world, {{ name }}', | ||
{ name: 'James' }); | ||
expect(s).to.equal('Hello world, James'); | ||
it('should escape newlines', function() { | ||
render('foo\\nbar').should.equal('foo\\nbar'); | ||
}); | ||
s = render('Hello world, {{name}}{{suffix}}, how are you', | ||
{ name: 'James', | ||
suffix: ' Long'}); | ||
expect(s).to.equal('Hello world, James Long, how are you'); | ||
}); | ||
it('should compile references', function() { | ||
var s = render('{{ foo.bar }}', | ||
it('should escape newlines', function() { | ||
expect(render('foo\\nbar')).to.be('foo\\nbar'); | ||
}); | ||
it('should compile references', function() { | ||
var s = render('{{ foo.bar }}', | ||
{ foo: { bar: 'baz' }}); | ||
expect(s).to.be('baz'); | ||
s = render('{{ foo["bar"] }}', | ||
{ foo: { bar: 'baz' }}); | ||
s.should.equal('baz'); | ||
expect(s).to.be('baz'); | ||
}); | ||
s = render('{{ foo["bar"] }}', | ||
{ foo: { bar: 'baz' }}); | ||
s.should.equal('baz'); | ||
}); | ||
it('should fail silently on undefined values', function() { | ||
var s = render('{{ foo }}'); | ||
expect(s).to.be(''); | ||
it('should fail silently on undefined values', function() { | ||
var s = render('{{ foo }}'); | ||
s.should.equal(''); | ||
s = render('{{ foo.bar }}'); | ||
expect(s).to.be(''); | ||
var s = render('{{ foo.bar }}'); | ||
s.should.equal(''); | ||
s = render('{{ foo.bar.baz }}'); | ||
expect(s).to.be(''); | ||
var s = render('{{ foo.bar.baz }}'); | ||
s.should.equal(''); | ||
s = render('{{ foo.bar.baz["biz"].mumble }}'); | ||
expect(s).to.be(''); | ||
}); | ||
var s = render('{{ foo.bar.baz["biz"].mumble }}'); | ||
s.should.equal(''); | ||
}); | ||
it('should not treat falsy values the same as undefined', function() { | ||
var s = render('{{ foo }}', {foo: 0}); | ||
expect(s).to.be('0'); | ||
it('should not treat falsy values the same as undefined', function() { | ||
var s = render('{{ foo }}', {foo: 0}); | ||
s.should.equal('0'); | ||
s = render('{{ foo }}', {foo: false}); | ||
expect(s).to.be('false'); | ||
}); | ||
var s = render('{{ foo }}', {foo: false}); | ||
s.should.equal('false'); | ||
}); | ||
it('should compile function calls', function() { | ||
var s = render('{{ foo("msg") }}', | ||
{ foo: function(str) { return str + 'hi'; }}); | ||
expect(s).to.be('msghi'); | ||
}); | ||
it('should compile function calls', function() { | ||
var s = render('{{ foo("msg") }}', | ||
{ foo: function(str) { return str + 'hi'; }}); | ||
s.should.equal('msghi'); | ||
}); | ||
it('should compile function calls with correct scope', function() { | ||
var s = render('{{ foo.bar() }}', { | ||
foo: { | ||
bar: function() { return this.baz; }, | ||
baz: 'hello' | ||
} | ||
}); | ||
it('should compile function calls with correct scope', function() { | ||
var s = render('{{ foo.bar() }}', | ||
{ foo: { bar: function() { return this.baz }, baz: 'hello' }}); | ||
s.should.equal('hello'); | ||
}); | ||
expect(s).to.be('hello'); | ||
}); | ||
it('should compile if blocks', function() { | ||
var tmpl = ('Give me some {% if hungry %}pizza' + | ||
'{% else %}water{% endif %}'); | ||
it('should compile if blocks', function() { | ||
var tmpl = ('Give me some {% if hungry %}pizza' + | ||
'{% else %}water{% endif %}'); | ||
var s = render(tmpl, { hungry: true }); | ||
s.should.equal('Give me some pizza'); | ||
var s = render(tmpl, { hungry: true }); | ||
expect(s).to.be('Give me some pizza'); | ||
s = render(tmpl, { hungry: false }); | ||
s.should.equal('Give me some water'); | ||
s = render(tmpl, { hungry: false }); | ||
expect(s).to.be('Give me some water'); | ||
s = render('{% if not hungry %}good{% endif %}', | ||
{ hungry: false }); | ||
s.should.equal('good'); | ||
s = render('{% if not hungry %}good{% endif %}', | ||
{ hungry: false }); | ||
expect(s).to.be('good'); | ||
s = render('{% if hungry and like_pizza %}good{% endif %}', | ||
{ hungry: true, like_pizza: true }); | ||
s.should.equal('good'); | ||
s = render('{% if hungry and like_pizza %}good{% endif %}', | ||
{ hungry: true, like_pizza: true }); | ||
expect(s).to.be('good'); | ||
s = render('{% if hungry or like_pizza %}good{% endif %}', | ||
{ hungry: false, like_pizza: true }); | ||
s.should.equal('good'); | ||
s = render('{% if hungry or like_pizza %}good{% endif %}', | ||
{ hungry: false, like_pizza: true }); | ||
expect(s).to.be('good'); | ||
s = render('{% if (hungry or like_pizza) and anchovies %}good{% endif %}', | ||
{ hungry: false, like_pizza: true, anchovies: true }); | ||
s.should.equal('good'); | ||
s = render('{% if (hungry or like_pizza) and anchovies %}good{% endif %}', | ||
{ hungry: false, like_pizza: true, anchovies: true }); | ||
expect(s).to.be('good'); | ||
s = render('{% if food == "pizza" %}pizza{% endif %}' + | ||
'{% if food =="beer" %}beer{% endif %}', | ||
{ food: 'beer' }); | ||
s.should.equal('beer'); | ||
}); | ||
s = render('{% if food == "pizza" %}pizza{% endif %}' + | ||
'{% if food =="beer" %}beer{% endif %}', | ||
{ food: 'beer' }); | ||
expect(s).to.be('beer'); | ||
}); | ||
it('should compile for blocks', function() { | ||
var s = render('{% for i in arr %}{{ i }}{% endfor %}', | ||
{ arr: [1, 2, 3, 4, 5] }); | ||
s.should.equal('12345'); | ||
it('should compile the ternary operator', function() { | ||
var s = render('{{ "foo" if bar else "baz" }}'); | ||
expect(s).to.be('baz'); | ||
s = render('{% for a, b, c in arr %}' + | ||
var s = render('{{ "foo" if bar else "baz" }}', { bar: true }); | ||
expect(s).to.be('foo'); | ||
}); | ||
it('should compile inline conditionals', function() { | ||
var tmpl = 'Give me some {{ "pizza" if hungry else "water" }}'; | ||
var s = render(tmpl, { hungry: true }); | ||
expect(s).to.be('Give me some pizza'); | ||
s = render(tmpl, { hungry: false }); | ||
expect(s).to.be('Give me some water'); | ||
s = render('{{ "good" if not hungry }}', | ||
{ hungry: false }); | ||
expect(s).to.be('good'); | ||
s = render('{{ "good" if hungry and like_pizza }}', | ||
{ hungry: true, like_pizza: true }); | ||
expect(s).to.be('good'); | ||
s = render('{{ "good" if hungry or like_pizza }}', | ||
{ hungry: false, like_pizza: true }); | ||
expect(s).to.be('good'); | ||
s = render('{{ "good" if (hungry or like_pizza) and anchovies }}', | ||
{ hungry: false, like_pizza: true, anchovies: true }); | ||
expect(s).to.be('good'); | ||
s = render('{{ "pizza" if food == "pizza" }}' + | ||
'{{ "beer" if food == "beer" }}', | ||
{ food: 'beer' }); | ||
expect(s).to.be('beer'); | ||
}); | ||
it('should compile for blocks', function() { | ||
var s = render('{% for i in arr %}{{ i }}{% endfor %}', | ||
{ arr: [1, 2, 3, 4, 5] }); | ||
expect(s).to.be('12345'); | ||
s = render('{% for a, b, c in arr %}' + | ||
'{{ a }},{{ b }},{{ c }}.{% endfor %}', | ||
{ arr: [['x', 'y', 'z'], ['1', '2', '3']] }); | ||
s.should.equal('x,y,z.1,2,3.'); | ||
expect(s).to.be('x,y,z.1,2,3.'); | ||
s = render('{% for item in arr | batch(2) %}{{ item[0] }}{% endfor %}', | ||
{ arr: ['a', 'b', 'c', 'd'] }); | ||
s.should.equal('ac'); | ||
s = render('{% for item in arr | batch(2) %}{{ item[0] }}{% endfor %}', | ||
{ arr: ['a', 'b', 'c', 'd'] }); | ||
expect(s).to.be('ac'); | ||
s = render('{% for k, v in { one: 1, two: 2 } %}' + | ||
'-{{ k }}:{{ v }}-{% endfor %}'); | ||
s.should.equal('-one:1--two:2-'); | ||
s = render('{% for k, v in { one: 1, two: 2 } %}' + | ||
'-{{ k }}:{{ v }}-{% endfor %}'); | ||
expect(s).to.be('-one:1--two:2-'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.index }}{% endfor %}'); | ||
s.should.equal('123'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.index }}{% endfor %}'); | ||
expect(s).to.be('123'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.index0 }}{% endfor %}'); | ||
s.should.equal('012'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.index0 }}{% endfor %}'); | ||
expect(s).to.be('012'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.revindex }}{% endfor %}'); | ||
s.should.equal('321'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.revindex }}{% endfor %}'); | ||
expect(s).to.be('321'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.revindex0 }}{% endfor %}'); | ||
s.should.equal('210'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.revindex0 }}{% endfor %}'); | ||
expect(s).to.be('210'); | ||
s = render('{% for i in [7,3,6] %}{% if loop.first %}{{ i }}{% endif %}{% endfor %}'); | ||
s.should.equal('7'); | ||
s = render('{% for i in [7,3,6] %}{% if loop.first %}{{ i }}{% endif %}{% endfor %}'); | ||
expect(s).to.be('7'); | ||
s = render('{% for i in [7,3,6] %}{% if loop.last %}{{ i }}{% endif %}{% endfor %}'); | ||
s.should.equal('6'); | ||
s = render('{% for i in [7,3,6] %}{% if loop.last %}{{ i }}{% endif %}{% endfor %}'); | ||
expect(s).to.be('6'); | ||
s = render('{% for i in [7,3,6] %}{{ loop.length }}{% endfor %}'); | ||
s.should.equal('333'); | ||
}); | ||
s = render('{% for i in [7,3,6] %}{{ loop.length }}{% endfor %}'); | ||
expect(s).to.be('333'); | ||
it('should compile operators', function() { | ||
render('{{ 3 + 4 - 5 * 6 / 10 }}').should.equal('4'); | ||
render('{{ 4**5 }}').should.equal('1024'); | ||
render('{{ 9//5 }}').should.equal('1'); | ||
render('{{ 9%5 }}').should.equal('4'); | ||
render('{{ -5 }}').should.equal('-5'); | ||
s = render('{% for i in foo %}{{ i }}{% endfor %}'); | ||
expect(s).to.be(''); | ||
render('{% if 3 < 4 %}yes{% endif %}').should.equal('yes'); | ||
render('{% if 3 > 4 %}yes{% endif %}').should.equal(''); | ||
render('{% if 9 >= 10 %}yes{% endif %}').should.equal(''); | ||
render('{% if 10 >= 10 %}yes{% endif %}').should.equal('yes'); | ||
render('{% if 9 <= 10 %}yes{% endif %}').should.equal('yes'); | ||
render('{% if 10 <= 10 %}yes{% endif %}').should.equal('yes'); | ||
render('{% if 11 <= 10 %}yes{% endif %}').should.equal(''); | ||
s = render('{% for i in foo.bar %}{{ i }}{% endfor %}', { foo: {} }); | ||
expect(s).to.be(''); | ||
}); | ||
render('{% if 10 != 10 %}yes{% endif %}').should.equal(''); | ||
render('{% if 10 == 10 %}yes{% endif %}').should.equal('yes'); | ||
it('should compile operators', function() { | ||
expect(render('{{ 3 + 4 - 5 * 6 / 10 }}')).to.be('4'); | ||
expect(render('{{ 4**5 }}')).to.be('1024'); | ||
expect(render('{{ 9//5 }}')).to.be('1'); | ||
expect(render('{{ 9%5 }}')).to.be('4'); | ||
expect(render('{{ -5 }}')).to.be('-5'); | ||
render('{% if foo(20) > bar %}yes{% endif %}', | ||
{ foo: function(n) { return n - 1; }, | ||
bar: 15 }) | ||
.should.equal('yes'); | ||
}); | ||
expect(render('{% if 3 < 4 %}yes{% endif %}')).to.be('yes'); | ||
expect(render('{% if 3 > 4 %}yes{% endif %}')).to.be(''); | ||
expect(render('{% if 9 >= 10 %}yes{% endif %}')).to.be(''); | ||
expect(render('{% if 10 >= 10 %}yes{% endif %}')).to.be('yes'); | ||
expect(render('{% if 9 <= 10 %}yes{% endif %}')).to.be('yes'); | ||
expect(render('{% if 10 <= 10 %}yes{% endif %}')).to.be('yes'); | ||
expect(render('{% if 11 <= 10 %}yes{% endif %}')).to.be(''); | ||
it('should compile macros', function() { | ||
var s = render('{% macro foo() %}This is a macro{% endmacro %}' + | ||
'{{ foo() }}'); | ||
s.should.equal('This is a macro'); | ||
expect(render('{% if 10 != 10 %}yes{% endif %}')).to.be(''); | ||
expect(render('{% if 10 == 10 %}yes{% endif %}')).to.be('yes'); | ||
s = render('{% macro foo(x, y) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1) }}'); | ||
s.should.equal(''); | ||
var s = render('{% if foo(20) > bar %}yes{% endif %}', | ||
{ foo: function(n) { return n - 1; }, | ||
bar: 15 }); | ||
expect(s).to.be('yes'); | ||
}); | ||
s = render('{% macro foo(x, y) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1, 2) }}'); | ||
s.should.equal('2'); | ||
it('should compile macros', function() { | ||
var s = render('{% macro foo() %}This is a macro{% endmacro %}' + | ||
'{{ foo() }}'); | ||
expect(s).to.be('This is a macro'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1, 2) }}'); | ||
s.should.equal('2'); | ||
s = render('{% macro foo(x, y) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1) }}'); | ||
expect(s).to.be(''); | ||
s = render('{% macro foo(x, y, z=5) %}{{ z }}{% endmacro %}' + | ||
'{{ foo(1, 2) }}'); | ||
s.should.equal('5'); | ||
s = render('{% macro foo(x, y) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1, 2) }}'); | ||
expect(s).to.be('2'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1, y=2) }}'); | ||
s.should.equal('2'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1, 2) }}'); | ||
expect(s).to.be('2'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(x=1, y=2) }}'); | ||
s.should.equal('125'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ z }}{% endmacro %}' + | ||
'{{ foo(1, 2) }}'); | ||
expect(s).to.be('5'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(x=1, y=2, z=3) }}'); | ||
s.should.equal('123'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ y }}{% endmacro %}' + | ||
'{{ foo(1, y=2) }}'); | ||
expect(s).to.be('2'); | ||
s = render('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(1, z=3) }}'); | ||
s.should.equal('123'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(x=1, y=2) }}'); | ||
expect(s).to.be('125'); | ||
s = render('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(1) }}'); | ||
s.should.equal('125'); | ||
s = render('{% macro foo(x, y, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(x=1, y=2, z=3) }}'); | ||
expect(s).to.be('123'); | ||
s = render('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(1, 10, 20) }}'); | ||
s.should.equal('11020'); | ||
s = render('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(1, z=3) }}'); | ||
expect(s).to.be('123'); | ||
s = render('{% extends "base.html" %}' + | ||
'{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{% block block1 %}' + | ||
'{{ foo(1) }}' + | ||
'{% endblock %}'); | ||
s.should.equal('Foo125BazFizzle'); | ||
s = render('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(1) }}'); | ||
expect(s).to.be('125'); | ||
s = render('{% block bar %}' + | ||
'{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{% endblock %}' + | ||
'{% block baz %}' + | ||
'{{ foo(1) }}' + | ||
'{% endblock %}'); | ||
s.should.equal('125'); | ||
}); | ||
s = render('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{{ foo(1, 10, 20) }}'); | ||
expect(s).to.be('11020'); | ||
it('should import templates', function() { | ||
var s = render('{% import "import.html" as imp %}' + | ||
'{{ imp.foo() }} {{ imp.bar }}'); | ||
s.should.equal("Here's a macro baz"); | ||
s = render('{% extends "base.html" %}' + | ||
'{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{% block block1 %}' + | ||
'{{ foo(1) }}' + | ||
'{% endblock %}'); | ||
expect(s).to.be('Foo125BazFizzle'); | ||
s = render('{% from "import.html" import foo as baz, bar %}' + | ||
s = render('{% block bar %}' + | ||
'{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' + | ||
'{% endmacro %}' + | ||
'{% endblock %}' + | ||
'{% block baz %}' + | ||
'{{ foo(1) }}' + | ||
'{% endblock %}'); | ||
expect(s).to.be('125'); | ||
}); | ||
it('should import templates', function() { | ||
var s = render('{% import "import.html" as imp %}' + | ||
'{{ imp.foo() }} {{ imp.bar }}'); | ||
expect(s).to.be("Here's a macro baz"); | ||
s = render('{% from "import.html" import foo as baz, bar %}' + | ||
'{{ bar }} {{ baz() }}'); | ||
s.should.equal("baz Here's a macro"); | ||
expect(s).to.be("baz Here's a macro"); | ||
s = render('{% for i in [1,2] %}' + | ||
'start: {{ num }}' + | ||
'{% from "import.html" import bar as num %}' + | ||
'end: {{ num }}' + | ||
'{% endfor %}' + | ||
'final: {{ num }}'); | ||
// TODO: Should the for loop create a new frame for each | ||
// iteration? As it is, `num` is set on all iterations after | ||
// the first one sets it | ||
s.should.equal('start: end: bazstart: bazend: bazfinal: '); | ||
}); | ||
s = render('{% for i in [1,2] %}' + | ||
'start: {{ num }}' + | ||
'{% from "import.html" import bar as num %}' + | ||
'end: {{ num }}' + | ||
'{% endfor %}' + | ||
'final: {{ num }}'); | ||
// TODO: Should the for loop create a new frame for each | ||
// iteration? As it is, `num` is set on all iterations after | ||
// the first one sets it | ||
expect(s).to.be('start: end: bazstart: bazend: bazfinal: '); | ||
}); | ||
it('should inherit templates', function() { | ||
var s = render('{% extends "base.html" %}'); | ||
s.should.equal('FooBarBazFizzle'); | ||
it('should inherit templates', function() { | ||
var s = render('{% extends "base.html" %}'); | ||
expect(s).to.be('FooBarBazFizzle'); | ||
s = render('hola {% extends "base.html" %} hizzle mumble'); | ||
s.should.equal('FooBarBazFizzle'); | ||
s = render('hola {% extends "base.html" %} hizzle mumble'); | ||
expect(s).to.be('FooBarBazFizzle'); | ||
s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}BAR{% endblock %}'); | ||
s.should.equal('FooBARBazFizzle'); | ||
s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}BAR{% endblock %}'); | ||
expect(s).to.be('FooBARBazFizzle'); | ||
s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}BAR{% endblock %}' + | ||
'{% block block2 %}BAZ{% endblock %}'); | ||
s.should.equal('FooBARBAZFizzle'); | ||
s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}BAR{% endblock %}' + | ||
'{% block block2 %}BAZ{% endblock %}'); | ||
expect(s).to.be('FooBARBAZFizzle'); | ||
s = render('hola {% extends tmpl %} hizzle mumble', | ||
{ tmpl: 'base.html' }); | ||
s.should.equal('FooBarBazFizzle'); | ||
}); | ||
s = render('hola {% extends tmpl %} hizzle mumble', | ||
{ tmpl: 'base.html' }); | ||
expect(s).to.be('FooBarBazFizzle'); | ||
it('should render parent blocks with super()', function() { | ||
var s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}{{ super() }}BAR{% endblock %}'); | ||
s.should.equal('FooBarBARBazFizzle'); | ||
}); | ||
var count = 0; | ||
render('{% extends "base.html" %}' + | ||
'{% block notReal %}{{ foo() }}{% endblock %}', | ||
{ | ||
foo: function() { | ||
count++; | ||
} | ||
}); | ||
expect(count).to.be(0); | ||
}); | ||
it('should include templates', function() { | ||
var s = render('hello world {% include "include.html" %}'); | ||
s.should.equal('hello world FooInclude '); | ||
it('should render nested blocks in child template', function() { | ||
var s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}{% block nested %}BAR{% endblock %}{% endblock %}'); | ||
expect(s).to.be('FooBARBazFizzle'); | ||
}); | ||
s = render('hello world {% include "include.html" %}', | ||
{ name: 'james' }); | ||
s.should.equal('hello world FooInclude james'); | ||
it('should render parent blocks with super()', function() { | ||
var s = render('{% extends "base.html" %}' + | ||
'{% block block1 %}{{ super() }}BAR{% endblock %}'); | ||
expect(s).to.be('FooBarBARBazFizzle'); | ||
s = render('hello world {% include tmpl %}', | ||
{ name: 'thedude', tmpl: "include.html" }); | ||
s.should.equal('hello world FooInclude thedude'); | ||
// two levels of `super` should work | ||
s = render('{% extends "base-inherit.html" %}' + | ||
'{% block block1 %}*{{ super() }}*{% endblock %}'); | ||
expect(s).to.be('Foo**Bar**BazFizzle'); | ||
}); | ||
s = render('hello world {% include data.tmpl %}', | ||
{ name: 'thedude', data: {tmpl: "include.html"} }); | ||
s.should.equal('hello world FooInclude thedude'); | ||
}); | ||
it('should include templates', function() { | ||
var s = render('hello world {% include "include.html" %}'); | ||
expect(s).to.be('hello world FooInclude '); | ||
it('should maintain nested scopes', function() { | ||
var s = render('{% for i in [1,2] %}' + | ||
'{% for i in [3,4] %}{{ i }}{% endfor %}' + | ||
'{{ i }}{% endfor %}'); | ||
s.should.equal('341342'); | ||
}); | ||
s = render('hello world {% include "include.html" %}', | ||
{ name: 'james' }); | ||
expect(s).to.be('hello world FooInclude james'); | ||
it('should allow blocks in for loops', function() { | ||
var s = render('{% extends "base2.html" %}' + | ||
'{% block item %}hello{{ item }}{% endblock %}'); | ||
s.should.equal('hello1hello2'); | ||
}); | ||
s = render('hello world {% include tmpl %}', | ||
{ name: 'thedude', tmpl: "include.html" }); | ||
expect(s).to.be('hello world FooInclude thedude'); | ||
it('should make includes inherit scope', function() { | ||
var s = render('{% for item in [1,2] %}' + | ||
'{% include "item.html" %}' + | ||
'{% endfor %}'); | ||
s.should.equal('showing 1showing 2'); | ||
}); | ||
s = render('hello world {% include data.tmpl %}', | ||
{ name: 'thedude', data: {tmpl: "include.html"} }); | ||
expect(s).to.be('hello world FooInclude thedude'); | ||
}); | ||
it('should compile a set block', function() { | ||
var s = render('{% set username = "foo" %}{{ username }}', | ||
{ username: 'james' }); | ||
s.should.equal('foo'); | ||
it('should maintain nested scopes', function() { | ||
var s = render('{% for i in [1,2] %}' + | ||
'{% for i in [3,4] %}{{ i }}{% endfor %}' + | ||
'{{ i }}{% endfor %}'); | ||
expect(s).to.be('341342'); | ||
}); | ||
s = render('{% set x, y = "foo" %}{{ x }}{{ y }}'); | ||
s.should.equal('foofoo'); | ||
it('should allow blocks in for loops', function() { | ||
var s = render('{% extends "base2.html" %}' + | ||
'{% block item %}hello{{ item }}{% endblock %}'); | ||
expect(s).to.be('hello1hello2'); | ||
}); | ||
s = render('{% set x = 1 + 2 %}{{ x }}'); | ||
s.should.equal('3'); | ||
it('should make includes inherit scope', function() { | ||
var s = render('{% for item in [1,2] %}' + | ||
'{% include "item.html" %}' + | ||
'{% endfor %}'); | ||
expect(s).to.be('showing 1showing 2'); | ||
}); | ||
s = render('{% for i in [1] %}{% set foo=1 %}{% endfor %}{{ foo }}', | ||
{ foo: 2 }); | ||
s.should.equal('2'); | ||
it('should compile a set block', function() { | ||
var s = render('{% set username = "foo" %}{{ username }}', | ||
{ username: 'james' }); | ||
expect(s).to.be('foo'); | ||
s = render('{% include "set.html" %}{{ foo }}', | ||
{ foo: 'bar' }); | ||
s.should.equal('bar'); | ||
}); | ||
s = render('{% set x, y = "foo" %}{{ x }}{{ y }}'); | ||
expect(s).to.be('foofoo'); | ||
it('should compile set with frame references', function() { | ||
var s = render('{% set username = user.name %}{{ username }}', | ||
{ user: { name: 'james' } }); | ||
s.should.equal('james'); | ||
}); | ||
s = render('{% set x = 1 + 2 %}{{ x }}'); | ||
expect(s).to.be('3'); | ||
it('should throw errors', function() { | ||
(function() { | ||
render('{% from "import.html" import boozle %}'); | ||
}).should.throw(/cannot import 'boozle'/); | ||
s = render('{% for i in [1] %}{% set foo=1 %}{% endfor %}{{ foo }}', | ||
{ foo: 2 }); | ||
expect(s).to.be('2'); | ||
s = render('{% include "set.html" %}{{ foo }}', | ||
{ foo: 'bar' }); | ||
expect(s).to.be('bar'); | ||
}); | ||
it('should compile set with frame references', function() { | ||
var s = render('{% set username = user.name %}{{ username }}', | ||
{ user: { name: 'james' } }); | ||
expect(s).to.be('james'); | ||
}); | ||
it('should compile set assignments of the same variable', function() { | ||
var s = render('{% set x = "hello" %}' + | ||
'{% if false %}{% set x = "world" %}{% endif %}' + | ||
'{{ x }}'); | ||
expect(s).to.be('hello'); | ||
s = render('{% set x = "blue" %}' + | ||
'{% if true %}{% set x = "green" %}{% endif %}' + | ||
'{{ x }}'); | ||
expect(s).to.be('green'); | ||
}); | ||
it('should throw errors', function() { | ||
expect(function() { | ||
render('{% from "import.html" import boozle %}'); | ||
}).to.throwException(/cannot import 'boozle'/); | ||
}); | ||
it('should allow custom tag compilation', function() { | ||
function testExtension() { | ||
this.tags = ['test']; | ||
this.parse = function(parser, nodes) { | ||
parser.advanceAfterBlockEnd(); | ||
var content = parser.parseUntilBlocks("endtest"); | ||
var tag = new nodes.CallExtension(this, 'run', null, [content]); | ||
parser.advanceAfterBlockEnd(); | ||
return tag; | ||
}; | ||
this.run = function(context, content) { | ||
// Reverse the string | ||
return content().split("").reverse().join(""); | ||
}; | ||
} | ||
var opts = { extensions: { 'testExtension': new testExtension() }}; | ||
var output = render('{% test %}123456789{% endtest %}', null, opts); | ||
expect(output).to.be('987654321'); | ||
}); | ||
it('should allow complicated custom tag compilation', function() { | ||
function testExtension() { | ||
this.tags = ['test']; | ||
/* normally this is automatically done by Environment */ | ||
this._name = 'testExtension'; | ||
this.parse = function(parser, nodes, lexer) { | ||
var body, intermediate = null; | ||
parser.advanceAfterBlockEnd(); | ||
body = parser.parseUntilBlocks('intermediate', 'endtest'); | ||
if(parser.skipSymbol('intermediate')) { | ||
parser.skip(lexer.TOKEN_BLOCK_END); | ||
intermediate = parser.parseUntilBlocks('endtest'); | ||
} | ||
parser.advanceAfterBlockEnd(); | ||
return new nodes.CallExtension(this, 'run', null, [body, intermediate]); | ||
}; | ||
this.run = function(context, body, intermediate) { | ||
var output = body().split("").join(","); | ||
if(intermediate) { | ||
// Reverse the string. | ||
output += intermediate().split("").reverse().join(""); | ||
} | ||
return output; | ||
}; | ||
} | ||
var opts = { extensions: { 'testExtension': new testExtension() }}; | ||
var output = render('{% test %}abcdefg{% endtest %}', null, opts); | ||
expect(output).to.be('a,b,c,d,e,f,g'); | ||
output = render( | ||
'{% test %}abcdefg{% intermediate %}second half{% endtest %}', | ||
null, | ||
opts | ||
); | ||
expect(output).to.be('a,b,c,d,e,f,gflah dnoces'); | ||
}); | ||
it('should allow custom tag with args compilation', function() { | ||
function testExtension() { | ||
this.tags = ['test']; | ||
/* normally this is automatically done by Environment */ | ||
this._name = 'testExtension'; | ||
this.parse = function(parser, nodes, lexer) { | ||
var body, args = null; | ||
var tok = parser.nextToken(); | ||
// passing true makes it tolerate when no args exist | ||
args = parser.parseSignature(true); | ||
parser.advanceAfterBlockEnd(tok.value); | ||
body = parser.parseUntilBlocks('endtest'); | ||
parser.advanceAfterBlockEnd(); | ||
return new nodes.CallExtension(this, 'run', args, [body]); | ||
}; | ||
this.run = function(context, prefix, kwargs, body) { | ||
if(typeof prefix == 'function') { | ||
body = prefix; | ||
prefix = ''; | ||
kwargs = {}; | ||
} | ||
else if(typeof kwargs == 'function') { | ||
body = kwargs; | ||
kwargs = {}; | ||
} | ||
var output = prefix + body().split('').reverse().join(''); | ||
if(kwargs.cutoff) { | ||
output = output.slice(0, kwargs.cutoff); | ||
} | ||
return output; | ||
}; | ||
} | ||
var opts = { extensions: {'testExtension': new testExtension() }}; | ||
var output = render('{% test %}foobar{% endtest %}', null, opts); | ||
expect(output).to.be('raboof'); | ||
output = render('{% test("biz") %}foobar{% endtest %}', null, opts); | ||
expect(output).to.be('bizraboof'); | ||
output = render('{% test("biz", cutoff=5) %}foobar{% endtest %}', null, opts); | ||
expect(output).to.be('bizra'); | ||
}); | ||
it('should not autoescape by default', function() { | ||
var s = render('{{ foo }}', { foo: '"\'<>&'}); | ||
expect(s).to.be('"\'<>&'); | ||
}); | ||
it('should autoescape if autoescape is on', function() { | ||
var s = render('{{ foo }}', { foo: '"\'<>&'}, { autoescape: true }); | ||
expect(s).to.be('"'<>&'); | ||
var s = render('{{ foo|reverse }}', { foo: '"\'<>&'}, { autoescape: true }); | ||
expect(s).to.be('&><'"'); | ||
var s = render('{{ foo|reverse|safe }}', { foo: '"\'<>&'}, { autoescape: true }); | ||
expect(s).to.be('&><\'"'); | ||
}); | ||
it('should not autoescape safe strings', function() { | ||
var s = render('{{ foo|safe }}', { foo: '"\'<>&'}, { autoescape: true }); | ||
expect(s).to.be('"\'<>&'); | ||
}); | ||
it('should not autoescape macros', function() { | ||
var s = render( | ||
'{% macro foo(x, y) %}{{ x }} and {{ y }}{% endmacro %}' + | ||
'{{ foo("<>&", "<>") }}', | ||
null, | ||
{ autoescape: true } | ||
); | ||
expect(s).to.be('<>& and <>'); | ||
var s = render( | ||
'{% macro foo(x, y) %}{{ x|safe }} and {{ y }}{% endmacro %}' + | ||
'{{ foo("<>&", "<>") }}', | ||
null, | ||
{ autoescape: true } | ||
); | ||
expect(s).to.be('<>& and <>'); | ||
}); | ||
}); | ||
}); | ||
})(); |
@@ -0,288 +1,321 @@ | ||
(function() { | ||
var expect, render, lib; | ||
var render = require('./util').render; | ||
var lib = require('../src/lib'); | ||
if(typeof require != 'undefined') { | ||
expect = require('expect.js'); | ||
render = require('./util').render; | ||
lib = require('../src/lib'); | ||
} | ||
else { | ||
expect = window.expect; | ||
render = window.render; | ||
lib = nunjucks.require('lib'); | ||
} | ||
describe('filter', function() { | ||
it('abs', function() { | ||
render('{{ -3|abs }}').should.equal('3'); | ||
render('{{ -3.456|abs }}').should.equal('3.456'); | ||
}); | ||
describe('filter', function() { | ||
it('abs', function() { | ||
expect(render('{{ -3|abs }}')).to.be('3'); | ||
expect(render('{{ -3.456|abs }}')).to.be('3.456'); | ||
}); | ||
it('batch', function() { | ||
render('{% for a in [1,2,3,4,5,6]|batch(2) %}' + | ||
'-{% for b in a %}' + | ||
'{{ b }}' + | ||
'{% endfor %}-' + | ||
'{% endfor %}') | ||
.should.equal('-12--34--56-'); | ||
}); | ||
it('batch', function() { | ||
var s = render('{% for a in [1,2,3,4,5,6]|batch(2) %}' + | ||
'-{% for b in a %}' + | ||
'{{ b }}' + | ||
'{% endfor %}-' + | ||
'{% endfor %}'); | ||
expect(s).to.be('-12--34--56-'); | ||
}); | ||
it('capitalize', function() { | ||
var s = render('{{ "foo" | capitalize }}'); | ||
s.should.equal('Foo'); | ||
}); | ||
it('capitalize', function() { | ||
var s = render('{{ "foo" | capitalize }}'); | ||
expect(s).to.be('Foo'); | ||
}); | ||
it('center', function() { | ||
var s = render('{{ "fooo" | center }}'); | ||
s.should.equal(lib.repeat(' ', 38) + 'fooo' + | ||
lib.repeat(' ', 38)); | ||
it('center', function() { | ||
var s = render('{{ "fooo" | center }}'); | ||
expect(s).to.be(lib.repeat(' ', 38) + 'fooo' + | ||
lib.repeat(' ', 38)); | ||
s = render('{{ "foo" | center }}'); | ||
s.should.equal(lib.repeat(' ', 38) + 'foo' + | ||
lib.repeat(' ', 39)); | ||
s = render('{{ "foo" | center }}'); | ||
expect(s).to.be(lib.repeat(' ', 38) + 'foo' + | ||
lib.repeat(' ', 39)); | ||
}); | ||
}); | ||
it('default', function() { | ||
var s = render('{{ false | default("foo") }}'); | ||
expect(s).to.be('foo'); | ||
it('default', function() { | ||
var s = render('{{ false | default("foo") }}'); | ||
s.should.equal('foo'); | ||
s = render('{{ "bar" | default("foo") }}'); | ||
expect(s).to.be('bar'); | ||
}); | ||
s = render('{{ "bar" | default("foo") }}'); | ||
s.should.equal('bar'); | ||
}); | ||
it('escape', function() { | ||
var s = render('{{ "<html>" | escape }}'); | ||
expect(s).to.be('<html>'); | ||
}); | ||
it('escape', function() { | ||
var s = render('{{ "<html>" | escape }}'); | ||
s.should.equal('<html>'); | ||
}); | ||
it("dictsort", function() { | ||
// no real foolproof way to test that a js obj has been transformed | ||
// from unsorted -> sorted, as its enumeration ordering is undefined | ||
// and might fluke being sorted originally .. lets just init with some jumbled | ||
// keys | ||
it("dictsort", function() { | ||
// no real foolproof way to test that a js obj has been transformed | ||
// from unsorted -> sorted, as its enumeration ordering is undefined | ||
// and might fluke being sorted originally .. lets just init with some jumbled | ||
// keys | ||
// no params - should be case insensitive, by key | ||
var s = render('{% for item in items | dictsort %}' + | ||
// no params - should be case insensitive, by key | ||
var s = render('{% for item in items | dictsort %}' + | ||
'{{ item[0] }}{% endfor %}', { | ||
items: { | ||
"e": 1, | ||
"d": 2, | ||
"c": 3, | ||
"a": 4, | ||
"f": 5, | ||
"b": 6 | ||
} | ||
}); | ||
s.should.equal("abcdef"); | ||
items: { | ||
"e": 1, | ||
"d": 2, | ||
"c": 3, | ||
"a": 4, | ||
"f": 5, | ||
"b": 6 | ||
} | ||
}); | ||
expect(s).to.be("abcdef"); | ||
// case sensitive = true | ||
var s = render('{% for item in items | dictsort(true) %}' + | ||
// case sensitive = true | ||
var s = render('{% for item in items | dictsort(true) %}' + | ||
'{{ item[0] }},{% endfor %}', { | ||
items: { | ||
"ABC": 6, | ||
"ABc": 5, | ||
"Abc": 1, | ||
"abc": 2 | ||
} | ||
}); | ||
items: { | ||
"ABC": 6, | ||
"ABc": 5, | ||
"Abc": 1, | ||
"abc": 2 | ||
} | ||
}); | ||
s.should.equal("ABC,ABc,Abc,abc,"); | ||
expect(s).to.be("ABC,ABc,Abc,abc,"); | ||
// use values for sort | ||
var s = render('{% for item in items | dictsort(false, "value") %}' + | ||
// use values for sort | ||
var s = render('{% for item in items | dictsort(false, "value") %}' + | ||
'{{ item[0] }}{% endfor %}', { | ||
items: { | ||
"a": 6, | ||
"b": 5, | ||
"c": 1, | ||
"d": 2 | ||
} | ||
items: { | ||
"a": 6, | ||
"b": 5, | ||
"c": 1, | ||
"d": 2 | ||
} | ||
}); | ||
expect(s).to.be("cdba"); | ||
}); | ||
s.should.equal("cdba"); | ||
}); | ||
it('first', function() { | ||
var s = render('{{ [1,2,3] | first }}'); | ||
expect(s).to.be('1'); | ||
}); | ||
it('first', function() { | ||
var s = render('{{ [1,2,3] | first }}'); | ||
s.should.equal('1'); | ||
}); | ||
it('float/int', function() { | ||
var s = render('{{ "3.5" | float }}'); | ||
expect(s).to.be('3.5'); | ||
it('float/int', function() { | ||
var s = render('{{ "3.5" | float }}'); | ||
s.should.equal('3.5'); | ||
s = render('{{ "3.5" | int }}'); | ||
expect(s).to.be('3'); | ||
s = render('{{ "3.5" | int }}'); | ||
s.should.equal('3'); | ||
s = render('{{ "0" | int }}'); | ||
expect(s).to.be('0'); | ||
s = render('{{ "0" | int }}'); | ||
s.should.equal('0'); | ||
s = render('{{ "0" | float }}'); | ||
expect(s).to.be('0'); | ||
s = render('{{ "0" | float }}'); | ||
s.should.equal('0'); | ||
s = render('{{ "bob" | int("cat") }}'); | ||
expect(s).to.be('cat'); | ||
s = render('{{ "bob" | int("cat") }}'); | ||
s.should.equal('cat'); | ||
s = render('{{ "bob" | float("cat") }}'); | ||
expect(s).to.be('cat'); | ||
}); | ||
s = render('{{ "bob" | float("cat") }}'); | ||
s.should.equal('cat'); | ||
}); | ||
it('groupby', function() { | ||
var s = render('{% for type, items in items | groupby("type") %}' + | ||
':{{ type }}:' + | ||
'{% for item in items %}' + | ||
'{{ item.name }}' + | ||
'{% endfor %}' + | ||
'{% endfor %}', | ||
{ items: [{ name: 'james', | ||
type: 'green' }, | ||
{ name: 'john', | ||
type: 'blue' }, | ||
{ name: 'jim', | ||
type: 'blue' }, | ||
{ name: 'jessie', | ||
type: 'green' }]}); | ||
it('groupby', function() { | ||
var s = render('{% for type, items in items | groupby("type") %}' + | ||
':{{ type }}:' + | ||
'{% for item in items %}' + | ||
'{{ item.name }}' + | ||
'{% endfor %}' + | ||
'{% endfor %}', | ||
{ items: [{ name: 'james', | ||
type: 'green' }, | ||
{ name: 'john', | ||
type: 'blue' }, | ||
{ name: 'jim', | ||
type: 'blue' }, | ||
{ name: 'jessie', | ||
type: 'green' }]}); | ||
expect(s).to.be(':green:jamesjessie:blue:johnjim'); | ||
}); | ||
s.should.equal(':green:jamesjessie:blue:johnjim'); | ||
}); | ||
it('indent', function() { | ||
var s = render('{{ "one\ntwo\nthree" | indent }}'); | ||
expect(s).to.be('one\n two\n three\n'); | ||
it('indent', function() { | ||
var s = render('{{ "one\ntwo\nthree" | indent }}'); | ||
s.should.equal('one\n two\n three\n'); | ||
s = render('{{ "one\ntwo\nthree" | indent(2) }}'); | ||
expect(s).to.be('one\n two\n three\n'); | ||
s = render('{{ "one\ntwo\nthree" | indent(2) }}'); | ||
s.should.equal('one\n two\n three\n'); | ||
s = render('{{ "one\ntwo\nthree" | indent(2, true) }}'); | ||
expect(s).to.be(' one\n two\n three\n'); | ||
}); | ||
s = render('{{ "one\ntwo\nthree" | indent(2, true) }}'); | ||
s.should.equal(' one\n two\n three\n'); | ||
}); | ||
it('join', function() { | ||
var s = render('{{ items | join }}', | ||
{ items: [1, 2, 3] }); | ||
expect(s).to.be('123'); | ||
it('join', function() { | ||
var s = render('{{ items | join }}', | ||
{ items: [1, 2, 3] }); | ||
s.should.equal('123'); | ||
s = render('{{ items | join(",") }}', | ||
{ items: ['foo', 'bar', 'bear'] }); | ||
expect(s).to.be('foo,bar,bear'); | ||
s = render('{{ items | join(",") }}', | ||
{ items: ['foo', 'bar', 'bear'] }); | ||
s.should.equal('foo,bar,bear'); | ||
s = render('{{ items | join(",", "name") }}', | ||
{ items: [{ name: 'foo' }, | ||
{ name: 'bar' }, | ||
{ name: 'bear' }] }); | ||
expect(s).to.be('foo,bar,bear'); | ||
}); | ||
s = render('{{ items | join(",", "name") }}', | ||
{ items: [{ name: 'foo' }, | ||
{ name: 'bar' }, | ||
{ name: 'bear' }] }); | ||
s.should.equal('foo,bar,bear'); | ||
}); | ||
it('last', function() { | ||
var s = render('{{ [1,2,3] | last }}'); | ||
expect(s).to.be('3'); | ||
}); | ||
it('last', function() { | ||
var s = render('{{ [1,2,3] | last }}'); | ||
s.should.equal('3'); | ||
}); | ||
it('length', function() { | ||
var s = render('{{ [1,2,3] | length }}'); | ||
expect(s).to.be('3'); | ||
}); | ||
it('length', function() { | ||
var s = render('{{ [1,2,3] | length }}'); | ||
s.should.equal('3'); | ||
}); | ||
it('list', function() { | ||
var s = render('{% for i in "foobar" | list %}{{ i }},{% endfor %}'); | ||
expect(s).to.be('f,o,o,b,a,r,'); | ||
}); | ||
it('list', function() { | ||
var s = render('{% for i in "foobar" | list %}{{ i }},{% endfor %}'); | ||
s.should.equal('f,o,o,b,a,r,'); | ||
}); | ||
it('lower', function() { | ||
var s = render('{{ "fOObAr" | lower }}'); | ||
expect(s).to.be('foobar'); | ||
}); | ||
it('lower', function() { | ||
var s = render('{{ "fOObAr" | lower }}'); | ||
s.should.equal('foobar'); | ||
}); | ||
it('random', function() { | ||
for(var i=0; i<100; i++) { | ||
var s = render('{{ [1,2,3,4,5,6,7,8,9] | random }}'); | ||
var val = parseInt(s); | ||
expect(val).to.be.within(1, 9); | ||
} | ||
}); | ||
it('random', function() { | ||
for(var i=0; i<100; i++) { | ||
var s = render('{{ [1,2,3,4,5,6,7,8,9] | random }}'); | ||
var val = parseInt(s); | ||
val.should.be.within(1, 9); | ||
} | ||
}); | ||
it('replace', function() { | ||
var s = render('{{ "aaabbbccc" | replace("a", "x") }}'); | ||
expect(s).to.be('xxxbbbccc'); | ||
it('replace', function() { | ||
var s = render('{{ "aaabbbccc" | replace("a", "x") }}'); | ||
s.should.equal('xxxbbbccc'); | ||
s = render('{{ "aaabbbccc" | replace("a", "x", 2) }}'); | ||
expect(s).to.be('xxabbbccc'); | ||
s = render('{{ "aaabbbccc" | replace("a", "x", 2) }}'); | ||
s.should.equal('xxabbbccc'); | ||
s = render('{{ "aaabbbbbccc" | replace("b", "y", 4) }}'); | ||
expect(s).to.be('aaayyyybccc'); | ||
}); | ||
s = render('{{ "aaabbbbbccc" | replace("b", "y", 4) }}'); | ||
s.should.equal('aaayyyybccc'); | ||
}); | ||
it('reverse', function() { | ||
var s = render('{{ "abcdef" | reverse }}'); | ||
expect(s).to.be('fedcba'); | ||
it('reverse', function() { | ||
var s = render('{{ "abcdef" | reverse }}'); | ||
s.should.equal('fedcba'); | ||
s = render('{% for i in [1, 2, 3, 4] | reverse %}{{ i }}{% endfor %}'); | ||
expect(s).to.be('4321'); | ||
}); | ||
s = render('{% for i in [1, 2, 3, 4] | reverse %}{{ i }}{% endfor %}'); | ||
s.should.equal('4321'); | ||
}); | ||
it('round', function() { | ||
var s = render('{{ 4.5 | round }}'); | ||
expect(s).to.be('5'); | ||
it('round', function() { | ||
var s = render('{{ 4.5 | round }}'); | ||
s.should.equal('5'); | ||
s = render('{{ 4.5 | round(0, "floor") }}'); | ||
expect(s).to.be('4'); | ||
s = render('{{ 4.5 | round(0, "floor") }}'); | ||
s.should.equal('4'); | ||
s = render('{{ 4.12345 | round(4) }}'); | ||
expect(s).to.be('4.1235'); | ||
s = render('{{ 4.12345 | round(4) }}'); | ||
s.should.equal('4.1235'); | ||
s = render('{{ 4.12344 | round(4) }}'); | ||
expect(s).to.be('4.1234'); | ||
}); | ||
s = render('{{ 4.12344 | round(4) }}'); | ||
s.should.equal('4.1234'); | ||
}); | ||
it('slice', function() { | ||
var tmpl = '{% for items in arr | slice(3) %}' + | ||
'--' + | ||
'{% for item in items %}' + | ||
'{{ item }}' + | ||
'{% endfor %}' + | ||
'--' + | ||
'{% endfor %}'; | ||
it('slice', function() { | ||
var tmpl = '{% for items in arr | slice(3) %}' + | ||
'--' + | ||
'{% for item in items %}' + | ||
'{{ item }}' + | ||
'{% endfor %}' + | ||
'--' + | ||
'{% endfor %}'; | ||
var s = render(tmpl, | ||
{ arr: [1,2,3,4,5,6,7,8,9] }); | ||
expect(s).to.be('--123----456----789--'); | ||
var s = render(tmpl, | ||
{ arr: [1,2,3,4,5,6,7,8,9] }); | ||
s.should.equal('--123----456----789--'); | ||
s = render(tmpl, | ||
{ arr: [1,2,3,4,5,6,7,8,9,10] }); | ||
expect(s).to.be('--1234----567----8910--'); | ||
}); | ||
s = render(tmpl, | ||
{ arr: [1,2,3,4,5,6,7,8,9,10] }); | ||
s.should.equal('--1234----567----8910--'); | ||
}); | ||
it('sort', function() { | ||
var s = render('{% for i in [3,5,2,1,4,6] | sort %}{{ i }}{% endfor %}'); | ||
expect(s).to.be('123456'); | ||
it('sort', function() { | ||
var s = render('{% for i in [3,5,2,1,4,6] | sort %}{{ i }}{% endfor %}'); | ||
s.should.equal('123456'); | ||
s = render('{% for i in ["fOo", "Foo"] | sort %}{{ i }}{% endfor %}'); | ||
expect(s).to.be('fOoFoo'); | ||
s = render('{% for i in ["fOo", "Foo"] | sort %}{{ i }}{% endfor %}'); | ||
s.should.equal('fOoFoo'); | ||
s = render('{% for i in [1,6,3,7] | sort(true) %}' + | ||
'{{ i }}{% endfor %}'); | ||
expect(s).to.be('7631'); | ||
s = render('{% for i in [1,6,3,7] | sort(true) %}' + | ||
'{{ i }}{% endfor %}'); | ||
s.should.equal('7631'); | ||
s = render('{% for i in ["fOo", "Foo"] | sort(false, true) %}' + | ||
'{{ i }}{% endfor %}'); | ||
expect(s).to.be('FoofOo'); | ||
s = render('{% for i in ["fOo", "Foo"] | sort(false, true) %}' + | ||
'{{ i }}{% endfor %}'); | ||
s.should.equal('FoofOo'); | ||
s = render('{% for item in items | sort(false, false, "name") %}' + | ||
'{{ item.name }}{% endfor %}', | ||
{ items: [{ name: 'james' }, | ||
{ name: 'fred' }, | ||
{ name: 'john' }]}); | ||
expect(s).to.be('fredjamesjohn'); | ||
}); | ||
s = render('{% for item in items | sort(false, false, "name") %}' + | ||
'{{ item.name }}{% endfor %}', | ||
{ items: [{ name: 'james' }, | ||
{ name: 'fred' }, | ||
{ name: 'john' }]}); | ||
s.should.equal('fredjamesjohn'); | ||
}); | ||
it('string', function() { | ||
var s = render('{% for i in 1234 | string | list %}{{ i }},{% endfor %}'); | ||
expect(s).to.be('1,2,3,4,'); | ||
}); | ||
it('string', function() { | ||
var s = render('{% for i in 1234 | string | list %}{{ i }},{% endfor %}'); | ||
s.should.equal('1,2,3,4,'); | ||
}); | ||
it('title', function() { | ||
var s = render('{{ "foo bar baz" | title }}'); | ||
expect(s).to.be('Foo Bar Baz'); | ||
}); | ||
it('trim', function() { | ||
var s = render('{{ " foo " | trim }}'); | ||
s.should.equal('foo'); | ||
}); | ||
it('upper', function() { | ||
var s = render('{{ "foo" | upper }}'); | ||
s.should.equal('FOO'); | ||
}); | ||
it('trim', function() { | ||
var s = render('{{ " foo " | trim }}'); | ||
expect(s).to.be('foo'); | ||
}); | ||
it('wordcount', function() { | ||
var s = render('{{ "foo bar baz" | wordcount }}'); | ||
s.should.equal('3'); | ||
it('truncate', function() { | ||
var s = render('{{ "foo bar" | truncate(3) }}'); | ||
expect(s).to.be('foo...'); | ||
var s = render('{{ "foo bar baz" | truncate(6) }}'); | ||
expect(s).to.be('foo...'); | ||
var s = render('{{ "foo bar baz" | truncate(7) }}'); | ||
expect(s).to.be('foo bar...'); | ||
var s = render('{{ "foo bar baz" | truncate(5, true) }}'); | ||
expect(s).to.be('foo b...'); | ||
var s = render('{{ "foo bar baz" | truncate(6, true, "?") }}'); | ||
expect(s).to.be('foo ba?'); | ||
}); | ||
it('upper', function() { | ||
var s = render('{{ "foo" | upper }}'); | ||
expect(s).to.be('FOO'); | ||
}); | ||
it('wordcount', function() { | ||
var s = render('{{ "foo bar baz" | wordcount }}'); | ||
expect(s).to.be('3'); | ||
}); | ||
}); | ||
}); | ||
})(); |
@@ -1,223 +0,234 @@ | ||
var should = require('should'); | ||
var lib = require('../src/lib'); | ||
var lexer = require('../src/lexer'); | ||
(function() { | ||
var expect, lib, lexer; | ||
function _hasTokens(ws, tokens, types) { | ||
for(var i=0; i<types.length; i++) { | ||
var type = types[i]; | ||
var tok = tokens.nextToken(); | ||
if(typeof require != 'undefined') { | ||
expect = require('expect.js'); | ||
lib = require('../src/lib'); | ||
lexer = require('../src/lexer'); | ||
} | ||
else { | ||
expect = window.expect; | ||
lib = nunjucks.require('lib'); | ||
lexer = nunjucks.require('lexer'); | ||
} | ||
if(!ws) { | ||
while(tok && tok.type == lexer.TOKEN_WHITESPACE) { | ||
tok = tokens.nextToken(); | ||
function _hasTokens(ws, tokens, types) { | ||
for(var i=0; i<types.length; i++) { | ||
var type = types[i]; | ||
var tok = tokens.nextToken(); | ||
if(!ws) { | ||
while(tok && tok.type == lexer.TOKEN_WHITESPACE) { | ||
tok = tokens.nextToken(); | ||
} | ||
} | ||
} | ||
if(lib.isArray(type)) { | ||
tok.type.should.equal(type[0]); | ||
tok.value.should.equal(type[1]); | ||
if(lib.isArray(type)) { | ||
expect(tok.type).to.be(type[0]); | ||
expect(tok.value).to.be(type[1]); | ||
} | ||
else { | ||
expect(tok.type).to.be(type); | ||
} | ||
} | ||
else { | ||
tok.type.should.equal(type); | ||
} | ||
} | ||
} | ||
function hasTokens(tokens /*, types */) { | ||
return _hasTokens(false, tokens, lib.toArray(arguments).slice(1)); | ||
} | ||
function hasTokens(tokens /*, types */) { | ||
return _hasTokens(false, tokens, lib.toArray(arguments).slice(1)); | ||
} | ||
function hasTokensWithWS(tokens /*, types */) { | ||
return _hasTokens(true, tokens, lib.toArray(arguments).slice(1)); | ||
} | ||
function hasTokensWithWS(tokens /*, types */) { | ||
return _hasTokens(true, tokens, lib.toArray(arguments).slice(1)); | ||
} | ||
describe('lexer', function() { | ||
var tok, tmpl, tokens; | ||
describe('lexer', function() { | ||
var tok, tmpl, tokens; | ||
it('should parse template data', function() { | ||
tok = lexer.lex('3').nextToken(); | ||
tok.type.should.equal(lexer.TOKEN_DATA); | ||
tok.value.should.equal('3'); | ||
it('should parse template data', function() { | ||
tok = lexer.lex('3').nextToken(); | ||
expect(tok.type).to.be(lexer.TOKEN_DATA); | ||
expect(tok.value).to.be('3'); | ||
tmpl = 'foo bar bizzle 3 [1,2] !@#$%^&*()<>?:"{}|'; | ||
tok = lexer.lex(tmpl).nextToken(); | ||
tok.type.should.equal(lexer.TOKEN_DATA); | ||
tok.value.should.equal(tmpl); | ||
}); | ||
tmpl = 'foo bar bizzle 3 [1,2] !@#$%^&*()<>?:"{}|'; | ||
tok = lexer.lex(tmpl).nextToken(); | ||
expect(tok.type).to.be(lexer.TOKEN_DATA); | ||
expect(tok.value).to.be(tmpl); | ||
}); | ||
it('should keep track of whitespace', function() { | ||
tokens = lexer.lex('data {% 1 2\n 3 %} data'); | ||
hasTokensWithWS(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_BLOCK_START, | ||
[lexer.TOKEN_WHITESPACE, ' '], | ||
lexer.TOKEN_INT, | ||
[lexer.TOKEN_WHITESPACE, ' '], | ||
lexer.TOKEN_INT, | ||
[lexer.TOKEN_WHITESPACE, '\n '], | ||
lexer.TOKEN_INT, | ||
[lexer.TOKEN_WHITESPACE, ' '], | ||
lexer.TOKEN_BLOCK_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should keep track of whitespace', function() { | ||
tokens = lexer.lex('data {% 1 2\n 3 %} data'); | ||
hasTokensWithWS(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_BLOCK_START, | ||
[lexer.TOKEN_WHITESPACE, ' '], | ||
lexer.TOKEN_INT, | ||
[lexer.TOKEN_WHITESPACE, ' '], | ||
lexer.TOKEN_INT, | ||
[lexer.TOKEN_WHITESPACE, '\n '], | ||
lexer.TOKEN_INT, | ||
[lexer.TOKEN_WHITESPACE, ' '], | ||
lexer.TOKEN_BLOCK_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse variable start and end', function() { | ||
tokens = lexer.lex('data {{ foo }} bar bizzle'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_SYMBOL, | ||
lexer.TOKEN_VARIABLE_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse variable start and end', function() { | ||
tokens = lexer.lex('data {{ foo }} bar bizzle'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_SYMBOL, | ||
lexer.TOKEN_VARIABLE_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse block start and end', function() { | ||
tokens = lexer.lex('data {% foo %} bar bizzle'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_BLOCK_START, | ||
lexer.TOKEN_SYMBOL, | ||
lexer.TOKEN_BLOCK_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse block start and end', function() { | ||
tokens = lexer.lex('data {% foo %} bar bizzle'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_BLOCK_START, | ||
lexer.TOKEN_SYMBOL, | ||
lexer.TOKEN_BLOCK_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse basic types', function() { | ||
tokens = lexer.lex('{{ 3 4.5 true false foo "hello" \'boo\' }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_FLOAT, | ||
lexer.TOKEN_BOOLEAN, | ||
lexer.TOKEN_BOOLEAN, | ||
lexer.TOKEN_SYMBOL, | ||
lexer.TOKEN_STRING, | ||
lexer.TOKEN_STRING, | ||
lexer.TOKEN_VARIABLE_END); | ||
}), | ||
it('should parse basic types', function() { | ||
tokens = lexer.lex('{{ 3 4.5 true false foo "hello" \'boo\' }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_FLOAT, | ||
lexer.TOKEN_BOOLEAN, | ||
lexer.TOKEN_BOOLEAN, | ||
lexer.TOKEN_SYMBOL, | ||
lexer.TOKEN_STRING, | ||
lexer.TOKEN_STRING, | ||
lexer.TOKEN_VARIABLE_END); | ||
}), | ||
it('should parse function calls', function() { | ||
tokens = lexer.lex('{{ foo(bar) }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
[lexer.TOKEN_SYMBOL, 'foo'], | ||
lexer.TOKEN_LEFT_PAREN, | ||
[lexer.TOKEN_SYMBOL, 'bar'], | ||
lexer.TOKEN_RIGHT_PAREN, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse function calls', function() { | ||
tokens = lexer.lex('{{ foo(bar) }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
[lexer.TOKEN_SYMBOL, 'foo'], | ||
lexer.TOKEN_LEFT_PAREN, | ||
[lexer.TOKEN_SYMBOL, 'bar'], | ||
lexer.TOKEN_RIGHT_PAREN, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse groups', function() { | ||
tokens = lexer.lex('{{ (1, 2, 3) }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_LEFT_PAREN, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_RIGHT_PAREN, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse groups', function() { | ||
tokens = lexer.lex('{{ (1, 2, 3) }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_LEFT_PAREN, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_RIGHT_PAREN, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse arrays', function() { | ||
tokens = lexer.lex('{{ [1, 2, 3] }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_LEFT_BRACKET, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_RIGHT_BRACKET, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse arrays', function() { | ||
tokens = lexer.lex('{{ [1, 2, 3] }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_LEFT_BRACKET, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_COMMA, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_RIGHT_BRACKET, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse dicts', function() { | ||
tokens = lexer.lex('{{ {one:1, "two":2} }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_LEFT_CURLY, | ||
[lexer.TOKEN_SYMBOL, 'one'], | ||
lexer.TOKEN_COLON, | ||
[lexer.TOKEN_INT, '1'], | ||
lexer.TOKEN_COMMA, | ||
[lexer.TOKEN_STRING, 'two'], | ||
lexer.TOKEN_COLON, | ||
[lexer.TOKEN_INT, '2'], | ||
lexer.TOKEN_RIGHT_CURLY, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse dicts', function() { | ||
tokens = lexer.lex('{{ {one:1, "two":2} }}'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_LEFT_CURLY, | ||
[lexer.TOKEN_SYMBOL, 'one'], | ||
lexer.TOKEN_COLON, | ||
[lexer.TOKEN_INT, '1'], | ||
lexer.TOKEN_COMMA, | ||
[lexer.TOKEN_STRING, 'two'], | ||
lexer.TOKEN_COLON, | ||
[lexer.TOKEN_INT, '2'], | ||
lexer.TOKEN_RIGHT_CURLY, | ||
lexer.TOKEN_VARIABLE_END); | ||
}); | ||
it('should parse blocks without whitespace', function() { | ||
tokens = lexer.lex('data{{hello}}{%if%}data'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_VARIABLE_START, | ||
[lexer.TOKEN_SYMBOL, 'hello'], | ||
lexer.TOKEN_VARIABLE_END, | ||
lexer.TOKEN_BLOCK_START, | ||
[lexer.TOKEN_SYMBOL, 'if'], | ||
lexer.TOKEN_BLOCK_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse blocks without whitespace', function() { | ||
tokens = lexer.lex('data{{hello}}{%if%}data'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_VARIABLE_START, | ||
[lexer.TOKEN_SYMBOL, 'hello'], | ||
lexer.TOKEN_VARIABLE_END, | ||
lexer.TOKEN_BLOCK_START, | ||
[lexer.TOKEN_SYMBOL, 'if'], | ||
lexer.TOKEN_BLOCK_END, | ||
lexer.TOKEN_DATA); | ||
}); | ||
it('should parse filters', function() { | ||
hasTokens(lexer.lex('{{ foo|bar }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
[lexer.TOKEN_SYMBOL, 'foo'], | ||
lexer.TOKEN_PIPE, | ||
[lexer.TOKEN_SYMBOL, 'bar'], | ||
lexer.TOKEN_VARIABLE_END); | ||
}), | ||
it('should parse filters', function() { | ||
hasTokens(lexer.lex('{{ foo|bar }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
[lexer.TOKEN_SYMBOL, 'foo'], | ||
lexer.TOKEN_PIPE, | ||
[lexer.TOKEN_SYMBOL, 'bar'], | ||
lexer.TOKEN_VARIABLE_END); | ||
}), | ||
it('should parse operators', function() { | ||
hasTokens(lexer.lex('{{ 3+3-3*3/3 }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_VARIABLE_END); | ||
it('should parse operators', function() { | ||
hasTokens(lexer.lex('{{ 3+3-3*3/3 }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_VARIABLE_END); | ||
hasTokens(lexer.lex('{{ 3**4//5 }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_VARIABLE_END); | ||
hasTokens(lexer.lex('{{ 3**4//5 }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_VARIABLE_END); | ||
hasTokens(lexer.lex('{{ 3 != 4 == 5 <= 6 >= 7 < 8 > 9 }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_VARIABLE_END); | ||
}), | ||
hasTokens(lexer.lex('{{ 3 != 4 == 5 <= 6 >= 7 < 8 > 9 }}'), | ||
lexer.TOKEN_VARIABLE_START, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_OPERATOR, | ||
lexer.TOKEN_INT, | ||
lexer.TOKEN_VARIABLE_END); | ||
}), | ||
it('should parse comments', function() { | ||
tokens = lexer.lex('data data {# comment #} data'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_COMMENT, | ||
lexer.TOKEN_DATA); | ||
it('should parse comments', function() { | ||
tokens = lexer.lex('data data {# comment #} data'); | ||
hasTokens(tokens, | ||
lexer.TOKEN_DATA, | ||
lexer.TOKEN_COMMENT, | ||
lexer.TOKEN_DATA); | ||
}); | ||
}); | ||
}); | ||
})(); |
@@ -1,379 +0,501 @@ | ||
var should = require('should'); | ||
var lib = require('../src/lib'); | ||
var nodes = require('../src/nodes'); | ||
var parser = require('../src/parser'); | ||
(function() { | ||
var expect, lib, nodes, parser; | ||
function _isAST(node1, node2) { | ||
// Compare ASTs | ||
// TODO: Clean this up | ||
if(typeof require != 'undefined') { | ||
expect = require('expect.js'); | ||
lib = require('../src/lib'); | ||
nodes = require('../src/nodes'); | ||
parser = require('../src/parser'); | ||
} | ||
else { | ||
expect = window.expect; | ||
lib = nunjucks.require('lib'); | ||
nodes = nunjucks.require('nodes'); | ||
parser = nunjucks.require('parser'); | ||
} | ||
function _isAST(node1, node2) { | ||
// Compare ASTs | ||
// TODO: Clean this up (seriously, really) | ||
node1.typename.should.equal(node2.typename); | ||
expect(node1.typename).to.be(node2.typename); | ||
var children1 = (node1.children && node1.children.length) || 'null'; | ||
var children2 = (node2.children && node2.children.length) || 'null'; | ||
var children1 = (node1.children && node1.children.length) || 'null'; | ||
var children2 = (node2.children && node2.children.length) || 'null'; | ||
if(node2 instanceof nodes.NodeList) { | ||
var lit = ': num-children: '; | ||
var sig2 = (node2.typename + lit + node2.children.length); | ||
if(node2 instanceof nodes.NodeList) { | ||
var lit = ': num-children: '; | ||
var sig2 = (node2.typename + lit + node2.children.length); | ||
should.exist(node1.children); | ||
var sig1 = (node1.typename + lit + node1.children.length); | ||
sig1.should.equal(sig2); | ||
expect(node1.children).to.be.ok(); | ||
var sig1 = (node1.typename + lit + node1.children.length); | ||
for(var i=0, l=node2.children.length; i<l; i++) { | ||
_isAST(node1.children[i], node2.children[i]); | ||
expect(sig1).to.be(sig2); | ||
for(var i=0, l=node2.children.length; i<l; i++) { | ||
_isAST(node1.children[i], node2.children[i]); | ||
} | ||
} | ||
} | ||
else { | ||
node2.iterFields(function(value, field) { | ||
if(value instanceof nodes.Node) { | ||
_isAST(node1[field], value); | ||
} | ||
else if(node1[field] !== null || value !== null) { | ||
if(node1[field] === null) { | ||
throw new Error(value + ' expected for "' + field + | ||
'", null found'); | ||
else { | ||
node2.iterFields(function(value, field) { | ||
var ofield = node1[field]; | ||
if(value instanceof nodes.Node) { | ||
_isAST(ofield, value); | ||
} | ||
else if(lib.isArray(ofield) && lib.isArray(value)) { | ||
expect('num-children: ' + ofield.length).to.be('num-children: ' + value.length); | ||
if(value === null) { | ||
throw new Error(node1[field] + ' expected to be null for "' + | ||
field + '"'); | ||
lib.each(ofield, function(v, i) { | ||
if(ofield[i] instanceof nodes.Node) { | ||
_isAST(ofield[i], value[i]); | ||
} | ||
else if(ofield[i] !== null && value[i] !== null) { | ||
expect(ofield[i]).to.be(value[i]); | ||
} | ||
}); | ||
} | ||
else if((ofield !== null || value !== null) && | ||
(ofield !== undefined || value !== undefined)) { | ||
if(ofield === null) { | ||
throw new Error(value + ' expected for "' + field + | ||
'", null found'); | ||
} | ||
// We want good errors and tracebacks, so test on | ||
// whichever object exists | ||
if(!node1[field]) { | ||
value.should.equal(node1[field]); | ||
if(value === null) { | ||
throw new Error(ofield + ' expected to be null for "' + | ||
field + '"'); | ||
} | ||
// We want good errors and tracebacks, so test on | ||
// whichever object exists | ||
if(!ofield) { | ||
expect(value).to.be(ofield); | ||
} | ||
else { | ||
expect(ofield).to.be(value); | ||
} | ||
} | ||
else { | ||
node1[field].should.equal(value); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
function isAST(node1, ast) { | ||
// Compare the ASTs, the second one is an AST literal so transform | ||
// it into a real one | ||
return _isAST(node1, toNodes(ast)); | ||
} | ||
// We'll be doing a lot of AST comparisons, so this defines a kind | ||
// of "AST literal" that you can specify with arrays. This | ||
// transforms it into a real AST. | ||
function toNodes(ast) { | ||
if(!(ast && lib.isArray(ast))) { | ||
return ast; | ||
function isAST(node1, ast) { | ||
// Compare the ASTs, the second one is an AST literal so transform | ||
// it into a real one | ||
return _isAST(node1, toNodes(ast)); | ||
} | ||
var type = ast[0]; | ||
var dummy = Object.create(type.prototype); | ||
// We'll be doing a lot of AST comparisons, so this defines a kind | ||
// of "AST literal" that you can specify with arrays. This | ||
// transforms it into a real AST. | ||
function toNodes(ast) { | ||
if(!(ast && lib.isArray(ast))) { | ||
return ast; | ||
} | ||
if(dummy instanceof nodes.NodeList) { | ||
return new type(0, 0, lib.map(ast.slice(1), toNodes)); | ||
var type = ast[0]; | ||
var dummy = Object.create(type.prototype); | ||
if(dummy instanceof nodes.NodeList) { | ||
return new type(0, 0, lib.map(ast.slice(1), toNodes)); | ||
} | ||
else if(dummy instanceof nodes.CallExtension) { | ||
return new type(ast[1], ast[2], ast[3] ? toNodes(ast[3]) : ast[3], | ||
lib.isArray(ast[4]) ? lib.map(ast[4], toNodes) : ast[4]); | ||
} | ||
else { | ||
return new type(0, 0, | ||
toNodes(ast[1]), | ||
toNodes(ast[2]), | ||
toNodes(ast[3]), | ||
toNodes(ast[4]), | ||
toNodes(ast[5])); | ||
} | ||
} | ||
else { | ||
return new type(0, 0, | ||
toNodes(ast[1]), | ||
toNodes(ast[2]), | ||
toNodes(ast[3]), | ||
toNodes(ast[4]), | ||
toNodes(ast[5])); | ||
} | ||
} | ||
describe('parser', function() { | ||
it('should parse basic types', function() { | ||
isAST(parser.parse('{{ 1 }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 1]]]); | ||
describe('parser', function() { | ||
it('should parse basic types', function() { | ||
isAST(parser.parse('{{ 1 }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 1]]]); | ||
isAST(parser.parse('{{ 4.567 }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 4.567]]]); | ||
isAST(parser.parse('{{ 4.567 }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 4.567]]]); | ||
isAST(parser.parse('{{ "foo" }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 'foo']]]); | ||
isAST(parser.parse('{{ "foo" }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 'foo']]]); | ||
isAST(parser.parse("{{ 'foo' }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 'foo']]]); | ||
isAST(parser.parse("{{ 'foo' }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, 'foo']]]); | ||
isAST(parser.parse("{{ true }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, true]]]); | ||
isAST(parser.parse("{{ true }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, true]]]); | ||
isAST(parser.parse("{{ false }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, false]]]); | ||
isAST(parser.parse("{{ false }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Literal, false]]]); | ||
isAST(parser.parse("{{ foo }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Symbol, 'foo']]]); | ||
}); | ||
isAST(parser.parse("{{ foo }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Symbol, 'foo']]]); | ||
}); | ||
it('should parse aggregate types', function() { | ||
isAST(parser.parse("{{ [1,2,3] }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Array, | ||
[nodes.Literal, 1], | ||
[nodes.Literal, 2], | ||
[nodes.Literal, 3]]]]); | ||
it('should parse aggregate types', function() { | ||
isAST(parser.parse("{{ [1,2,3] }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Array, | ||
[nodes.Literal, 1], | ||
[nodes.Literal, 2], | ||
[nodes.Literal, 3]]]]); | ||
isAST(parser.parse("{{ (1,2,3) }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Group, | ||
[nodes.Literal, 1], | ||
[nodes.Literal, 2], | ||
[nodes.Literal, 3]]]]); | ||
isAST(parser.parse("{{ (1,2,3) }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Group, | ||
[nodes.Literal, 1], | ||
[nodes.Literal, 2], | ||
[nodes.Literal, 3]]]]); | ||
isAST(parser.parse("{{ {foo: 1, 'two': 2} }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Dict, | ||
[nodes.Pair, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.Literal, 1]], | ||
[nodes.Pair, | ||
[nodes.Literal, 'two'], | ||
[nodes.Literal, 2]]]]]); | ||
}); | ||
isAST(parser.parse("{{ {foo: 1, 'two': 2} }}"), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Dict, | ||
[nodes.Pair, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.Literal, 1]], | ||
[nodes.Pair, | ||
[nodes.Literal, 'two'], | ||
[nodes.Literal, 2]]]]]); | ||
}); | ||
it('should parse variables', function() { | ||
isAST(parser.parse('hello {{ foo }}, how are you'), | ||
[nodes.Root, | ||
[nodes.Output, [nodes.TemplateData, 'hello ']], | ||
[nodes.Output, [nodes.Symbol, 'foo']], | ||
[nodes.Output, [nodes.TemplateData, ', how are you']]]); | ||
}); | ||
it('should parse variables', function() { | ||
isAST(parser.parse('hello {{ foo }}, how are you'), | ||
[nodes.Root, | ||
[nodes.Output, [nodes.TemplateData, 'hello ']], | ||
[nodes.Output, [nodes.Symbol, 'foo']], | ||
[nodes.Output, [nodes.TemplateData, ', how are you']]]); | ||
}); | ||
it('should parse blocks', function() { | ||
var n = parser.parse('want some {% if hungry %}pizza{% else %}' + | ||
'water{% endif %}?'); | ||
n.children[1].typename.should.equal('If'); | ||
it('should parse blocks', function() { | ||
var n = parser.parse('want some {% if hungry %}pizza{% else %}' + | ||
'water{% endif %}?'); | ||
expect(n.children[1].typename).to.be('If'); | ||
n = parser.parse('{% block foo %}stuff{% endblock %}'); | ||
n.children[0].typename.should.equal('Block'); | ||
n = parser.parse('{% block foo %}stuff{% endblock %}'); | ||
expect(n.children[0].typename).to.be('Block'); | ||
n = parser.parse('{% extends "test.html" %}stuff'); | ||
n.children[0].typename.should.equal('Extends'); | ||
n = parser.parse('{% extends "test.html" %}stuff'); | ||
expect(n.children[0].typename).to.be('Extends'); | ||
n = parser.parse('{% include "test.html" %}'); | ||
n.children[0].typename.should.equal('Include'); | ||
n = parser.parse('{% include "test.html" %}'); | ||
expect(n.children[0].typename).to.be('Include'); | ||
}); | ||
}); | ||
it('should parse filters', function() { | ||
isAST(parser.parse('{{ foo | bar }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'foo']]]]]); | ||
it('should parse filters', function() { | ||
isAST(parser.parse('{{ foo | bar }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'foo']]]]]); | ||
isAST(parser.parse('{{ foo | bar | baz }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'baz'], | ||
[nodes.NodeList, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'foo']]]]]]]); | ||
isAST(parser.parse('{{ foo | bar | baz }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'baz'], | ||
[nodes.NodeList, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'foo']]]]]]]); | ||
isAST(parser.parse('{{ foo | bar(3) }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.Literal, 3]]]]]); | ||
}); | ||
isAST(parser.parse('{{ foo | bar(3) }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.Filter, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.Literal, 3]]]]]); | ||
}); | ||
it('should parse macro definitions', function() { | ||
var ast = parser.parse('{% macro foo(bar, baz="foobar") %}' + | ||
'This is a macro' + | ||
'{% endmacro %}'); | ||
isAST(ast, | ||
[nodes.Root, | ||
[nodes.Macro, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.KeywordArgs, | ||
[nodes.Pair, | ||
[nodes.Symbol, 'baz'], [nodes.Literal, 'foobar']]]], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'This is a macro']]]]]); | ||
}); | ||
it('should parse macro definitions', function() { | ||
var ast = parser.parse('{% macro foo(bar, baz="foobar") %}' + | ||
'This is a macro' + | ||
'{% endmacro %}'); | ||
isAST(ast, | ||
[nodes.Root, | ||
[nodes.Macro, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'bar'], | ||
[nodes.KeywordArgs, | ||
[nodes.Pair, | ||
[nodes.Symbol, 'baz'], [nodes.Literal, 'foobar']]]], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'This is a macro']]]]]); | ||
}); | ||
it('should parse raw', function() { | ||
isAST(parser.parse('{% raw %}hello {{ {% %} }}{% endraw %}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hello {{ {% %} }}']]]); | ||
}); | ||
it('should parse raw', function() { | ||
isAST(parser.parse('{% raw %}hello {{ {% %} }}{% endraw %}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hello {{ {% %} }}']]]); | ||
}); | ||
it('should parse keyword and non-keyword arguments', function() { | ||
isAST(parser.parse('{{ foo("bar", falalalala, baz="foobar") }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.FunCall, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.NodeList, | ||
[nodes.Literal, 'bar'], | ||
[nodes.Symbol, 'falalalala'], | ||
[nodes.KeywordArgs, | ||
[nodes.Pair, | ||
[nodes.Symbol, 'baz'], | ||
[nodes.Literal, 'foobar']]]]]]]); | ||
}); | ||
it('should parse keyword and non-keyword arguments', function() { | ||
isAST(parser.parse('{{ foo("bar", falalalala, baz="foobar") }}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.FunCall, | ||
[nodes.Symbol, 'foo'], | ||
[nodes.NodeList, | ||
[nodes.Literal, 'bar'], | ||
[nodes.Symbol, 'falalalala'], | ||
[nodes.KeywordArgs, | ||
[nodes.Pair, | ||
[nodes.Symbol, 'baz'], | ||
[nodes.Literal, 'foobar']]]]]]]); | ||
}); | ||
it('should parse imports', function() { | ||
isAST(parser.parse('{% import "foo/bar.html" as baz %}'), | ||
[nodes.Root, | ||
[nodes.Import, | ||
[nodes.Literal, 'foo/bar.html'], | ||
[nodes.Symbol, 'baz']]]); | ||
it('should parse imports', function() { | ||
isAST(parser.parse('{% import "foo/bar.html" as baz %}'), | ||
[nodes.Root, | ||
[nodes.Import, | ||
[nodes.Literal, 'foo/bar.html'], | ||
[nodes.Symbol, 'baz']]]); | ||
isAST(parser.parse('{% from "foo/bar.html" import baz, ' + | ||
' foobar as foobarbaz %}'), | ||
[nodes.Root, | ||
[nodes.FromImport, | ||
[nodes.Literal, "foo/bar.html"], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'baz'], | ||
[nodes.Pair, | ||
[nodes.Symbol, 'foobar'], | ||
[nodes.Symbol, 'foobarbaz']]]]]); | ||
}); | ||
isAST(parser.parse('{% from "foo/bar.html" import baz, ' + | ||
' foobar as foobarbaz %}'), | ||
[nodes.Root, | ||
[nodes.FromImport, | ||
[nodes.Literal, "foo/bar.html"], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'baz'], | ||
[nodes.Pair, | ||
[nodes.Symbol, 'foobar'], | ||
[nodes.Symbol, 'foobarbaz']]]]]); | ||
}); | ||
it('should parse whitespace control', function() { | ||
// Every start/end tag with "-" should trim the whitespace | ||
// before or after it | ||
it('should parse whitespace control', function() { | ||
// Every start/end tag with "-" should trim the whitespace | ||
// before or after it | ||
isAST(parser.parse('{% if x %}\n hi \n{% endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, '\n hi \n']]]]]); | ||
isAST(parser.parse('{% if x %}\n hi \n{% endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, '\n hi \n']]]]]); | ||
isAST(parser.parse('{% if x -%}\n hi \n{% endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi \n']]]]]); | ||
isAST(parser.parse('{% if x -%}\n hi \n{% endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi \n']]]]]); | ||
isAST(parser.parse('{% if x %}\n hi \n{%- endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, '\n hi']]]]]); | ||
isAST(parser.parse('{% if x %}\n hi \n{%- endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, '\n hi']]]]]); | ||
isAST(parser.parse('{% if x -%}\n hi \n{%- endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi']]]]]); | ||
isAST(parser.parse('{% if x -%}\n hi \n{%- endif %}'), | ||
[nodes.Root, | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi']]]]]); | ||
isAST(parser.parse('poop \n{%- if x -%}\n hi \n{%- endif %}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'poop']], | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi']]]]]); | ||
isAST(parser.parse('poop \n{%- if x -%}\n hi \n{%- endif %}'), | ||
[nodes.Root, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'poop']], | ||
[nodes.If, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi']]]]]); | ||
// The from statement required a special case so make sure to | ||
// test it | ||
isAST(parser.parse('{% from x import y %}\n hi \n'), | ||
[nodes.Root, | ||
[nodes.FromImport, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'y']]], | ||
[nodes.Output, | ||
[nodes.TemplateData, '\n hi \n']]]); | ||
// The from statement required a special case so make sure to | ||
// test it | ||
isAST(parser.parse('{% from x import y %}\n hi \n'), | ||
[nodes.Root, | ||
[nodes.FromImport, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'y']]], | ||
[nodes.Output, | ||
[nodes.TemplateData, '\n hi \n']]]); | ||
isAST(parser.parse('{% from x import y -%}\n hi \n'), | ||
[nodes.Root, | ||
[nodes.FromImport, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'y']]], | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi \n']]]); | ||
}); | ||
isAST(parser.parse('{% from x import y -%}\n hi \n'), | ||
[nodes.Root, | ||
[nodes.FromImport, | ||
[nodes.Symbol, 'x'], | ||
[nodes.NodeList, | ||
[nodes.Symbol, 'y']]], | ||
[nodes.Output, | ||
[nodes.TemplateData, 'hi \n']]]); | ||
}); | ||
it('should throw errors', function() { | ||
expect(function() { | ||
parser.parse('hello {{ foo'); | ||
}).to.throwException(/expected variable end/); | ||
it('should throw errors', function() { | ||
(function() { | ||
parser.parse('hello {{ foo'); | ||
}).should.throw(/expected variable end/); | ||
expect(function() { | ||
parser.parse('hello {% if'); | ||
}).to.throwException(/expected expression/); | ||
(function() { | ||
parser.parse('hello {% if'); | ||
}).should.throw(/expected expression/); | ||
expect(function() { | ||
parser.parse('hello {% if sdf zxc'); | ||
}).to.throwException(/expected block end/); | ||
(function() { | ||
parser.parse('hello {% if sdf zxc'); | ||
}).should.throw(/expected block end/); | ||
expect(function() { | ||
parser.parse('hello {% if sdf %} data'); | ||
}).to.throwException(/expected endif, else, or endif/); | ||
(function() { | ||
parser.parse('hello {% if sdf %} data'); | ||
}).should.throw(/expected endif, else, or endif/); | ||
expect(function() { | ||
parser.parse('hello {% block sdf %} data'); | ||
}).to.throwException(/expected endblock/); | ||
(function() { | ||
parser.parse('hello {% block sdf %} data'); | ||
}).should.throw(/expected endblock/); | ||
expect(function() { | ||
parser.parse('hello {% bar %} dsfsdf'); | ||
}).to.throwException(/unknown block tag/); | ||
(function() { | ||
parser.parse('hello {% bar %} dsfsdf'); | ||
}).should.throw(/unknown block tag/); | ||
expect(function() { | ||
parser.parse('{{ foo(bar baz) }}'); | ||
}).to.throwException(/expected comma after expression/); | ||
(function() { | ||
parser.parse('{{ foo(bar baz) }}'); | ||
}).should.throw(/expected comma after expression/); | ||
expect(function() { | ||
parser.parse('{% import "foo" %}'); | ||
}).to.throwException(/expected "as" keyword/); | ||
(function() { | ||
parser.parse('{% import "foo" %}'); | ||
}).should.throw(/expected "as" keyword/); | ||
expect(function() { | ||
parser.parse('{% from "foo" %}'); | ||
}).to.throwException(/expected import/); | ||
(function() { | ||
parser.parse('{% from "foo" %}'); | ||
}).should.throw(/expected import/); | ||
expect(function() { | ||
parser.parse('{% from "foo" import bar baz %}'); | ||
}).to.throwException(/expected comma/); | ||
(function() { | ||
parser.parse('{% from "foo" import bar baz %}'); | ||
}).should.throw(/expected comma/); | ||
expect(function() { | ||
parser.parse('{% from "foo" import _bar %}'); | ||
}).to.throwException(/names starting with an underscore cannot be imported/); | ||
}); | ||
it('should parse custom tags', function() { | ||
(function() { | ||
parser.parse('{% from "foo" import _bar %}'); | ||
}).should.throw(/names starting with an underscore cannot be imported/); | ||
function testtagExtension() { | ||
this.tags = ['testtag']; | ||
/* normally this is automatically done by Environment */ | ||
this._name = 'testtagExtension'; | ||
this.parse = function(parser, nodes) { | ||
var begun = parser.peekToken(); | ||
parser.advanceAfterBlockEnd(); | ||
return new nodes.CallExtension(this, 'foo'); | ||
}; | ||
} | ||
function testblocktagExtension() { | ||
this.tags = ['testblocktag']; | ||
this._name = 'testblocktagExtension'; | ||
this.parse = function(parser, nodes) { | ||
var begun = parser.peekToken(); | ||
parser.advanceAfterBlockEnd(); | ||
var content = parser.parseUntilBlocks('endtestblocktag'); | ||
var tag = new nodes.CallExtension(this, 'bar', null, [1, content]); | ||
parser.advanceAfterBlockEnd(); | ||
return tag; | ||
}; | ||
} | ||
function testargsExtension() { | ||
this.tags = ['testargs']; | ||
this._name = 'testargsExtension'; | ||
this.parse = function(parser, nodes, tokens) { | ||
var begun = parser.peekToken(); | ||
var args = null; | ||
// Skip the name | ||
parser.nextToken(); | ||
args = parser.parseSignature(true); | ||
parser.advanceAfterBlockEnd(begun.value); | ||
return new nodes.CallExtension(this, 'biz', args); | ||
}; | ||
} | ||
var extensions = [new testtagExtension(), | ||
new testblocktagExtension(), | ||
new testargsExtension()]; | ||
isAST(parser.parse('{% testtag %}', extensions), | ||
[nodes.Root, | ||
[nodes.CallExtension, extensions[0], 'foo', undefined, undefined]]); | ||
isAST(parser.parse('{% testblocktag %}sdfd{% endtestblocktag %}', | ||
extensions), | ||
[nodes.Root, | ||
[nodes.CallExtension, extensions[1], 'bar', null, | ||
[1, [nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.TemplateData, "sdfd"]]]]]]); | ||
isAST(parser.parse('{% testblocktag %}{{ 123 }}{% endtestblocktag %}', | ||
extensions), | ||
[nodes.Root, | ||
[nodes.CallExtension, extensions[1], 'bar', null, | ||
[1, [nodes.NodeList, | ||
[nodes.Output, | ||
[nodes.Literal, 123]]]]]]); | ||
isAST(parser.parse('{% testargs(123, "abc", foo="bar") %}', extensions), | ||
[nodes.Root, | ||
[nodes.CallExtension, extensions[2], 'biz', | ||
// The only arg is the list of run-time arguments | ||
// coming from the template | ||
[nodes.NodeList, | ||
[nodes.Literal, 123], | ||
[nodes.Literal, "abc"], | ||
[nodes.KeywordArgs, | ||
[nodes.Pair, | ||
[nodes.Symbol, "foo"], | ||
[nodes.Literal, "bar"]]]]]]); | ||
isAST(parser.parse('{% testargs %}', extensions), | ||
[nodes.Root, | ||
[nodes.CallExtension, extensions[2], 'biz', null]]); | ||
}); | ||
}); | ||
}); | ||
})(); |
@@ -0,14 +1,44 @@ | ||
(function() { | ||
var Environment, Template, loader, templatesPath; | ||
var env = require('../src/environment'); | ||
var loaders = require('../src/node-loaders'); | ||
if(typeof require != 'undefined') { | ||
Environment = require('../src/environment').Environment; | ||
Template = require('../src/environment').Template; | ||
loader = require('../src/node-loaders').FileSystemLoader; | ||
templatesPath = 'tests/templates'; | ||
} | ||
else { | ||
Environment = nunjucks.Environment; | ||
Template = nunjucks.Template; | ||
loader = nunjucks.HttpLoader; | ||
templatesPath = '../templates'; | ||
} | ||
function render(str, ctx) { | ||
var e = new env.Environment(new loaders.FileSystemLoader('tests/templates'), null, true); | ||
ctx = ctx || {}; | ||
var t = new env.Template(str, e); | ||
return t.render(ctx); | ||
} | ||
function render(str, ctx, opts) { | ||
opts = opts || { dev: true }; | ||
var e = new Environment(new loader(templatesPath), opts); | ||
module.exports = { | ||
render: render | ||
}; | ||
if(opts.filters) { | ||
for(var name in opts.filters) { | ||
e.addFilter(name, opts.filters[name]); | ||
} | ||
} | ||
if(opts.extensions) { | ||
for(var name in opts.extensions) { | ||
e.addExtension(name, opts.extensions[name]); | ||
} | ||
} | ||
ctx = ctx || {}; | ||
var t = new Template(str, e); | ||
return t.render(ctx); | ||
} | ||
if(typeof module != 'undefined') { | ||
module.exports.render = render; | ||
} | ||
else { | ||
window.render = render; | ||
} | ||
})(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
714609
59
20261
37
3
3
12
4