Permalink
Browse files

All tests are now passing

  • Loading branch information...
RayMorgan
RayMorgan committed Dec 20, 2010
1 parent 5d413c7 commit 9b03e49d49291ac6ec41371f357e07bf2f89c500
Showing with 507 additions and 185 deletions.
  1. +38 −0 lib/mu-cli.js
  2. +94 −32 lib/mu.js
  3. +30 −7 lib/mu/parser.js
  4. +84 −58 lib/mu/renderer.js
  5. +0 −85 lib/mu/stream.js
  6. +7 −0 package.json
  7. +6 −0 test.txt
  8. +38 −0 test/bench.js
  9. +1 −0 test/examples/comments.html
  10. +5 −0 test/examples/comments.js
  11. +1 −0 test/examples/comments.txt
  12. +1 −0 test/examples/complex.html
  13. +19 −0 test/examples/complex.js
  14. +1 −0 test/examples/complex.txt
  15. +2 −0 test/examples/deep_partial.html
  16. +3 −0 test/examples/deep_partial.js
  17. +3 −0 test/examples/deep_partial.txt
  18. +6 −0 test/examples/delimiters.html
  19. +6 −0 test/examples/delimiters.js
  20. +6 −0 test/examples/delimiters.txt
  21. +1 −0 test/examples/error_not_found.html
  22. +1 −0 test/examples/error_not_found.js
  23. 0 test/examples/error_not_found.txt
  24. +1 −0 test/examples/escaped.html
  25. +5 −0 test/examples/escaped.js
  26. +1 −0 test/examples/escaped.txt
  27. +3 −0 test/examples/hash_instead_of_array.html
  28. +5 −0 test/examples/hash_instead_of_array.js
  29. +2 −0 test/examples/hash_instead_of_array.txt
  30. +1 −0 test/examples/inner_partial.html
  31. +2 −0 test/examples/inverted.html
  32. +3 −0 test/examples/inverted.js
  33. +2 −0 test/examples/inverted.txt
  34. +2 −0 test/examples/partial.html
  35. +3 −0 test/examples/partial.js
  36. +2 −0 test/examples/partial.txt
  37. +5 −0 test/examples/recursion_with_same_names.html
  38. +8 −0 test/examples/recursion_with_same_names.js
  39. +7 −0 test/examples/recursion_with_same_names.txt
  40. +5 −0 test/examples/reuse_of_enumerables.html
  41. +6 −0 test/examples/reuse_of_enumerables.js
  42. +9 −0 test/examples/reuse_of_enumerables.txt
  43. +5 −0 test/examples/simple.html
  44. +8 −0 test/examples/simple.js
  45. +4 −0 test/examples/simple.txt
  46. +1 −0 test/examples/two_in_a_row.html
  47. +4 −0 test/examples/two_in_a_row.js
  48. +1 −0 test/examples/two_in_a_row.txt
  49. +1 −0 test/examples/unescaped.html
  50. +5 −0 test/examples/unescaped.js
  51. +1 −0 test/examples/unescaped.txt
  52. +47 −0 test/run_examples_test.js
  53. +5 −3 try.js
View
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+
+var util = require('util'),
+ mu = require('./mu'),
+ parser = require('./mu/parser');
+
+var stdin = process.openStdin();
+
+
+stdin.on('data', function (template) {
+ template = template.toString('utf8');
+ var parsed = parser.parse(template);
+
+ if (~process.argv.indexOf('--tokens')) {
+ console.log(util.inspect(parsed, false, 20));
+ return;
+ }
+
+ process.argv.forEach(function (arg) {
+ if (arg.indexOf('--view=') === 0) {
+ try {
+ var view = eval('(' + arg.replace('--view=', '') + ')');
+ } catch (e) {
+ console.log('\nData: ' + arg.replace('--view=', ''));
+ throw e;
+ }
+
+ mu.renderText(template, view)
+ .on('data', function (d) {
+ util.print(d);
+ });
+ }
+ });
+
+});
+
+
+
View
126 lib/mu.js
@@ -1,9 +1,9 @@
var sys = require('sys'),
fs = require('fs'),
path = require('path'),
+ Stream = require('stream').Stream,
parser = require('./mu/parser'),
renderer = require('./mu/renderer'),
- Stream = require('./mu/stream'),
errors = require('./mu/errors');
var mu = module.exports = {};
@@ -12,66 +12,109 @@ mu.root = process.cwd();
mu.cache = {};
mu.fs = function (filename, callback) {
- fs.readFile(path.join(mu.root, filename), 'utf8', callback);
+ filename = filename.indexOf('/') === 0 ? filename : path.join(mu.root, filename);
+ fs.readFile(filename, 'utf8', callback);
}
-mu.compile = function(filename, callback) {
- var parsed;
+/**
+ * Compiles a file. The result will be cached as the filename and can be
+ * rendered via that name.
+ *
+ * @param {String} filename The name of the file to compile. If the filename
+ * starts with a '/', the file is assumed to be absolute, else it is
+ * relative to mu.root.
+ * @param {Function(err, Parsed)} callback The function to call when the file has been compiled.
+ */
+mu.compile = function(filename, callback, unique) {
+ var parsed,
+ unique = unique || {};
mu.fs(filename, function (err, contents) {
if (err) {
callback(new Error('file_not_found'));//errors.fileNotFound(mu.root, filename, err)));
}
parsed = parser.parse(contents);
- mu.cache[filename] = parsed;
- callback(undefined, parsed);
+ mu.cache[filename] = [parsed, unique];
+
+ var i = 0;
+ (function next(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ if (i < parsed.partials.length) {
+ mu.compile(parsed.partials[i], next, unique);
+ i++;
+
+ } else {
+ callback(undefined, parsed);
+ }
+ }());
});
}
+/**
+ * Compiles a string into the parsed form. If `name` is provided the text
+ * will be cached by that name. Alternatively you can pass the return
+ * into mu.render.
+ *
+ * @param {String} name (Optional) The name to cache the parsed form as.
+ * @param {String} template The template to parse.
+ * @param {Function(err, Parsed)} callback (Optional) An optional callback that
+ * will be called when the text is parsed. This is only to unify the
+ * API with mu.compile.
+ *
+ * @returns {Parsed} The parsed template unless `callback` is provided.
+ */
mu.compileText = function (name, template, callback) {
var parsed;
- callback = callback || function() { /* noop */ };
-
if (typeof template === 'undefined') {
template = name;
name = undefined;
}
- parsed = parser.parse(template);
- if (name) {
- mu.cache[name] = parsed;
+ try {
+ parsed = parser.parse(template);
+
+ if (name) {
+ mu.cache[name] = [parsed, {}];
+ }
+
+ if (callback) callback(undefined, parsed); else return parsed;
+
+ } catch (err) {
+ if (callback) callback(err); else throw err;
}
-
- callback(undefined, parsed); // this is to unify the API
- return parsed;
}
-mu.render = function (filename, view) {
- var stream;
+/**
+ * Renders the previously parsed filename or the parsed object.
+ *
+ * @param {String|Parsed} filenameOrParsed Filename or parsed object to render.
+ * @param {Object} view The data to use when renderings.
+ *
+ * @returns {Stream} The render stream.
+ * @throws {Error(template_not_in_cache)} If filename was not found in cache.
+ */
+mu.render = function (filenameOrParsed, view) {
+ var stream,
+ parsed = typeof filenameOrParsed === 'object' ?
+ filenameOrParsed :
+ mu.cache[filenameOrParsed];
- if (mu.cache[filename]) {
- stream = new Stream();
- process.nextTick(function () {
- renderer.render(mu.cache[filename].tokens, view, mu.cache, stream, function () {
- stream.emit('end');
- });
- });
- return stream;
+ if (parsed) {
+ return beginRender(parsed[0].tokens, view, mu.cache);
} else {
throw new Error('template_not_in_cache'); //errors.templateNotInCache(filename)));
}
}
-mu.renderText = function (template, view, partials, callback) {
- var name, parsed, tokens;
-
- if (typeof callback === 'undefined') {
- callback = partials;
- partials = {};
- }
+mu.renderText = function (template, view, partials) {
+ var name, parsed, tokens, stream;
+ partials = partials || {};
partials = shallowCopy(partials);
partials.__proto__ = mu.cache;
@@ -84,12 +127,31 @@ mu.renderText = function (template, view, partials, callback) {
parsed = parser.parse(template);
tokens = parsed.tokens;
- renderer.render(tokens, view, partials, callback);
+ return beginRender(tokens, view, partials);
}
/// Private API
+var BUFFER_LENGTH = 1024 * 8;
+
+function beginRender(tokens, view, partials) {
+ var stream = new Stream();
+ var count = 0;
+
+ process.nextTick(function () {
+ try {
+ renderer.render(tokens, view, partials, stream, function () {
+ stream.emit('end');
+ });
+ } catch (err) {
+ stream.emit('error', err);
+ }
+ });
+
+ return stream;
+}
+
function shallowCopy(obj) {
var o = {};
View
@@ -1,20 +1,24 @@
var sys = require('sys'),
- Buffer = require('buffer').Buffer;
+ Buffer = require('buffer').Buffer,
+
+ newline = '__MU_NEWLINE__',
+ newlineRegExp = new RegExp(newline, 'g');
exports.parse = function (template, options) {
var parser = new Parser(template, options);
return parser.tokenize();
}
function Parser(template, options) {
- this.template = template;
+ this.template = template.replace(/\n/g, newline);
this.options = options || {};
this.sections = [];
this.tokens = ['multi'];
this.partials = [];
this.buffer = this.template;
this.state = 'static'; // 'static' or 'tag'
+ this.currentLine = '';
this.setTag(['{{', '}}']);
}
@@ -31,6 +35,13 @@ Parser.prototype = {
return {partials: this.partials, tokens: this.tokens};
},
+
+ appendMultiContent: function (content) {
+ for (var i = 0, len = this.sections.length; i < len; i++) {
+ var multi = this.sections[i][1];
+ multi = multi[multi.length - 1][3] += content;
+ }
+ },
setTag: function (tags) {
this.otag = tags[0] || '{{';
@@ -44,14 +55,19 @@ Parser.prototype = {
index = this.buffer.length;
}
- var content = this.buffer.substring(0, index);
+ var content = this.buffer.substring(0, index).replace(newlineRegExp, '\n');
buffer = new Buffer(Buffer.byteLength(content));
if (content !== '') {
buffer.write(content, 'utf8', 0);
+ this.appendMultiContent(content);
this.tokens.push(['static', content, buffer]);
}
-
+
+ var line = this.currentLine + content;
+
+ this.currentLine = line.substring(line.lastIndexOf('\n') + 1, line.length);
+ // console.log('line:', this.buffer.lastIndexOf(newline) + newline.length, index, '>', this.currentLine, '/end');
this.buffer = this.buffer.substring(index + this.otag.length);
this.state = 'tag';
},
@@ -81,22 +97,27 @@ Parser.prototype = {
var sigil = match[1],
content = match[2].trim(),
- remainder = match[3];
+ remainder = match[3],
+ tagText = this.otag + this.buffer.substring(0, this.buffer.length - remainder.length);
+
switch (sigil) {
case undefined:
this.tokens.push(['mustache', 'etag', content]);
+ this.appendMultiContent(tagText);
break;
case '>':
case '<':
this.tokens.push(['mustache', 'partial', content]);
this.partials.push(content);
+ this.appendMultiContent(tagText);
break;
case '{':
case '&':
this.tokens.push(['mustache', 'utag', content]);
+ this.appendMultiContent(tagText);
break;
case '!':
@@ -106,14 +127,16 @@ Parser.prototype = {
case '=':
sys.puts("Changing tag: " + content)
this.setTag(content.split(' '));
+ this.appendMultiContent(tagText);
break;
case '#':
case '^':
+ this.appendMultiContent(tagText);
var type = sigil === '#' ? 'section' : 'inverted_section';
block = ['multi'];
- this.tokens.push(['mustache', type, content, block]);
+ this.tokens.push(['mustache', type, content, '', block]);
this.sections.push([content, this.tokens]);
this.tokens = block;
break;
@@ -129,12 +152,12 @@ Parser.prototype = {
} else if (name !== content) {
throw new Error("Unclosed section " + name);
}
+ this.appendMultiContent(tagText);
break;
}
this.buffer = remainder;
this.state = 'static';
-
}
}
Oops, something went wrong.

0 comments on commit 9b03e49

Please sign in to comment.