diff --git a/mustache.js b/mustache.js index ecd2b862a..6b4565ce8 100644 --- a/mustache.js +++ b/mustache.js @@ -68,7 +68,7 @@ var spaceRe = /\s+/; var equalsRe = /\s*=/; var curlyRe = /\s*\}/; - var tagRe = /#|\^|\/|>|\{|&|=|!/; + var tagRe = /#|\^|\/|>|\{|&|=|!|\$|') value = this.renderPartial(token, context, partials, originalTemplate); + else if (symbol === '<') value = this.renderBlock(token, context, partials, originalTemplate); + else if (symbol === '$') value = this.renderBlockVariable(token, context, partials, originalTemplate); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context); else if (symbol === 'text') value = this.rawValue(token); @@ -540,6 +581,34 @@ return this.renderTokens(this.parse(value), context, partials, value); }; + Writer.prototype.renderBlock = function renderBlock (token, context, partials, originalTemplate) { + if (!partials) return; + + var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; + if (value != null) + // Ignore any wrongly set block vars before we started. + context.clearBlockVars(); + // We are only rendering to record the default block variables. + this.renderTokens(token[4], context, partials, originalTemplate); + // Now we render and return the result. + var result = this.renderTokens(this.parse(value), context, partials, value); + // Don't leak the block variables outside this include. + context.clearBlockVars(); + return result; + }; + + Writer.prototype.renderBlockVariable = function renderBlockVariable (token, context, partials, originalTemplate) { + var value = token[1]; + + var exists = context.getBlockVar(value); + if (!exists) { + context.setBlockVar(value, originalTemplate.slice(token[3], token[5])); + return this.renderTokens(token[4], context, partials, originalTemplate); + } else { + return this.renderTokens(this.parse(exists), context, partials, exists); + } + }; + Writer.prototype.unescapedValue = function unescapedValue (token, context) { var value = context.lookup(token[1]); if (value != null) diff --git a/mustache.min.js b/mustache.min.js index 29cdc1d57..f6862e15f 100644 --- a/mustache.min.js +++ b/mustache.min.js @@ -1 +1 @@ -(function defineMustache(global,factory){if(typeof exports==="object"&&exports){factory(exports)}else if(typeof define==="function"&&define.amd){define(["exports"],factory)}else{global.Mustache={};factory(Mustache)}})(this,function mustacheFactory(mustache){var objectToString=Object.prototype.toString;var isArray=Array.isArray||function isArrayPolyfill(object){return objectToString.call(object)==="[object Array]"};function isFunction(object){return typeof object==="function"}function escapeRegExp(string){return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function hasProperty(obj,propName){return obj!=null&&typeof obj==="object"&&propName in obj}var regExpTest=RegExp.prototype.test;function testRegExp(re,string){return regExpTest.call(re,string)}var nonSpaceRe=/\S/;function isWhitespace(string){return!testRegExp(nonSpaceRe,string)}var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};function escapeHtml(string){return String(string).replace(/[&<>"'\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function parseTemplate(template,tags){if(!template)return[];var sections=[];var tokens=[];var spaces=[];var hasTag=false;var nonSpace=false;function stripSpace(){if(hasTag&&!nonSpace){while(spaces.length)delete tokens[spaces.pop()]}else{spaces=[]}hasTag=false;nonSpace=false}var openingTagRe,closingTagRe,closingCurlyRe;function compileTags(tagsToCompile){if(typeof tagsToCompile==="string")tagsToCompile=tagsToCompile.split(spaceRe,2);if(!isArray(tagsToCompile)||tagsToCompile.length!==2)throw new Error("Invalid tags: "+tagsToCompile);openingTagRe=new RegExp(escapeRegExp(tagsToCompile[0])+"\\s*");closingTagRe=new RegExp("\\s*"+escapeRegExp(tagsToCompile[1]));closingCurlyRe=new RegExp("\\s*"+escapeRegExp("}"+tagsToCompile[1]))}compileTags(tags||mustache.tags);var scanner=new Scanner(template);var start,type,value,chr,token,openSection;while(!scanner.eos()){start=scanner.pos;value=scanner.scanUntil(openingTagRe);if(value){for(var i=0,valueLength=value.length;i0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){value=context.view;names=name.split(".");index=0;while(value!=null&&index")value=this.renderPartial(token,context,partials,originalTemplate);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j":">",'"':""","'":"'","/":"/"};function escapeHtml(string){return String(string).replace(/[&<>"'\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!|\$|0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.blocks={};this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.setBlockVar=function set(name,value){var blocks=this.blocks;blocks[name]=value;return value};Context.prototype.clearBlockVars=function clearBlockVars(){this.blocks={}};Context.prototype.getBlockVar=function getBlockVar(name){var blocks=this.blocks;var value;if(blocks.hasOwnProperty(name)){value=blocks[name]}else{if(this.parent){value=this.parent.getBlockVar(name)}}return value};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){value=context.view;names=name.split(".");index=0;while(value!=null&&index")value=this.renderPartial(token,context,partials,originalTemplate);else if(symbol==="<")value=this.renderBlock(token,context,partials,originalTemplate);else if(symbol==="$")value=this.renderBlockVariable(token,context,partials,originalTemplate);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j diff --git a/test/_files/inheritance_empty_list_section.txt b/test/_files/inheritance_empty_list_section.txt new file mode 100644 index 000000000..0d6086115 --- /dev/null +++ b/test/_files/inheritance_empty_list_section.txt @@ -0,0 +1 @@ + diff --git a/test/_files/inheritance_false_section.js b/test/_files/inheritance_false_section.js new file mode 100644 index 000000000..31b244639 --- /dev/null +++ b/test/_files/inheritance_false_section.js @@ -0,0 +1 @@ +({ "inheritable": false }) diff --git a/test/_files/inheritance_false_section.mustache b/test/_files/inheritance_false_section.mustache new file mode 100644 index 000000000..2847508ef --- /dev/null +++ b/test/_files/inheritance_false_section.mustache @@ -0,0 +1 @@ +<{{$inheritable}}rendered{{/inheritable}}> diff --git a/test/_files/inheritance_false_section.txt b/test/_files/inheritance_false_section.txt new file mode 100644 index 000000000..0d6086115 --- /dev/null +++ b/test/_files/inheritance_false_section.txt @@ -0,0 +1 @@ + diff --git a/test/_files/inheritance_missing_section.js b/test/_files/inheritance_missing_section.js new file mode 100644 index 000000000..ce598d2d3 --- /dev/null +++ b/test/_files/inheritance_missing_section.js @@ -0,0 +1 @@ +({ }) diff --git a/test/_files/inheritance_missing_section.mustache b/test/_files/inheritance_missing_section.mustache new file mode 100644 index 000000000..dcf8f5633 --- /dev/null +++ b/test/_files/inheritance_missing_section.mustache @@ -0,0 +1 @@ +{{$inheritable}}rendered{{/inheritable}} diff --git a/test/_files/inheritance_missing_section.txt b/test/_files/inheritance_missing_section.txt new file mode 100644 index 000000000..e69bcfc51 --- /dev/null +++ b/test/_files/inheritance_missing_section.txt @@ -0,0 +1 @@ +rendered diff --git a/test/_files/inheritance_no_new_context.js b/test/_files/inheritance_no_new_context.js new file mode 100644 index 000000000..1125694dc --- /dev/null +++ b/test/_files/inheritance_no_new_context.js @@ -0,0 +1 @@ +({ "subject": "rendered", "inheritable": { "subject": "ignored" } }) diff --git a/test/_files/inheritance_no_new_context.mustache b/test/_files/inheritance_no_new_context.mustache new file mode 100644 index 000000000..297983294 --- /dev/null +++ b/test/_files/inheritance_no_new_context.mustache @@ -0,0 +1 @@ +<{{$inheritable}}{{subject}}{{/inheritable}}> diff --git a/test/_files/inheritance_no_new_context.txt b/test/_files/inheritance_no_new_context.txt new file mode 100644 index 000000000..0d6086115 --- /dev/null +++ b/test/_files/inheritance_no_new_context.txt @@ -0,0 +1 @@ + diff --git a/test/_files/inheritance_only_once.js b/test/_files/inheritance_only_once.js new file mode 100644 index 000000000..90a6ac86f --- /dev/null +++ b/test/_files/inheritance_only_once.js @@ -0,0 +1 @@ +({ "inheritable": [0,1,2,3] }) diff --git a/test/_files/inheritance_only_once.mustache b/test/_files/inheritance_only_once.mustache new file mode 100644 index 000000000..2847508ef --- /dev/null +++ b/test/_files/inheritance_only_once.mustache @@ -0,0 +1 @@ +<{{$inheritable}}rendered{{/inheritable}}> diff --git a/test/_files/inheritance_only_once.txt b/test/_files/inheritance_only_once.txt new file mode 100644 index 000000000..0d6086115 --- /dev/null +++ b/test/_files/inheritance_only_once.txt @@ -0,0 +1 @@ + diff --git a/test/_files/inheritance_partials_can_embed.js b/test/_files/inheritance_partials_can_embed.js new file mode 100644 index 000000000..ce598d2d3 --- /dev/null +++ b/test/_files/inheritance_partials_can_embed.js @@ -0,0 +1 @@ +({ }) diff --git a/test/_files/inheritance_partials_can_embed.mustache b/test/_files/inheritance_partials_can_embed.mustache new file mode 100644 index 000000000..fcc6bfa35 --- /dev/null +++ b/test/_files/inheritance_partials_can_embed.mustache @@ -0,0 +1 @@ +before {{ diff --git a/test/_files/inheritance_partials_can_override_recursive.partial b/test/_files/inheritance_partials_can_override_recursive.partial new file mode 100644 index 000000000..40f826154 --- /dev/null +++ b/test/_files/inheritance_partials_can_override_recursive.partial @@ -0,0 +1 @@ +{{content}}<{{#nodes}}{{>partial}}{{/nodes}}> \ No newline at end of file diff --git a/test/_files/inheritance_partials_can_override_recursive.txt b/test/_files/inheritance_partials_can_override_recursive.txt new file mode 100644 index 000000000..d84e94fe5 --- /dev/null +++ b/test/_files/inheritance_partials_can_override_recursive.txt @@ -0,0 +1 @@ +>> diff --git a/test/_files/inheritance_partials_can_override_recursive_section.js b/test/_files/inheritance_partials_can_override_recursive_section.js new file mode 100644 index 000000000..5e771c3d0 --- /dev/null +++ b/test/_files/inheritance_partials_can_override_recursive_section.js @@ -0,0 +1 @@ +({ "content": "X", "nodes": [ { "content": "Y", "nodes": [] } ] }) diff --git a/test/_files/inheritance_partials_can_override_recursive_section.mustache b/test/_files/inheritance_partials_can_override_recursive_section.mustache new file mode 100644 index 000000000..f83e0eef1 --- /dev/null +++ b/test/_files/inheritance_partials_can_override_recursive_section.mustache @@ -0,0 +1 @@ +<{{partial}}{{/value}}{{/partial}}> diff --git a/test/_files/inheritance_partials_can_override_recursive_section.partial b/test/_files/inheritance_partials_can_override_recursive_section.partial new file mode 100644 index 000000000..c741b3675 --- /dev/null +++ b/test/_files/inheritance_partials_can_override_recursive_section.partial @@ -0,0 +1 @@ +{{content}}<{{#nodes}}{{$value}}overriden{{/value}}{{/nodes}}> \ No newline at end of file diff --git a/test/_files/inheritance_partials_can_override_recursive_section.txt b/test/_files/inheritance_partials_can_override_recursive_section.txt new file mode 100644 index 000000000..d84e94fe5 --- /dev/null +++ b/test/_files/inheritance_partials_can_override_recursive_section.txt @@ -0,0 +1 @@ +>> diff --git a/test/_files/inheritance_partials_cannot_overide.js b/test/_files/inheritance_partials_cannot_overide.js new file mode 100644 index 000000000..bc31ba589 --- /dev/null +++ b/test/_files/inheritance_partials_cannot_overide.js @@ -0,0 +1 @@ +({ "section": true }) diff --git a/test/_files/inheritance_partials_cannot_overide.mustache b/test/_files/inheritance_partials_cannot_overide.mustache new file mode 100644 index 000000000..69dd160a1 --- /dev/null +++ b/test/_files/inheritance_partials_cannot_overide.mustache @@ -0,0 +1 @@ +{{