[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>'}),
+      '&nbsp;<script>');
+  });
+
   test('escape &<script>', function () {
     assert.equal(ejs.render('<%= name %>', {name: ' <script>'}),
         '&nbsp;<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