Created
April 17, 2013 20:12
-
-
Save dylanized/5407367 to your computer and use it in GitHub Desktop.
Modified version of ejs.js, with "snippet" and "resolveSnippet" stubbed out
This file contains hidden or 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
/*! | |
* EJS | |
* Copyright(c) 2012 TJ Holowaychuk <[email protected]> | |
* MIT Licensed | |
*/ | |
/** | |
* Module dependencies. | |
*/ | |
var utils = require('./utils') | |
, path = require('path') | |
, basename = path.basename | |
, dirname = path.dirname | |
, extname = path.extname | |
, join = path.join | |
, fs = require('fs') | |
, read = fs.readFileSync; | |
/** | |
* Filters. | |
* | |
* @type Object | |
*/ | |
var filters = exports.filters = require('./filters'); | |
/** | |
* Intermediate js cache. | |
* | |
* @type Object | |
*/ | |
var cache = {}; | |
/** | |
* Clear intermediate js cache. | |
* | |
* @api public | |
*/ | |
exports.clearCache = function(){ | |
cache = {}; | |
}; | |
/** | |
* Translate filtered code into function calls. | |
* | |
* @param {String} js | |
* @return {String} | |
* @api private | |
*/ | |
function filtered(js) { | |
return js.substr(1).split('|').reduce(function(js, filter){ | |
var parts = filter.split(':') | |
, name = parts.shift() | |
, args = parts.shift() || ''; | |
if (args) args = ', ' + args; | |
return 'filters.' + name + '(' + js + args + ')'; | |
}); | |
}; | |
/** | |
* Re-throw the given `err` in context to the | |
* `str` of ejs, `filename`, and `lineno`. | |
* | |
* @param {Error} err | |
* @param {String} str | |
* @param {String} filename | |
* @param {String} lineno | |
* @api private | |
*/ | |
function rethrow(err, str, filename, lineno){ | |
var lines = str.split('\n') | |
, start = Math.max(lineno - 3, 0) | |
, end = Math.min(lines.length, lineno + 3); | |
// Error context | |
var context = lines.slice(start, end).map(function(line, i){ | |
var curr = i + start + 1; | |
return (curr == lineno ? ' >> ' : ' ') | |
+ curr | |
+ '| ' | |
+ line; | |
}).join('\n'); | |
// Alter exception message | |
err.path = filename; | |
err.message = (filename || 'ejs') + ':' | |
+ lineno + '\n' | |
+ context + '\n\n' | |
+ err.message; | |
throw err; | |
} | |
/** | |
* Parse the given `str` of ejs, returning the function body. | |
* | |
* @param {String} str | |
* @return {String} | |
* @api public | |
*/ | |
var parse = exports.parse = function(str, options){ | |
var options = options || {} | |
, open = options.open || exports.open || '<%' | |
, close = options.close || exports.close || '%>' | |
, filename = options.filename | |
, compileDebug = options.compileDebug !== false | |
, buf = []; | |
buf.push('var buf = [];'); | |
if (false !== options._with) buf.push('\nwith (locals || {}) {'); | |
buf.push('\n buf.push(\''); | |
var lineno = 1; | |
var consumeEOL = false; | |
for (var i = 0, len = str.length; i < len; ++i) { | |
if (str.slice(i, open.length + i) == open) { | |
i += open.length | |
var prefix, postfix, line = (compileDebug ? '__stack.lineno=' : '') + lineno; | |
switch (str.substr(i, 1)) { | |
case '=': | |
prefix = "', escape((" + line + ', '; | |
postfix = ")), '"; | |
++i; | |
break; | |
case '-': | |
prefix = "', (" + line + ', '; | |
postfix = "), '"; | |
++i; | |
break; | |
default: | |
prefix = "');" + line + ';'; | |
postfix = "; buf.push('"; | |
} | |
var end = str.indexOf(close, i) | |
, js = str.substring(i, end) | |
, start = i | |
, include = null | |
, n = 0; | |
if ('-' == js[js.length-1]){ | |
js = js.substring(0, js.length - 2); | |
consumeEOL = true; | |
} | |
if (0 == js.trim().indexOf('include')) { | |
var name = js.trim().slice(7).trim(); | |
if (!filename) throw new Error('filename option is required for includes'); | |
var path = resolveInclude(name, filename); | |
include = read(path, 'utf8'); | |
include = exports.parse(include, { filename: path, _with: false, open: open, close: close }); | |
buf.push("' + (function(){" + include + "})() + '"); | |
js = ''; | |
} | |
if (0 == js.trim().indexOf('snippet')) { | |
var name = js.trim().slice(7).trim(); | |
if (!filename) throw new Error('filename option is required for includes'); | |
var path = resolveSnippet(name, filename); | |
include = read(path, 'utf8'); | |
include = exports.parse(include, { filename: path, _with: false, open: open, close: close }); | |
buf.push("' + (function(){" + include + "})() + '"); | |
js = ''; | |
} | |
while (~(n = js.indexOf("\n", n))) n++, lineno++; | |
if (js.substr(0, 1) == ':') js = filtered(js); | |
if (js) { | |
if (js.lastIndexOf('//') > js.lastIndexOf('\n')) js += '\n'; | |
buf.push(prefix, js, postfix); | |
} | |
i += end - start + close.length - 1; | |
} else if (str.substr(i, 1) == "\\") { | |
buf.push("\\\\"); | |
} else if (str.substr(i, 1) == "'") { | |
buf.push("\\'"); | |
} else if (str.substr(i, 1) == "\r") { | |
buf.push(" "); | |
} else if (str.substr(i, 1) == "\n") { | |
if (consumeEOL) { | |
consumeEOL = false; | |
} else { | |
buf.push("\\n"); | |
lineno++; | |
} | |
} else { | |
buf.push(str.substr(i, 1)); | |
} | |
} | |
if (false !== options._with) buf.push("');\n}\nreturn buf.join('');") | |
else buf.push("');\nreturn buf.join('');"); | |
return buf.join(''); | |
}; | |
/** | |
* Compile the given `str` of ejs into a `Function`. | |
* | |
* @param {String} str | |
* @param {Object} options | |
* @return {Function} | |
* @api public | |
*/ | |
var compile = exports.compile = function(str, options){ | |
options = options || {}; | |
var input = JSON.stringify(str) | |
, compileDebug = options.compileDebug !== false | |
, client = options.client | |
, filename = options.filename | |
? JSON.stringify(options.filename) | |
: 'undefined'; | |
if (compileDebug) { | |
// Adds the fancy stack trace meta info | |
str = [ | |
'var __stack = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };', | |
rethrow.toString(), | |
'try {', | |
exports.parse(str, options), | |
'} catch (err) {', | |
' rethrow(err, __stack.input, __stack.filename, __stack.lineno);', | |
'}' | |
].join("\n"); | |
} else { | |
str = exports.parse(str, options); | |
} | |
if (options.debug) console.log(str); | |
if (client) str = 'escape = escape || ' + utils.escape.toString() + ';\n' + str; | |
var fn = new Function('locals, filters, escape', str); | |
if (client) return fn; | |
return function(locals){ | |
return fn.call(this, locals, filters, utils.escape); | |
} | |
}; | |
/** | |
* Render the given `str` of ejs. | |
* | |
* Options: | |
* | |
* - `locals` Local variables object | |
* - `cache` Compiled functions are cached, requires `filename` | |
* - `filename` Used by `cache` to key caches | |
* - `scope` Function execution context | |
* - `debug` Output generated function body | |
* - `open` Open tag, defaulting to "<%" | |
* - `close` Closing tag, defaulting to "%>" | |
* | |
* @param {String} str | |
* @param {Object} options | |
* @return {String} | |
* @api public | |
*/ | |
exports.render = function(str, options){ | |
var fn | |
, options = options || {}; | |
if (options.cache) { | |
if (options.filename) { | |
fn = cache[options.filename] || (cache[options.filename] = compile(str, options)); | |
} else { | |
throw new Error('"cache" option requires "filename".'); | |
} | |
} else { | |
fn = compile(str, options); | |
} | |
options.__proto__ = options.locals; | |
return fn.call(options.scope, options); | |
}; | |
/** | |
* Render an EJS file at the given `path` and callback `fn(err, str)`. | |
* | |
* @param {String} path | |
* @param {Object|Function} options or callback | |
* @param {Function} fn | |
* @api public | |
*/ | |
exports.renderFile = function(path, options, fn){ | |
var key = path + ':string'; | |
if ('function' == typeof options) { | |
fn = options, options = {}; | |
} | |
options.filename = path; | |
try { | |
var str = options.cache | |
? cache[key] || (cache[key] = read(path, 'utf8')) | |
: read(path, 'utf8'); | |
fn(null, exports.render(str, options)); | |
} catch (err) { | |
fn(err); | |
} | |
}; | |
/** | |
* Resolve include `name` relative to `filename`. | |
* | |
* @param {String} name | |
* @param {String} filename | |
* @return {String} | |
* @api private | |
*/ | |
function resolveInclude(name, filename) { | |
var path = join(dirname(filename), name); | |
var ext = extname(name); | |
if (!ext) path += '.ejs'; | |
return path; | |
} | |
function resolveSnippet(name, filename) { | |
var path = join(dirname(filename), name); | |
// need to resolve path from /views folder | |
var ext = extname(name); | |
if (!ext) path += '.ejs'; | |
return path; | |
} | |
// express support | |
exports.__express = exports.renderFile; | |
/** | |
* Expose to require(). | |
*/ | |
if (require.extensions) { | |
require.extensions['.ejs'] = function(module, filename) { | |
source = require('fs').readFileSync(filename, 'utf-8'); | |
module._compile(compile(source, {}), filename); | |
}; | |
} else if (require.registerExtension) { | |
require.registerExtension('.ejs', function(src) { | |
return compile(src, {}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment