Skip to content

Instantly share code, notes, and snippets.

@raycmorgan
Created January 25, 2010 17:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raycmorgan/286071 to your computer and use it in GitHub Desktop.
Save raycmorgan/286071 to your computer and use it in GitHub Desktop.
Mu.js -- pre refactor
var Posix = require('posix'),
Path = require('path'),
sys = require('sys');
var Mu = exports;
// capture the base proto
var baseProto = ({}).__proto__;
Mu.cache = {};
Mu.templatePaths = ['.', ''];
Mu.templateExtension = ".mu";
// Errors
Mu.CompileError = function (msg) {
this.message = msg || "CompileError";
}
Mu.CompileError.prototype.__proto__ = Error.prototype;
Mu.FileNotFound = function (msg) {
this.message = msg || "FileNotFound";
}
Mu.FileNotFound.prototype.__proto__ = Error.prototype;
//
Mu.compile = function Mu_compile(filename) {
var compilePromise = new process.Promise();
var promise = new process.Promise();
compilePromise.addCallback(function (source) {
promise.emitSuccess(Mu.compileSource(source));
})
.addErrback(function (e) {
promise.emitError(e);
});
Mu.compileFile(compilePromise, filename);
return promise;
}
Mu.compileSource = function Mu_compileSource(source) {
var M = Mu;
source =
'(function (context) {\n' +
' var Mu = M, result = "";\n' +
' ' + source +
' return result;\n' +
'})';
return eval(source);
}
Mu.compileFile = function Mu_compileFile(promise, filename, i) {
i = i || 0;
if (i < Mu.templatePaths.length) {
var path = Path.join(Mu.templatePaths[i], filename + Mu.templateExtension);
Posix.cat(path)
.addCallback(function (source) {
try {
var compiled = compilePart(source.replace(/\n/g, '\\n'));
promise.emitSuccess(compiled);
} catch (e) {
promise.emitError(e ||
new Mu.CompileError("Compile Error: " + e.message)
);
}
})
.addErrback(function () {
Mu.compileFile(promise, filename, ++i);
});
} else {
promise.emitError(new Mu.FileNotFound());
}
}
Mu.compilePartial = function Mu_compilePartial(filename) {
var promise = new process.Promise();
Mu.compileFile(promise, filename);
return promise.wait(); // wait is in here for temp solution.
}
/**
* HTML escapes a string.
*
* @param {String} string The string to escape.
* @returns {String} The escaped string.
*/
Mu.escape = function Mu_escape(string) {
return string.replace(/[&<>"]/g, escapeReplace);
}
/**
* Normalizes the param by calling it if it is a function, calling .toString
* or simply returning a blank string.
*
* @param {Object} val The value to normalize.
* @returns {String} The normalized value.
*/
Mu.normalize = function Mu_normalize(context, name) {
var val = context[name];
if (typeof(val) === 'function') {
val = val.call(context);
}
return val === 0 ? '0' : val ? val.toString() : '';
}
/**
* Depending on the val passed into this function, different things happen.
*
* If val is a boolean, fn is called if it is true and the return value is
* returned.
* If val is an Array, fn is called once for each element in the array and
* the strings returned from those calls are collected and returned.
* Else if val is defined fn is called with the val.
* Otherwise an empty string is returned.
*
* @param {Object} context The context that fn is called with if the val
* is a true boolean.
* @param {Boolean|Array|Object} val The value that decides what happens.
* @param {Function} fn The callback.
*/
Mu.enumerable = function Mu_enumerable(context, val, fn) {
if (typeof(val) === 'function') {
val = val.call(context);
}
if (typeof(val) === 'undefined') {
return '';
}
if (typeof(val) === 'boolean') {
return val ? fn(context) : '';
}
if (val instanceof Array) {
var result = '';
for (var i = 0, len = val.length; i < len; i++) {
// val[i].__proto__ = context;
// result += fn(val[i]);
insertProto(val[i], context);
result += fn(val[i]);
insertProto(val[i], baseProto, context);
}
return result;
}
if (typeof(val) === 'object') {
// val.__proto__ = context;
// return fn(val);
insertProto(val, context);
var ret = fn(val);
insertProto(val, baseProto, context);
return ret;
}
return '';
}
function insertProto(obj, newProto, replaceProto) {
replaceProto = replaceProto || baseProto;
var proto = obj.__proto__;
while (proto !== replaceProto) {
obj = proto;
proto = proto.__proto__;
}
obj.__proto__ = newProto;
}
var begin = begin || '{{';
var end = end || '}}';
var beginNE = '{{{';
var endNE = '}}}';
var rbegin = new RegExp(begin + '$');
var rend = new RegExp(end + '$');
var rbeginNE = new RegExp(beginNE + '$');
var rendNE = new RegExp(endNE + '$');
function compilePart(source) {
var code = [];
var buffer = '';
for (var i = 0, len = source.length; i < source.length; i++) {
var letter = source.charAt(i);
buffer += letter;
// Check to see if we have hit a tag '{{'
if (buffer.match(rbegin)) {
if (buffer.length > begin.length) {
buffer = buffer.substring(0, buffer.length - end.length);
code.push('result += "' + buffer.replace(/"/g, '\\"') + '";');
}
buffer = '';
i = compileTag(code, source, ++i);
continue;
}
}
if (buffer) {
code.push('result += "' + buffer.replace(/"/g, '\\"') + '";');
}
return code.join('');
}
function compileTag(code, source, i) {
var buffer = '';
var escape = true;
var comment = false;
var partial = false;
for (; i < source.length; i++) {
var letter = source.charAt(i);
if (letter === ' ') {
continue;
}
// Case when the tag is specifing an enumerable
if (letter === '#' && buffer === '') {
return compileEnumerableTag(code, source, ++i);
}
// Case when the tag is not suppose to be escaped
if (letter === '{' && buffer === '') {
escape = false;
continue;
}
if (letter === '!' && buffer === '') {
comment = true;
continue;
}
if (letter === '>' && buffer === '') {
partial = true;
continue;
}
buffer += letter;
if (comment && buffer.match(rend)) {
return i;
}
if (partial && buffer.match(rend)) {
buffer = buffer.substring(0, buffer.length - end.length);
var partialSource = Mu.compilePartial(buffer);
code.push(partialSource);
return i;
}
if (escape) {
if (buffer.match(rend)) {
buffer = buffer.substring(0, buffer.length - end.length);
code.push(
"result += Mu.escape(Mu.normalize(context, '" + buffer + "'));"
);
return i;
}
} else {
if (buffer.match(rendNE)) {
buffer = buffer.substring(0, buffer.length - endNE.length);
code.push("result += Mu.normalize(context, '" + buffer + "');");
return i;
}
}
}
throw "Unexpected End";
}
function compileEnumerableTag(code, source, i) {
var buffer = '';
for (; i < source.length; i++) {
var letter = source.charAt(i);
if (letter === ' ') {
continue;
}
buffer += letter;
if (buffer.match(rend)) {
buffer = buffer.substring(0, buffer.length - end.length);
(function inEnumerable(varName) {
i++;
var buffer = '';
var regexp = new RegExp('(.*){{ */ *' + varName + ' *}}$');
for (; i < source.length; i++) {
var letter = source.charAt(i);
buffer += letter;
var match = buffer.match(regexp);
if (match) {
code.push(
'result += Mu.enumerable(context, context.' + varName + ', function enum_' + varName + '(context) {',
'var result = "";',
compilePart(match[1], begin, end),
'return result;',
'});'
);
return;
}
}
throw new Mu.CompileError("Unexpected End");
})(buffer);
return i;
}
}
}
function escapeReplace(char) {
switch (char) {
case '<': return '&lt;';
case '>': return '&gt;';
case '&': return '&amp;';
case '"': return '&quot;';
default: return c;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment