Explorar el Código

Submit template compiler.

tags/0.5.0-vsc
unknown hace 16 años
padre
commit
97736b7a49
Se han modificado 2 ficheros con 301 adiciones y 95 borrados
  1. +288
    -95
      mustache.js
  2. +13
    -0
      test/unit.js

+ 288
- 95
mustache.js Ver fichero

@@ -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,'&lt;')
.replace(/>/g,'&gt;');
}
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,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;');
}

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<n; ++i) {
contextStack.push(create_context(value[i]));
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
}
} else if (this.is_object(value)) { // Object, Use it as subcontext!
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
contextStack.push(value);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
} else if (this.is_function(value)) {
// higher order section
var that = this;
var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
var tempStream = [];
var old_send_func = that.send_func;
that.send_func = function(text) { tempStream.push(text); };
tokens = that.tokenize(resultFragment, openTag, closeTag);
that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack);
that.send_func = old_send_func;
return tempStream.join('');
});
this.user_send_func(result);
}
} else {
throw new ParserException('Unknown section type ' + sectionType);
}
}
},
var value = this.find_in_stack(key, contextStack);
compiler: {
text: function() {
var outputText = this.cached_output.join('');
this.cached_output = [];
this.user_send_func(function(contextStack, send_func) {
send_func(outputText);
});
},
variable: function(key/*, contextStack*/) {
function escapeHTML(str) {
return ('' + str).replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;');
}

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; i<n; ++i) {
commands[i](contextStack, send_func);
}
for (var i=0, n=value.length; i<n; ++i) {
contextStack.push(create_context(value[i]));
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
if (that.is_object(res)) {
contextStack.pop();
}
} else if (this.is_object(value)) { // Object, Use it as subcontext!
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
contextStack.push(value);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
} else if (this.is_function(value)) {
// higher order section
var that = this;
var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
var tempStream = [];
var old_send_func = that.send_func;
that.send_func = function(text) { tempStream.push(text); };
});
},
section: function(sectionType, mustacheFragment, key, reserved/*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 = '.';
tokens = that.tokenize(resultFragment, openTag, closeTag);
that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack);
if(that.pragmas["IMPLICIT-ITERATOR"] &&
that.pragmas["IMPLICIT-ITERATOR"].iterator) {
iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
}
}
var old_user_send_func = this.user_send_func;
var commands = [];
this.user_send_func = function(command) { commands.push(command); };
var tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
this.user_send_func = old_user_send_func;
var section_command = function(contextStack, send_func) {
for (var i=0, n=commands.length; i<n; ++i) {
commands[i](contextStack, send_func);
}
};
var that = this;
if (sectionType==='invertedSection') {
this.user_send_func(function(contextStack, send_func) {
var value = that.find_in_stack(key, contextStack);
that.send_func = old_send_func;
if (!value || that.is_array(value) && value.length === 0) {
// false or empty list, render it
section_command(contextStack, send_func);
}
});
} else if (sectionType==='section') {
this.user_send_func(function(contextStack, send_func) {
var value = that.find_in_stack(key, contextStack);
return tempStream.join('');
if (that.is_array(value)) { // Enumerable, Let's loop!
for (var i=0, n=value.length; i<n; ++i) {
contextStack.push(create_context(value[i]));
section_command(contextStack, send_func);
contextStack.pop();
}
} else if (that.is_object(value)) { // Object, Use it as subcontext!
contextStack.push(value);
section_command(contextStack, send_func);
contextStack.pop();
} else if (that.is_function(value)) {
// higher order section
// note that HOS triggers a full interpreter call on the result fragment
// this is slow in comparison to a compiled call
var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
var o = [];
var s = function(output) { o.push(output); };
var hos_renderer = new Renderer(s, 'interpreter');

resultFragment = hos_renderer.parse_pragmas(resultFragment, openTag, closeTag);
var tokens = hos_renderer.tokenize(resultFragment, openTag, closeTag);
hos_renderer.parse(hos_renderer.createParserContext(tokens, partials, openTag, closeTag), contextStack);

return o.join('');
});
send_func(result);
}
});
this.send_func(result);
} else if (value) { // boolean section
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
}
} else {
throw new ParserException('Unknown section type ' + sectionType);
} else {
throw new ParserException('Unknown section type ' + sectionType);
}
}
}
}
@@ -581,15 +753,36 @@ var Mustache = function() {
Turns a template and view into HTML
*/
to_html: function(template, view, partials, send_func) {
var o = [];
var o = send_func ? undefined : [];
var s = send_func || function(output) { o.push(output); };
var renderer = new Renderer(s);
var renderer = new Renderer(s, 'interpreter');
renderer.render(template, view, partials);
if (!send_func) {
return o.join('');
}
},
compile: function(template, partials) {
var commands = [];
var s = function(command) { commands.push(command); };
var renderer = new Renderer(s, 'compiler');
renderer.render(template, {}, partials);

return function(view, send_func) {
var o = send_func ? undefined : [];
var s = send_func || function(output) { o.push(output); };
for (var i=0,n=commands.length; i<n; ++i) {
commands[i]([view], s);
}
if (!send_func) {
return o.join('');
}
};
}
});
}();

+ 13
- 0
test/unit.js Ver fichero

@@ -53,6 +53,19 @@ test("Parser", function() {
});

test("Compiler", function() {
expect(3);
var template = Mustache.compile('the grand poobah says: {{variable}}.', {});
equals(template({variable: 'hello'}), 'the grand poobah says: hello.');

template = Mustache.compile('the grand poobah says: {{>partial}}.', {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 '<b>' + text + '</b>'; } }}), 'the grand poobah says: <b>i love chocolate</b>.');
});

test("Basic Variables", function() {
expect(3);


Cargando…
Cancelar
Guardar