diff --git a/History.md b/History.md index c26f168c..df3ccf60 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,11 @@ +0.8.5 / 2013-11-21 +================== + + * fix: Escape apostrophe & don't over-match existing entities + * fix function name changed by uglify + * fixes require, closes #78 + 0.8.4 / 2013-05-08 ================== diff --git a/ejs.js b/ejs.js index b2e84bd6..ab44a80c 100644 --- a/ejs.js +++ b/ejs.js @@ -4,6 +4,7 @@ ejs = (function(){ function require(p){ if ('fs' == p) return {}; + if ('path' == p) return {}; var path = require.resolve(p) , mod = require.modules[path]; if (!mod) throw new Error('failed to require "' + p + '"'); @@ -61,17 +62,16 @@ require.register("ejs.js", function(module, exports, require){ */ var utils = require('./utils') - , fs = require('fs'); - -/** - * Library version. - */ - -exports.version = '0.7.2'; + , path = require('path') + , dirname = path.dirname + , extname = path.extname + , join = path.join + , fs = require('fs') + , read = fs.readFileSync; /** * Filters. - * + * * @type Object */ @@ -79,7 +79,7 @@ var filters = exports.filters = require('./filters'); /** * Intermediate js cache. - * + * * @type Object */ @@ -107,7 +107,7 @@ function filtered(js) { return js.substr(1).split('|').reduce(function(js, filter){ var parts = filter.split(':') , name = parts.shift() - , args = parts.shift() || ''; + , args = parts.join(':') || ''; if (args) args = ', ' + args; return 'filters.' + name + '(' + js + args + ')'; }); @@ -140,9 +140,9 @@ function rethrow(err, str, filename, lineno){ // Alter exception message err.path = filename; - err.message = (filename || 'ejs') + ':' - + lineno + '\n' - + context + '\n\n' + err.message = (filename || 'ejs') + ':' + + lineno + '\n' + + context + '\n\n' + err.message; throw err; @@ -159,23 +159,25 @@ function rethrow(err, str, filename, lineno){ var parse = exports.parse = function(str, options){ var options = options || {} , open = options.open || exports.open || '<%' - , close = options.close || exports.close || '%>'; + , close = options.close || exports.close || '%>' + , filename = options.filename + , compileDebug = options.compileDebug !== false + , buf = ""; + + buf += 'var buf = [];'; + if (false !== options._with) buf += '\nwith (locals || {}) { (function(){ '; + buf += '\n buf.push(\''; - var buf = [ - "var buf = [];" - , "\nwith (locals) {" - , "\n buf.push('" - ]; - var lineno = 1; var consumeEOL = false; for (var i = 0, len = str.length; i < len; ++i) { + var stri = str[i]; if (str.slice(i, open.length + i) == open) { i += open.length - var prefix, postfix, line = '__stack.lineno=' + lineno; - switch (str.substr(i, 1)) { + var prefix, postfix, line = (compileDebug ? '__stack.lineno=' : '') + lineno; + switch (str[i]) { case '=': prefix = "', escape((" + line + ', '; postfix = ")), '"; @@ -194,38 +196,55 @@ var parse = exports.parse = function(str, options){ 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, compileDebug: compileDebug }); + buf += "' + (function(){" + include + "})() + '"; + js = ''; + } + while (~(n = js.indexOf("\n", n))) n++, lineno++; if (js.substr(0, 1) == ':') js = filtered(js); - buf.push(prefix, js, postfix); + if (js) { + if (js.lastIndexOf('//') > js.lastIndexOf('\n')) js += '\n'; + buf += prefix; + buf += js; + buf += 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") { + } else if (stri == "\\") { + buf += "\\\\"; + } else if (stri == "'") { + buf += "\\'"; + } else if (stri == "\r") { + // ignore + } else if (stri == "\n") { if (consumeEOL) { consumeEOL = false; } else { - buf.push("\\n"); + buf += "\\n"; lineno++; } } else { - buf.push(str.substr(i, 1)); + buf += stri; } } - buf.push("');\n}\nreturn buf.join('');"); - return buf.join(''); + if (false !== options._with) buf += "'); })();\n} \nreturn buf.join('');"; + else buf += "');\nreturn buf.join('');"; + return buf; }; /** @@ -239,27 +258,48 @@ var parse = exports.parse = function(str, options){ var compile = exports.compile = function(str, options){ options = options || {}; + var escape = options.escape || utils.escape; var input = JSON.stringify(str) + , compileDebug = options.compileDebug !== false + , client = options.client , filename = options.filename ? JSON.stringify(options.filename) : 'undefined'; - // 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"); + 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); - var fn = new Function('locals, filters, escape', str); + if (client) str = 'escape = escape || ' + escape.toString() + ';\n' + str; + + try { + var fn = new Function('locals, filters, escape, rethrow', str); + } catch (err) { + if ('SyntaxError' == err.name) { + err.message += options.filename + ? ' in ' + filename + : ' while compiling ejs'; + } + throw err; + } + + if (client) return fn; + return function(locals){ - return fn.call(this, locals, filters, utils.escape); + return fn.call(this, locals, filters, escape, rethrow); } }; @@ -318,17 +358,34 @@ exports.renderFile = function(path, options, fn){ options.filename = path; + var str; try { - var str = options.cache - ? cache[key] || (cache[key] = fs.readFileSync(path, 'utf8')) - : fs.readFileSync(path, 'utf8'); - - fn(null, exports.render(str, options)); + str = options.cache + ? cache[key] || (cache[key] = read(path, 'utf8')) + : read(path, 'utf8'); } catch (err) { fn(err); + return; } + fn(null, exports.render(str, options)); }; +/** + * 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; +} + // express support exports.__express = exports.renderFile; @@ -338,9 +395,12 @@ exports.__express = exports.renderFile; */ if (require.extensions) { - require.extensions['.ejs'] = function(module, filename) { - source = require('fs').readFileSync(filename, 'utf-8'); - module._compile(compile(source, {}), filename); + require.extensions['.ejs'] = function (module, filename) { + filename = filename || module.filename; + var options = { filename: filename, client: true } + , template = fs.readFileSync(filename).toString() + , fn = compile(template, options); + module._compile('module.exports = ' + fn.toString() + ';', filename); }; } else if (require.registerExtension) { require.registerExtension('.ejs', function(src) { @@ -351,7 +411,6 @@ if (require.extensions) { }); // module: ejs.js require.register("filters.js", function(module, exports, require){ - /*! * EJS - Filters * Copyright(c) 2010 TJ Holowaychuk @@ -475,10 +534,8 @@ exports.join = function(obj, str){ exports.truncate = function(str, len, append){ str = String(str); if (str.length > len) { - str = str.substr(0, len); - if (append && typeof append == 'string') { - str += append; - } + str = str.slice(0, len); + if (append) str += append; } return str; }; @@ -555,6 +612,7 @@ exports.get = function(obj, prop){ exports.json = function(obj){ return JSON.stringify(obj); }; + }); // module: filters.js require.register("utils.js", function(module, exports, require){ @@ -575,13 +633,15 @@ require.register("utils.js", function(module, exports, require){ exports.escape = function(html){ return String(html) - .replace(/&(?!\w+;)/g, '&') + .replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&') .replace(//g, '>') + .replace(/'/g, ''') .replace(/"/g, '"'); }; + }); // module: utils.js return require("ejs"); -})(); +})(); \ No newline at end of file diff --git a/ejs.min.js b/ejs.min.js index 611ee425..cce06af3 100644 --- a/ejs.min.js +++ b/ejs.min.js @@ -1 +1 @@ -ejs=function(){function require(p){if("fs"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');return mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path))),mod.exports}return require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i> ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message,err}var parse=exports.parse=function(str,options){var options=options||{},open=options.open||exports.open||"<%",close=options.close||exports.close||"%>",buf=["var buf = [];","\nwith (locals) {","\n buf.push('"],lineno=1,consumeEOL=!1;for(var i=0,len=str.length;ib?1:a/g,">").replace(/"/g,""")}}),require("ejs")}(); \ No newline at end of file +ejs=function(){function require(p){if("fs"==p)return{};if("path"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');if(!mod.exports){mod.exports={};mod.call(mod.exports,mod,mod.exports,require.relative(path))}return mod.exports}require.modules={};require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig};require.register=function(path,fn){require.modules[path]=fn};require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}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+="var buf = [];";if(false!==options._with)buf+="\nwith (locals || {}) { (function(){ ";buf+="\n buf.push('";var lineno=1;var consumeEOL=false;for(var i=0,len=str.length;ijs.lastIndexOf("\n"))js+="\n";buf+=prefix;buf+=js;buf+=postfix}i+=end-start+close.length-1}else if(stri=="\\"){buf+="\\\\"}else if(stri=="'"){buf+="\\'"}else if(stri=="\r"){}else if(stri=="\n"){if(consumeEOL){consumeEOL=false}else{buf+="\\n";lineno++}}else{buf+=stri}}if(false!==options._with)buf+="'); })();\n} \nreturn buf.join('');";else buf+="');\nreturn buf.join('');";return buf};var compile=exports.compile=function(str,options){options=options||{};var escape=options.escape||utils.escape;var input=JSON.stringify(str),compileDebug=options.compileDebug!==false,client=options.client,filename=options.filename?JSON.stringify(options.filename):"undefined";if(compileDebug){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 || "+escape.toString()+";\n"+str;try{var fn=new Function("locals, filters, escape, rethrow",str)}catch(err){if("SyntaxError"==err.name){err.message+=options.filename?" in "+filename:" while compiling ejs"}throw err}if(client)return fn;return function(locals){return fn.call(this,locals,filters,escape,rethrow)}};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)};exports.renderFile=function(path,options,fn){var key=path+":string";if("function"==typeof options){fn=options,options={}}options.filename=path;var str;try{str=options.cache?cache[key]||(cache[key]=read(path,"utf8")):read(path,"utf8")}catch(err){fn(err);return}fn(null,exports.render(str,options))};function resolveInclude(name,filename){var path=join(dirname(filename),name);var ext=extname(name);if(!ext)path+=".ejs";return path}exports.__express=exports.renderFile;if(require.extensions){require.extensions[".ejs"]=function(module,filename){filename=filename||module.filename;var options={filename:filename,client:true},template=fs.readFileSync(filename).toString(),fn=compile(template,options);module._compile("module.exports = "+fn.toString()+";",filename)}}else if(require.registerExtension){require.registerExtension(".ejs",function(src){return compile(src,{})})}});require.register("filters.js",function(module,exports,require){exports.first=function(obj){return obj[0]};exports.last=function(obj){return obj[obj.length-1]};exports.capitalize=function(str){str=String(str);return str[0].toUpperCase()+str.substr(1,str.length)};exports.downcase=function(str){return String(str).toLowerCase()};exports.upcase=function(str){return String(str).toUpperCase()};exports.sort=function(obj){return Object.create(obj).sort()};exports.sort_by=function(obj,prop){return Object.create(obj).sort(function(a,b){a=a[prop],b=b[prop];if(a>b)return 1;if(alen){str=str.slice(0,len);if(append)str+=append}return str};exports.truncate_words=function(str,n){var str=String(str),words=str.split(/ +/);return words.slice(0,n).join(" ")};exports.replace=function(str,pattern,substitution){return String(str).replace(pattern,substitution||"")};exports.prepend=function(obj,val){return Array.isArray(obj)?[val].concat(obj):val+obj};exports.append=function(obj,val){return Array.isArray(obj)?obj.concat(val):obj+val};exports.map=function(arr,prop){return arr.map(function(obj){return obj[prop]})};exports.reverse=function(obj){return Array.isArray(obj)?obj.reverse():String(obj).split("").reverse().join("")};exports.get=function(obj,prop){return obj[prop]};exports.json=function(obj){return JSON.stringify(obj)}});require.register("utils.js",function(module,exports,require){exports.escape=function(html){return String(html).replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")}});return require("ejs")}(); \ No newline at end of file diff --git a/examples/list.ejs b/examples/list.ejs index d571330a..0b378a9c 100644 --- a/examples/list.ejs +++ b/examples/list.ejs @@ -1,7 +1,7 @@ <% if (names.length) { %>
    <% names.forEach(function(name){ %> -
  • <%= name %>
  • +
  • '><%= name %>
  • <% }) %>
<% } %> \ No newline at end of file diff --git a/package.json b/package.json index b8510dae..0851fef7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ejs", "description": "Embedded JavaScript templates", - "version": "0.8.4", + "version": "0.8.5", "author": "TJ Holowaychuk ", "keywords": ["template", "engine", "ejs"], "devDependencies": {