diff --git a/mustache.js b/mustache.js index 784fbab..4441d73 100644 --- a/mustache.js +++ b/mustache.js @@ -9,8 +9,24 @@ var Mustache = function() { this.message = message; } - var Renderer = function(send_func) { - this.send_func = send_func; + var Renderer = function(send_func, mode) { + this.user_send_func = send_func; + if (mode==='interpreter' || !mode) { + this.commandSet = this.interpreter; + + this.send_func = function(text) { + this.user_send_func(text); + } + } else if (mode==='compiler') { + this.commandSet = this.compiler; + + this.cached_output = []; + this.send_func = function(text) { + this.cached_output.push(text); + } + } else { + throw new ParserException('Unsupported mode.'); + } this.pragmas = {}; }; @@ -119,6 +135,8 @@ var Mustache = function() { // make sure the parser finished at an appropriate terminal state if (state!=='text') { this.stateMachine['endOfDoc'].call(this, parserContext, contextStack); + } else { + this.commandSet.text.call(this); } }, @@ -141,6 +159,8 @@ var Mustache = function() { text: function(parserContext, contextStack) { switch (parserContext.tokens[parserContext.index]) { case parserContext.openTag: + this.commandSet.text.call(this); + return 'openMustache'; default: this.send_func(parserContext.tokens[parserContext.index]); @@ -367,13 +387,13 @@ var Mustache = function() { parserContext.stack.push({sectionType:'invertedSection', key:key, content:[], depth:1}); return 'endSectionScan'; case 'variable': - this.render_variable(key, contextStack); + this.commandSet.variable.call(this, key, contextStack); return 'text'; case 'unescapedVariable': - this.render_unescaped_variable(key, contextStack); + this.commandSet.unescaped_variable.call(this, key, contextStack); return 'text'; case 'partial': - this.render_partial(key, + this.commandSet.partial.call(this, key, contextStack, parserContext.partials, parserContext.openTag, @@ -384,7 +404,7 @@ var Mustache = function() { var section = parserContext.stack.pop(); if (--section.depth === 0) { if (section.key === key) { - this.render_section(section.sectionType, + this.commandSet.section.call(this, section.sectionType, section.content.join(''), key, contextStack, @@ -466,109 +486,261 @@ var Mustache = function() { return Object.prototype.toString.call(a) === '[object Array]'; }, - render_variable: function(key, contextStack) { - function escapeHTML(str) { - return ('' + str).replace(/&/g,'&') - .replace(//g,'>'); - } + interpreter: { + text: function() { + // in this implementation, rendering text is meaningless + // since the send_func method simply forwards to user_send_func + }, + variable: function(key, contextStack) { + function escapeHTML(str) { + return ('' + str).replace(/&/g,'&') + .replace(//g,'>'); + } - var result = this.find_in_stack(key, contextStack); - if (result!==undefined) { - this.send_func(escapeHTML(result)); - } - }, - render_unescaped_variable: function(key, contextStack) { - var result = this.find_in_stack(key, contextStack); - if (result!==undefined) { - this.send_func(result); - } - }, - render_partial: function(key, contextStack, partials, openTag, closeTag) { - if (!partials || partials[key] === undefined) { - throw new ParserException('Unknown partial \'' + key + '\''); - } - - var res = this.find_in_stack(key, contextStack); - if (this.is_object(res)) { - contextStack.push(res); - } - - var tokens = this.tokenize(partials[key], openTag, closeTag); + var result = this.find_in_stack(key, contextStack); + if (result!==undefined) { + this.user_send_func(escapeHTML(result)); + } + }, + unescaped_variable: function(key, contextStack) { + var result = this.find_in_stack(key, contextStack); + if (result!==undefined) { + this.user_send_func(result); + } + }, + partial: function(key, contextStack, partials, openTag, closeTag) { + if (!partials || partials[key] === undefined) { + throw new ParserException('Unknown partial \'' + key + '\''); + } + + var res = this.find_in_stack(key, contextStack); + if (this.is_object(res)) { + contextStack.push(res); + } + + var tokens = this.tokenize(partials[key], openTag, closeTag); - this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); + this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); + + if (this.is_object(res)) { + contextStack.pop(); + } + }, + section: function(sectionType, mustacheFragment, key, contextStack, partials, openTag, closeTag) { + // by @langalex, support for arrays of strings + var that = this; + function create_context(_context) { + if(that.is_object(_context)) { + return _context; + } else { + var iterator = '.'; + + if(that.pragmas["IMPLICIT-ITERATOR"] && + that.pragmas["IMPLICIT-ITERATOR"].iterator) { + iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator; + } + var ctx = {}; + ctx[iterator] = _context; + return ctx; + } + } - if (this.is_object(res)) { - contextStack.pop(); - } - }, - render_section: function(sectionType, mustacheFragment, key, contextStack, partials, openTag, closeTag) { - // by @langalex, support for arrays of strings - var that = this; - function create_context(_context) { - if(that.is_object(_context)) { - return _context; - } else { - var iterator = '.'; - - if(that.pragmas["IMPLICIT-ITERATOR"] && - that.pragmas["IMPLICIT-ITERATOR"].iterator) { - iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator; + var value = this.find_in_stack(key, contextStack); + + var tokens; + if (sectionType==='invertedSection') { + if (!value || this.is_array(value) && value.length === 0) { + // false or empty list, render it + tokens = this.tokenize(mustacheFragment, openTag, closeTag); + + this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); } - var ctx = {}; - ctx[iterator] = _context; - return ctx; + } else if (sectionType==='section') { + if (this.is_array(value)) { // Enumerable, Let's loop! + tokens = this.tokenize(mustacheFragment, openTag, closeTag); + + for (var i=0, n=value.length; i/g,'>'); + } - var tokens; - if (sectionType==='invertedSection') { - if (!value || this.is_array(value) && value.length === 0) { - // false or empty list, render it - tokens = this.tokenize(mustacheFragment, openTag, closeTag); - - this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); + var that = this; + this.user_send_func(function(contextStack, send_func) { + var result = that.find_in_stack(key, contextStack); + if (result!==undefined) { + send_func(escapeHTML(result)); + } + }); + }, + unescaped_variable: function(key/*, contextStack*/) { + var that = this; + this.user_send_func(function(contextStack, send_func) { + var result = that.find_in_stack(key, contextStack); + if (result!==undefined) { + send_func(result); + } + }); + }, + partial: function(key, reserved/*contextStack*/, partials, openTag, closeTag) { + if (!partials || partials[key] === undefined) { + throw new ParserException('Unknown partial \'' + key + '\''); } - } else if (sectionType==='section') { - if (this.is_array(value)) { // Enumerable, Let's loop! - tokens = this.tokenize(mustacheFragment, openTag, closeTag); + + var old_user_send_func = this.user_send_func; + var commands = []; + this.user_send_func = function(command) { commands.push(command); }; + + var tokens = this.tokenize(partials[key], openTag, closeTag); + this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved); + + this.user_send_func = old_user_send_func; + + var that = this; + this.user_send_func(function(contextStack, send_func) { + var res = that.find_in_stack(key, contextStack); + if (that.is_object(res)) { + contextStack.push(res); + } + + for (var i=0,n=commands.length; ipartial}}.', {partial: 'i love {{sugar}}'}); + equals(template({sugar: 'chocolate'}), 'the grand poobah says: i love chocolate.'); + + template = Mustache.compile('the grand poobah says: {{#hos}}i love chocolate{{/hos}}.', {}); + equals(template({hos:function() { return function(text, renderer) { return '' + text + ''; } }}), 'the grand poobah says: i love chocolate.'); +}); + test("Basic Variables", function() { expect(3);