diff --git a/Rakefile b/Rakefile
index 37cb9b3..7240f8b 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,20 +1,18 @@
require 'rake'
require 'rake/clean'
-task :default => :spec
+task :default => 'test:integration'
-desc "Run all specs"
-task :spec do
- require 'rspec/core/rake_task'
- RSpec::Core::RakeTask.new(:spec) do |t|
- #t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
- t.pattern = 'spec/*_spec.rb'
+namespace :test do
+ desc "Run all integration tests"
+ task :integration do
+ require File.expand_path('../test/integration', __FILE__)
end
-end
-desc "Run all unit tests"
-task :test do
- exec "vows test/*_test.js"
+ desc "Run all unit tests"
+ task :unit do
+ require File.expand_path('../test/unit', __FILE__)
+ end
end
# Creates a task that uses the various template wrappers to make a wrapped
diff --git a/mustache.js b/mustache.js
index 641cebd..7b34bbb 100644
--- a/mustache.js
+++ b/mustache.js
@@ -9,12 +9,18 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {};
exports.name = "mustache.js";
exports.version = "0.5.0-dev";
exports.tags = ["{{", "}}"];
+
exports.parse = parse;
+ exports.clearCache = clearCache;
exports.compile = compile;
+ exports.compilePartial = compilePartial;
exports.render = render;
- exports.clearCache = clearCache;
- // This is here for backwards compatibility with 0.4.x.
+ exports.Scanner = Scanner;
+ exports.Context = Context;
+ exports.Renderer = Renderer;
+
+ // // This is here for backwards compatibility with 0.4.x.
exports.to_html = function (template, view, partials, send) {
var result = render(template, view, partials);
@@ -25,63 +31,30 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {};
}
};
- var _toString = Object.prototype.toString;
- var _isArray = Array.isArray;
- var _forEach = Array.prototype.forEach;
- var _trim = String.prototype.trim;
-
- var isArray;
- if (_isArray) {
- isArray = _isArray;
- } else {
- isArray = function (obj) {
- return _toString.call(obj) === "[object Array]";
- };
- }
-
- var forEach;
- if (_forEach) {
- forEach = function (obj, callback, scope) {
- return _forEach.call(obj, callback, scope);
- };
- } else {
- forEach = function (obj, callback, scope) {
- for (var i = 0, len = obj.length; i < len; ++i) {
- callback.call(scope, obj[i], i, obj);
- }
- };
- }
-
- var spaceRe = /^\s*$/;
+ var whiteRe = /\s*/;
+ var spaceRe = /\s+/;
+ var nonSpaceRe = /\S/;
+ var eqRe = /\s*=/;
+ var curlyRe = /\s*\}/;
+ var tagRe = /#|\^|\/|>|\{|&|=|!/;
function isWhitespace(string) {
- return spaceRe.test(string);
+ return !nonSpaceRe.test(string);
}
- var trim;
- if (_trim) {
- trim = function (string) {
- return string == null ? "" : _trim.call(string);
- };
- } else {
- var trimLeft, trimRight;
+ var isArray = Array.isArray || function (obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ };
- if (isWhitespace("\xA0")) {
- trimLeft = /^\s+/;
- trimRight = /\s+$/;
- } else {
- // IE doesn't match non-breaking spaces with \s, thanks jQuery.
- trimLeft = /^[\s\xA0]+/;
- trimRight = /[\s\xA0]+$/;
- }
+ var quote = (typeof JSON !== "undefined" && JSON.stringify) || function (string) {
+ return '"' + String(string).replace(/(^|[^\\])"/g, '\\"') + '"';
+ };
- trim = function (string) {
- return string == null ? "" :
- String(string).replace(trimLeft, "").replace(trimRight, "");
- };
+ function escapeRe(string) {
+ return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
- var escapeMap = {
+ var entityMap = {
"&": "&",
"<": "<",
">": ">",
@@ -89,448 +62,519 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {};
"'": '''
};
- function escapeHTML(string) {
+ function escapeHtml(string) {
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
- return escapeMap[s] || s;
+ return entityMap[s];
});
}
+ // Export these utility functions.
+ exports.isWhitespace = isWhitespace;
+ exports.isArray = isArray;
+ exports.quote = quote;
+ exports.escapeRe = escapeRe;
+ exports.escapeHtml = escapeHtml;
+
+ function Scanner(string) {
+ this.string = string;
+ this.tail = string;
+ this.pos = 0;
+ }
+
/**
- * Adds the `template`, `line`, and `file` properties to the given error
- * object and alters the message to provide more useful debugging information.
+ * Returns `true` if the tail is empty (end of string).
*/
- function debug(e, template, line, file) {
- file = file || "";
-
- var lines = template.split("\n"),
- start = Math.max(line - 3, 0),
- end = Math.min(lines.length, line + 3),
- context = lines.slice(start, end);
-
- var c;
- for (var i = 0, len = context.length; i < len; ++i) {
- c = i + start + 1;
- context[i] = (c === line ? " >> " : " ") + context[i];
- }
+ Scanner.prototype.eos = function () {
+ return this.tail === "";
+ };
- e.template = template;
- e.line = line;
- e.file = file;
- e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
+ /**
+ * Tries to match the given regular expression at the current position.
+ * Returns the matched text if it can match, `null` otherwise.
+ */
+ Scanner.prototype.scan = function (re) {
+ var match = this.tail.match(re);
- return e;
- }
+ if (match && match.index === 0) {
+ this.tail = this.tail.substring(match[0].length);
+ this.pos += match[0].length;
+ return match[0];
+ }
+
+ return null;
+ };
/**
- * Looks up the value of the given `name` in the given context `stack`.
+ * Skips all text until the given regular expression can be matched. Returns
+ * the skipped string, which is the entire tail of this scanner if no match
+ * can be made.
*/
- function lookup(name, stack, defaultValue) {
- if (name === ".") {
- return stack[stack.length - 1];
+ Scanner.prototype.scanUntil = function (re) {
+ var match, pos = this.tail.search(re);
+
+ switch (pos) {
+ case -1:
+ match = this.tail;
+ this.pos += this.tail.length;
+ this.tail = "";
+ break;
+ case 0:
+ match = null;
+ break;
+ default:
+ match = this.tail.substring(0, pos);
+ this.tail = this.tail.substring(pos);
+ this.pos += pos;
}
- var names = name.split(".");
- var lastIndex = names.length - 1;
- var target = names[lastIndex];
+ return match;
+ };
- var value, context, i = stack.length, j, localStack;
- while (i) {
- localStack = stack.slice(0);
- context = stack[--i];
+ function Context(view, parent) {
+ this.view = view;
+ this.parent = parent;
+ this.clearCache();
+ }
- j = 0;
- while (j < lastIndex) {
- context = context[names[j++]];
+ Context.make = function (view) {
+ return (view instanceof Context) ? view : new Context(view);
+ };
- if (context == null) {
- break;
- }
+ Context.prototype.clearCache = function () {
+ this._cache = {};
+ };
- localStack.push(context);
- }
+ Context.prototype.push = function (view) {
+ return new Context(view, this);
+ };
- if (context && typeof context === "object" && target in context) {
- value = context[target];
- break;
+ Context.prototype.lookup = function (name) {
+ var value = this._cache[name];
+
+ if (!value) {
+ if (name === ".") {
+ value = this.view;
+ } else {
+ var context = this;
+
+ while (context) {
+ if (name.indexOf(".") > 0) {
+ var names = name.split("."), i = 0;
+
+ value = context.view;
+
+ while (value && i < names.length) {
+ value = value[names[i++]];
+ }
+ } else {
+ value = context.view[name];
+ }
+
+ if (value != null) {
+ break;
+ }
+
+ context = context.parent;
+ }
}
- }
- // If the value is a function, call it in the current context.
- if (typeof value === "function") {
- value = value.call(localStack[localStack.length - 1]);
+ this._cache[name] = value;
}
- if (value == null) {
- return defaultValue;
+ if (typeof value === "function") {
+ value = value.call(this.view);
}
return value;
+ };
+
+ function Renderer() {
+ this.clearCache();
}
- function renderSection(name, stack, callback, inverted) {
- var buffer = "";
- var value = lookup(name, stack);
+ Renderer.prototype.clearCache = function () {
+ this._cache = {};
+ this._partialCache = {};
+ };
- if (inverted) {
- // From the spec: inverted sections may render text once based on the
- // inverse value of the key. That is, they will be rendered if the key
- // doesn't exist, is false, or is an empty list.
- if (value == null || value === false || (isArray(value) && value.length === 0)) {
- buffer += callback();
- }
- } else if (isArray(value)) {
- forEach(value, function (value) {
- stack.push(value);
- buffer += callback();
- stack.pop();
- });
- } else if (typeof value === "object") {
- stack.push(value);
- buffer += callback();
- stack.pop();
- } else if (typeof value === "function") {
- var scope = stack[stack.length - 1];
- var scopedRender = function (template) {
- return render(template, scope);
- };
- buffer += value.call(scope, callback(), scopedRender) || "";
- } else if (value) {
- buffer += callback();
+ Renderer.prototype.compile = function (tokens, tags) {
+ var fn = compileTokens(tokens),
+ self = this;
+
+ return function (view) {
+ return fn(Context.make(view), self);
+ };
+ };
+
+ Renderer.prototype.compilePartial = function (name, tokens, tags) {
+ this._partialCache[name] = this.compile(tokens, tags);
+ return this._partialCache[name];
+ };
+
+ Renderer.prototype.render = function (template, view) {
+ var fn = this._cache[template];
+
+ if (!fn) {
+ fn = this.compile(template);
+ this._cache[template] = fn;
}
- return buffer;
- }
+ return fn(view);
+ };
- /**
- * Parses the given `template` and returns the source of a function that,
- * with the proper arguments, will render the template. Recognized options
- * include the following:
- *
- * - file The name of the file the template comes from (displayed in
- * error messages)
- * - tags An array of open and close tags the `template` uses. Defaults
- * to the value of Mustache.tags
- * - debug Set `true` to log the body of the generated function to the
- * console
- * - space Set `true` to preserve whitespace from lines that otherwise
- * contain only a {{tag}}. Defaults to `false`
- */
- function parse(template, options) {
- options = options || {};
-
- var tags = options.tags || exports.tags,
- openTag = tags[0],
- closeTag = tags[tags.length - 1];
-
- var code = [
- 'var buffer = "";', // output buffer
- "\nvar line = 1;", // keep track of source line number
- "\ntry {",
- '\nbuffer += "'
- ];
-
- var spaces = [], // indices of whitespace in code on the current line
- hasTag = false, // is there a {{tag}} on the current line?
- nonSpace = false; // is there a non-space char on the current line?
-
- // Strips all space characters from the code array for the current line
- // if there was a {{tag}} on it and otherwise only spaces.
- var stripSpace = function () {
- if (hasTag && !nonSpace && !options.space) {
- while (spaces.length) {
- code.splice(spaces.pop(), 1);
+ Renderer.prototype._section = function (name, context, callback) {
+ var value = context.lookup(name);
+
+ switch (typeof value) {
+ case "object":
+ if (isArray(value)) {
+ var buffer = "";
+ for (var i = 0, len = value.length; i < len; ++i) {
+ buffer += callback(context.push(value[i]), this);
}
+ return buffer;
} else {
- spaces = [];
+ return callback(context.push(value), this);
+ }
+ break;
+ case "function":
+ var sectionText = callback(context, this), self = this;
+ function scopedRender(template) {
+ return self.render(template, context);
}
+ return value.call(context.view, sectionText, scopedRender) || "";
+ break;
+ default:
+ if (value) {
+ return callback(context, this);
+ }
+ }
- hasTag = false;
- nonSpace = false;
- };
+ return "";
+ };
- var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
+ Renderer.prototype._inverted = function (name, context, callback) {
+ var value = context.lookup(name);
- var setTags = function (source) {
- tags = trim(source).split(/\s+/);
- nextOpenTag = tags[0];
- nextCloseTag = tags[tags.length - 1];
- };
+ // From the spec: inverted sections may render text once based on the
+ // inverse value of the key. That is, they will be rendered if the key
+ // doesn't exist, is false, or is an empty list.
+ if (value == null || value === false || (isArray(value) && value.length === 0)) {
+ return callback(context, this);
+ }
- var includePartial = function (source) {
- code.push(
- '";',
- updateLine,
- '\nvar partial = partials["' + trim(source) + '"];',
- '\nif (partial) {',
- '\n buffer += render(partial,stack[stack.length - 1],partials);',
- '\n}',
- '\nbuffer += "'
- );
- };
+ return "";
+ };
- var openSection = function (source, inverted) {
- var name = trim(source);
+ Renderer.prototype._partial = function (name, context) {
+ var fn = this._partialCache[name];
- if (name === "") {
- throw debug(new Error("Section name may not be empty"), template, line, options.file);
- }
+ if (fn) {
+ return fn(context, this);
+ }
- sectionStack.push({name: name, inverted: inverted});
-
- code.push(
- '";',
- updateLine,
- '\nvar name = "' + name + '";',
- '\nvar callback = (function () {',
- '\n return function () {',
- '\n var buffer = "";',
- '\nbuffer += "'
- );
- };
+ return "";
+ };
- var openInvertedSection = function (source) {
- openSection(source, true);
- };
+ Renderer.prototype._name = function (name, context, escape) {
+ var value = context.lookup(name);
- var closeSection = function (source) {
- var name = trim(source);
- var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
+ if (typeof value === "function") {
+ value = value.call(context.view);
+ }
- if (!openName || name != openName) {
- throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
- }
+ var string = (value == null) ? "" : String(value);
- var section = sectionStack.pop();
+ if (escape) {
+ return escapeHtml(string);
+ }
- code.push(
- '";',
- '\n return buffer;',
- '\n };',
- '\n})();'
- );
+ return string;
+ };
- if (section.inverted) {
- code.push("\nbuffer += renderSection(name,stack,callback,true);");
- } else {
- code.push("\nbuffer += renderSection(name,stack,callback);");
+ /**
+ * Low-level function that compiles the given `tokens` into a
+ * function that accepts two arguments: a Context and a
+ * Renderer. Returns the body of the function as a string if
+ * `returnBody` is true.
+ */
+ function compileTokens(tokens, returnBody) {
+ if (typeof tokens === "string") {
+ tokens = parse(tokens);
+ }
+
+ var body = ['""'];
+ var token, method, escape;
+
+ for (var i = 0, len = tokens.length; i < len; ++i) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case "#":
+ case "^":
+ method = (token.type === "#") ? "_section" : "_inverted";
+ body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" +
+ " " + compileTokens(token.tokens, true) + "\n" +
+ "})");
+ break;
+ case "{":
+ case "&":
+ case "name":
+ escape = token.type === "name" ? "true" : "false";
+ body.push("r._name(" + quote(token.value) + ", c, " + escape + ")");
+ break;
+ case ">":
+ body.push("r._partial(" + quote(token.value) + ", c)");
+ break;
+ case "text":
+ body.push(quote(token.value));
+ break;
}
+ }
- code.push('\nbuffer += "');
- };
+ // Convert to a string body.
+ body = "return " + body.join(" + ") + ";";
- var sendPlain = function (source) {
- code.push(
- '";',
- updateLine,
- '\nbuffer += lookup("' + trim(source) + '",stack,"");',
- '\nbuffer += "'
- );
- };
+ // Good for debugging.
+ // console.log(body);
- var sendEscaped = function (source) {
- code.push(
- '";',
- updateLine,
- '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
- '\nbuffer += "'
- );
- };
+ if (returnBody) {
+ return body;
+ }
- var line = 1, c, callback;
- for (var i = 0, len = template.length; i < len; ++i) {
- if (template.slice(i, i + openTag.length) === openTag) {
- i += openTag.length;
- c = template.substr(i, 1);
- updateLine = '\nline = ' + line + ';';
- nextOpenTag = openTag;
- nextCloseTag = closeTag;
- hasTag = true;
-
- switch (c) {
- case "!": // comment
- i++;
- callback = null;
- break;
- case "=": // change open/close tags, e.g. {{=<% %>=}}
- i++;
- closeTag = "=" + closeTag;
- callback = setTags;
- break;
- case ">": // include partial
- i++;
- callback = includePartial;
- break;
- case "#": // start section
- i++;
- callback = openSection;
- break;
- case "^": // start inverted section
- i++;
- callback = openInvertedSection;
- break;
- case "/": // end section
- i++;
- callback = closeSection;
- break;
- case "{": // plain variable
- closeTag = "}" + closeTag;
- // fall through
- case "&": // plain variable
- i++;
- nonSpace = true;
- callback = sendPlain;
- break;
- default: // escaped variable
- nonSpace = true;
- callback = sendEscaped;
- }
+ // For great evil!
+ return new Function("c, r", body);
+ }
- var end = template.indexOf(closeTag, i);
+ function escapeTags(tags) {
+ if (tags.length === 2) {
+ return [
+ new RegExp(escapeRe(tags[0]) + "\\s*"),
+ new RegExp("\\s*" + escapeRe(tags[1]))
+ ];
+ }
- if (end === -1) {
- throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
+ throw new Error("Invalid tags: " + tags.join(" "));
+ }
+
+ /**
+ * Forms the given linear array of `tokens` into a nested tree structure
+ * where tokens that represent a section have a "tokens" array property
+ * that contains all tokens that are in that section.
+ */
+ function nestTokens(tokens) {
+ var tree = [];
+ var collector = tree;
+ var sections = [];
+ var token, section;
+
+ for (var i = 0; i < tokens.length; ++i) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case "#":
+ case "^":
+ token.tokens = [];
+ sections.push(token);
+ collector.push(token);
+ collector = token.tokens;
+ break;
+ case "/":
+ if (sections.length === 0) {
+ throw new Error("Unopened section: " + token.value);
}
- var source = template.substring(i, end);
+ section = sections.pop();
- if (callback) {
- callback(source);
+ if (section.value !== token.value) {
+ throw new Error("Unclosed section: " + section.value);
}
- // Maintain line count for \n in source.
- var n = 0;
- while (~(n = source.indexOf("\n", n))) {
- line++;
- n++;
+ if (sections.length > 0) {
+ collector = sections[sections.length - 1].tokens;
+ } else {
+ collector = tree;
}
+ break;
+ default:
+ collector.push(token);
+ }
+ }
+
+ // Make sure there were no open sections when we're done.
+ section = sections.pop();
+
+ if (section) {
+ throw new Error("Unclosed section: " + section.value);
+ }
+
+ return tree;
+ }
+
+ /**
+ * Combines the values of consecutive text tokens in the given `tokens` array
+ * to a single token.
+ */
+ function squashTokens(tokens) {
+ var lastToken;
+
+ for (var i = 0; i < tokens.length; ++i) {
+ token = tokens[i];
+
+ if (lastToken && lastToken.type === "text" && token.type === "text") {
+ lastToken.value += token.value;
+ tokens.splice(i--, 1); // Remove this token from the array.
+ } else {
+ lastToken = token;
+ }
+ }
+ }
- i = end + closeTag.length - 1;
- openTag = nextOpenTag;
- closeTag = nextCloseTag;
+ /**
+ * Breaks up the given `template` string into a tree of token objects. If
+ * `tags` is given here it must be an array with two string values: the
+ * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
+ * course, the default is to use mustaches (i.e. Mustache.tags).
+ */
+ function parse(template, tags) {
+ tags = escapeTags(tags || exports.tags);
+
+ var scanner = new Scanner(template);
+
+ var tokens = [], // Buffer to hold the tokens
+ spaces = [], // Indices of whitespace tokens on the current line
+ hasTag = false, // Is there a {{tag}} on the current line?
+ nonSpace = false; // Is there a non-space char on the current line?
+
+ // Strips all whitespace tokens array for the current line
+ // if there was a {{#tag}} on it and otherwise only space.
+ function stripSpace() {
+ if (hasTag && !nonSpace) {
+ while (spaces.length) {
+ tokens.splice(spaces.pop(), 1);
+ }
} else {
- c = template.substr(i, 1);
-
- switch (c) {
- case '"':
- case "\\":
- nonSpace = true;
- code.push("\\" + c);
- break;
- case "\r":
- // Ignore carriage returns.
- break;
- case "\n":
- spaces.push(code.length);
- code.push("\\n");
- stripSpace(); // Check for whitespace on the current line.
- line++;
- break;
- default:
- if (isWhitespace(c)) {
- spaces.push(code.length);
+ spaces = [];
+ }
+
+ hasTag = false;
+ nonSpace = false;
+ }
+
+ var type, value, chr;
+
+ while (!scanner.eos()) {
+ value = scanner.scanUntil(tags[0]);
+
+ if (value) {
+ for (var i = 0, len = value.length; i < len; ++i) {
+ chr = value[i];
+
+ if (isWhitespace(chr)) {
+ spaces.push(tokens.length);
} else {
nonSpace = true;
}
- code.push(c);
+ tokens.push({type: "text", value: chr});
+
+ if (chr === "\n") {
+ stripSpace(); // Check for whitespace on the current line.
+ }
}
}
- }
- if (sectionStack.length != 0) {
- throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
- }
+ // Match the opening tag.
+ if (!scanner.scan(tags[0])) {
+ break;
+ }
+
+ hasTag = true;
+ type = scanner.scan(tagRe) || "name";
+
+ // Skip any whitespace between tag and value.
+ scanner.scan(whiteRe);
+
+ // Extract the tag value.
+ if (type === "=") {
+ value = scanner.scanUntil(eqRe);
+ scanner.scan(eqRe);
+ scanner.scanUntil(tags[1]);
+ } else if (type === "{") {
+ value = scanner.scanUntil(curlyRe);
+ scanner.scan(curlyRe);
+ scanner.scanUntil(tags[1]);
+ } else {
+ value = scanner.scanUntil(tags[1]);
+ }
- // Clean up any whitespace from a closing {{tag}} that was at the end
- // of the template without a trailing \n.
- stripSpace();
+ // Match the closing tag.
+ if (!scanner.scan(tags[1])) {
+ throw new Error("Unclosed tag at " + scanner.pos);
+ }
- code.push(
- '";',
- "\nreturn buffer;",
- "\n} catch (e) { throw {error: e, line: line}; }"
- );
+ tokens.push({type: type, value: value});
- // Ignore `buffer += "";` statements.
- var body = code.join("").replace(/buffer \+= "";\n/g, "");
+ if (type === "name" || type === "{" || type === "&") {
+ nonSpace = true;
+ }
- if (options.debug) {
- if (typeof console != "undefined" && console.log) {
- console.log(body);
- } else if (typeof print === "function") {
- print(body);
+ // Set the tags for the next time around.
+ if (type === "=") {
+ tags = escapeTags(value.split(spaceRe));
}
}
- return body;
- }
+ squashTokens(tokens);
- /**
- * Used by `compile` to generate a reusable function for the given `template`.
- */
- function _compile(template, options) {
- var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
- var body = parse(template, options);
- var fn = new Function(args, body);
-
- // This anonymous function wraps the generated function so we can do
- // argument coercion, setup some variables, and handle any errors
- // encountered while executing it.
- return function (view, partials) {
- partials = partials || {};
-
- var stack = [view]; // context stack
-
- try {
- return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
- } catch (e) {
- throw debug(e.error, template, e.line, options.file);
- }
- };
+ return nestTokens(tokens);
}
- // Cache of pre-compiled templates.
- var _cache = {};
+ // The high-level clearCache, compile, compilePartial, and render functions
+ // use this default renderer.
+ var _renderer = new Renderer;
/**
- * Clear the cache of compiled templates.
+ * Clears all cached templates and partials.
*/
function clearCache() {
- _cache = {};
+ _renderer.clearCache();
}
/**
- * Compiles the given `template` into a reusable function using the given
- * `options`. In addition to the options accepted by Mustache.parse,
- * recognized options include the following:
- *
- * - cache Set `false` to bypass any pre-compiled version of the given
- * template. Otherwise, a given `template` string will be cached
- * the first time it is parsed
+ * High-level API for compiling the given `tokens` down to a reusable
+ * function. If `tokens` is a string it will be parsed using the given `tags`
+ * before it is compiled.
*/
- function compile(template, options) {
- options = options || {};
-
- // Use a pre-compiled version from the cache if we have one.
- if (options.cache !== false) {
- if (!_cache[template]) {
- _cache[template] = _compile(template, options);
- }
-
- return _cache[template];
- }
+ function compile(tokens, tags) {
+ return _renderer.compile(tokens, tags);
+ }
- return _compile(template, options);
+ /**
+ * High-level API for compiling the `tokens` for the partial with the given
+ * `name` down to a reusable function. If `tokens` is a string it will be
+ * parsed using the given `tags` before it is compiled.
+ */
+ function compilePartial(name, tokens, tags) {
+ return _renderer.compilePartial(name, tokens, tags);
}
/**
- * High-level function that renders the given `template` using the given
- * `view` and `partials`. If you need to use any of the template options (see
- * `compile` above), you must compile in a separate step, and then call that
- * compiled function.
+ * High-level API for rendering the `template` using the given `view`. The
+ * optional `partials` object may be given here for convenience, but note that
+ * it will cause all partials to be re-compiled, thus hurting performance. Of
+ * course, this only matters if you're going to render the same template more
+ * than once. If so, it is best to call `compilePartial` before calling this
+ * function and to leave the `partials` argument blank.
*/
function render(template, view, partials) {
- return compile(template)(view, partials);
+ if (partials) {
+ for (var name in partials) {
+ compilePartial(name, partials[name]);
+ }
+ }
+
+ return _renderer.render(template, view);
}
})(Mustache);
diff --git a/spec/_files/ampersand_escape.js b/spec/_files/ampersand_escape.js
deleted file mode 100644
index 645efe1..0000000
--- a/spec/_files/ampersand_escape.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var ampersand_escape = {
- message: "Some Authors: "
-};
diff --git a/spec/_files/apostrophe.js b/spec/_files/apostrophe.js
deleted file mode 100644
index df69cd2..0000000
--- a/spec/_files/apostrophe.js
+++ /dev/null
@@ -1 +0,0 @@
-var apostrophe = {'apos': "'", 'control':'X'};
diff --git a/spec/_files/array_of_strings.js b/spec/_files/array_of_strings.js
deleted file mode 100644
index 12c4992..0000000
--- a/spec/_files/array_of_strings.js
+++ /dev/null
@@ -1 +0,0 @@
-var array_of_strings = {array_of_strings: ['hello', 'world']};
diff --git a/spec/_files/backslashes.js b/spec/_files/backslashes.js
deleted file mode 100644
index 0b9765b..0000000
--- a/spec/_files/backslashes.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var backslashes = {
- value: "\\abc"
-};
diff --git a/spec/_files/bug_11_eating_whitespace.js b/spec/_files/bug_11_eating_whitespace.js
deleted file mode 100644
index 78ce975..0000000
--- a/spec/_files/bug_11_eating_whitespace.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var bug_11_eating_whitespace = {
- tag: "yo"
-};
diff --git a/spec/_files/changing_delimiters.js b/spec/_files/changing_delimiters.js
deleted file mode 100644
index 4fbe9f3..0000000
--- a/spec/_files/changing_delimiters.js
+++ /dev/null
@@ -1,4 +0,0 @@
-var changing_delimiters = {
- "foo": "foooooooooooooo",
- "bar":"bar!"
-};
diff --git a/spec/_files/comments.js b/spec/_files/comments.js
deleted file mode 100644
index f85f979..0000000
--- a/spec/_files/comments.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var comments = {
- title: function() {
- return "A Comedy of Errors";
- }
-};
diff --git a/spec/_files/disappearing_whitespace.js b/spec/_files/disappearing_whitespace.js
deleted file mode 100644
index 3fe7121..0000000
--- a/spec/_files/disappearing_whitespace.js
+++ /dev/null
@@ -1,4 +0,0 @@
-var disappearing_whitespace = {
- bedrooms: true,
- total: 1
-};
diff --git a/spec/_files/empty_list.js b/spec/_files/empty_list.js
deleted file mode 100644
index 80555c0..0000000
--- a/spec/_files/empty_list.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var empty_list = {
- jobs: []
-};
diff --git a/spec/_files/empty_sections.js b/spec/_files/empty_sections.js
deleted file mode 100644
index 6e50514..0000000
--- a/spec/_files/empty_sections.js
+++ /dev/null
@@ -1 +0,0 @@
-var empty_sections = {};
diff --git a/spec/_files/empty_template.js b/spec/_files/empty_template.js
deleted file mode 100644
index 564c9e2..0000000
--- a/spec/_files/empty_template.js
+++ /dev/null
@@ -1 +0,0 @@
-var empty_template = {};
diff --git a/spec/_files/error_not_found.js b/spec/_files/error_not_found.js
deleted file mode 100644
index 6cdbdb3..0000000
--- a/spec/_files/error_not_found.js
+++ /dev/null
@@ -1 +0,0 @@
-var error_not_found = {bar: 2};
\ No newline at end of file
diff --git a/spec/_files/higher_order_sections.js b/spec/_files/higher_order_sections.js
deleted file mode 100644
index c7e558e..0000000
--- a/spec/_files/higher_order_sections.js
+++ /dev/null
@@ -1,9 +0,0 @@
-var higher_order_sections = {
- "name": "Tater",
- "helper": "To tinker?",
- "bolder": function() {
- return function(text, render) {
- return "" + render(text) + ' ' + this.helper;
- }
- }
-}
\ No newline at end of file
diff --git a/spec/_files/inverted_section.js b/spec/_files/inverted_section.js
deleted file mode 100644
index 2e07fc3..0000000
--- a/spec/_files/inverted_section.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var inverted_section = {
- "repos": []
-};
diff --git a/spec/_files/multiline_comment.js b/spec/_files/multiline_comment.js
deleted file mode 100644
index 24e7445..0000000
--- a/spec/_files/multiline_comment.js
+++ /dev/null
@@ -1 +0,0 @@
-var multiline_comment = {};
diff --git a/spec/_files/partial_array_of_partials.js b/spec/_files/partial_array_of_partials.js
deleted file mode 100644
index 706e7ed..0000000
--- a/spec/_files/partial_array_of_partials.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var partial_array_of_partials = {
- numbers: [{i: '1'}, {i: '2'}, {i: '3'}, {i: '4'}]
-};
diff --git a/spec/_files/partial_array_of_partials_implicit.js b/spec/_files/partial_array_of_partials_implicit.js
deleted file mode 100644
index 864efa3..0000000
--- a/spec/_files/partial_array_of_partials_implicit.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var partial_array_of_partials_implicit = {
- numbers: ['1', '2', '3', '4']
-};
diff --git a/spec/_files/partial_empty.js b/spec/_files/partial_empty.js
deleted file mode 100644
index 3f36df6..0000000
--- a/spec/_files/partial_empty.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var partial_empty = {
- foo: 1
-};
diff --git a/spec/_files/partial_template.js b/spec/_files/partial_template.js
deleted file mode 100644
index 196f1e8..0000000
--- a/spec/_files/partial_template.js
+++ /dev/null
@@ -1,6 +0,0 @@
-var partial_template = {
- title: function() {
- return "Welcome";
- },
- again: "Goodbye"
-};
diff --git a/spec/_files/string_as_context.js b/spec/_files/string_as_context.js
deleted file mode 100644
index 784368b..0000000
--- a/spec/_files/string_as_context.js
+++ /dev/null
@@ -1,4 +0,0 @@
-var string_as_context = {
- a_string: 'aa',
- a_list: ['a','b','c']
-};
diff --git a/spec/_files/two_sections.js b/spec/_files/two_sections.js
deleted file mode 100644
index 8546f64..0000000
--- a/spec/_files/two_sections.js
+++ /dev/null
@@ -1 +0,0 @@
-var two_sections = {};
\ No newline at end of file
diff --git a/spec/_files/unescaped.js b/spec/_files/unescaped.js
deleted file mode 100644
index 0bd20b8..0000000
--- a/spec/_files/unescaped.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var unescaped = {
- title: function() {
- return "Bear > Shark";
- }
-};
diff --git a/spec/mustache_spec.rb b/spec/mustache_spec.rb
deleted file mode 100644
index 3c27d80..0000000
--- a/spec/mustache_spec.rb
+++ /dev/null
@@ -1,218 +0,0 @@
-require 'rubygems'
-require 'json'
-
-ROOT = File.expand_path('../..', __FILE__)
-SPEC = File.join(ROOT, 'spec')
-FILES = File.join(SPEC, '_files')
-
-MUSTACHE = File.read(File.join(ROOT, "mustache.js"))
-
-TESTS = Dir.glob(File.join(FILES, '*.js')).map do |name|
- File.basename name, '.js'
-end
-
-NODE_PATH = `which node`.strip
-JS_PATH = `which js`.strip
-JSC_PATH = "/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"
-RHINO_JAR = "org.mozilla.javascript.tools.shell.Main"
-
-def load_test(name)
- template = File.read(File.join(FILES, "#{name}.mustache"))
- view = File.read(File.join(FILES, "#{name}.js"))
- partial_file = File.join(FILES, "#{name}.partial")
- partial = if File.exist?(partial_file)
- File.read(partial_file)
- end
- expect = File.read(File.join(FILES, "#{name}.txt"))
-
- [template, view, partial, expect]
-end
-
-def run_js(runner, js)
- cmd = case runner
- when :spidermonkey
- JS_PATH
- when :jsc
- JSC_PATH
- when :rhino
- "java #{RHINO_JAR}"
- when :v8
- NODE_PATH
- end
-
- runner_file = "runner.js"
- File.open(runner_file, 'w') {|file| file.write(js) }
- `#{cmd} #{runner_file}`
-ensure
- FileUtils.rm_r(runner_file)
-end
-
-$engines_run = 0
-
-describe "mustache" do
- shared_examples_for "mustache rendering" do
- before(:all) do
- $engines_run += 1
- end
-
- it "should return the same result when invoked multiple times" do
- js = <<-JS
- #{@boilerplate}
- Mustache.render("x")
- print(Mustache.render("x"));
- JS
-
- run_js(@runner, js).should == "x\n"
- end
-
- it "should clear the context after each run" do
- js = <<-JS
- #{@boilerplate}
- Mustache.render("{{#list}}{{x}}{{/list}}", {list: [{x: 1}]})
- try {
- print(Mustache.render("{{#list}}{{x}}{{/list}}", {list: [{}]}));
- } catch(e) {
- print('ERROR: ' + e.message);
- }
- JS
-
- run_js(@runner, js).should == "\n"
- end
-
- TESTS.each do |test|
- describe test do
- it "should render the correct output" do
- template, view, partial, expect = load_test(test)
-
- js = <<-JS
- try {
- #{@boilerplate}
- var template = #{template.to_json};
- #{view}
- var partials = {partial: #{partial ? partial.to_json : '""'}};
- print(Mustache.render(template, #{test}, partials));
- } catch(e) {
- print('ERROR: ' + e.message);
- }
- JS
-
- run_js(@runner, js).chomp.should == expect
- end
-
- # it "should send the correct output" do
- # template, view, partial, expect = load_test(test)
- #
- # js = <<-JS
- # try {
- # #{@boilerplate}
- # var template = #{template.to_json};
- # #{view}
- # var partials = {
- # "partial": #{(partial || '').to_json}
- # };
- # var buffer = [];
- # var send = function (chunk) {
- # buffer.push(chunk);
- # };
- # Mustache.render(template, #{test}, partials, send);
- # print(buffer.join(""));
- # } catch(e) {
- # print('ERROR: ' + e.message);
- # }
- # JS
- #
- # run_js(@runner, js).chomp.should == expect
- # end
- end
- end
- end
-
- context "running in V8 (Chrome, node)" do
- if File.exist?(NODE_PATH)
- before(:all) do
- $stdout.write "Testing in V8 "
- @runner = :v8
- @boilerplate = MUSTACHE.dup
- @boilerplate << <<-JS
- var print = console.log;
- JS
- end
-
- after(:all) do
- puts " Done!"
- end
-
- it_should_behave_like "mustache rendering"
- else
- puts "Skipping tests in V8 (node not found)"
- end
- end
-
- context "running in SpiderMonkey (Mozilla, Firefox)" do
- if File.exist?(JS_PATH)
- before(:all) do
- $stdout.write "Testing in SpiderMonkey "
- @runner = :spidermonkey
- @boilerplate = MUSTACHE.dup
- end
-
- after(:all) do
- puts " Done!"
- end
-
- it_should_behave_like "mustache rendering"
- else
- puts "Skipping tests in SpiderMonkey (js not found)"
- end
- end
-
- context "running in JavaScriptCore (WebKit, Safari)" do
- if File.exist?(JSC_PATH)
- before(:all) do
- $stdout.write "Testing in JavaScriptCore "
- @runner = :jsc
- @boilerplate = MUSTACHE.dup
- end
-
- after(:all) do
- puts " Done!"
- end
-
- it_should_behave_like "mustache rendering"
- else
- puts "Skipping tests in JavaScriptCore (jsc not found)"
- end
- end
-
- context "running in Rhino (Mozilla, Java)" do
- if `java #{RHINO_JAR} 'foo' 2>&1` !~ /ClassNotFoundException/
- before(:all) do
- $stdout.write "Testing in Rhino "
- @runner = :rhino
- @boilerplate = MUSTACHE.dup
- end
-
- after(:all) do
- puts " Done!"
- end
-
- it_should_behave_like "mustache rendering"
- else
- puts "Skipping tests in Rhino (JAR #{RHINO_JAR} was not found)"
- end
- end
-
- context "suite" do
- before(:each) do
- $stdout.write "Verifying that we ran the tests in at least one engine ... "
- end
-
- after(:each) do
- puts @exception.nil? ? "OK" : "ERROR"
- end
-
- it "should have run at least one time" do
- $engines_run.should > 0
- end
- end
-end
diff --git a/test/_files/ampersand_escape.js b/test/_files/ampersand_escape.js
new file mode 100644
index 0000000..df6409f
--- /dev/null
+++ b/test/_files/ampersand_escape.js
@@ -0,0 +1,3 @@
+({
+ message: "Some "
+})
diff --git a/spec/_files/ampersand_escape.mustache b/test/_files/ampersand_escape.mustache
similarity index 100%
rename from spec/_files/ampersand_escape.mustache
rename to test/_files/ampersand_escape.mustache
diff --git a/spec/_files/ampersand_escape.txt b/test/_files/ampersand_escape.txt
similarity index 100%
rename from spec/_files/ampersand_escape.txt
rename to test/_files/ampersand_escape.txt
diff --git a/test/_files/apostrophe.js b/test/_files/apostrophe.js
new file mode 100644
index 0000000..183c6d5
--- /dev/null
+++ b/test/_files/apostrophe.js
@@ -0,0 +1,4 @@
+({
+ 'apos': "'",
+ 'control': 'X'
+})
diff --git a/spec/_files/apostrophe.mustache b/test/_files/apostrophe.mustache
similarity index 100%
rename from spec/_files/apostrophe.mustache
rename to test/_files/apostrophe.mustache
diff --git a/spec/_files/apostrophe.txt b/test/_files/apostrophe.txt
similarity index 100%
rename from spec/_files/apostrophe.txt
rename to test/_files/apostrophe.txt
diff --git a/test/_files/array_of_strings.js b/test/_files/array_of_strings.js
new file mode 100644
index 0000000..6926612
--- /dev/null
+++ b/test/_files/array_of_strings.js
@@ -0,0 +1,3 @@
+({
+ array_of_strings: ['hello', 'world']
+})
diff --git a/spec/_files/array_of_strings.mustache b/test/_files/array_of_strings.mustache
similarity index 100%
rename from spec/_files/array_of_strings.mustache
rename to test/_files/array_of_strings.mustache
diff --git a/spec/_files/array_of_strings.txt b/test/_files/array_of_strings.txt
similarity index 100%
rename from spec/_files/array_of_strings.txt
rename to test/_files/array_of_strings.txt
diff --git a/test/_files/backslashes.js b/test/_files/backslashes.js
new file mode 100644
index 0000000..427acd9
--- /dev/null
+++ b/test/_files/backslashes.js
@@ -0,0 +1,3 @@
+({
+ value: "\\abc"
+})
diff --git a/spec/_files/backslashes.mustache b/test/_files/backslashes.mustache
similarity index 100%
rename from spec/_files/backslashes.mustache
rename to test/_files/backslashes.mustache
diff --git a/spec/_files/backslashes.txt b/test/_files/backslashes.txt
similarity index 100%
rename from spec/_files/backslashes.txt
rename to test/_files/backslashes.txt
diff --git a/test/_files/bug_11_eating_whitespace.js b/test/_files/bug_11_eating_whitespace.js
new file mode 100644
index 0000000..e41ccd1
--- /dev/null
+++ b/test/_files/bug_11_eating_whitespace.js
@@ -0,0 +1,3 @@
+({
+ tag: "yo"
+})
diff --git a/spec/_files/bug_11_eating_whitespace.mustache b/test/_files/bug_11_eating_whitespace.mustache
similarity index 100%
rename from spec/_files/bug_11_eating_whitespace.mustache
rename to test/_files/bug_11_eating_whitespace.mustache
diff --git a/spec/_files/bug_11_eating_whitespace.txt b/test/_files/bug_11_eating_whitespace.txt
similarity index 100%
rename from spec/_files/bug_11_eating_whitespace.txt
rename to test/_files/bug_11_eating_whitespace.txt
diff --git a/test/_files/changing_delimiters.js b/test/_files/changing_delimiters.js
new file mode 100644
index 0000000..b808f4c
--- /dev/null
+++ b/test/_files/changing_delimiters.js
@@ -0,0 +1,4 @@
+({
+ "foo": "foooooooooooooo",
+ "bar": "bar!"
+})
diff --git a/spec/_files/changing_delimiters.mustache b/test/_files/changing_delimiters.mustache
similarity index 100%
rename from spec/_files/changing_delimiters.mustache
rename to test/_files/changing_delimiters.mustache
diff --git a/spec/_files/changing_delimiters.txt b/test/_files/changing_delimiters.txt
similarity index 100%
rename from spec/_files/changing_delimiters.txt
rename to test/_files/changing_delimiters.txt
diff --git a/test/_files/comments.js b/test/_files/comments.js
new file mode 100644
index 0000000..f20b8b1
--- /dev/null
+++ b/test/_files/comments.js
@@ -0,0 +1,5 @@
+({
+ title: function () {
+ return "A Comedy of Errors";
+ }
+})
diff --git a/spec/_files/comments.mustache b/test/_files/comments.mustache
similarity index 100%
rename from spec/_files/comments.mustache
rename to test/_files/comments.mustache
diff --git a/spec/_files/comments.txt b/test/_files/comments.txt
similarity index 100%
rename from spec/_files/comments.txt
rename to test/_files/comments.txt
diff --git a/spec/_files/complex.js b/test/_files/complex.js
similarity index 74%
rename from spec/_files/complex.js
rename to test/_files/complex.js
index a0dfb63..68a4809 100644
--- a/spec/_files/complex.js
+++ b/test/_files/complex.js
@@ -1,5 +1,5 @@
-var complex = {
- header: function() {
+({
+ header: function () {
return "Colors";
},
item: [
@@ -7,13 +7,13 @@ var complex = {
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
],
- link: function() {
+ link: function () {
return this["current"] !== true;
},
- list: function() {
+ list: function () {
return this.item.length !== 0;
},
- empty: function() {
+ empty: function () {
return this.item.length === 0;
}
-};
+})
diff --git a/spec/_files/complex.mustache b/test/_files/complex.mustache
similarity index 100%
rename from spec/_files/complex.mustache
rename to test/_files/complex.mustache
diff --git a/spec/_files/complex.txt b/test/_files/complex.txt
similarity index 100%
rename from spec/_files/complex.txt
rename to test/_files/complex.txt
diff --git a/spec/_files/context_lookup.js b/test/_files/context_lookup.js
similarity index 73%
rename from spec/_files/context_lookup.js
rename to test/_files/context_lookup.js
index 49d4453..8ce6299 100644
--- a/spec/_files/context_lookup.js
+++ b/test/_files/context_lookup.js
@@ -1,8 +1,8 @@
-var context_lookup = {
+({
"outer": {
"id": 1,
"second": {
"nothing": 2
}
}
-};
+})
diff --git a/spec/_files/context_lookup.mustache b/test/_files/context_lookup.mustache
similarity index 100%
rename from spec/_files/context_lookup.mustache
rename to test/_files/context_lookup.mustache
diff --git a/spec/_files/context_lookup.txt b/test/_files/context_lookup.txt
similarity index 100%
rename from spec/_files/context_lookup.txt
rename to test/_files/context_lookup.txt
diff --git a/spec/_files/delimiters.js b/test/_files/delimiters.js
similarity index 89%
rename from spec/_files/delimiters.js
rename to test/_files/delimiters.js
index 220d0d2..365d01e 100644
--- a/spec/_files/delimiters.js
+++ b/test/_files/delimiters.js
@@ -1,6 +1,6 @@
-var delimiters = {
+({
first: "It worked the first time.",
second: "And it worked the second time.",
third: "Then, surprisingly, it worked the third time.",
fourth: "Fourth time also fine!."
-}
+})
diff --git a/spec/_files/delimiters.mustache b/test/_files/delimiters.mustache
similarity index 100%
rename from spec/_files/delimiters.mustache
rename to test/_files/delimiters.mustache
diff --git a/spec/_files/delimiters.txt b/test/_files/delimiters.txt
similarity index 100%
rename from spec/_files/delimiters.txt
rename to test/_files/delimiters.txt
diff --git a/test/_files/disappearing_whitespace.js b/test/_files/disappearing_whitespace.js
new file mode 100644
index 0000000..973dd1c
--- /dev/null
+++ b/test/_files/disappearing_whitespace.js
@@ -0,0 +1,4 @@
+({
+ bedrooms: true,
+ total: 1
+})
diff --git a/spec/_files/disappearing_whitespace.mustache b/test/_files/disappearing_whitespace.mustache
similarity index 100%
rename from spec/_files/disappearing_whitespace.mustache
rename to test/_files/disappearing_whitespace.mustache
diff --git a/spec/_files/disappearing_whitespace.txt b/test/_files/disappearing_whitespace.txt
similarity index 100%
rename from spec/_files/disappearing_whitespace.txt
rename to test/_files/disappearing_whitespace.txt
diff --git a/spec/_files/dot_notation.js b/test/_files/dot_notation.js
similarity index 81%
rename from spec/_files/dot_notation.js
rename to test/_files/dot_notation.js
index c1295f5..ca995f7 100644
--- a/spec/_files/dot_notation.js
+++ b/test/_files/dot_notation.js
@@ -1,9 +1,9 @@
-var dot_notation = {
+({
name: "A Book",
authors: ["John Power", "Jamie Walsh"],
- price:{
+ price: {
value: 200,
- vat: function() {
+ vat: function () {
return this.value * 0.2;
},
currency: {
@@ -11,7 +11,7 @@ var dot_notation = {
name: 'Euro'
}
},
- availability:{
+ availability: {
status: true,
text: "In Stock"
},
@@ -20,4 +20,4 @@ var dot_notation = {
zero: 0,
notTrue: false
}
-};
+})
diff --git a/spec/_files/dot_notation.mustache b/test/_files/dot_notation.mustache
similarity index 84%
rename from spec/_files/dot_notation.mustache
rename to test/_files/dot_notation.mustache
index da1bad7..138ddd0 100644
--- a/spec/_files/dot_notation.mustache
+++ b/test/_files/dot_notation.mustache
@@ -2,7 +2,7 @@
{{name}}
{{#authors}}
Price: {{price.currency.symbol}}{{price.value}} {{#price.currency}}{{name}} {{availability.text}}{{/price.currency}}
-VAT: {{price.currency.symbol}}{{price.vat}}
+VAT: {{price.currency.symbol}}{{#price}}{{vat}}{{/price}}
Zero: {{truthy.zero}}
diff --git a/spec/_files/dot_notation.txt b/test/_files/dot_notation.txt similarity index 100% rename from spec/_files/dot_notation.txt rename to test/_files/dot_notation.txt diff --git a/spec/_files/double_render.js b/test/_files/double_render.js similarity index 65% rename from spec/_files/double_render.js rename to test/_files/double_render.js index 24125dc..28acb2c 100644 --- a/spec/_files/double_render.js +++ b/test/_files/double_render.js @@ -1,5 +1,5 @@ -var double_render = { +({ foo: true, bar: "{{win}}", win: "FAIL" -}; \ No newline at end of file +}) diff --git a/spec/_files/double_render.mustache b/test/_files/double_render.mustache similarity index 100% rename from spec/_files/double_render.mustache rename to test/_files/double_render.mustache diff --git a/spec/_files/double_render.txt b/test/_files/double_render.txt similarity index 100% rename from spec/_files/double_render.txt rename to test/_files/double_render.txt diff --git a/test/_files/empty_list.js b/test/_files/empty_list.js new file mode 100644 index 0000000..c0e1159 --- /dev/null +++ b/test/_files/empty_list.js @@ -0,0 +1,3 @@ +({ + jobs: [] +}) diff --git a/spec/_files/empty_list.mustache b/test/_files/empty_list.mustache similarity index 100% rename from spec/_files/empty_list.mustache rename to test/_files/empty_list.mustache diff --git a/spec/_files/empty_list.txt b/test/_files/empty_list.txt similarity index 100% rename from spec/_files/empty_list.txt rename to test/_files/empty_list.txt diff --git a/test/_files/empty_sections.js b/test/_files/empty_sections.js new file mode 100644 index 0000000..b4100a5 --- /dev/null +++ b/test/_files/empty_sections.js @@ -0,0 +1 @@ +({}) diff --git a/spec/_files/empty_sections.mustache b/test/_files/empty_sections.mustache similarity index 100% rename from spec/_files/empty_sections.mustache rename to test/_files/empty_sections.mustache diff --git a/spec/_files/empty_sections.txt b/test/_files/empty_sections.txt similarity index 100% rename from spec/_files/empty_sections.txt rename to test/_files/empty_sections.txt diff --git a/spec/_files/empty_string.js b/test/_files/empty_string.js similarity index 73% rename from spec/_files/empty_string.js rename to test/_files/empty_string.js index 2151336..be6e058 100644 --- a/spec/_files/empty_string.js +++ b/test/_files/empty_string.js @@ -1,6 +1,6 @@ -var empty_string = { +({ description: "That is all!", child: { description: "" } -}; +}) diff --git a/spec/_files/empty_string.mustache b/test/_files/empty_string.mustache similarity index 100% rename from spec/_files/empty_string.mustache rename to test/_files/empty_string.mustache diff --git a/spec/_files/empty_string.txt b/test/_files/empty_string.txt similarity index 100% rename from spec/_files/empty_string.txt rename to test/_files/empty_string.txt diff --git a/test/_files/empty_template.js b/test/_files/empty_template.js new file mode 100644 index 0000000..b4100a5 --- /dev/null +++ b/test/_files/empty_template.js @@ -0,0 +1 @@ +({}) diff --git a/spec/_files/empty_template.mustache b/test/_files/empty_template.mustache similarity index 100% rename from spec/_files/empty_template.mustache rename to test/_files/empty_template.mustache diff --git a/spec/_files/empty_template.txt b/test/_files/empty_template.txt similarity index 100% rename from spec/_files/empty_template.txt rename to test/_files/empty_template.txt diff --git a/test/_files/error_not_found.js b/test/_files/error_not_found.js new file mode 100644 index 0000000..10e4709 --- /dev/null +++ b/test/_files/error_not_found.js @@ -0,0 +1,3 @@ +({ + bar: 2 +}) diff --git a/spec/_files/error_not_found.mustache b/test/_files/error_not_found.mustache similarity index 100% rename from spec/_files/error_not_found.mustache rename to test/_files/error_not_found.mustache diff --git a/spec/_files/error_not_found.txt b/test/_files/error_not_found.txt similarity index 100% rename from spec/_files/error_not_found.txt rename to test/_files/error_not_found.txt diff --git a/spec/_files/escaped.js b/test/_files/escaped.js similarity index 56% rename from spec/_files/escaped.js rename to test/_files/escaped.js index 7a8baef..7735877 100644 --- a/spec/_files/escaped.js +++ b/test/_files/escaped.js @@ -1,6 +1,6 @@ -var escaped = { - title: function() { +({ + title: function () { return "Bear > Shark"; }, entities: """ -}; +}) diff --git a/spec/_files/escaped.mustache b/test/_files/escaped.mustache similarity index 100% rename from spec/_files/escaped.mustache rename to test/_files/escaped.mustache diff --git a/spec/_files/escaped.txt b/test/_files/escaped.txt similarity index 100% rename from spec/_files/escaped.txt rename to test/_files/escaped.txt diff --git a/test/_files/higher_order_sections.js b/test/_files/higher_order_sections.js new file mode 100644 index 0000000..7b52c0d --- /dev/null +++ b/test/_files/higher_order_sections.js @@ -0,0 +1,9 @@ +({ + name: "Tater", + helper: "To tinker?", + bolder: function () { + return function (text, render) { + return "" + render(text) + ' ' + this.helper; + } + } +}) diff --git a/spec/_files/higher_order_sections.mustache b/test/_files/higher_order_sections.mustache similarity index 100% rename from spec/_files/higher_order_sections.mustache rename to test/_files/higher_order_sections.mustache diff --git a/spec/_files/higher_order_sections.txt b/test/_files/higher_order_sections.txt similarity index 100% rename from spec/_files/higher_order_sections.txt rename to test/_files/higher_order_sections.txt diff --git a/spec/_files/included_tag.js b/test/_files/included_tag.js similarity index 55% rename from spec/_files/included_tag.js rename to test/_files/included_tag.js index a63df27..eb032a4 100644 --- a/spec/_files/included_tag.js +++ b/test/_files/included_tag.js @@ -1,3 +1,3 @@ -var included_tag = { +({ html: "I like {{mustache}}" -}; +}) diff --git a/spec/_files/included_tag.mustache b/test/_files/included_tag.mustache similarity index 100% rename from spec/_files/included_tag.mustache rename to test/_files/included_tag.mustache diff --git a/spec/_files/included_tag.txt b/test/_files/included_tag.txt similarity index 100% rename from spec/_files/included_tag.txt rename to test/_files/included_tag.txt diff --git a/test/_files/inverted_section.js b/test/_files/inverted_section.js new file mode 100644 index 0000000..f8f08fd --- /dev/null +++ b/test/_files/inverted_section.js @@ -0,0 +1,3 @@ +({ + "repos": [] +}) diff --git a/spec/_files/inverted_section.mustache b/test/_files/inverted_section.mustache similarity index 100% rename from spec/_files/inverted_section.mustache rename to test/_files/inverted_section.mustache diff --git a/spec/_files/inverted_section.txt b/test/_files/inverted_section.txt similarity index 100% rename from spec/_files/inverted_section.txt rename to test/_files/inverted_section.txt diff --git a/spec/_files/keys_with_questionmarks.js b/test/_files/keys_with_questionmarks.js similarity index 50% rename from spec/_files/keys_with_questionmarks.js rename to test/_files/keys_with_questionmarks.js index 55a220d..becd631 100644 --- a/spec/_files/keys_with_questionmarks.js +++ b/test/_files/keys_with_questionmarks.js @@ -1,5 +1,5 @@ -var keys_with_questionmarks = { +({ "person?": { name: "Jon" } -} +}) diff --git a/spec/_files/keys_with_questionmarks.mustache b/test/_files/keys_with_questionmarks.mustache similarity index 100% rename from spec/_files/keys_with_questionmarks.mustache rename to test/_files/keys_with_questionmarks.mustache diff --git a/spec/_files/keys_with_questionmarks.txt b/test/_files/keys_with_questionmarks.txt similarity index 100% rename from spec/_files/keys_with_questionmarks.txt rename to test/_files/keys_with_questionmarks.txt diff --git a/test/_files/multiline_comment.js b/test/_files/multiline_comment.js new file mode 100644 index 0000000..b4100a5 --- /dev/null +++ b/test/_files/multiline_comment.js @@ -0,0 +1 @@ +({}) diff --git a/spec/_files/multiline_comment.mustache b/test/_files/multiline_comment.mustache similarity index 100% rename from spec/_files/multiline_comment.mustache rename to test/_files/multiline_comment.mustache diff --git a/spec/_files/multiline_comment.txt b/test/_files/multiline_comment.txt similarity index 100% rename from spec/_files/multiline_comment.txt rename to test/_files/multiline_comment.txt diff --git a/spec/_files/nested_iterating.js b/test/_files/nested_iterating.js similarity index 71% rename from spec/_files/nested_iterating.js rename to test/_files/nested_iterating.js index a8275f9..2708b2d 100644 --- a/spec/_files/nested_iterating.js +++ b/test/_files/nested_iterating.js @@ -1,8 +1,8 @@ -var nested_iterating = { +({ inner: [{ foo: 'foo', inner: [{ bar: 'bar' }] }] -}; +}) diff --git a/spec/_files/nested_iterating.mustache b/test/_files/nested_iterating.mustache similarity index 100% rename from spec/_files/nested_iterating.mustache rename to test/_files/nested_iterating.mustache diff --git a/spec/_files/nested_iterating.txt b/test/_files/nested_iterating.txt similarity index 100% rename from spec/_files/nested_iterating.txt rename to test/_files/nested_iterating.txt diff --git a/spec/_files/nesting.js b/test/_files/nesting.js similarity index 76% rename from spec/_files/nesting.js rename to test/_files/nesting.js index 1f76cd0..264cc2f 100644 --- a/spec/_files/nesting.js +++ b/test/_files/nesting.js @@ -1,7 +1,7 @@ -var nesting = { +({ foo: [ {a: {b: 1}}, {a: {b: 2}}, {a: {b: 3}} ] -}; +}) diff --git a/spec/_files/nesting.mustache b/test/_files/nesting.mustache similarity index 100% rename from spec/_files/nesting.mustache rename to test/_files/nesting.mustache diff --git a/spec/_files/nesting.txt b/test/_files/nesting.txt similarity index 100% rename from spec/_files/nesting.txt rename to test/_files/nesting.txt diff --git a/spec/_files/nesting_same_name.js b/test/_files/nesting_same_name.js similarity index 71% rename from spec/_files/nesting_same_name.js rename to test/_files/nesting_same_name.js index a5fb65b..10a0c14 100644 --- a/spec/_files/nesting_same_name.js +++ b/test/_files/nesting_same_name.js @@ -1,8 +1,8 @@ -var nesting_same_name = { +({ items: [ { name: 'name', items: [1, 2, 3, 4] } ] -}; +}) diff --git a/spec/_files/nesting_same_name.mustache b/test/_files/nesting_same_name.mustache similarity index 100% rename from spec/_files/nesting_same_name.mustache rename to test/_files/nesting_same_name.mustache diff --git a/spec/_files/nesting_same_name.txt b/test/_files/nesting_same_name.txt similarity index 100% rename from spec/_files/nesting_same_name.txt rename to test/_files/nesting_same_name.txt diff --git a/spec/_files/null_string.js b/test/_files/null_string.js similarity index 84% rename from spec/_files/null_string.js rename to test/_files/null_string.js index ab5291f..984ee51 100644 --- a/spec/_files/null_string.js +++ b/test/_files/null_string.js @@ -1,4 +1,4 @@ -var null_string = { +({ name: "Elise", glytch: true, binary: false, @@ -7,4 +7,4 @@ var null_string = { numeric: function() { return NaN; } -}; +}) diff --git a/spec/_files/null_string.mustache b/test/_files/null_string.mustache similarity index 100% rename from spec/_files/null_string.mustache rename to test/_files/null_string.mustache diff --git a/spec/_files/null_string.txt b/test/_files/null_string.txt similarity index 100% rename from spec/_files/null_string.txt rename to test/_files/null_string.txt diff --git a/spec/_files/partial_array.js b/test/_files/partial_array.js similarity index 55% rename from spec/_files/partial_array.js rename to test/_files/partial_array.js index f16e9eb..2a6ddf1 100644 --- a/spec/_files/partial_array.js +++ b/test/_files/partial_array.js @@ -1,3 +1,3 @@ -var partial_array = { +({ array: ['1', '2', '3', '4'] -}; \ No newline at end of file +}) diff --git a/spec/_files/partial_array.mustache b/test/_files/partial_array.mustache similarity index 100% rename from spec/_files/partial_array.mustache rename to test/_files/partial_array.mustache diff --git a/spec/_files/partial_array.partial b/test/_files/partial_array.partial similarity index 100% rename from spec/_files/partial_array.partial rename to test/_files/partial_array.partial diff --git a/spec/_files/partial_array.txt b/test/_files/partial_array.txt similarity index 100% rename from spec/_files/partial_array.txt rename to test/_files/partial_array.txt diff --git a/test/_files/partial_array_of_partials.js b/test/_files/partial_array_of_partials.js new file mode 100644 index 0000000..03f13c9 --- /dev/null +++ b/test/_files/partial_array_of_partials.js @@ -0,0 +1,8 @@ +({ + numbers: [ + {i: '1'}, + {i: '2'}, + {i: '3'}, + {i: '4'} + ] +}) diff --git a/spec/_files/partial_array_of_partials.mustache b/test/_files/partial_array_of_partials.mustache similarity index 100% rename from spec/_files/partial_array_of_partials.mustache rename to test/_files/partial_array_of_partials.mustache diff --git a/spec/_files/partial_array_of_partials.partial b/test/_files/partial_array_of_partials.partial similarity index 100% rename from spec/_files/partial_array_of_partials.partial rename to test/_files/partial_array_of_partials.partial diff --git a/spec/_files/partial_array_of_partials.txt b/test/_files/partial_array_of_partials.txt similarity index 100% rename from spec/_files/partial_array_of_partials.txt rename to test/_files/partial_array_of_partials.txt diff --git a/test/_files/partial_array_of_partials_implicit.js b/test/_files/partial_array_of_partials_implicit.js new file mode 100644 index 0000000..9ec0c00 --- /dev/null +++ b/test/_files/partial_array_of_partials_implicit.js @@ -0,0 +1,3 @@ +({ + numbers: ['1', '2', '3', '4'] +}) diff --git a/spec/_files/partial_array_of_partials_implicit.mustache b/test/_files/partial_array_of_partials_implicit.mustache similarity index 100% rename from spec/_files/partial_array_of_partials_implicit.mustache rename to test/_files/partial_array_of_partials_implicit.mustache diff --git a/spec/_files/partial_array_of_partials_implicit.partial b/test/_files/partial_array_of_partials_implicit.partial similarity index 100% rename from spec/_files/partial_array_of_partials_implicit.partial rename to test/_files/partial_array_of_partials_implicit.partial diff --git a/spec/_files/partial_array_of_partials_implicit.txt b/test/_files/partial_array_of_partials_implicit.txt similarity index 100% rename from spec/_files/partial_array_of_partials_implicit.txt rename to test/_files/partial_array_of_partials_implicit.txt diff --git a/test/_files/partial_empty.js b/test/_files/partial_empty.js new file mode 100644 index 0000000..82b8c22 --- /dev/null +++ b/test/_files/partial_empty.js @@ -0,0 +1,3 @@ +({ + foo: 1 +}) diff --git a/spec/_files/partial_empty.mustache b/test/_files/partial_empty.mustache similarity index 100% rename from spec/_files/partial_empty.mustache rename to test/_files/partial_empty.mustache diff --git a/spec/_files/partial_empty.partial b/test/_files/partial_empty.partial similarity index 100% rename from spec/_files/partial_empty.partial rename to test/_files/partial_empty.partial diff --git a/spec/_files/partial_empty.txt b/test/_files/partial_empty.txt similarity index 100% rename from spec/_files/partial_empty.txt rename to test/_files/partial_empty.txt diff --git a/spec/_files/partial_recursion.js b/test/_files/partial_recursion.js similarity index 78% rename from spec/_files/partial_recursion.js rename to test/_files/partial_recursion.js index 3094eba..e39d64d 100644 --- a/spec/_files/partial_recursion.js +++ b/test/_files/partial_recursion.js @@ -1,4 +1,4 @@ -var partial_recursion = { +({ name: '1', kids: [ { @@ -8,4 +8,4 @@ var partial_recursion = { ] } ] -}; +}) diff --git a/spec/_files/partial_recursion.mustache b/test/_files/partial_recursion.mustache similarity index 100% rename from spec/_files/partial_recursion.mustache rename to test/_files/partial_recursion.mustache diff --git a/spec/_files/partial_recursion.partial b/test/_files/partial_recursion.partial similarity index 72% rename from spec/_files/partial_recursion.partial rename to test/_files/partial_recursion.partial index ba6f8fa..457d2a0 100644 --- a/spec/_files/partial_recursion.partial +++ b/test/_files/partial_recursion.partial @@ -1,4 +1,4 @@ {{name}} {{#children}} {{>partial}} -{{/children}} +{{/children}} \ No newline at end of file diff --git a/spec/_files/partial_recursion.txt b/test/_files/partial_recursion.txt similarity index 100% rename from spec/_files/partial_recursion.txt rename to test/_files/partial_recursion.txt diff --git a/test/_files/partial_template.js b/test/_files/partial_template.js new file mode 100644 index 0000000..a913f87 --- /dev/null +++ b/test/_files/partial_template.js @@ -0,0 +1,6 @@ +({ + title: function () { + return "Welcome"; + }, + again: "Goodbye" +}) diff --git a/spec/_files/partial_template.mustache b/test/_files/partial_template.mustache similarity index 100% rename from spec/_files/partial_template.mustache rename to test/_files/partial_template.mustache diff --git a/spec/_files/partial_template.partial b/test/_files/partial_template.partial similarity index 100% rename from spec/_files/partial_template.partial rename to test/_files/partial_template.partial diff --git a/spec/_files/partial_template.txt b/test/_files/partial_template.txt similarity index 100% rename from spec/_files/partial_template.txt rename to test/_files/partial_template.txt diff --git a/spec/_files/partial_view.js b/test/_files/partial_view.js similarity index 60% rename from spec/_files/partial_view.js rename to test/_files/partial_view.js index 4ea8a12..3ad70d3 100644 --- a/spec/_files/partial_view.js +++ b/test/_files/partial_view.js @@ -1,16 +1,14 @@ -var partial_view = { - greeting: function() { +({ + greeting: function () { return "Welcome"; }, - - farewell: function() { + farewell: function () { return "Fair enough, right?"; }, - name: "Chris", value: 10000, - taxed_value: function() { + taxed_value: function () { return this.value - (this.value * 0.4); }, in_ca: true -}; +}) diff --git a/spec/_files/partial_view.mustache b/test/_files/partial_view.mustache similarity index 100% rename from spec/_files/partial_view.mustache rename to test/_files/partial_view.mustache diff --git a/spec/_files/partial_view.partial b/test/_files/partial_view.partial similarity index 89% rename from spec/_files/partial_view.partial rename to test/_files/partial_view.partial index 2fea632..03df206 100644 --- a/spec/_files/partial_view.partial +++ b/test/_files/partial_view.partial @@ -2,4 +2,4 @@ Hello {{name}} You have just won ${{value}}! {{#in_ca}} Well, ${{ taxed_value }}, after taxes. -{{/in_ca}} +{{/in_ca}} \ No newline at end of file diff --git a/spec/_files/partial_view.txt b/test/_files/partial_view.txt similarity index 100% rename from spec/_files/partial_view.txt rename to test/_files/partial_view.txt diff --git a/spec/_files/partial_whitespace.js b/test/_files/partial_whitespace.js similarity index 59% rename from spec/_files/partial_whitespace.js rename to test/_files/partial_whitespace.js index f4b1ee5..3ad70d3 100644 --- a/spec/_files/partial_whitespace.js +++ b/test/_files/partial_whitespace.js @@ -1,17 +1,14 @@ -var partial_whitespace = { - greeting: function() { +({ + greeting: function () { return "Welcome"; }, - - farewell: function() { + farewell: function () { return "Fair enough, right?"; }, - name: "Chris", value: 10000, - taxed_value: function() { + taxed_value: function () { return this.value - (this.value * 0.4); }, in_ca: true -}; - +}) diff --git a/spec/_files/partial_whitespace.mustache b/test/_files/partial_whitespace.mustache similarity index 100% rename from spec/_files/partial_whitespace.mustache rename to test/_files/partial_whitespace.mustache diff --git a/spec/_files/partial_whitespace.partial b/test/_files/partial_whitespace.partial similarity index 87% rename from spec/_files/partial_whitespace.partial rename to test/_files/partial_whitespace.partial index 9c46084..30de8f6 100644 --- a/spec/_files/partial_whitespace.partial +++ b/test/_files/partial_whitespace.partial @@ -2,4 +2,4 @@ Hello {{ name}} You have just won ${{value }}! {{# in_ca }} Well, ${{ taxed_value }}, after taxes. -{{/ in_ca }} +{{/ in_ca }} \ No newline at end of file diff --git a/spec/_files/partial_whitespace.txt b/test/_files/partial_whitespace.txt similarity index 100% rename from spec/_files/partial_whitespace.txt rename to test/_files/partial_whitespace.txt diff --git a/spec/_files/recursion_with_same_names.js b/test/_files/recursion_with_same_names.js similarity index 56% rename from spec/_files/recursion_with_same_names.js rename to test/_files/recursion_with_same_names.js index 5cceb08..ce26502 100644 --- a/spec/_files/recursion_with_same_names.js +++ b/test/_files/recursion_with_same_names.js @@ -1,8 +1,8 @@ -var recursion_with_same_names = { +({ name: 'name', description: 'desc', terms: [ {name: 't1', index: 0}, - {name: 't2', index: 1}, + {name: 't2', index: 1} ] -}; \ No newline at end of file +}) diff --git a/spec/_files/recursion_with_same_names.mustache b/test/_files/recursion_with_same_names.mustache similarity index 100% rename from spec/_files/recursion_with_same_names.mustache rename to test/_files/recursion_with_same_names.mustache diff --git a/spec/_files/recursion_with_same_names.txt b/test/_files/recursion_with_same_names.txt similarity index 100% rename from spec/_files/recursion_with_same_names.txt rename to test/_files/recursion_with_same_names.txt diff --git a/spec/_files/reuse_of_enumerables.js b/test/_files/reuse_of_enumerables.js similarity index 69% rename from spec/_files/reuse_of_enumerables.js rename to test/_files/reuse_of_enumerables.js index a3168d3..4368b57 100644 --- a/spec/_files/reuse_of_enumerables.js +++ b/test/_files/reuse_of_enumerables.js @@ -1,6 +1,6 @@ -var reuse_of_enumerables = { +({ terms: [ {name: 't1', index: 0}, {name: 't2', index: 1} ] -}; \ No newline at end of file +}) diff --git a/spec/_files/reuse_of_enumerables.mustache b/test/_files/reuse_of_enumerables.mustache similarity index 100% rename from spec/_files/reuse_of_enumerables.mustache rename to test/_files/reuse_of_enumerables.mustache diff --git a/spec/_files/reuse_of_enumerables.txt b/test/_files/reuse_of_enumerables.txt similarity index 100% rename from spec/_files/reuse_of_enumerables.txt rename to test/_files/reuse_of_enumerables.txt diff --git a/spec/_files/section_as_context.js b/test/_files/section_as_context.js similarity index 53% rename from spec/_files/section_as_context.js rename to test/_files/section_as_context.js index 81ca1be..425b29c 100644 --- a/spec/_files/section_as_context.js +++ b/test/_files/section_as_context.js @@ -1,7 +1,10 @@ -var section_as_context = { +({ a_object: { title: 'this is an object', description: 'one of its attributes is a list', - a_list: [{label: 'listitem1'}, {label: 'listitem2'}] + a_list: [ + {label: 'listitem1'}, + {label: 'listitem2'} + ] } -}; +}) diff --git a/spec/_files/section_as_context.mustache b/test/_files/section_as_context.mustache similarity index 100% rename from spec/_files/section_as_context.mustache rename to test/_files/section_as_context.mustache diff --git a/spec/_files/section_as_context.txt b/test/_files/section_as_context.txt similarity index 100% rename from spec/_files/section_as_context.txt rename to test/_files/section_as_context.txt diff --git a/spec/_files/simple.js b/test/_files/simple.js similarity index 67% rename from spec/_files/simple.js rename to test/_files/simple.js index 30f9834..1d8d6f4 100644 --- a/spec/_files/simple.js +++ b/test/_files/simple.js @@ -1,8 +1,8 @@ -var simple = { +({ name: "Chris", value: 10000, - taxed_value: function() { + taxed_value: function () { return this.value - (this.value * 0.4); }, in_ca: true -}; +}) diff --git a/spec/_files/simple.mustache b/test/_files/simple.mustache similarity index 100% rename from spec/_files/simple.mustache rename to test/_files/simple.mustache diff --git a/spec/_files/simple.txt b/test/_files/simple.txt similarity index 100% rename from spec/_files/simple.txt rename to test/_files/simple.txt diff --git a/test/_files/string_as_context.js b/test/_files/string_as_context.js new file mode 100644 index 0000000..e8bb4da --- /dev/null +++ b/test/_files/string_as_context.js @@ -0,0 +1,4 @@ +({ + a_string: 'aa', + a_list: ['a','b','c'] +}) diff --git a/spec/_files/string_as_context.mustache b/test/_files/string_as_context.mustache similarity index 100% rename from spec/_files/string_as_context.mustache rename to test/_files/string_as_context.mustache diff --git a/spec/_files/string_as_context.txt b/test/_files/string_as_context.txt similarity index 100% rename from spec/_files/string_as_context.txt rename to test/_files/string_as_context.txt diff --git a/spec/_files/two_in_a_row.js b/test/_files/two_in_a_row.js similarity index 60% rename from spec/_files/two_in_a_row.js rename to test/_files/two_in_a_row.js index 09c1809..9c17c11 100644 --- a/spec/_files/two_in_a_row.js +++ b/test/_files/two_in_a_row.js @@ -1,4 +1,4 @@ -var two_in_a_row = { +({ name: "Joe", greeting: "Welcome" -}; +}) diff --git a/spec/_files/two_in_a_row.mustache b/test/_files/two_in_a_row.mustache similarity index 100% rename from spec/_files/two_in_a_row.mustache rename to test/_files/two_in_a_row.mustache diff --git a/spec/_files/two_in_a_row.txt b/test/_files/two_in_a_row.txt similarity index 100% rename from spec/_files/two_in_a_row.txt rename to test/_files/two_in_a_row.txt diff --git a/test/_files/two_sections.js b/test/_files/two_sections.js new file mode 100644 index 0000000..b4100a5 --- /dev/null +++ b/test/_files/two_sections.js @@ -0,0 +1 @@ +({}) diff --git a/spec/_files/two_sections.mustache b/test/_files/two_sections.mustache similarity index 100% rename from spec/_files/two_sections.mustache rename to test/_files/two_sections.mustache diff --git a/spec/_files/two_sections.txt b/test/_files/two_sections.txt similarity index 100% rename from spec/_files/two_sections.txt rename to test/_files/two_sections.txt diff --git a/test/_files/unescaped.js b/test/_files/unescaped.js new file mode 100644 index 0000000..b6d064f --- /dev/null +++ b/test/_files/unescaped.js @@ -0,0 +1,5 @@ +({ + title: function () { + return "Bear > Shark"; + } +}) diff --git a/spec/_files/unescaped.mustache b/test/_files/unescaped.mustache similarity index 100% rename from spec/_files/unescaped.mustache rename to test/_files/unescaped.mustache diff --git a/spec/_files/unescaped.txt b/test/_files/unescaped.txt similarity index 100% rename from spec/_files/unescaped.txt rename to test/_files/unescaped.txt diff --git a/spec/_files/whitespace.js b/test/_files/whitespace.js similarity index 60% rename from spec/_files/whitespace.js rename to test/_files/whitespace.js index 97f53c1..f41cb56 100644 --- a/spec/_files/whitespace.js +++ b/test/_files/whitespace.js @@ -1,4 +1,4 @@ -var whitespace = { +({ tag1: "Hello", tag2: "World" -}; +}) diff --git a/spec/_files/whitespace.mustache b/test/_files/whitespace.mustache similarity index 100% rename from spec/_files/whitespace.mustache rename to test/_files/whitespace.mustache diff --git a/spec/_files/whitespace.txt b/test/_files/whitespace.txt similarity index 100% rename from spec/_files/whitespace.txt rename to test/_files/whitespace.txt diff --git a/test/context_test.js b/test/context_test.js new file mode 100644 index 0000000..71c5830 --- /dev/null +++ b/test/context_test.js @@ -0,0 +1,47 @@ +var assert = require("assert"), + vows = require("vows"), + Context = require("./../mustache").Context; + +vows.describe("Mustache.Context").addBatch({ + "A Context": { + topic: function () { + var view = { name: 'parent', message: 'hi', a: { b: 'b' } }; + var context = new Context(view); + return context; + }, + "should be able to lookup properties of its own view": function (context) { + assert.equal(context.lookup("name"), "parent"); + }, + "should be able to lookup nested properties of its own view": function (context) { + assert.equal(context.lookup("a.b"), "b"); + }, + "when pushed": { + topic: function (context) { + var view = { name: 'child', c: { d: 'd' } }; + return context.push(view); + }, + "should return the child context": function (context) { + assert.equal(context.view.name, "child"); + assert.equal(context.parent.view.name, "parent"); + }, + "should be able to lookup properties of its own view": function (context) { + assert.equal(context.lookup("name"), "child"); + }, + "should be able to lookup properties of the parent context's view": function (context) { + assert.equal(context.lookup("message"), "hi"); + }, + "should be able to lookup nested properties of its own view": function (context) { + assert.equal(context.lookup("c.d"), "d"); + }, + "should be able to lookup nested properties of its parent view": function (context) { + assert.equal(context.lookup("a.b"), "b"); + } + } // when pushed + }, // A Context + "make": { + "should return the same object when given a Context": function () { + var context = new Context; + assert.strictEqual(Context.make(context), context); + } + } +}).export(module); diff --git a/test/helper.rb b/test/helper.rb new file mode 100644 index 0000000..baadb94 --- /dev/null +++ b/test/helper.rb @@ -0,0 +1,48 @@ +require 'test/unit' + +module Mustache + extend self + + ROOT = File.expand_path('../..', __FILE__) + TEST = File.join(ROOT, 'test') + TEST_FILES = File.join(TEST, '_files') + + MUSTACHE_JS = File.read(File.join(ROOT, 'mustache.js')) + + TESTS = Dir.glob(File.join(TEST_FILES, '*.js')).map do |name| + File.basename name, '.js' + end + + NODE_PATH = `which node`.strip + JS_PATH = `which js`.strip + JSC_PATH = "/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc" + RHINO_JAR = "org.mozilla.javascript.tools.shell.Main" + + def javascript_engines + %w[v8 spidermonkey javascriptcore rhino] + end + + def available_javascript_engines + javascript_engines.select {|engine| send("has_#{engine}?") } + end + + def has_any_engines? + available_javascript_engines.any? + end + + def has_v8? + File.exist?(NODE_PATH) + end + + def has_spidermonkey? + File.exist?(JS_PATH) + end + + def has_javascriptcore? + File.exist?(JSC_PATH) + end + + def has_rhino? + `java #{RHINO_JAR} 'foo' 2>&1` !~ /ClassNotFoundException/ + end +end diff --git a/test/integration.rb b/test/integration.rb new file mode 100644 index 0000000..d3d482d --- /dev/null +++ b/test/integration.rb @@ -0,0 +1,76 @@ +require File.expand_path('../helper', __FILE__) +require 'fileutils' +require 'json' + +module Mustache + class Test < Test::Unit::TestCase + + def self.run_tests_for(engine) + TESTS.each do |test| + define_method("test_#{engine}_#{test}") do + template, view, partial, expect = load_test(test) + + assert_equal expect, run_js(engine, <<-JS).chomp + try { + #{boilerplate_for(engine)} + var template = #{template.to_json}; + var view = #{view}; + var partials = {partial: #{partial.to_json}}; + print(Mustache.render(template, view, partials)); + } catch(e) { + print('ERROR: ' + e.message); + } + JS + end + end + end + + unless Mustache.has_any_engines? + abort "ERROR: Please install node, SpiderMonkey, JavaScriptCore or Rhino" + end + + run_tests_for :v8 if Mustache.has_v8? + run_tests_for :spidermonkey if Mustache.has_spidermonkey? + run_tests_for :javascriptcore if Mustache.has_javascriptcore? + run_tests_for :rhino if Mustache.has_rhino? + + private + + def load_test(name) + template_file = File.join(TEST_FILES, "#{name}.mustache") + view_file = File.join(TEST_FILES, "#{name}.js") + partial_file = File.join(TEST_FILES, "#{name}.partial") + expect_file = File.join(TEST_FILES, "#{name}.txt") + + [template_file, view_file, partial_file, expect_file].map do |file| + File.exist?(file) ? File.read(file) : "" + end + end + + def runner_file + "runner.js" + end + + def run_js(engine, js) + cmd = case engine + when :v8 then NODE_PATH + when :spidermonkey then JS_PATH + when :javascriptcore then JSC_PATH + when :rhino then "java #{RHINO_JAR}" + end + + File.open(runner_file, 'w') {|file| file.write(js) } + + `#{cmd} #{runner_file}` + ensure + FileUtils.rm_r(runner_file) + end + + def boilerplate_for(engine) + boilerplate = MUSTACHE_JS + boilerplate += "\nvar print = console.log;" if engine == :v8 + boilerplate + end + + end +end diff --git a/test/parse_test.js b/test/parse_test.js new file mode 100644 index 0000000..8586a25 --- /dev/null +++ b/test/parse_test.js @@ -0,0 +1,67 @@ +var assert = require("assert"), + vows = require("vows"), + parse = require("./../mustache").parse; + +// A map of templates to their expected token output. +var expectations = { + "{{hi}}" : [ { type: 'name', value: 'hi' } ], + "{{hi.world}}" : [ { type: 'name', value: 'hi.world' } ], + "{{hi . world}}" : [ { type: 'name', value: 'hi . world' } ], + "{{ hi}}" : [ { type: 'name', value: 'hi' } ], + "{{hi }}" : [ { type: 'name', value: 'hi' } ], + "{{ hi }}" : [ { type: 'name', value: 'hi' } ], + "{{{hi}}}" : [ { type: '{', value: 'hi' } ], + "{{!hi}}" : [ { type: '!', value: 'hi' } ], + "{{! hi}}" : [ { type: '!', value: 'hi' } ], + "{{! hi }}" : [ { type: '!', value: 'hi' } ], + "{{ !hi}}" : [ { type: '!', value: 'hi' } ], + "{{ ! hi}}" : [ { type: '!', value: 'hi' } ], + "{{ ! hi }}" : [ { type: '!', value: 'hi' } ], + "a{{hi}}" : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' } ], + "a {{hi}}" : [ { type: 'text', value: 'a ' }, { type: 'name', value: 'hi' } ], + " a{{hi}}" : [ { type: 'text', value: ' a' }, { type: 'name', value: 'hi' } ], + " a {{hi}}" : [ { type: 'text', value: ' a ' }, { type: 'name', value: 'hi' } ], + "a{{hi}}b" : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' }, { type: 'text', value: 'b' } ], + "a{{hi}} b" : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' }, { type: 'text', value: ' b' } ], + "a{{hi}}b " : [ { type: 'text', value: 'a' }, { type: 'name', value: 'hi' }, { type: 'text', value: 'b ' } ], + "a\n{{hi}} b \n" : [ { type: 'text', value: 'a\n' }, { type: 'name', value: 'hi' }, { type: 'text', value: ' b \n' } ], + "a\n {{hi}} \nb" : [ { type: 'text', value: 'a\n ' }, { type: 'name', value: 'hi' }, { type: 'text', value: ' \nb' } ], + "a\n {{!hi}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '!', value: 'hi' }, { type: 'text', value: 'b' } ], + "a\n{{#a}}{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}{{/a}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n{{#a}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}\n{{/a}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n{{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [] }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: 'b' } ], + "a\n{{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [ { type: '#', value: 'b', tokens: [] } ] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [ { type: '#', value: 'b', tokens: [] } ] }, { type: 'text', value: 'b' } ], + "a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}} \nb" : [ { type: 'text', value: 'a\n' }, { type: '#', value: 'a', tokens: [ { type: '#', value: 'b', tokens: [] } ] }, { type: 'text', value: 'b' } ], + "{{>abc}}" : [ { type: '>', value: 'abc' } ], + "{{> abc }}" : [ { type: '>', value: 'abc' } ], + "{{ > abc }}" : [ { type: '>', value: 'abc' } ], + "{{=<% %>=}}" : [ { type: '=', value: '<% %>' } ], + "{{= <% %> =}}" : [ { type: '=', value: '<% %>' } ], + "{{=<% %>=}}<%={{ }}=%>" : [ { type: '=', value: '<% %>' }, { type: '=', value: '{{ }}' } ], + "{{=<% %>=}}<%hi%>" : [ { type: '=', value: '<% %>' }, { type: 'name', value: 'hi' } ], + "{{#a}}{{/a}}hi{{#b}}{{/b}}\n" : [ { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'hi' }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: '\n' } ], + "{{a}}\n{{b}}\n\n{{#c}}\n{{/c}}\n" : [ { type: 'name', value: 'a' }, { type: 'text', value: '\n' }, { type: 'name', value: 'b' }, { type: 'text', value: '\n\n' }, { type: '#', value: 'c', tokens: [] } ], + "{{#foo}}\n {{#a}}\n {{b}}\n {{/a}}\n{{/foo}}\n" + : [ { type: "#", value: "foo", tokens: [ { type: "#", value: "a", tokens: [ { type: "text", value: " " }, { type: "name", value: "b" }, { type: "text", value: "\n" } ] } ] } ] +}; + +var spec = {}; + +for (var template in expectations) { + (function (template, tokens) { + spec["knows how to parse " + JSON.stringify(template)] = function () { + assert.deepEqual(parse(template), tokens); + }; + })(template, expectations[template]); +} + +vows.describe("Mustache.parse").addBatch({ + "parse": spec +}).export(module); diff --git a/test/render_test.js b/test/render_test.js new file mode 100644 index 0000000..9638638 --- /dev/null +++ b/test/render_test.js @@ -0,0 +1,63 @@ +var fs = require("fs"), + path = require("path"), + assert = require("assert"), + vows = require("vows"); + +var Mustache = require(path.join(__dirname, "..", "mustache")); +var _files = path.join(__dirname, "_files"); + +function getContents(testName, ext) { + var file = path.join(_files, testName + "." + ext); + try { + return fs.readFileSync(file, "utf8"); + } catch (e) {} +} + +// You can put the name of a specific test to run in the TEST environment +// variable (e.g. TEST=backslashes vows test/render_test.js) +var testToRun = process.env["TEST"]; + +var testNames; +if (testToRun) { + testNames = [testToRun]; +} else { + testNames = fs.readdirSync(_files).filter(function (file) { + return (/\.js$/).test(file); + }).map(function (file) { + return path.basename(file).replace(/\.js$/, ""); + }); +} + +var spec = {}; + +testNames.forEach(function (testName) { + var view = getContents(testName, "js"); + + if (view) { + view = eval(view); + } else { + console.log("Cannot find view for test: " + testName); + process.exit(); + } + + var template = getContents(testName, "mustache"); + var expect = getContents(testName, "txt"); + var partial = getContents(testName, "partial"); + + spec["knows how to render " + testName] = function () { + Mustache.clearCache(); + + var output; + if (partial) { + output = Mustache.render(template, view, {partial: partial}); + } else { + output = Mustache.render(template, view); + } + + assert.equal(output, expect); + }; +}); + +vows.describe("Mustache.render").addBatch({ + "render": spec +}).export(module); diff --git a/test/scanner_test.js b/test/scanner_test.js new file mode 100644 index 0000000..79def2f --- /dev/null +++ b/test/scanner_test.js @@ -0,0 +1,117 @@ +var assert = require("assert"), + vows = require("vows"), + Scanner = require("./../mustache").Scanner; + +vows.describe("Mustache.Scanner").addBatch({ + "A Scanner": { + "for an empty string": { + topic: new Scanner(""), + "should be at the end of the string": function () { + var scanner = new Scanner(""); + assert(scanner.eos()); + } + }, + "for a non-empty string": { + topic: "a b c", + "when calling scan": { + "when the regexp matches the entire string": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scan(/a b c/); + this.callback(scanner, match); + }, + "it should return the entire string": function (scanner, match) { + assert.equal(match, scanner.string); + }, + "it should be at the end of the string": function (scanner, match) { + assert(scanner.eos()); + } + }, // when the regexp matches the entire string + "when the regexp matches": { + "at the 0th index": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scan(/a/); + this.callback(scanner, match); + }, + "it should return the portion of the string that was matched": function (scanner, match) { + assert.equal(match, "a"); + }, + "it should advance the internal pointer the length of the match": function (scanner, match) { + assert.equal(scanner.pos, 1); + } + }, // at the 0th index + "at some index other than 0": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scan(/b/); + this.callback(scanner, match); + }, + "it should return null": function (scanner, match) { + assert.equal(match, null); + }, + "it should not advance the internal pointer": function (scanner, match) { + assert.equal(scanner.pos, 0); + } + } // at some index other than 0 + }, // when the regexp matches + "when the regexp doesn't match": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scan(/z/); + this.callback(scanner, match); + }, + "it should return null": function (scanner, match) { + assert.equal(match, null); + }, + "it should not advance the internal pointer": function (scanner, match) { + assert.equal(scanner.pos, 0); + } + } + }, // when calling scan + "when calling scanUntil": { + "when the regexp matches": { + "at the 0th index": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scanUntil(/a/); + this.callback(scanner, match); + }, + "it should return null": function (scanner, match) { + assert.equal(match, null) + }, + "it should not advance the internal pointer": function (scanner, match) { + assert.equal(scanner.pos, 0); + } + }, + "at index 2": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scanUntil(/b/); + this.callback(scanner, match); + }, + "it should return the portion of the string it scanned": function (scanner, match) { + assert.equal(match, "a "); + }, + "it should advance the internal pointer the length of the match": function (scanner, match) { + assert.equal(scanner.pos, 2); + } + } + }, // when the regexp matches + "when the regexp doesn't match": { + topic: function (string) { + var scanner = new Scanner(string); + var match = scanner.scanUntil(/z/); + this.callback(scanner, match); + }, + "it should return the entire string": function (scanner, match) { + assert.equal(match, scanner.string); + }, + "it should be at the end of the string": function (scanner, match) { + assert(scanner.eos()); + } + } // when the regexp doesn't match + } // when calling scanUntil + } // for a non-empty string + } +}).export(module); diff --git a/test/unit.rb b/test/unit.rb new file mode 100644 index 0000000..eb473bd --- /dev/null +++ b/test/unit.rb @@ -0,0 +1,7 @@ +require File.expand_path('../helper', __FILE__) + +if Mustache.has_v8? + exec "vows #{Mustache::TEST}/*_test.js" +else + abort "ERROR: Please install node" +end