Created
January 20, 2011 15:54
-
-
Save jankuca/788091 to your computer and use it in GitHub Desktop.
EJS Node.js module; checkout the branch "client" for a client-side version
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 rsplit = function (string, regex) { | |
var result = regex.exec(string), | |
retArr = [], | |
first_idx, | |
last_idx, | |
first_bit; | |
while (result !== null) { | |
first_idx = result.index; | |
last_idx = regex.lastIndex; | |
if (first_idx !== 0) { | |
first_bit = string.substring(0, first_idx); | |
retArr.push(string.substring(0, first_idx)); | |
string = string.slice(first_idx); | |
} | |
retArr.push(result[0]); | |
string = string.slice(result[0].length); | |
result = regex.exec(string); | |
} | |
if (string !== '') { | |
retArr.push(string); | |
} | |
return retArr; | |
}; | |
var chop = function (string) { | |
return string.substr(0, string.length - 1); | |
}; | |
var extend = function (d, s) { | |
var n; | |
for (n in s) { | |
if (s.hasOwnProperty(n)) { | |
d[n] = s[n]; | |
} | |
} | |
}; | |
var improve = function (token) { | |
// (1..4) | |
token = token.replace(/\([^\(\)]+?\.\.\.?[^\(\)]+?\)/g, function (a) { | |
var b = a.match(/\((.*)\)/)[1].split(/\.\.\.?/); | |
return '(function (a, b) {' + | |
'var r = [];' + | |
'for (' + | |
'var c = a < b;' + | |
'c ? a <' + (a.indexOf('...') ? '=' : '') + ' b : a >' + (a.indexOf('...') ? '=' : '') + ' b;' + | |
'c ? ++a : --a' + | |
') {' + | |
'r.push(a);' + | |
'}' + | |
'return r;' + | |
'}(' + b[0] + ',' + b[1] + '))'; | |
}); | |
// 12.fn() | |
token = token.replace(/(\d+)\.(\D)/g, function (a) { | |
a = a.split('.'); | |
return '(' + a[0] + ').' + a[1]; | |
}); | |
return token; | |
} | |
var EJS = function (options) { | |
if (typeof options === 'string') { | |
options = { | |
'view': options | |
}; | |
} | |
this.set_options(options); | |
if (options.precompiled) { | |
this.template = {}; | |
var to_be_evaled = '/*' + this.name + '*/ ' + | |
'this.template.process = function (_CONTEXT,_VIEW) { ' + | |
'try { ' + | |
'var helpers = {}; ' + | |
'var key; ' + | |
'for (key in _VIEW) { ' + | |
'if (_VIEW.__proto__[key] !== void 0 && typeof _VIEW[key] === \'function\' && _CONTEXT[key] === void 0) { ' + | |
'_CONTEXT[key] = _VIEW[key]; ' + | |
'} ' + | |
'} ' + | |
'with (_CONTEXT) { ' + | |
'return (function () { "use strict"; ' + | |
options.precompiled + ' ' + | |
'return ___ViewO.join(\'\'); ' + | |
'}()); ' + | |
'} ' + | |
'} catch (e) { ' + | |
'e.lineNumber = null; ' + | |
'throw e; ' + | |
'} ' + | |
'};'; | |
try { | |
eval(to_be_evaled); | |
} catch (err) { | |
this.err_ = err; | |
} | |
EJS.update(this.name, this); | |
return; | |
} | |
var template = new EJS.Compiler(this.text, this.type); | |
template.compile(options, this.name); | |
EJS.update(this.name, this); | |
this.template = template; | |
}; | |
EJS.prototype = { | |
'render': function (object, extra_helpers) { | |
if (this.err_) { | |
throw this.err_; | |
} | |
object = object || {}; | |
this._extra_helpers = extra_helpers; | |
var v = new EJS.Helpers(object, extra_helpers || {}); | |
return this.template.process.call(object, object, v); | |
}, | |
'out': function () { | |
return this.template.out; | |
}, | |
'set_options': function (options) { | |
this.type = options.type || EJS.type; | |
this.cache = (options.cache !== null) ? options.cache : EJS.cache; | |
this.text = options.text || null; | |
this.name = options.name || null; | |
this.ext = options.ext || EJS.ext; | |
this.extMatch = new RegExp(this.ext.replace(/\./, '\\.')); | |
} | |
}; | |
EJS.endExt = function (path, match) { | |
if (!path) { | |
return null; | |
} | |
match.lastIndex = 0; | |
return path + (match.test(path) ? '' : this.ext); | |
}; | |
EJS.Scanner = function (source, left, right) { | |
extend(this, { | |
'left_delimiter': left + '%', | |
'right_delimiter': '%' + right, | |
'double_left': left + '%%', | |
'double_right': '%%' + right, | |
'left_equal': left + '%=', | |
'left_comment': left + '%#' | |
}); | |
this.SplitRegexp = (left === '[') ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('(' + this.double_left + ')|(%%' + this.double_right + ')|(' + this.left_equal + ')|(' + this.left_comment + ')|(' + this.left_delimiter + ')|(' + this.right_delimiter + '\n)|(' + this.right_delimiter + ')|(\n)'); | |
this.source = source; | |
this.stag = null; | |
this.lines = 0; | |
}; | |
EJS.Scanner.to_text = function (input) { | |
if (input === null || input === undefined) { | |
return ''; | |
} | |
if (input instanceof Date) { | |
return input.toDateString(); | |
} | |
if (input.toString) { | |
return input.toString(); | |
} | |
return ''; | |
}; | |
EJS.Scanner.prototype = { | |
'scan': function (block) { | |
var regex = this.SplitRegexp; | |
if (this.source !== '') { | |
var source_split = rsplit(this.source, /\n/); | |
var i, ii; | |
for (i = 0, ii = source_split.length; i < ii; ++i) { | |
var item = source_split[i]; | |
this.scanline(item, regex, block); | |
} | |
} | |
}, | |
'scanline': function (line, regex, block) { | |
++this.lines; | |
var line_split = rsplit(line, regex); | |
var i, ii; | |
for (i = 0, ii = line_split.length; i < ii; ++i) { | |
var token = line_split[i]; | |
if (token === null) { | |
continue; | |
} | |
try { | |
block(token, this); | |
} catch (e) { | |
throw { | |
'type': 'EJS.Scanner', | |
'line': this.lines | |
}; | |
} | |
} | |
} | |
}; | |
EJS.Buffer = function (pre_cmd, post_cmd) { | |
this.line = []; | |
this.script = ''; | |
this.pre_cmd = pre_cmd; | |
this.post_cmd = post_cmd; | |
var i, ii; | |
for (i = 0, ii = this.pre_cmd.length; i < ii; ++i) { | |
this.push(pre_cmd[i]); | |
} | |
}; | |
EJS.Buffer.prototype = { | |
'push': function (cmd) { | |
this.line.push(cmd); | |
}, | |
'cr': function () { | |
this.script = this.script + this.line.join('; '); | |
this.line = []; | |
this.script = this.script + "\n"; | |
}, | |
'close': function () { | |
if (this.line.length > 0) { | |
var i, ii; | |
for (i = 0, ii = this.post_cmd.length; i < ii; ++i) { | |
this.push(this.post_cmd[i]); | |
} | |
this.script = this.script + this.line.join('; '); | |
this.line = null; | |
} | |
} | |
}; | |
EJS.Compiler = function (source, left) { | |
this.pre_cmd = ['var ___ViewO = [];']; | |
this.post_cmd = []; | |
this.source = ' '; | |
if (source !== null) { | |
if (typeof source === 'string') { | |
source = source.replace(/\r\n/g, "\n"); | |
source = source.replace(/\r/g, "\n"); | |
this.source = source; | |
} else if (source.innerHTML) { | |
this.source = source.innerHTML; | |
} | |
if (typeof this.source !== 'string') { | |
this.source = ''; | |
} | |
} | |
left = left || '<'; | |
var right = '>'; | |
switch (left) { | |
case '[': | |
right = ']'; | |
break; | |
case '<': | |
break; | |
default: | |
throw left + ' is not a supported deliminator'; | |
} | |
this.scanner = new EJS.Scanner(this.source, left, right); | |
this.out = ''; | |
}; | |
EJS.Compiler.prototype = { | |
'compile': function (options, name) { | |
options = options || {}; | |
this.out = ''; | |
var put_cmd = '___ViewO.push(', | |
insert_cmd = put_cmd, | |
buff = new EJS.Buffer(this.pre_cmd, this.post_cmd), | |
content = ''; | |
var clean = function (content) { | |
content = content.replace(/\\/g, '\\\\'); | |
content = content.replace(/\n/g, '\\n'); | |
content = content.replace(/"/g, '\\"'); | |
return content; | |
}; | |
this.scanner.scan(function (token, scanner) { | |
if (scanner.stag === null) { | |
switch (token) { | |
case '\n': | |
content = content + "\n"; | |
buff.push(put_cmd + '"' + clean(content) + '");'); | |
buff.cr(); | |
content = ''; | |
break; | |
case scanner.left_delimiter: | |
case scanner.left_equal: | |
case scanner.left_comment: | |
scanner.stag = token; | |
if (content.length > 0) { | |
buff.push(put_cmd + '"' + clean(content) + '")'); | |
} | |
content = ''; | |
break; | |
case scanner.double_left: | |
content = content + scanner.left_delimiter; | |
break; | |
default: | |
content = content + token; | |
} | |
} else { | |
switch (token) { | |
case scanner.right_delimiter: | |
switch (scanner.stag) { | |
case scanner.left_delimiter: | |
if (content[content.length - 1] === '\n') { | |
content = chop(content); | |
buff.push(content); | |
buff.cr(); | |
} else { | |
buff.push(content); | |
} | |
break; | |
case scanner.left_equal: | |
buff.push(insert_cmd + '(EJS.Scanner.to_text(' + content + ')))'); | |
break; | |
} | |
scanner.stag = null; | |
content = ''; | |
break; | |
case scanner.double_right: | |
content = content + scanner.right_delimiter; | |
break; | |
default: | |
content = content + improve(token); | |
break; | |
} | |
} | |
}); | |
if (content.length > 0) { | |
buff.push(put_cmd + '"' + clean(content) + '")'); | |
} | |
buff.close(); | |
this.out = buff.script + ';'; | |
//var to_be_evaled = '/*' + name + '*/this.process = function (_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) { ' + this.out + " return ___ViewO.join('');}}}catch(e) { e.lineNumber=null;throw e;}};"; | |
var to_be_evaled = '/*' + this.name + '*/ ' + | |
'this.process = function (_CONTEXT,_VIEW) { ' + | |
'try { ' + | |
'var helpers = {}; ' + | |
'var key; ' + | |
'for (key in _VIEW) { ' + | |
'if (_VIEW.__proto__[key] !== void 0 && typeof _VIEW[key] === \'function\' && _CONTEXT[key] === void 0) { ' + | |
'_CONTEXT[key] = _VIEW[key]; ' + | |
'} ' + | |
'} ' + | |
'with (_CONTEXT) { ' + | |
'return (function () { "use strict"; ' + | |
this.out + ' ' + | |
'return ___ViewO.join(\'\'); ' + | |
'}()); ' + | |
'} ' + | |
'} catch (e) { ' + | |
'e.lineNumber = null; ' + | |
'throw e; ' + | |
'} ' + | |
'};'; | |
try { | |
eval(to_be_evaled); | |
} catch (err) { | |
this.err_ = err; | |
} | |
} | |
}; | |
EJS.config = function (options) { | |
EJS.cache = (options.cache !== null) ? options.cache : EJS.cache; | |
EJS.type = (options.type !== null) ? options.type : EJS.type; | |
EJS.ext = (options.ext !== null) ? options.ext : EJS.ext; | |
var templates_directory = EJS.templates_directory || {}; | |
EJS.templates_directory = templates_directory; | |
EJS.get = function (path, cache) { | |
if (cache === false) { | |
return null; | |
} | |
if (templates_directory[path]) { | |
return templates_directory[path]; | |
} | |
return null; | |
}; | |
EJS.update = function (path, template) { | |
if (path === null) { | |
return; | |
} | |
templates_directory[path] = template; | |
}; | |
EJS.INVALID_PATH = -1; | |
}; | |
EJS.config({ | |
'cache': true, | |
'type': '<', | |
'ext': '.ejs' | |
}); | |
EJS.Helpers = function (data, extras) { | |
this._data = data; | |
this._extras = extras; | |
extend(this, extras); | |
}; | |
EJS.Helpers.prototype = { | |
'view': function (options, data, helpers) { | |
if (!helpers) { | |
helpers = this._extras; | |
} | |
if (!data) { | |
data = this._data; | |
} | |
return new EJS(options).render(data, helpers); | |
}, | |
'to_text': function (input, null_text) { | |
if (input === null || input === undefined) { | |
return null_text || ''; | |
} | |
if (input instanceof Date) { | |
return input.toDateString(); | |
} | |
if (input.toString) { | |
return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'"); | |
} | |
return ''; | |
} | |
}; | |
exports.EJS = EJS; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment