Created
January 25, 2010 17:56
-
-
Save raycmorgan/286071 to your computer and use it in GitHub Desktop.
Mu.js -- pre refactor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 '<'; | |
case '>': return '>'; | |
case '&': return '&'; | |
case '"': return '"'; | |
default: return c; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment