[Pkg-javascript-commits] [node-ejs] 01/04: New upstream version 2.5.7
Praveen Arimbrathodiyil
praveen at moszumanska.debian.org
Tue Oct 10 11:51:03 UTC 2017
This is an automated email from the git hooks/post-receive script.
praveen pushed a commit to branch master
in repository node-ejs.
commit f9136ef0289464b7d4cfe5a87f6e8fe8bbf3080a
Author: Pirate Praveen <praveen at debian.org>
Date: Tue Oct 10 17:05:56 2017 +0530
New upstream version 2.5.7
---
.eslintignore | 4 +
.eslintrc.json | 10 +-
.gitignore | 2 +
.gitignore => .npmignore | 8 +-
.travis.yml | 6 +-
CHANGELOG.md | 28 +++
Jakefile | 78 ++++--
README.md | 16 ++
benchmark/bench-ejs.js.bak | 194 +++++++++++++++
docs/jsdoc/fileLoader.jsdoc | 10 +
docs/jsdoc/options.jsdoc | 15 +-
docs/syntax.md | 4 +-
examples/functions.js | 22 +-
examples/list.js | 8 +-
lib/ejs.js | 403 +++++++++++++++++-------------
lib/utils.js | 45 +++-
package.json | 11 +-
test/ejs.js | 451 ++++++++++++++++++++++++----------
test/fixtures/include-escaped.ejs | 3 +
test/fixtures/include-escaped.html | 4 +
test/fixtures/include-expression.ejs | 3 +
test/fixtures/include-expression.html | 4 +
test/fixtures/include-with-error.ejs | 1 +
test/fixtures/no.newlines.error.ejs | 2 +-
test/fixtures/views-include.ejs | 1 +
test/fixtures/views-old.ejs | 1 +
test/fixtures/views.ejs | 1 +
test/fixtures/views/views-include.ejs | 1 +
28 files changed, 958 insertions(+), 378 deletions(-)
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..b8a8542
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+coverage
+out
+/ejs.js
+/ejs.min.js
diff --git a/.eslintrc.json b/.eslintrc.json
index ee48f8e..e995186 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -10,9 +10,17 @@
"error",
"unix"
],
+ "no-trailing-spaces": 2,
+ "indent": [
+ "error",
+ 2
+ ],
"quotes": [
"error",
- "single"
+ "single",
+ {
+ "avoidEscape": true
+ }
],
"semi": [
"error",
diff --git a/.gitignore b/.gitignore
index 5bdbee3..bc349d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+# If you add anything here, consider also adding to .npmignore
v8.log
*.swp
*.swo
@@ -20,3 +21,4 @@ coverage/
/ejs.js
/ejs.min.js
out/
+pkg/
diff --git a/.gitignore b/.npmignore
similarity index 80%
copy from .gitignore
copy to .npmignore
index 5bdbee3..c4e590c 100644
--- a/.gitignore
+++ b/.npmignore
@@ -1,3 +1,6 @@
+test/
+
+# Copied from .gitignore
v8.log
*.swp
*.swo
@@ -7,7 +10,6 @@ dist
tags
nbproject/
spec/browser/autogen_suite.js
-node_modules
tmtags
*.DS_Store
examples/*/log/*
@@ -15,8 +17,6 @@ site/log/*
.log
npm-debug.log
doc/
-test/tmp
coverage/
-/ejs.js
-/ejs.min.js
out/
+pkg/
diff --git a/.travis.yml b/.travis.yml
index 595b83a..881635a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,6 @@
language: node_js
sudo: false
node_js:
- - "0.10"
- - "0.12"
- "4"
- "6"
-before_install:
- - npm update -g npm
- - npm --version
+ - "8"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 520e5e1..00a655d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,31 @@
+## v2.5.7: 2017-07-29
+
+ * Pass configured escape function to `rethrow` (@straker)
+ + Added vulnerabilities info into README (@mde)
+ * Avoid creating function object in hot execution path (@User4martin)
+ + Added benchmark (@User4martin)
+ + Tests for looped includes (@User4martin)
+
+## v2.5.6: 2017-02-16
+
+ * Use configured escape function for filenames in errors (@mde)
+ + Make file-loader configurable to allow template pre-processing (@hacke2)
+ * Improved `renderFile` performance (@nwoltman)
+
+## v2.5.5: 2016-12-06
+
+* Allow 'filename' for opts-in-data, but sanitize when rendered (@mde)
+
+## v2.5.4: 2016-12-05
+
+* Blackist more options from opts-in-data (@mde)
+* Allow trailing comments in output modes (@mde)
++ Added 'name' attribute for easy identification (@designfrontier)
+
+## v2.5.3: 2016-11-28
+
+* Blackist 'root' option from opts-in-data (@mde)
+
## v2.5.2: 2016-07-25
+ Added link to EJS Playground (@RyanZim)
diff --git a/Jakefile b/Jakefile
index 3789eee..31e07d4 100644
--- a/Jakefile
+++ b/Jakefile
@@ -1,10 +1,12 @@
-var fs = require('fs')
- , buildOpts = {
- printStdout: true
- , printStderr: true
- };
+var fs = require('fs');
+var execSync = require('child_process').execSync;
+var exec = function (cmd) {
+ execSync(cmd, {stdio: 'inherit'});
+};
-task('build', ['browserify', 'minify'], function () {
+/* global jake, task, desc, publishTask */
+
+task('build', ['lint', 'clean', 'browserify', 'minify'], function () {
console.log('Build completed.');
});
@@ -12,33 +14,57 @@ desc('Cleans browerified/minified files and package files');
task('clean', ['clobber'], function () {
jake.rmRf('./ejs.js');
jake.rmRf('./ejs.min.js');
+ console.log('Cleaned up compiled files.');
+});
+
+desc('Lints the source code');
+task('lint', function () {
+ exec('./node_modules/.bin/eslint \"**/*.js\" Jakefile');
+ console.log('Linting completed.');
});
-task('browserify', {async: true}, function () {
- jake.exec('./node_modules/browserify/bin/cmd.js --standalone ejs lib/ejs.js > ejs.js',
- buildOpts, function () {
- console.log('Browserification completed.');
- setTimeout(complete, 0);
- });
+task('browserify', function () {
+ exec('./node_modules/browserify/bin/cmd.js --standalone ejs lib/ejs.js > ejs.js');
+ console.log('Browserification completed.');
});
-task('minify', {async: true}, function () {
- jake.exec('./node_modules/uglify-js/bin/uglifyjs ejs.js > ejs.min.js',
- buildOpts, function () {
- console.log('Minification completed.');
- setTimeout(complete, 0);
- });
+task('minify', function () {
+ exec('./node_modules/uglify-js/bin/uglifyjs ejs.js > ejs.min.js');
+ console.log('Minification completed.');
+});
+
+task('doc', function (dev) {
+ jake.rmRf('out');
+ var p = dev ? '-p' : '';
+ exec('./node_modules/.bin/jsdoc ' + p + ' -c jsdoc.json lib/* docs/jsdoc/*');
+ console.log('Documentation generated.');
+});
+
+task('docPublish', ['doc'], function () {
+ fs.writeFileSync('out/CNAME', 'api.ejs.co');
+ console.log('Pushing docs to gh-pages...');
+ exec('./node_modules/.bin/git-directory-deploy --directory out/');
+ console.log('Docs published to gh-pages.');
+});
+
+task('test', ['lint'], function () {
+ exec('./node_modules/.bin/mocha');
});
publishTask('ejs', ['build'], function () {
this.packageFiles.include([
- 'Jakefile'
- , 'README.md'
- , 'LICENSE'
- , 'package.json'
- , 'ejs.js'
- , 'ejs.min.js'
- , 'lib/**'
- , 'test/**'
+ 'Jakefile',
+ 'README.md',
+ 'LICENSE',
+ 'package.json',
+ 'ejs.js',
+ 'ejs.min.js',
+ 'lib/**'
]);
});
+
+jake.Task.publish.on('complete', function () {
+ console.log('Updating hosted docs...');
+ console.log('If this fails, run jake docPublish to re-try.');
+ jake.Task.docPublish.invoke();
+});
diff --git a/README.md b/README.md
index 1dd860f..f9609eb 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@ Embedded JavaScript templates
[![Build Status](https://img.shields.io/travis/mde/ejs/master.svg?style=flat)](https://travis-ci.org/mde/ejs)
[![Developing Dependencies](https://img.shields.io/david/dev/mde/ejs.svg?style=flat)](https://david-dm.org/mde/ejs?type=dev)
+[![Known Vulnerabilities](https://snyk.io/test/npm/ejs/badge.svg?style=flat-square)](https://snyk.io/test/npm/ejs)
## Installation
@@ -162,6 +163,21 @@ If you want to clear the EJS cache, call `ejs.clearCache`. If you're using the
LRU cache and need a different limit, simple reset `ejs.cache` to a new instance
of the LRU.
+## Custom FileLoader
+
+The default file loader is `fs.readFileSync`, if you want to customize it, you can set ejs.fileLoader.
+
+```javascript
+var ejs = require('ejs');
+var myFileLoad = function (filePath) {
+ return 'myFileLoad: ' + fs.readFileSync(filePath);
+};
+
+ejs.fileLoader = myFileLoad;
+```
+
+With this feature, you can preprocess the template before reading it.
+
## Layouts
EJS does not specifically support blocks, but layouts can be implemented by
diff --git a/benchmark/bench-ejs.js.bak b/benchmark/bench-ejs.js.bak
new file mode 100755
index 0000000..62848a8
--- /dev/null
+++ b/benchmark/bench-ejs.js.bak
@@ -0,0 +1,194 @@
+'use strict';
+
+var ejs = require('..');
+
+ejs.fileLoader = function(n) { return files[n.replace(/^\//, '').replace(/\.ejs$/, '')]; };
+
+var loops = 10000;
+var runs = 9; // min 4 for median
+
+var runCompile = false;
+var runNoCache = false;
+var runCache = false;
+
+var i = 1;
+while (i < process.argv.length) {
+ var a = process.argv[i];
+ i++;
+ var b;
+ if (i < process.argv.length) b = process.argv[i];
+ switch (a) {
+ case '-r': if(b) runs = b;
+ i++;
+ break;
+ case '-l': if(b) loops = b;
+ i++;
+ break;
+ case '--compile':
+ runCompile = true;
+ break;
+ case '--nocache':
+ runNoCache = true;
+ break;
+ case '--cache':
+ runCache = true;
+ break;
+ }
+}
+
+if (! (runCompile || runNoCache || runCache)) {
+ runCompile = true;
+ runNoCache = true;
+ runCache = true;
+}
+
+var files = {
+ bench1: `<h1><$= name $></h1>
+ <div><%- num+1 -%></div>
+ <% if(num > 10) { %>
+ <$= cheese $>
+ <% } %>
+ <%# comment #%>
+ <%% literal <$= name $> %%>
+ `,
+
+ bench2: `<h1><$= name $></h1>
+ <div><%- num+1 -%></div>
+ <% if(num > 10) { %>
+ <$= cheese $>
+ <% } %>
+ <%# comment #%>
+ <%% literal <$= name $> %%>
+ `.repeat(100),
+
+ simple1: `<h1><$= name $></h1>
+ <div><%- num+1 -%></div>
+ `,
+
+ locals1: `<h1><$= locals.name $></h1>
+ <div><%- locals.num+1 -%></div>
+ <% if(locals.num > 10) { %>
+ <$= locals.cheese $>
+ <% } %>
+ <%# comment #%>
+ <%% literal <$= locals.name $> %%>
+ `.repeat(10),
+
+ include1: `<h1><$= name $></h1>
+ <div><% include('/simple1') %></div>
+ <div><% include('/simple1') %></div>
+ <div><% include('/simple1') %></div>
+ `,
+
+ include2: `<h1><$= name $></h1>
+ <div><% include /include1 %></div>
+ <div><% include /simple1 %></div>
+ `,
+};
+
+var data = {
+ name: 'foo',
+ num: 42,
+ cheese: 'out of',
+};
+
+var sp = ' ';
+function fill(s, l) { s=String(s); return s + sp.slice(0,l-s.length); }
+function fillR(s, l) { s=String(s); return sp.slice(0,l-s.length)+s; }
+
+function log(name, runTimes, totalLoops) {
+ runTimes = runTimes.sort(function(a,b) { return a-b; });
+ var m = Math.trunc(runs/2);
+ var m2 = (runs % 2 == 0) ? m-1 : m;
+ var med1 = Math.round((runTimes[m]+runTimes[m2])/2);
+ var med2;
+ if (runs % 2 == 0)
+ med2 = Math.round((runTimes[m2-1]+runTimes[m2]+runTimes[m]+runTimes[m+1])/4);
+ else
+ med2 = Math.round((runTimes[m-1]+runTimes[m]+runTimes[m+1])/3);
+ var avg = Math.round(runTimes.reduce(function(a,b) {return a+b;}) / runTimes.length);
+ console.log(fill(name +': ',30), fill(avg/1000,10), fill(med1/1000,10), fill(med2/1000,10), fill(runTimes[0]/1000,10), fill(runTimes[runTimes.length-1]/1000,10),fillR(totalLoops, 15));
+}
+
+function benchRender(name, file, data, opts, benchOpts) {
+ ejs.cache.reset();
+ var runTimes = [];
+ opts = opts || {};
+ benchOpts = benchOpts || {};
+ opts.filename = file;
+ var totalLoops = Math.round(loops * (benchOpts.loopFactor || 1))
+ var tmpl = files[file];
+ for (var r = 0; r < runs; r++) {
+ ejs.render(tmpl, data, opts); // one run in advance
+
+ var t = Date.now();
+ for (var i = 0; i < totalLoops; i++) {
+ ejs.render(tmpl, data, opts);
+ }
+ t = Date.now() - t;
+ runTimes.push(t);
+ }
+ log(name, runTimes, totalLoops);
+}
+
+function benchCompile(name, file, opts, benchOpts) {
+ ejs.cache.reset();
+ var runTimes = [];
+ opts = opts || {};
+ benchOpts = benchOpts || {};
+ opts.filename = file;
+ var totalLoops = Math.round(loops * (benchOpts.loopFactor || 1))
+ var tmpl = files[file];
+ for (var r = 0; r < runs; r++) {
+ ejs.compile(tmpl, opts); // one run in advance
+
+ var t = Date.now();
+ for (var i = 0; i < totalLoops; i++) {
+ ejs.compile(tmpl, opts);
+ }
+ t = Date.now() - t;
+ runTimes.push(t);
+ }
+ log(name, runTimes, totalLoops);
+}
+
+if (runCompile) {
+ console.log('Running avg accross: ', runs);
+ console.log(fill('name: ',30), fill('avg',10), fill('med',10), fill('med/avg',10), fill('min',10), fill('max',10), fillR('loops',15));
+
+ benchCompile('single tmpl compile', 'bench1', {compileDebug: false}, { loopFactor: 2 });
+ benchCompile('single tmpl compile (debug)', 'bench1', {compileDebug: true}, { loopFactor: 2 });
+
+ benchCompile('large tmpl compile', 'bench2', {compileDebug: false}, { loopFactor: 0.1 });
+
+ benchCompile('include-1 compile', 'include1', {compileDebug: false}, { loopFactor: 2 });
+ console.log('-');
+};
+
+
+
+if (runCache) {
+ benchRender('single tmpl cached', 'bench1', data, {cache:true, compileDebug: false}, { loopFactor: 5 });
+ benchRender('single tmpl cached (debug)', 'bench1', data, {cache:true, compileDebug: true}, { loopFactor: 5 });
+
+ benchRender('large tmpl cached', 'bench2', data, {cache:true, compileDebug: false}, { loopFactor: 0.4 });
+ benchRender('include-1 cached', 'include1', data, {cache:true, compileDebug: false}, { loopFactor: 2 });
+ benchRender('include-2 cached', 'include2', data, {cache:true, compileDebug: false}, { loopFactor: 2 });
+
+
+ benchRender('locals tmpl cached "with"', 'locals1', data, {cache:true, compileDebug: false, _with: true}, { loopFactor: 3 });
+ benchRender('locals tmpl cached NO-"with"', 'locals1', data, {cache:true, compileDebug: false, _with: false}, { loopFactor: 3 });
+
+ console.log('-');
+}
+
+
+if (runNoCache) {
+ benchRender('single tmpl NO-cache', 'bench1', data, {cache:false, compileDebug: false});
+ benchRender('single tmpl NO-cache (debug)', 'bench1', data, {cache:false, compileDebug: true});
+
+ benchRender('large tmpl NO-cache', 'bench2', data, {cache:false, compileDebug: false}, { loopFactor: 0.1 });
+
+ benchRender('include-1 NO-cache', 'include1', data, {cache:false, compileDebug: false});
+ benchRender('include-2 NO-cache', 'include2', data, {cache:false, compileDebug: false});
+}
diff --git a/docs/jsdoc/fileLoader.jsdoc b/docs/jsdoc/fileLoader.jsdoc
new file mode 100644
index 0000000..20b978d
--- /dev/null
+++ b/docs/jsdoc/fileLoader.jsdoc
@@ -0,0 +1,10 @@
+/**
+ * A file read function, similar to fs.readFileSync, this function
+ * can be read a file by path, return a string after processing
+ *
+ * @function
+ * @name fileLoader
+ * @param {String} path the path of the file to be read
+ * @return {String|Object} the contents of the file as a string or objects that implement the toString() method
+ * @global
+ */
diff --git a/docs/jsdoc/options.jsdoc b/docs/jsdoc/options.jsdoc
index 8579c8e..e587644 100644
--- a/docs/jsdoc/options.jsdoc
+++ b/docs/jsdoc/options.jsdoc
@@ -24,22 +24,22 @@
* line).
*
* @property {Boolean} [client=false]
- * Whether or not to compile a {@link ClientFunction} that can be rendered
- * in the browser without depending on ejs.js. Otherwise, a {@link TemplateFunction}
+ * Whether or not to compile a {@link ClientFunction} that can be rendered
+ * in the browser without depending on ejs.js. Otherwise, a {@link TemplateFunction}
* will be compiled.
- *
+ *
* @property {EscapeCallback} [escape={@link module:utils.escapeXML}]
* The escaping function used with `<%=` construct. It is used in rendering
* and is `.toString()`ed in the generation of client functions.
*
* @property {String} [filename=undefined]
- * The filename of the template. Required for inclusion and caching unless
+ * The filename of the template. Required for inclusion and caching unless
* you are using {@link module:ejs.renderFile}. Also used for error reporting.
- *
+ *
* @property {String} [root=undefined]
- * The path to the project root. When this is set, absolute paths for includes
+ * The path to the project root. When this is set, absolute paths for includes
* (/filename.ejs) will be relative to the project root.
- *
+ *
* @property {String} [delimiter='%']
* The delimiter used in template compilation.
*
@@ -60,4 +60,3 @@
* @static
* @global
*/
-
diff --git a/docs/syntax.md b/docs/syntax.md
index 61975f4..1feb713 100644
--- a/docs/syntax.md
+++ b/docs/syntax.md
@@ -42,8 +42,8 @@ Delimiters
The *starting* and *closing* tags contain a special string called the
delimiter. In this document, all tags are shown using the `%` delimiter, which
-is the default. You can, however, change that to your liking. See
-https://github.com/mde/ejs#custom-delimiters for more information on how to
+is the default. You can, however, change that to your liking. See
+https://github.com/mde/ejs#custom-delimiters for more information on how to
change it.
Starting tags
diff --git a/examples/functions.js b/examples/functions.js
index 976819c..2308c9f 100644
--- a/examples/functions.js
+++ b/examples/functions.js
@@ -2,17 +2,17 @@
* Believe it or not, you can declare and use functions in EJS templates too.
*/
-var ejs = require('../')
- , read = require('fs').readFileSync
- , join = require('path').join
- , path = join(__dirname, '/functions.ejs')
- , data = {
- users: [
- { name: 'Tobi', age: 2, species: 'ferret' }
- , { name: 'Loki', age: 2, species: 'ferret' }
- , { name: 'Jane', age: 6, species: 'ferret' }
- ]
- };
+var ejs = require('../');
+var read = require('fs').readFileSync;
+var join = require('path').join;
+var path = join(__dirname, '/functions.ejs');
+var data = {
+ users: [
+ { name: 'Tobi', age: 2, species: 'ferret' },
+ { name: 'Loki', age: 2, species: 'ferret' },
+ { name: 'Jane', age: 6, species: 'ferret' }
+ ]
+};
var ret = ejs.compile(read(path, 'utf8'), {filename: path})(data);
diff --git a/examples/list.js b/examples/list.js
index c9839d8..efb1d29 100644
--- a/examples/list.js
+++ b/examples/list.js
@@ -3,10 +3,10 @@
* template.
*/
-var ejs = require('../')
- , read = require('fs').readFileSync
- , join = require('path').join
- , str = read(join(__dirname, '/list.ejs'), 'utf8');
+var ejs = require('../');
+var read = require('fs').readFileSync;
+var join = require('path').join;
+var str = read(join(__dirname, '/list.ejs'), 'utf8');
var ret = ejs.compile(str)({
names: ['foo', 'bar', 'baz']
diff --git a/lib/ejs.js b/lib/ejs.js
old mode 100644
new mode 100755
index 39a7b73..9973dcd
--- a/lib/ejs.js
+++ b/lib/ejs.js
@@ -19,7 +19,7 @@
'use strict';
/**
- * @file Embedded JavaScript templating engine.
+ * @file Embedded JavaScript templating engine. {@link http://ejs.co}
* @author Matthew Eernisse <mde at fleegix.org>
* @author Tiancheng "Timothy" Gu <timothygu99 at gmail.com>
* @project EJS
@@ -52,11 +52,14 @@ var scopeOptionWarned = false;
var _VERSION_STRING = require('../package.json').version;
var _DEFAULT_DELIMITER = '%';
var _DEFAULT_LOCALS_NAME = 'locals';
+var _NAME = 'ejs';
var _REGEX_STRING = '(<%%|%%>|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)';
-var _OPTS = [ 'cache', 'filename', 'delimiter', 'scope', 'context',
- 'debug', 'compileDebug', 'client', '_with', 'root', 'rmWhitespace',
- 'strict', 'localsName'];
-var _TRAILING_SEMCOL = /;\s*$/;
+var _OPTS = ['delimiter', 'scope', 'context', 'debug', 'compileDebug',
+ 'client', '_with', 'rmWhitespace', 'strict', 'filename'];
+// We don't allow 'cache' option to be passed in the data obj
+// for the normal `render` call, but this is where Express puts it
+// so we make an exception for `renderFile`
+var _OPTS_EXPRESS = _OPTS.concat('cache');
var _BOM = /^\uFEFF/;
/**
@@ -70,9 +73,18 @@ var _BOM = /^\uFEFF/;
exports.cache = utils.cache;
/**
+ * Custom file loader. Useful for template preprocessing or restricting access
+ * to a certain part of the filesystem.
+ *
+ * @type {fileLoader}
+ */
+
+exports.fileLoader = fs.readFileSync;
+
+/**
* Name of the object containing the locals.
*
- * This variable is overriden by {@link Options}`.localsName` if it is not
+ * This variable is overridden by {@link Options}`.localsName` if it is not
* `undefined`.
*
* @type {String}
@@ -104,21 +116,41 @@ exports.resolveInclude = function(name, filename, isDir) {
/**
* Get the path to the included file by Options
- *
+ *
* @param {String} path specified path
* @param {Options} options compilation options
* @return {String}
*/
-function getIncludePath(path, options){
+function getIncludePath(path, options) {
var includePath;
+ var filePath;
+ var views = options.views;
+
+ // Abs path
if (path.charAt(0) == '/') {
includePath = exports.resolveInclude(path.replace(/^\/*/,''), options.root || '/', true);
}
+ // Relative paths
else {
- if (!options.filename) {
- throw new Error('`include` use relative path requires the \'filename\' option.');
+ // Look relative to a passed filename first
+ if (options.filename) {
+ filePath = exports.resolveInclude(path, options.filename);
+ if (fs.existsSync(filePath)) {
+ includePath = filePath;
+ }
+ }
+ // Then look in any views directories
+ if (!includePath) {
+ if (Array.isArray(views) && views.some(function (v) {
+ filePath = exports.resolveInclude(path, v, true);
+ return fs.existsSync(filePath);
+ })) {
+ includePath = filePath;
+ }
+ }
+ if (!includePath) {
+ throw new Error('Could not find include include file.');
}
- includePath = exports.resolveInclude(path, options.filename);
}
return includePath;
}
@@ -155,7 +187,7 @@ function handleCache(options, template) {
return func;
}
if (!hasTemplate) {
- template = fs.readFileSync(filename).toString().replace(_BOM, '');
+ template = fileLoader(filename).toString().replace(_BOM, '');
}
}
else if (!hasTemplate) {
@@ -164,7 +196,7 @@ function handleCache(options, template) {
throw new Error('Internal EJS error: no file name or template '
+ 'provided');
}
- template = fs.readFileSync(filename).toString().replace(_BOM, '');
+ template = fileLoader(filename).toString().replace(_BOM, '');
}
func = exports.compile(template, options);
if (options.cache) {
@@ -174,6 +206,41 @@ function handleCache(options, template) {
}
/**
+ * Try calling handleCache with the given options and data and call the
+ * callback with the result. If an error occurs, call the callback with
+ * the error. Used by renderFile().
+ *
+ * @memberof module:ejs-internal
+ * @param {Options} options compilation options
+ * @param {Object} data template data
+ * @param {RenderFileCallback} cb callback
+ * @static
+ */
+
+function tryHandleCache(options, data, cb) {
+ var result;
+ try {
+ result = handleCache(options)(data);
+ }
+ catch (err) {
+ return cb(err);
+ }
+ return cb(null, result);
+}
+
+/**
+ * fileLoader is independent
+ *
+ * @param {String} filePath ejs file path.
+ * @return {String} The contents of the specified file.
+ * @static
+ */
+
+function fileLoader(filePath){
+ return exports.fileLoader(filePath);
+}
+
+/**
* Get the template function.
*
* If `options.cache` is `true`, then the template is cached.
@@ -206,8 +273,8 @@ function includeSource(path, options) {
var opts = utils.shallowCopy({}, options);
var includePath;
var template;
- includePath = getIncludePath(path,opts);
- template = fs.readFileSync(includePath).toString().replace(_BOM, '');
+ includePath = getIncludePath(path, opts);
+ template = fileLoader(includePath).toString().replace(_BOM, '');
opts.filename = includePath;
var templ = new Template(template, opts);
templ.generateSource();
@@ -231,10 +298,11 @@ function includeSource(path, options) {
* @static
*/
-function rethrow(err, str, filename, lineno){
+function rethrow(err, str, flnm, lineno, esc){
var lines = str.split('\n');
var start = Math.max(lineno - 3, 0);
var end = Math.min(lines.length, lineno + 3);
+ var filename = esc(flnm); // eslint-disable-line
// Error context
var context = lines.slice(start, end).map(function (line, i){
var curr = i + start + 1;
@@ -254,24 +322,8 @@ function rethrow(err, str, filename, lineno){
throw err;
}
-/**
- * Copy properties in data object that are recognized as options to an
- * options object.
- *
- * This is used for compatibility with earlier versions of EJS and Express.js.
- *
- * @memberof module:ejs-internal
- * @param {Object} data data object
- * @param {Options} opts options object
- * @static
- */
-
-function cpOptsInData(data, opts) {
- _OPTS.forEach(function (p) {
- if (typeof data[p] != 'undefined') {
- opts[p] = data[p];
- }
- });
+function stripSemi(str){
+ return str.replace(/;(\s*$)/, '$1');
}
/**
@@ -326,7 +378,7 @@ exports.render = function (template, d, o) {
// No options object -- if there are optiony names
// in the data, copy them to options
if (arguments.length == 2) {
- cpOptsInData(data, opts);
+ utils.shallowCopyFromList(opts, data, _OPTS);
}
return handleCache(opts, template)(data);
@@ -346,37 +398,43 @@ exports.render = function (template, d, o) {
*/
exports.renderFile = function () {
- var args = Array.prototype.slice.call(arguments);
- var filename = args.shift();
- var cb = args.pop();
- var data = args.shift() || {};
- var opts = args.pop() || {};
- var result;
-
- // Don't pollute passed in opts obj with new vals
- opts = utils.shallowCopy({}, opts);
-
- // No options object -- if there are optiony names
- // in the data, copy them to options
- if (arguments.length == 3) {
- // Express 4
- if (data.settings && data.settings['view options']) {
- cpOptsInData(data.settings['view options'], opts);
+ var filename = arguments[0];
+ var cb = arguments[arguments.length - 1];
+ var opts = {filename: filename};
+ var data;
+
+ if (arguments.length > 2) {
+ data = arguments[1];
+
+ // No options object -- if there are optiony names
+ // in the data, copy them to options
+ if (arguments.length === 3) {
+ // Express 4
+ if (data.settings) {
+ if (data.settings['view options']) {
+ utils.shallowCopyFromList(opts, data.settings['view options'], _OPTS_EXPRESS);
+ }
+ if (data.settings.views) {
+ opts.views = data.settings.views;
+ }
+ }
+ // Express 3 and lower
+ else {
+ utils.shallowCopyFromList(opts, data, _OPTS_EXPRESS);
+ }
}
- // Express 3 and lower
else {
- cpOptsInData(data, opts);
+ // Use shallowCopy so we don't pollute passed in opts obj with new vals
+ utils.shallowCopy(opts, arguments[2]);
}
- }
- opts.filename = filename;
- try {
- result = handleCache(opts)(data);
+ opts.filename = filename;
}
- catch(err) {
- return cb(err);
+ else {
+ data = {};
}
- return cb(null, result);
+
+ return tryHandleCache(opts, data, cb);
};
/**
@@ -409,6 +467,7 @@ function Template(text, opts) {
options.rmWhitespace = opts.rmWhitespace;
options.root = opts.root;
options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME;
+ options.views = opts.views;
if (options.strict) {
options._with = false;
@@ -444,7 +503,7 @@ Template.prototype = {
var opts = this.opts;
var prepended = '';
var appended = '';
- var escape = opts.escapeFunction;
+ var escapeFn = opts.escapeFunction;
if (!this.source) {
this.generateSource();
@@ -465,19 +524,15 @@ Template.prototype = {
+ 'try {' + '\n'
+ this.source
+ '} catch (e) {' + '\n'
- + ' rethrow(e, __lines, __filename, __line);' + '\n'
+ + ' rethrow(e, __lines, __filename, __line, escapeFn);' + '\n'
+ '}' + '\n';
}
else {
src = this.source;
}
- if (opts.debug) {
- console.log(src);
- }
-
if (opts.client) {
- src = 'escape = escape || ' + escape.toString() + ';' + '\n' + src;
+ src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
if (opts.compileDebug) {
src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
}
@@ -486,9 +541,12 @@ Template.prototype = {
if (opts.strict) {
src = '"use strict";\n' + src;
}
+ if (opts.debug) {
+ console.log(src);
+ }
try {
- fn = new Function(opts.localsName + ', escape, include, rethrow', src);
+ fn = new Function(opts.localsName + ', escapeFn, include, rethrow', src);
}
catch(e) {
// istanbul ignore else
@@ -496,7 +554,9 @@ Template.prototype = {
if (opts.filename) {
e.message += ' in ' + opts.filename;
}
- e.message += ' while compiling ejs';
+ e.message += ' while compiling ejs\n\n';
+ e.message += 'If the above error is not helpful, you may want to try EJS-Lint:\n';
+ e.message += 'https://github.com/RyanZim/EJS-Lint';
}
throw e;
}
@@ -517,7 +577,7 @@ Template.prototype = {
}
return includeFile(path, opts)(d);
};
- return fn.apply(opts.context, [data || {}, escape, include, rethrow]);
+ return fn.apply(opts.context, [data || {}, escapeFn, include, rethrow]);
};
returnedFn.dependencies = this.dependencies;
return returnedFn;
@@ -576,7 +636,7 @@ Template.prototype = {
+ ' try {' + '\n'
+ includeObj.source
+ ' } catch (e) {' + '\n'
- + ' rethrow(e, __lines, __filename, __line);' + '\n'
+ + ' rethrow(e, __lines, __filename, __line, escapeFn);' + '\n'
+ ' }' + '\n'
+ ' ; }).call(this)' + '\n';
}else{
@@ -622,118 +682,115 @@ Template.prototype = {
return arr;
},
- scanLine: function (line) {
- var self = this;
- var d = this.opts.delimiter;
- var newLineCount = 0;
+ _addOutput: function (line) {
+ if (this.truncate) {
+ // Only replace single leading linebreak in the line after
+ // -%> tag -- this is the single, trailing linebreak
+ // after the tag that the truncation mode replaces
+ // Handle Win / Unix / old Mac linebreaks -- do the \r\n
+ // combo first in the regex-or
+ line = line.replace(/^(?:\r\n|\r|\n)/, '');
+ this.truncate = false;
+ }
+ else if (this.opts.rmWhitespace) {
+ // rmWhitespace has already removed trailing spaces, just need
+ // to remove linebreaks
+ line = line.replace(/^\n/, '');
+ }
+ if (!line) {
+ return line;
+ }
- function _addOutput() {
- if (self.truncate) {
- // Only replace single leading linebreak in the line after
- // -%> tag -- this is the single, trailing linebreak
- // after the tag that the truncation mode replaces
- // Handle Win / Unix / old Mac linebreaks -- do the \r\n
- // combo first in the regex-or
- line = line.replace(/^(?:\r\n|\r|\n)/, '');
- self.truncate = false;
- }
- else if (self.opts.rmWhitespace) {
- // Gotta be more careful here.
- // .replace(/^(\s*)\n/, '$1') might be more appropriate here but as
- // rmWhitespace already removes trailing spaces anyway so meh.
- line = line.replace(/^\n/, '');
- }
- if (!line) {
- return;
- }
+ // Preserve literal slashes
+ line = line.replace(/\\/g, '\\\\');
- // Preserve literal slashes
- line = line.replace(/\\/g, '\\\\');
+ // Convert linebreaks
+ line = line.replace(/\n/g, '\\n');
+ line = line.replace(/\r/g, '\\r');
- // Convert linebreaks
- line = line.replace(/\n/g, '\\n');
- line = line.replace(/\r/g, '\\r');
+ // Escape double-quotes
+ // - this will be the delimiter during execution
+ line = line.replace(/"/g, '\\"');
+ this.source += ' ; __append("' + line + '")' + '\n';
+ },
- // Escape double-quotes
- // - this will be the delimiter during execution
- line = line.replace(/"/g, '\\"');
- self.source += ' ; __append("' + line + '")' + '\n';
- }
+ scanLine: function (line) {
+ var self = this;
+ var d = this.opts.delimiter;
+ var newLineCount = 0;
newLineCount = (line.split('\n').length - 1);
switch (line) {
- case '<' + d:
- case '<' + d + '_':
- this.mode = Template.modes.EVAL;
- break;
- case '<' + d + '=':
- this.mode = Template.modes.ESCAPED;
- break;
- case '<' + d + '-':
- this.mode = Template.modes.RAW;
- break;
- case '<' + d + '#':
- this.mode = Template.modes.COMMENT;
- break;
- case '<' + d + d:
- this.mode = Template.modes.LITERAL;
- this.source += ' ; __append("' + line.replace('<' + d + d, '<' + d) + '")' + '\n';
- break;
- case d + d + '>':
- this.mode = Template.modes.LITERAL;
- this.source += ' ; __append("' + line.replace(d + d + '>', d + '>') + '")' + '\n';
- break;
- case d + '>':
- case '-' + d + '>':
- case '_' + d + '>':
- if (this.mode == Template.modes.LITERAL) {
- _addOutput();
- }
+ case '<' + d:
+ case '<' + d + '_':
+ this.mode = Template.modes.EVAL;
+ break;
+ case '<' + d + '=':
+ this.mode = Template.modes.ESCAPED;
+ break;
+ case '<' + d + '-':
+ this.mode = Template.modes.RAW;
+ break;
+ case '<' + d + '#':
+ this.mode = Template.modes.COMMENT;
+ break;
+ case '<' + d + d:
+ this.mode = Template.modes.LITERAL;
+ this.source += ' ; __append("' + line.replace('<' + d + d, '<' + d) + '")' + '\n';
+ break;
+ case d + d + '>':
+ this.mode = Template.modes.LITERAL;
+ this.source += ' ; __append("' + line.replace(d + d + '>', d + '>') + '")' + '\n';
+ break;
+ case d + '>':
+ case '-' + d + '>':
+ case '_' + d + '>':
+ if (this.mode == Template.modes.LITERAL) {
+ this._addOutput(line);
+ }
- this.mode = null;
- this.truncate = line.indexOf('-') === 0 || line.indexOf('_') === 0;
- break;
- default:
+ this.mode = null;
+ this.truncate = line.indexOf('-') === 0 || line.indexOf('_') === 0;
+ break;
+ default:
// In script mode, depends on type of tag
- if (this.mode) {
+ if (this.mode) {
// If '//' is found without a line break, add a line break.
- switch (this.mode) {
- case Template.modes.EVAL:
- case Template.modes.ESCAPED:
- case Template.modes.RAW:
- if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
- line += '\n';
- }
+ switch (this.mode) {
+ case Template.modes.EVAL:
+ case Template.modes.ESCAPED:
+ case Template.modes.RAW:
+ if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
+ line += '\n';
}
- switch (this.mode) {
+ }
+ switch (this.mode) {
// Just executing code
- case Template.modes.EVAL:
- this.source += ' ; ' + line + '\n';
- break;
+ case Template.modes.EVAL:
+ this.source += ' ; ' + line + '\n';
+ break;
// Exec, esc, and output
- case Template.modes.ESCAPED:
- this.source += ' ; __append(escape(' +
- line.replace(_TRAILING_SEMCOL, '').trim() + '))' + '\n';
- break;
+ case Template.modes.ESCAPED:
+ this.source += ' ; __append(escapeFn(' + stripSemi(line) + '))' + '\n';
+ break;
// Exec and output
- case Template.modes.RAW:
- this.source += ' ; __append(' +
- line.replace(_TRAILING_SEMCOL, '').trim() + ')' + '\n';
- break;
- case Template.modes.COMMENT:
+ case Template.modes.RAW:
+ this.source += ' ; __append(' + stripSemi(line) + ')' + '\n';
+ break;
+ case Template.modes.COMMENT:
// Do nothing
- break;
+ break;
// Literal <%% mode, append as raw output
- case Template.modes.LITERAL:
- _addOutput();
- break;
- }
+ case Template.modes.LITERAL:
+ this._addOutput(line);
+ break;
}
+ }
// In string mode, just add the output
- else {
- _addOutput();
- }
+ else {
+ this._addOutput(line);
+ }
}
if (self.opts.compileDebug && newLineCount) {
@@ -774,10 +831,10 @@ if (require.extensions) {
require.extensions['.ejs'] = function (module, flnm) {
var filename = flnm || /* istanbul ignore next */ module.filename;
var options = {
- filename: filename,
- client: true
- };
- var template = fs.readFileSync(filename).toString();
+ filename: filename,
+ client: true
+ };
+ var template = fileLoader(filename).toString();
var fn = exports.compile(template, options);
module._compile('module.exports = ' + fn.toString() + ';', filename);
};
@@ -793,6 +850,16 @@ if (require.extensions) {
exports.VERSION = _VERSION_STRING;
+/**
+ * Name for detection of EJS.
+ *
+ * @readonly
+ * @type {String}
+ * @public
+ */
+
+exports.name = _NAME;
+
/* istanbul ignore if */
if (typeof window != 'undefined') {
window.ejs = exports;
diff --git a/lib/utils.js b/lib/utils.js
index 9e2c1d0..1b539da 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -45,17 +45,17 @@ exports.escapeRegExpChars = function (string) {
};
var _ENCODE_HTML_RULES = {
- '&': '&'
- , '<': '<'
- , '>': '>'
- , '"': '"'
- , "'": '''
- }
- , _MATCH_HTML = /[&<>\'"]/g;
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+};
+var _MATCH_HTML = /[&<>\'"]/g;
function encode_char(c) {
return _ENCODE_HTML_RULES[c] || c;
-};
+}
/**
* Stringified version of constants used by {@link module:utils.escapeXML}.
@@ -98,11 +98,13 @@ exports.escapeXML = function (markup) {
.replace(_MATCH_HTML, encode_char);
};
exports.escapeXML.toString = function () {
- return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr
+ return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr;
};
/**
- * Copy all properties from one object to another, in a shallow fashion.
+ * Naive copy of properties from one object to another.
+ * Does not recurse into non-scalar properties
+ * Does not check to see if the property has a value before copying
*
* @param {Object} to Destination object
* @param {Object} from Source object
@@ -119,6 +121,28 @@ exports.shallowCopy = function (to, from) {
};
/**
+ * Naive copy of a list of key names, from one object to another.
+ * Only copies property if it is actually defined
+ * Does not recurse into non-scalar properties
+ *
+ * @param {Object} to Destination object
+ * @param {Object} from Source object
+ * @param {Array} list List of properties to copy
+ * @return {Object} Destination object
+ * @static
+ * @private
+ */
+exports.shallowCopyFromList = function (to, from, list) {
+ for (var i = 0; i < list.length; i++) {
+ var p = list[i];
+ if (typeof from[p] != 'undefined') {
+ to[p] = from[p];
+ }
+ }
+ return to;
+};
+
+/**
* Simple in-process cache implementation. Does not implement limits of any
* sort.
*
@@ -138,4 +162,3 @@ exports.cache = {
this._data = {};
}
};
-
diff --git a/package.json b/package.json
index fb178ba..76fc2d7 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"engine",
"ejs"
],
- "version": "2.5.2",
+ "version": "2.5.7",
"author": "Matthew Eernisse <mde at fleegix.org> (http://fleegix.org)",
"contributors": [
"Timothy Gu <timothygu99 at gmail.com> (https://timothygu.github.io)"
@@ -23,21 +23,22 @@
"devDependencies": {
"browserify": "^13.0.1",
"eslint": "^3.0.0",
+ "git-directory-deploy": "^1.5.1",
"istanbul": "~0.4.3",
"jake": "^8.0.0",
"jsdoc": "^3.4.0",
"lru-cache": "^4.0.1",
"mocha": "^3.0.2",
- "rimraf": "^2.2.8",
"uglify-js": "^2.6.2"
},
"engines": {
"node": ">=0.10.0"
},
"scripts": {
- "test": "mocha",
+ "test": "jake test",
+ "lint": "eslint \"**/*.js\" Jakefile",
"coverage": "istanbul cover node_modules/mocha/bin/_mocha",
- "doc": "rimraf out && jsdoc -c jsdoc.json lib/* docs/jsdoc/*",
- "devdoc": "rimraf out && jsdoc -p -c jsdoc.json lib/* docs/jsdoc/*"
+ "doc": "jake doc",
+ "devdoc": "jake doc[dev]"
}
}
diff --git a/test/ejs.js b/test/ejs.js
old mode 100644
new mode 100755
index a2d593d..0c18d7e
--- a/test/ejs.js
+++ b/test/ejs.js
@@ -1,15 +1,16 @@
/* jshint mocha: true */
+/* eslint-env node, mocha */
/**
* Module dependencies.
*/
-var ejs = require('..')
- , fs = require('fs')
- , read = fs.readFileSync
- , assert = require('assert')
- , path = require('path')
- , LRU = require('lru-cache');
+var ejs = require('..');
+var fs = require('fs');
+var read = fs.readFileSync;
+var assert = require('assert');
+var path = require('path');
+var LRU = require('lru-cache');
try {
fs.mkdirSync(__dirname + '/tmp');
@@ -103,10 +104,28 @@ suite('ejs.compile(str, options)', function () {
delete ejs.delimiter;
});
+ test('support custom escape function', function () {
+ var customEscape;
+ var fn;
+ customEscape = function customEscape(str) {
+ return !str ? '' : str.toUpperCase();
+ };
+ fn = ejs.compile('HELLO <%= name %>', {escape: customEscape});
+ assert.equal(fn({name: 'world'}), 'HELLO WORLD');
+ });
+
+ test('strict mode works', function () {
+ assert.equal(ejs.render(fixture('strict.ejs'), {}, {strict: true}), 'true');
+ });
+
+});
+
+suite('client mode', function () {
+
test('have a working client option', function () {
- var fn
- , str
- , preFn;
+ var fn;
+ var str;
+ var preFn;
fn = ejs.compile('<p><%= foo %></p>', {client: true});
str = fn.toString();
if (!process.env.running_under_istanbul) {
@@ -116,9 +135,9 @@ suite('ejs.compile(str, options)', function () {
});
test('support client mode without locals', function () {
- var fn
- , str
- , preFn;
+ var fn;
+ var str;
+ var preFn;
fn = ejs.compile('<p><%= "foo" %></p>', {client: true});
str = fn.toString();
if (!process.env.running_under_istanbul) {
@@ -129,27 +148,17 @@ suite('ejs.compile(str, options)', function () {
test('not include rethrow() in client mode if compileDebug is false', function () {
var fn = ejs.compile('<p><%= "foo" %></p>', {
- client: true
- , compileDebug: false
- });
+ client: true,
+ compileDebug: false
+ });
// There could be a `rethrow` in the function declaration
assert((fn.toString().match(/rethrow/g) || []).length <= 1);
});
- test('support custom escape function', function () {
- var customEscape
- , fn;
- customEscape = function customEscape(str) {
- return !str ? '' : str.toUpperCase();
- };
- fn = ejs.compile('HELLO <%= name %>', {escape: customEscape});
- assert.equal(fn({name: 'world'}), 'HELLO WORLD');
- });
-
test('support custom escape function in client mode', function () {
- var customEscape
- , fn
- , str;
+ var customEscape;
+ var fn;
+ var str;
customEscape = function customEscape(str) {
return !str ? '' : str.toUpperCase();
};
@@ -157,14 +166,31 @@ suite('ejs.compile(str, options)', function () {
str = fn.toString();
if (!process.env.running_under_istanbul) {
eval('var preFn = ' + str);
- assert.equal(preFn({name: 'world'}), 'HELLO WORLD');
+ assert.equal(preFn({name: 'world'}), 'HELLO WORLD'); // eslint-disable-line no-undef
}
});
- test('strict mode works', function () {
- assert.equal(ejs.render(fixture('strict.ejs'), {}, {strict: true}), 'true');
+ test('escape filename in errors in client mode', function () {
+ assert.throws(function () {
+ var fn = ejs.compile('<% throw new Error("whoops"); %>', {client: true, filename: '<script>'});
+ fn();
+ }, /Error: <script>/);
});
+});
+/* Old API -- remove when this shim goes away */
+suite('ejs.render(str, dataAndOpts)', function () {
+ test('render the template with data/opts passed together', function () {
+ assert.equal(ejs.render('<p><?= foo ?></p>', {foo: 'yay', delimiter: '?'}),
+ '<p>yay</p>');
+ });
+
+ test('disallow unsafe opts passed along in data', function () {
+ assert.equal(ejs.render('<p><?= locals.foo ?></p>',
+ // localsName should not get reset because it's blacklisted
+ {_with: false, foo: 'yay', delimiter: '?', localsName: '_'}),
+ '<p>yay</p>');
+ });
});
suite('ejs.render(str, data, opts)', function () {
@@ -228,10 +254,10 @@ suite('ejs.render(str, data, opts)', function () {
});
test('support caching', function () {
- var file = __dirname + '/tmp/render.ejs'
- , options = {cache: true, filename: file}
- , out = ejs.render('<p>Old</p>', {}, options)
- , expected = '<p>Old</p>';
+ var file = __dirname + '/tmp/render.ejs';
+ var options = {cache: true, filename: file};
+ var out = ejs.render('<p>Old</p>', {}, options);
+ var expected = '<p>Old</p>';
assert.equal(out, expected);
// Assert no change, still in cache
out = ejs.render('<p>New</p>', {}, options);
@@ -239,11 +265,11 @@ suite('ejs.render(str, data, opts)', function () {
});
test('support LRU caching', function () {
- var oldCache = ejs.cache
- , file = __dirname + '/tmp/render.ejs'
- , options = {cache: true, filename: file}
- , out
- , expected = '<p>Old</p>';
+ var oldCache = ejs.cache;
+ var file = __dirname + '/tmp/render.ejs';
+ var options = {cache: true, filename: file};
+ var out;
+ var expected = '<p>Old</p>';
// Switch to LRU
ejs.cache = LRU();
@@ -259,10 +285,11 @@ suite('ejs.render(str, data, opts)', function () {
});
test('opts.context', function () {
- var ctxt = {foo: 'FOO'}
- , out = ejs.render('<%= this.foo %>', {}, {context: ctxt});
+ var ctxt = {foo: 'FOO'};
+ var out = ejs.render('<%= this.foo %>', {}, {context: ctxt});
assert.equal(out, ctxt.foo);
});
+
});
suite('ejs.renderFile(path, [data], [options], fn)', function () {
@@ -277,8 +304,8 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
});
test('accept locals', function(done) {
- var data = {name: 'fonebone'}
- , options = {delimiter: '$'};
+ var data = {name: 'fonebone'};
+ var options = {delimiter: '$'};
ejs.renderFile('test/fixtures/user.ejs', data, options, function(err, html) {
if (err) {
return done(err);
@@ -289,11 +316,10 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
});
test('accept locals without using with() {}', function(done) {
- var data = {name: 'fonebone'}
- , options = {delimiter: '$', _with: false}
- , doneCount = 0;
- ejs.renderFile('test/fixtures/user-no-with.ejs', data, options,
- function(err, html) {
+ var data = {name: 'fonebone'};
+ var options = {delimiter: '$', _with: false};
+ var doneCount = 0;
+ ejs.renderFile('test/fixtures/user-no-with.ejs', data, options, function(err, html) {
if (err) {
if (doneCount === 2) {
return;
@@ -323,9 +349,9 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
});
test('not catch err thrown by callback', function(done) {
- var data = {name: 'fonebone'}
- , options = {delimiter: '$'}
- , counter = 0;
+ var data = {name: 'fonebone'};
+ var options = {delimiter: '$'};
+ var counter = 0;
var d = require('domain').create();
d.on('error', function (err) {
@@ -340,8 +366,7 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
// domains. Have to make it async. Ticket closed because: "domains are
// deprecated :D"
process.nextTick(function () {
- ejs.renderFile('test/fixtures/user.ejs', data, options,
- function(err) {
+ ejs.renderFile('test/fixtures/user.ejs', data, options, function(err) {
counter++;
if (err) {
assert.notEqual(err.message, 'Exception in callback');
@@ -354,9 +379,9 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
});
test('support caching', function (done) {
- var expected = '<p>Old</p>'
- , file = __dirname + '/tmp/renderFile.ejs'
- , options = {cache: true};
+ var expected = '<p>Old</p>';
+ var file = __dirname + '/tmp/renderFile.ejs';
+ var options = {cache: true};
fs.writeFileSync(file, '<p>Old</p>');
ejs.renderFile(file, {}, options, function (err, out) {
@@ -379,8 +404,7 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
test('opts.context', function (done) {
var ctxt = {foo: 'FOO'};
- ejs.renderFile('test/fixtures/with-context.ejs', {},
- {context: ctxt}, function(err, html) {
+ ejs.renderFile('test/fixtures/with-context.ejs', {}, {context: ctxt}, function(err, html) {
if (err) {
return done(err);
}
@@ -389,14 +413,89 @@ suite('ejs.renderFile(path, [data], [options], fn)', function () {
});
});
+
+ test('support express multiple views folders, falls back to second if first is not available', function (done) {
+ var data = {
+ viewsText: 'test',
+ includePath: 'views-include.ejs',
+ settings: {
+ views: [
+ path.join(__dirname, 'fixtures/nonexistent-folder'),
+ path.join(__dirname, 'fixtures')
+ ]
+ }
+ };
+ ejs.renderFile(path.join(__dirname, 'fixtures/views.ejs'), data, function(error, data){
+ assert.ifError(error);
+ assert.equal('<div><p>global test</p>\n</div>\n', data);
+ done();
+ });
+
+ });
+
+ test('support express multiple views folders, falls back to second if first is not available (include preprocessor)', function (done) {
+ var data = {
+ viewsText: 'test',
+ settings: {
+ views: [
+ path.join(__dirname, 'fixtures/nonexistent-folder'),
+ path.join(__dirname, 'fixtures')
+ ]
+ }
+ };
+ ejs.renderFile(path.join(__dirname, 'fixtures/views-old.ejs'), data, function(error, data){
+ assert.ifError(error);
+ assert.equal('<div><p>global test</p>\n</div>\n', data);
+ done();
+ });
+
+ });
+
+ test('looks relative to the containing file first (include preprocessor)', function (done) {
+ var data = {
+ viewsText: 'test',
+ settings: {
+ views: [
+ path.join(__dirname, 'fixtures/views'),
+ path.join(__dirname, 'fixtures')
+ ]
+ }
+ };
+ ejs.renderFile(path.join(__dirname, 'fixtures/views-old.ejs'), data, function(error, data){
+ assert.ifError(error);
+ assert.equal('<div><p>global test</p>\n</div>\n', data);
+ done();
+ });
+
+ });
+
+ test('can reference by paths with directory names', function (done) {
+ var data = {
+ viewsText: 'test',
+ includePath: 'views/views-include.ejs',
+ settings: {
+ views: [
+ path.join(__dirname, 'fixtures/views'),
+ path.join(__dirname, 'fixtures')
+ ]
+ }
+ };
+ ejs.renderFile(path.join(__dirname, 'fixtures/views.ejs'), data, function(error, data){
+ assert.ifError(error);
+ assert.equal('<div><p>custom test</p>\n</div>\n', data);
+ done();
+ });
+
+ });
+
});
suite('cache specific', function () {
test('`clearCache` work properly', function () {
- var expected = '<p>Old</p>'
- , file = __dirname + '/tmp/clearCache.ejs'
- , options = {cache: true, filename: file}
- , out = ejs.render('<p>Old</p>', {}, options);
+ var expected = '<p>Old</p>';
+ var file = __dirname + '/tmp/clearCache.ejs';
+ var options = {cache: true, filename: file};
+ var out = ejs.render('<p>Old</p>', {}, options);
assert.equal(out, expected);
ejs.clearCache();
@@ -407,11 +506,11 @@ suite('cache specific', function () {
});
test('`clearCache` work properly, LRU', function () {
- var expected = '<p>Old</p>'
- , oldCache = ejs.cache
- , file = __dirname + '/tmp/clearCache.ejs'
- , options = {cache: true, filename: file}
- , out;
+ var expected = '<p>Old</p>';
+ var oldCache = ejs.cache;
+ var file = __dirname + '/tmp/clearCache.ejs';
+ var options = {cache: true, filename: file};
+ var out;
ejs.cache = LRU();
@@ -426,11 +525,11 @@ suite('cache specific', function () {
});
test('LRU with cache-size 1', function () {
- var oldCache = ejs.cache
- , options
- , out
- , expected
- , file;
+ var oldCache = ejs.cache;
+ var options;
+ var out;
+ var expected;
+ var file;
ejs.cache = LRU(1);
@@ -476,6 +575,11 @@ suite('<%', function () {
});
suite('<%=', function () {
+ test('should not throw an error with a // comment on the final line', function () {
+ assert.equal(ejs.render('<%=\n// a comment\nname\n// another comment %>', {name: ' <script>'}),
+ ' <script>');
+ });
+
test('escape &<script>', function () {
assert.equal(ejs.render('<%= name %>', {name: ' <script>'}),
' <script>');
@@ -505,6 +609,11 @@ suite('<%=', function () {
});
suite('<%-', function () {
+ test('should not throw an error with a // comment on the final line', function () {
+ assert.equal(ejs.render('<%-\n// a comment\nname\n// another comment %>', {name: ' <script>'}),
+ ' <script>');
+ });
+
test('not escape', function () {
assert.equal(ejs.render('<%- name %>', {name: '<script>'}),
'<script>');
@@ -555,13 +664,13 @@ suite('-%>', function () {
});
test('works with unix style', function () {
- var content = "<ul><% -%>\n"
- + "<% users.forEach(function(user){ -%>\n"
- + "<li><%= user.name -%></li>\n"
- + "<% }) -%>\n"
- + "</ul><% -%>\n";
+ var content = '<ul><% -%>\n'
+ + '<% users.forEach(function(user){ -%>\n'
+ + '<li><%= user.name -%></li>\n'
+ + '<% }) -%>\n'
+ + '</ul><% -%>\n';
- var expectedResult = "<ul><li>geddy</li>\n<li>neil</li>\n<li>alex</li>\n</ul>";
+ var expectedResult = '<ul><li>geddy</li>\n<li>neil</li>\n<li>alex</li>\n</ul>';
var fn;
fn = ejs.compile(content);
assert.equal(fn({users: users}),
@@ -569,13 +678,13 @@ suite('-%>', function () {
});
test('works with windows style', function () {
- var content = "<ul><% -%>\r\n"
- + "<% users.forEach(function(user){ -%>\r\n"
- + "<li><%= user.name -%></li>\r\n"
- + "<% }) -%>\r\n"
- + "</ul><% -%>\r\n";
+ var content = '<ul><% -%>\r\n'
+ + '<% users.forEach(function(user){ -%>\r\n'
+ + '<li><%= user.name -%></li>\r\n'
+ + '<% }) -%>\r\n'
+ + '</ul><% -%>\r\n';
- var expectedResult = "<ul><li>geddy</li>\r\n<li>neil</li>\r\n<li>alex</li>\r\n</ul>";
+ var expectedResult = '<ul><li>geddy</li>\r\n<li>neil</li>\r\n<li>alex</li>\r\n</ul>';
var fn;
fn = ejs.compile(content);
assert.equal(fn({users: users}),
@@ -669,8 +778,8 @@ suite('exceptions', function () {
var unhook = null;
test('log JS source when debug is set', function (done) {
- var out = ''
- , needToExit = false;
+ var out = '';
+ var needToExit = false;
unhook = hook_stdio(process.stdout, function (str) {
out += str;
if (needToExit) {
@@ -685,6 +794,22 @@ suite('exceptions', function () {
});
ejs.render(fixture('hello-world.ejs'), {}, {debug: true});
});
+
+ test('escape filename in errors', function () {
+ assert.throws(function () {
+ ejs.render('<% throw new Error("whoops"); %>', {}, {filename: '<script>'});
+ }, /Error: <script>/);
+ });
+
+ test('filename in errors uses custom escape', function () {
+ assert.throws(function () {
+ ejs.render('<% throw new Error("whoops"); %>', {}, {
+ filename: '<script>',
+ escape: function () { return 'zooby'; }
+ });
+ }, /Error: zooby/);
+ });
+
teardown(function() {
if (!unhook) {
return;
@@ -708,12 +833,24 @@ suite('include()', function () {
fixture('include-simple.html'));
});
+ test('include and escape ejs', function () {
+ var file = 'test/fixtures/include-escaped.ejs';
+ assert.equal(ejs.render(fixture('include-escaped.ejs'), {}, {filename: file}),
+ fixture('include-escaped.html'));
+ });
+
+ test('include in expression ejs', function () {
+ var file = 'test/fixtures/include-expression.ejs';
+ assert.equal(ejs.render(fixture('include-expression.ejs'), {}, {filename: file}),
+ fixture('include-expression.html'));
+ });
+
test('include ejs fails without `filename`', function () {
try {
ejs.render(fixture('include-simple.ejs'));
}
catch (err) {
- assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
+ assert.ok(err.message.indexOf('Could not find') > -1);
return;
}
throw new Error('expected inclusion error');
@@ -741,11 +878,10 @@ suite('include()', function () {
});
test('include ejs with set root path', function () {
- var file = 'test/fixtures/include-root.ejs',
- viewsPath = path.join(__dirname, 'fixtures');
+ var file = 'test/fixtures/include-root.ejs';
+ var viewsPath = path.join(__dirname, 'fixtures');
assert.equal(ejs.render(fixture('include-root.ejs'), {pets: users}, {filename: file, delimiter: '@',root:viewsPath}),
fixture('include.html'));
-
});
test('work when nested', function () {
@@ -755,8 +891,8 @@ suite('include()', function () {
});
test('work with a variable path', function () {
- var file = 'test/fixtures/menu_var.ejs',
- includePath = 'includes/menu-item';
+ var file = 'test/fixtures/menu_var.ejs';
+ var includePath = 'includes/menu-item';
assert.equal(ejs.render(fixture('menu.ejs'), {pets: users, varPath: includePath}, {filename: file}),
fixture('menu.html'));
});
@@ -768,12 +904,12 @@ suite('include()', function () {
});
test('pass compileDebug to include', function () {
- var file = 'test/fixtures/include.ejs'
- , fn;
+ var file = 'test/fixtures/include.ejs';
+ var fn;
fn = ejs.compile(fixture('include.ejs'), {
- filename: file
- , delimiter: '@'
- , compileDebug: false
+ filename: file,
+ delimiter: '@',
+ compileDebug: false
});
try {
// Render without a required variable reference
@@ -789,9 +925,9 @@ suite('include()', function () {
test('is dynamic', function () {
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>Old</p>');
- var file = 'test/fixtures/include_cache.ejs'
- , options = {filename: file}
- , out = ejs.compile(fixture('include_cache.ejs'), options);
+ var file = 'test/fixtures/include_cache.ejs';
+ var options = {filename: file};
+ var out = ejs.compile(fixture('include_cache.ejs'), options);
assert.equal(out(), '<p>Old</p>\n');
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
@@ -800,10 +936,10 @@ suite('include()', function () {
test('support caching', function () {
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>Old</p>');
- var file = 'test/fixtures/include_cache.ejs'
- , options = {cache: true, filename: file}
- , out = ejs.render(fixture('include_cache.ejs'), {}, options)
- , expected = fixture('include_cache.html');
+ var file = 'test/fixtures/include_cache.ejs';
+ var options = {cache: true, filename: file};
+ var out = ejs.render(fixture('include_cache.ejs'), {}, options);
+ var expected = fixture('include_cache.html');
assert.equal(out, expected);
out = ejs.render(fixture('include_cache.ejs'), {}, options);
// No change, still in cache
@@ -813,6 +949,17 @@ suite('include()', function () {
assert.equal(out, expected);
});
+ test('handles errors in included file', function() {
+ try {
+ ejs.render('<%- include("fixtures/include-with-error") %>', {}, {filename: path.join(__dirname, 'f.ejs')});
+ }
+ catch (err) {
+ assert.ok(err.message.indexOf('foobar is not defined') > -1);
+ return;
+ }
+ throw new Error('expected inclusion error');
+ });
+
});
suite('preprocessor include', function () {
@@ -831,7 +978,7 @@ suite('preprocessor include', function () {
ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {delimiter: '@'});
}
catch (err) {
- assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
+ assert.ok(err.message.indexOf('Could not find') > -1);
return;
}
throw new Error('expected inclusion error');
@@ -851,8 +998,8 @@ suite('preprocessor include', function () {
});
test('tracks dependency correctly', function () {
- var file = 'test/fixtures/menu_preprocessor.ejs'
- , fn = ejs.compile(fixture('menu_preprocessor.ejs'), {filename: file});
+ var file = 'test/fixtures/menu_preprocessor.ejs';
+ var fn = ejs.compile(fixture('menu_preprocessor.ejs'), {filename: file});
assert(fn.dependencies.length);
});
@@ -863,12 +1010,12 @@ suite('preprocessor include', function () {
});
test('pass compileDebug to include', function () {
- var file = 'test/fixtures/include_preprocessor.ejs'
- , fn;
+ var file = 'test/fixtures/include_preprocessor.ejs';
+ var fn;
fn = ejs.compile(fixture('include_preprocessor.ejs'), {
- filename: file
- , delimiter: '@'
- , compileDebug: false
+ filename: file,
+ delimiter: '@',
+ compileDebug: false
});
try {
// Render without a required variable reference
@@ -884,9 +1031,9 @@ suite('preprocessor include', function () {
test('is static', function () {
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>Old</p>');
- var file = 'test/fixtures/include_preprocessor_cache.ejs'
- , options = {filename: file}
- , out = ejs.compile(fixture('include_preprocessor_cache.ejs'), options);
+ var file = 'test/fixtures/include_preprocessor_cache.ejs';
+ var options = {filename: file};
+ var out = ejs.compile(fixture('include_preprocessor_cache.ejs'), options);
assert.equal(out(), '<p>Old</p>\n');
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
@@ -895,10 +1042,10 @@ suite('preprocessor include', function () {
test('support caching', function () {
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>Old</p>');
- var file = 'test/fixtures/include_preprocessor_cache.ejs'
- , options = {cache: true, filename: file}
- , out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options)
- , expected = fixture('include_preprocessor_cache.html');
+ var file = 'test/fixtures/include_preprocessor_cache.ejs';
+ var options = {cache: true, filename: file};
+ var out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options);
+ var expected = fixture('include_preprocessor_cache.html');
assert.equal(out, expected);
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options);
@@ -906,13 +1053,24 @@ suite('preprocessor include', function () {
});
test('whitespace slurp and rmWhitespace work', function() {
- var file = 'test/fixtures/include_preprocessor_line_slurp.ejs'
- , template = fixture('include_preprocessor_line_slurp.ejs')
- , expected = fixture('include_preprocessor_line_slurp.html')
- , options = {rmWhitespace: true, filename: file};
- assert.equal(ejs.render(template, options),
+ var file = 'test/fixtures/include_preprocessor_line_slurp.ejs';
+ var template = fixture('include_preprocessor_line_slurp.ejs');
+ var expected = fixture('include_preprocessor_line_slurp.html');
+ var options = {rmWhitespace: true, filename: file};
+ assert.equal(ejs.render(template, {}, options),
expected);
- })
+ });
+
+ test('handles errors in included file', function() {
+ try {
+ ejs.render('<%- include fixtures/include-with-error %>', {}, {filename: path.join(__dirname, 'f.ejs')});
+ }
+ catch (err) {
+ assert.ok(err.message.indexOf('foobar is not defined') > -1);
+ return;
+ }
+ throw new Error('expected inclusion error');
+ });
});
@@ -927,12 +1085,31 @@ suite('require', function () {
// Only works with inline/preprocessor includes
test('allow ejs templates to be required as node modules', function () {
- var file = 'test/fixtures/include_preprocessor.ejs'
- , template = require(__dirname + '/fixtures/menu_preprocessor.ejs');
- if (!process.env.running_under_istanbul) {
- assert.equal(template({filename: file, pets: users}),
+ var file = 'test/fixtures/include_preprocessor.ejs';
+ var template = require(__dirname + '/fixtures/menu_preprocessor.ejs');
+ if (!process.env.running_under_istanbul) {
+ assert.equal(template({filename: file, pets: users}),
fixture('menu_preprocessor.html'));
+ }
+ });
+});
+
+suite('test fileloader', function () {
+
+ var myFileLoad = function (filePath) {
+ return 'myFileLoad: ' + fs.readFileSync(filePath);
+ };
+
+ test('test custom fileload', function (done) {
+ ejs.fileLoader = myFileLoad;
+ ejs.renderFile('test/fixtures/para.ejs', function(err, html) {
+ if (err) {
+ return done(err);
}
+ assert.equal(html, 'myFileLoad: <p>hey</p>\n');
+ done();
+ });
+
});
});
@@ -944,8 +1121,8 @@ suite('examples', function () {
}
suite(f, function () {
test('doesn\'t throw any errors', function () {
- var stderr = hook_stdio(process.stderr, noop)
- , stdout = hook_stdio(process.stdout, noop);
+ var stderr = hook_stdio(process.stderr, noop);
+ var stdout = hook_stdio(process.stdout, noop);
try {
require('../examples/' + f);
}
@@ -960,3 +1137,13 @@ suite('examples', function () {
});
});
});
+
+suite('meta information', function () {
+ test('has a version', function () {
+ assert.strictEqual(ejs.VERSION, require('../package.json').version);
+ });
+
+ test('had a name', function () {
+ assert.strictEqual(ejs.name, 'ejs');
+ });
+});
diff --git a/test/fixtures/include-escaped.ejs b/test/fixtures/include-escaped.ejs
new file mode 100755
index 0000000..63ca827
--- /dev/null
+++ b/test/fixtures/include-escaped.ejs
@@ -0,0 +1,3 @@
+<ul>
+ <%= include('hello-world'); %>
+</ul>
diff --git a/test/fixtures/include-escaped.html b/test/fixtures/include-escaped.html
new file mode 100755
index 0000000..c126745
--- /dev/null
+++ b/test/fixtures/include-escaped.html
@@ -0,0 +1,4 @@
+<ul>
+ <p>Hello world!</p>
+
+</ul>
diff --git a/test/fixtures/include-expression.ejs b/test/fixtures/include-expression.ejs
new file mode 100755
index 0000000..888c687
--- /dev/null
+++ b/test/fixtures/include-expression.ejs
@@ -0,0 +1,3 @@
+<ul>
+ <%- 'ping: ' + include('hello-world').replace(/world/, 'planet'); %>
+</ul>
diff --git a/test/fixtures/include-expression.html b/test/fixtures/include-expression.html
new file mode 100755
index 0000000..3c960a4
--- /dev/null
+++ b/test/fixtures/include-expression.html
@@ -0,0 +1,4 @@
+<ul>
+ ping: <p>Hello planet!</p>
+
+</ul>
diff --git a/test/fixtures/include-with-error.ejs b/test/fixtures/include-with-error.ejs
new file mode 100644
index 0000000..240d938
--- /dev/null
+++ b/test/fixtures/include-with-error.ejs
@@ -0,0 +1 @@
+<p><%- foobar() %></p>
\ No newline at end of file
diff --git a/test/fixtures/no.newlines.error.ejs b/test/fixtures/no.newlines.error.ejs
index 17bca4e..bf1d71e 100644
--- a/test/fixtures/no.newlines.error.ejs
+++ b/test/fixtures/no.newlines.error.ejs
@@ -1,5 +1,5 @@
AAA
-<% data = "test"; -%>
+<% var data = "test"; -%>
BBB
<%= qdata %>
CCC
diff --git a/test/fixtures/views-include.ejs b/test/fixtures/views-include.ejs
new file mode 100644
index 0000000..45a750b
--- /dev/null
+++ b/test/fixtures/views-include.ejs
@@ -0,0 +1 @@
+<p>global <%= viewsText %></p>
diff --git a/test/fixtures/views-old.ejs b/test/fixtures/views-old.ejs
new file mode 100644
index 0000000..a60e7a0
--- /dev/null
+++ b/test/fixtures/views-old.ejs
@@ -0,0 +1 @@
+<div><%- include views-include.ejs %></div>
diff --git a/test/fixtures/views.ejs b/test/fixtures/views.ejs
new file mode 100644
index 0000000..f0e123a
--- /dev/null
+++ b/test/fixtures/views.ejs
@@ -0,0 +1 @@
+<div><%- include(includePath, {viewsText: 'test'}) %></div>
diff --git a/test/fixtures/views/views-include.ejs b/test/fixtures/views/views-include.ejs
new file mode 100644
index 0000000..23c6820
--- /dev/null
+++ b/test/fixtures/views/views-include.ejs
@@ -0,0 +1 @@
+<p>custom <%= viewsText %></p>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-ejs.git
More information about the Pkg-javascript-commits
mailing list