[Pkg-javascript-commits] [node-ejs] 01/02: Imported Upstream version 2.4.2

Thorsten Alteholz alteholz at moszumanska.debian.org
Sun Jul 17 13:10:20 UTC 2016


This is an automated email from the git hooks/post-receive script.

alteholz pushed a commit to branch master
in repository node-ejs.

commit 3b613ebc55510a9b24da247f8cb7fe2ccae9879a
Author: Thorsten Alteholz <debian at alteholz.de>
Date:   Sun Jul 17 15:10:17 2016 +0200

    Imported Upstream version 2.4.2
---
 .gitignore                                    |  22 +
 .jshintignore                                 |   5 +
 .jshintrc                                     |  14 +
 .travis.yml                                   |  10 +
 CHANGELOG.md                                  | 154 +++++
 CODE_OF_CONDUCT.md                            |  50 ++
 Jakefile                                      |  46 ++
 LICENSE                                       | 202 ++++++
 README.md                                     | 239 +++++++
 docs/jsdoc/cache.jsdoc                        |  37 +
 docs/jsdoc/callbacks.jsdoc                    |   9 +
 docs/jsdoc/options.jsdoc                      |  59 ++
 docs/jsdoc/template-functions.jsdoc           |  70 ++
 docs/syntax.md                                | 603 +++++++++++++++++
 examples/client-compilation.html              | 141 ++++
 examples/client-injection.html                |  46 ++
 examples/functions.ejs                        |   9 +
 examples/functions.js                         |  19 +
 examples/list.ejs                             |   8 +
 examples/list.js                              |  15 +
 jsdoc.json                                    |  15 +
 lib/ejs.js                                    | 759 +++++++++++++++++++++
 lib/utils.js                                  | 141 ++++
 package.json                                  |  43 ++
 sample/body.html                              |   2 +
 sample/header.html                            |   3 +
 sample/index.html                             |  20 +
 sample/index.js                               |  11 +
 test/ejs.js                                   | 932 ++++++++++++++++++++++++++
 test/fixtures/backslash.ejs                   |   1 +
 test/fixtures/backslash.html                  |   1 +
 test/fixtures/comments.ejs                    |   7 +
 test/fixtures/comments.html                   |   6 +
 test/fixtures/consecutive-tags.ejs            |   1 +
 test/fixtures/consecutive-tags.html           |   1 +
 test/fixtures/double-quote.ejs                |   1 +
 test/fixtures/double-quote.html               |   1 +
 test/fixtures/error.ejs                       |   5 +
 test/fixtures/error.out                       |   8 +
 test/fixtures/fail.ejs                        |   1 +
 test/fixtures/hello-world.ejs                 |   1 +
 test/fixtures/include-abspath.ejs             |   5 +
 test/fixtures/include-simple.ejs              |   3 +
 test/fixtures/include-simple.html             |   4 +
 test/fixtures/include.css.ejs                 |   1 +
 test/fixtures/include.css.html                |   4 +
 test/fixtures/include.ejs                     |   5 +
 test/fixtures/include.html                    |  12 +
 test/fixtures/include_cache.ejs               |   1 +
 test/fixtures/include_cache.html              |   1 +
 test/fixtures/include_preprocessor.css.ejs    |   1 +
 test/fixtures/include_preprocessor.css.html   |   4 +
 test/fixtures/include_preprocessor.ejs        |   5 +
 test/fixtures/include_preprocessor.html       |  12 +
 test/fixtures/include_preprocessor_cache.ejs  |   1 +
 test/fixtures/include_preprocessor_cache.html |   1 +
 test/fixtures/includes/bom.ejs                |   1 +
 test/fixtures/includes/menu-item.ejs          |   1 +
 test/fixtures/includes/menu/item.ejs          |   1 +
 test/fixtures/literal.ejs                     |   3 +
 test/fixtures/literal.html                    |   3 +
 test/fixtures/menu.ejs                        |  15 +
 test/fixtures/menu.html                       |   9 +
 test/fixtures/menu_preprocessor.ejs           |  11 +
 test/fixtures/menu_preprocessor.html          |   8 +
 test/fixtures/menu_var.ejs                    |  15 +
 test/fixtures/messed.ejs                      |   1 +
 test/fixtures/messed.html                     |   1 +
 test/fixtures/newlines.ejs                    |   5 +
 test/fixtures/newlines.html                   |   9 +
 test/fixtures/newlines.mixed.ejs              |   6 +
 test/fixtures/newlines.mixed.html             |   4 +
 test/fixtures/no.newlines.ejs                 |   5 +
 test/fixtures/no.newlines.error.ejs           |   5 +
 test/fixtures/no.newlines.html                |   5 +
 test/fixtures/no.semicolons.ejs               |   8 +
 test/fixtures/no.semicolons.html              |   3 +
 test/fixtures/para.ejs                        |   1 +
 test/fixtures/pet.ejs                         |   1 +
 test/fixtures/rmWhitespace.ejs                |  14 +
 test/fixtures/rmWhitespace.html               |   8 +
 test/fixtures/single-quote.ejs                |   1 +
 test/fixtures/single-quote.html               |   1 +
 test/fixtures/space-and-tab-slurp.ejs         |   5 +
 test/fixtures/space-and-tab-slurp.html        |   5 +
 test/fixtures/style.css                       |   3 +
 test/fixtures/user-no-with.ejs                |   1 +
 test/fixtures/user.ejs                        |   1 +
 test/fixtures/with-context.ejs                |   1 +
 test/mocha.opts                               |   3 +
 90 files changed, 3942 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5bdbee3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+v8.log
+*.swp
+*.swo
+auth_info.js
+dist
+.idea/
+tags
+nbproject/
+spec/browser/autogen_suite.js
+node_modules
+tmtags
+*.DS_Store
+examples/*/log/*
+site/log/*
+.log
+npm-debug.log
+doc/
+test/tmp
+coverage/
+/ejs.js
+/ejs.min.js
+out/
diff --git a/.jshintignore b/.jshintignore
new file mode 100644
index 0000000..5095308
--- /dev/null
+++ b/.jshintignore
@@ -0,0 +1,5 @@
+node_modules
+coverage
+out
+ejs.js
+ejs.min.js
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..1e93967
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,14 @@
+{
+  "evil": true,
+  "laxcomma": true,
+  "laxbreak": true,
+  "supernew": true,
+  "curly": true,
+  "immed": true,
+  "undef": true,
+  "node": true,
+  "unused": true,
+  "globals": {
+    "window": false
+  }
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d826a40
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+sudo: false
+node_js:
+  - "0.10"
+  - "0.12"
+  - "iojs-1.1"
+  - "iojs-1.2"
+before_install:
+  - npm update -g npm
+  - npm --version
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1572a5f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,154 @@
+## v2.4.1: 2016-05-24
+
++ Added LICENCSE file to release package
+* Various documentation improvements (@RyanZim)
+* Better line-numbers in errors (@dgofman)
+
+## v2.4.1: 2016-01-23
+
++ Strict-mode support (@mde)
++ Express 4 support (@mde)
++ Configurable localsName option (@mde)
+
+## v2.3.4: 2015-09-04
+
++ Whitespace slurp tag syntax -- `<%_  _%>` (@andidev)
+
+## v2.3.3: 2015-07-11
+
+* Fixed false positives for old `include` preprocessor directive (@mde)
+
+## v2.3.2: 2015-06-28
+
+* Do not require semicolons in `<%- %>` (@TimothyGu)
+* Use `__append` instead of `pushToOutput` (@dominykas)
+* Cache the character-encoding function (@alubbe)
+* Correctly specify execution context with opts.context (@mde)
+
+## v2.3.1: 2015-02-22
+
+* Stop deferring execution of `renderFile` callback, revert to sync
+  execution (@mde)
++ Generated template functions are now prettier (@whitneyit)
++ Add official documentation for EJS syntax (#9) (@TimothyGu)
++ Add inline JSDoc-style documentation for public functions (#9) (@TimothyGu)
++ Add a new dynamic client-side template compilation example in
+  `examples/client-compile.html` (@TimothyGu)
+* Fix running on Node.js v0.8. Note that we still do not support 0.8
+  officially, but if you found something that can be fixed easily please
+  point it out. (#57) (@TimothyGu)
+* Do not trim newlines at the end of files. This might be considered
+  incompatible by some, but the new behavior is the correct one, and is
+  consistent with EJS v1. (#60) (@TimothyGu)
+* Readd deprecation warning for `scope` option that was removed in v2.2.4. It
+  never caused any problems with Express or anything else so its removal was
+  a mistake. (@TimothyGu)
+* Always rethrow the error from `new Function()` (@TimothyGu)
+
+## v2.2.4: 2015-02-01
+
++ Ability to customize name of the locals object with `ejs.localsName` (@mde)
++ Ability to override `resolveInclude` for include-path lookup
+  (@olivierkaisin)
+* Only bundle rethrow() in client scripts when compileDebug is enabled
+  (@TimothyGu)
+* Copy `_with` from locals object to options object (@TimothyGu)
+* Removed deprecation warnings (@mde)
+* Significantly increased performance (@TimothyGu)
+* Defer execution for `renderFile` callback, ensure async (@TimothyGu)
+
+## v2.2.3: 2015-01-23
+
+* Better filtering for deprecation notice when called from Express (@mde)
+
+## v2.2.2: 2015-01-21
+
+* Fix handling of variable output containing semicolons (@TimothyGu)
+* Fix included files caching (@TimothyGu)
+* Simplified caching routine (@TimothyGu)
+* Filter out deprecation warning for `renderFile` when called from
+  Express (@mde)
+
+## v2.2.1: 2015-01-19
+
++ 4x faster HTML escaping function, especially beneficial if you use lots
+  of escaped locals (@TimothyGu)
++ Up to 4x faster compiled functions in addition to above (@TimothyGu)
++ Caching mode regression test coverage (@TimothyGu)
+* Fix `//` in an expanded string (@TimothyGu)
+* Fix literal mode without an end tag (@TimothyGu)
+* Fix setting options to renderFile() through the legacy 3-argument interface
+  (as is the case for Express.js) (@TimothyGu)
++ Added version string to exported object for use in browsers (@mde)
+
+## v2.1.4: 2015-01-12
+
+* Fix harmony mode (@mde)
+
+## v2.1.3: 2015-01-11
+
+* Fix `debug` option (@TimothyGu)
+* Fix two consecutive tags together (@TimothyGu)
+
+## v2.1.2: 2015-01-11
+
+* Fix `scope` option handling
++ Improve testing coverage (@TimothyGu)
+
+## v2.1.1: 2015-01-11
+
++ Add `_with` option to control whether or not to use `with() {}` constructs
+  (@TimothyGu)
++ Improve test coverage (@mde & @TimothyGu)
++ Add a few more metadata fields to `package.json` (@TimothyGu)
+- Revert hack for Etherpad Lite (@TimothyGu)
+* Do not claim node < 0.10.0 support (@TimothyGu)
+* Pin dependencies more loosely (@TimothyGu)
+* Fix client function generation without using locals (@TimothyGu)
+* Fix error case where the callback be called twice (@TimothyGu)
+* Add `"use strict";` to all JS files (@TimothyGu)
+* Fix absolute path inclusion (@TimothyGu) (#11)
+
+## v2.0.8: 2015-01-06
+
+* Fix crash on missing file
+
+## v2.0.7: 2015-01-05
+
+* Linting and cosmetics
+
+## v2.0.6: 2015-01-04
+
+* Temporary hack for Etherpad Lite. It will be removed soon.
+
+## v2.0.5: 2015-01-04
+
+* Fix leaking global `fn`
+
+## v2.0.4: 2015-01-04
+
+* Fix leaking global `includeSource`
+* Update client-side instructions
+
+## v2.0.3: 2015-01-04
+
++ Add Travis CI support
++ Add LICENSE file
++ Better compatibility with EJS v1 for options
++ Add `debug` option
+* Fix typos in examples in README
+
+## v2.0.2: 2015-01-03
+
+* Use lowercase package name in `package.json`
+
+## v2.0.1: 2015-01-02
+
++ Completely rewritten
++ Single custom delimiter (e.g., `?`) with `delimiter` option instead of
+  `open`/`close` options
++ `include` now runtime function call instead of preprocessor directive
++ Variable-based includes now possible
++ Comment tag support (`<%#`)
+* Data and options now separate params (i.e., `render(str, data, options);`)
+- Removed support for filters
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..22bc94c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,50 @@
+# Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of
+fostering an open and welcoming community, we pledge to respect all people who
+contribute through reporting issues, posting feature requests, updating
+documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, ethnicity, age, religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic
+  addresses, without explicit permission
+* Other unethical or unprofessional conduct
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+By adopting this Code of Conduct, project maintainers commit themselves to
+fairly and consistently applying these principles to every aspect of managing
+this project. Project maintainers who do not follow or enforce the Code of
+Conduct may be permanently removed from the project team.
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting a project maintainer at mde at fleegix.org. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. Maintainers are
+obligated to maintain confidentiality with regard to the reporter of an
+incident.
+
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.3.0, available at
+[http://contributor-covenant.org/version/1/3/0/][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/3/0/
diff --git a/Jakefile b/Jakefile
new file mode 100644
index 0000000..28defba
--- /dev/null
+++ b/Jakefile
@@ -0,0 +1,46 @@
+var fs = require('fs')
+  , buildOpts = {
+      printStdout: true
+    , printStderr: true
+    };
+
+task('build', ['browserify', 'minify'], function () {
+  console.log('Build completed.');
+});
+
+desc('Cleans browerified/minified files and package files');
+task('clean', ['clobber'], function () {
+  jake.rmRf('./ejs.js');
+  jake.rmRf('./ejs.min.js');
+});
+
+task('browserify', {async: true}, function () {
+  jake.exec('./node_modules/browserify/bin/cmd.js lib/ejs.js > ejs.js',
+      buildOpts, function () {
+    console.log('Browserification completed.');
+    setTimeout(complete, 0);
+  });
+});
+
+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);
+  });
+});
+
+publishTask('ejs', ['build'], function () {
+  this.packageFiles.include([
+    'Jakefile'
+  , 'README.md'
+  , 'LICENSE'
+  , 'package.json'
+  , 'ejs.js'
+  , 'ejs.min.js'
+  , 'lib/**'
+  , 'test/**'
+  ]);
+});
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b88b7b4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,239 @@
+# EJS
+
+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#info=devDependencies)
+
+## Installation
+
+```bash
+$ npm install ejs
+```
+
+## Features
+
+  * Control flow with `<% %>`
+  * Escaped output with `<%= %>` (escape function configurable)
+  * Unescaped raw output with `<%- %>`
+  * Newline-trim mode ('newline slurping') with `-%>` ending tag
+  * Whitespace-trim mode (slurp all whitespace) for control flow with `<%_ _%>`
+  * Custom delimiters (e.g., use '<? ?>' instead of '<% %>')
+  * Includes
+  * Client-side support
+  * Static caching of intermediate JavaScript
+  * Static caching of templates
+  * Complies with the [Express](http://expressjs.com) view system
+
+## Example
+
+```html
+<% if (user) { %>
+  <h2><%= user.name %></h2>
+<% } %>
+```
+
+## Usage
+
+```javascript
+var template = ejs.compile(str, options);
+template(data);
+// => Rendered HTML string
+
+ejs.render(str, data, options);
+// => Rendered HTML string
+
+ejs.renderFile(filename, data, options, function(err, str){
+    // str => Rendered HTML string
+});
+```
+
+It is also possible to use `ejs.render(dataAndOptions);` where you pass
+everything in a single object. In that case, you'll end up with local variables
+for all the passed options. However, be aware that your code could break if we 
+add an option with the same name as one of your data object's properties.
+Therefore, we do not recommend using this shortcut.
+
+## Options
+
+  - `cache`           Compiled functions are cached, requires `filename`
+  - `filename`        The name of the file being rendered. Not required if you 
+    are using `renderFile()`. Used by `cache` to key caches, and for includes.
+  - `context`         Function execution context
+  - `compileDebug`    When `false` no debug instrumentation is compiled
+  - `client`          When `true`, compiles a function that can be rendered 
+    in the browser without needing to load the EJS Runtime 
+    ([ejs.min.js](https://github.com/mde/ejs/releases/latest)).
+  - `delimiter`       Character to use with angle brackets for open/close
+  - `debug`           Output generated function body
+  - `strict`          When set to `true`, generated function is in strict mode
+  - `_with`           Whether or not to use `with() {}` constructs. If `false` then the locals will be stored in the `locals` object. Set to `false` in strict mode.
+  - `localsName`      Name to use for the object storing local variables when not using `with` Defaults to `locals`
+  - `rmWhitespace`    Remove all safe-to-remove whitespace, including leading
+    and trailing whitespace. It also enables a safer version of `-%>` line
+    slurping for all scriptlet tags (it does not strip new lines of tags in
+    the middle of a line).
+  - `escape`          The escaping function used with `<%=` construct. It is
+    used in rendering and is `.toString()`ed in the generation of client functions. (By default escapes XML).
+
+This project uses [JSDoc](http://usejsdoc.org/). For the full public API 
+documentation, clone the repository and run `npm run doc`. This will run JSDoc 
+with the proper options and output the documentation to `out/`. If you want 
+the both the public & private API docs, run `npm run devdoc` instead.
+
+## Tags
+
+  - `<%`              'Scriptlet' tag, for control-flow, no output
+  - `<%_`             'Whitespace Slurping' Scriptlet tag, strips all whitespace before it
+  - `<%=`             Outputs the value into the template (escaped)
+  - `<%-`             Outputs the unescaped value into the template
+  - `<%#`             Comment tag, no execution, no output
+  - `<%%`             Outputs a literal '<%'
+  - `%>`              Plain ending tag
+  - `-%>`             Trim-mode ('newline slurp') tag, trims following newline
+  - `_%>`             'Whitespace Slurping' ending tag, removes all whitespace after it
+
+For the full syntax documentation, please see [docs/syntax.md](https://github.com/mde/ejs/blob/master/docs/syntax.md).
+
+## Includes
+
+Includes either have to be an absolute path, or, if not, are assumed as
+relative to the template with the `include` call. For example if you are 
+including `./views/user/show.ejs` from `./views/users.ejs` you would 
+use `<%- include('user/show') %>`.
+
+You must specify the `filename` option for the template with the `include` 
+call unless you are using `renderFile()`.
+
+You'll likely want to use the raw output tag (`<%-`) with your include to avoid
+double-escaping the HTML output.
+
+```html
+<ul>
+  <% users.forEach(function(user){ %>
+    <%- include('user/show', {user: user}) %>
+  <% }); %>
+</ul>
+```
+
+Includes are inserted at runtime, so you can use variables for the path in the
+`include` call (for example `<%- include(somePath) %>`). Variables in your
+top-level data object are available to all your includes, but local variables
+need to be passed down.
+
+NOTE: Include preprocessor directives (`<% include user/show %>`) are
+still supported.
+
+## Custom delimiters
+
+Custom delimiters can be applied on a per-template basis, or globally:
+
+```javascript
+var ejs = require('ejs'),
+    users = ['geddy', 'neil', 'alex'];
+
+// Just one template
+ejs.render('<?= users.join(" | "); ?>', {users: users}, {delimiter: '?'});
+// => 'geddy | neil | alex'
+
+// Or globally
+ejs.delimiter = '$';
+ejs.render('<$= users.join(" | "); $>', {users: users});
+// => 'geddy | neil | alex'
+```
+
+## Caching
+
+EJS ships with a basic in-process cache for caching the intermediate JavaScript
+functions used to render templates. It's easy to plug in LRU caching using
+Node's `lru-cache` library:
+
+```javascript
+var ejs = require('ejs')
+  , LRU = require('lru-cache');
+ejs.cache = LRU(100); // LRU cache with 100-item limit
+```
+
+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.
+
+## Layouts
+
+EJS does not specifically support blocks, but layouts can be implemented by
+including headers and footers, like so:
+
+
+```html
+<%- include('header') -%>
+<h1>
+  Title
+</h1>
+<p>
+  My page
+</p>
+<%- include('footer') -%>
+```
+
+## Client-side support
+
+Go to the [Latest Release](https://github.com/mde/ejs/releases/latest), download
+`./ejs.js` or `./ejs.min.js`. Alternately, you can compile it yourself by cloning 
+the repository and running `jake build` (or `$(npm bin)/jake build` if jake is 
+not installed globally).
+
+Include one of these files on your page, and `ejs` should be available globally.
+
+### Example
+
+```html
+<div id="output"></div>
+<script src="ejs.min.js"></script>
+<script>
+  var people = ['geddy', 'neil', 'alex'],
+      html = ejs.render('<%= people.join(", "); %>', {people: people});
+  // With jQuery:
+  $('#output').html(html);
+  // Vanilla JS:
+  document.getElementById('output').innerHTML = html;
+</script>
+```
+
+### Caveats
+
+Most of EJS will work as expected; however, there are a few things to note:
+
+1. Obviously, since you do not have access to the filesystem, `ejs.renderFile()` won't work.
+2. For the same reason, `include`s do not work unless you use an `IncludeCallback`. Here is an example:
+  ```javascript
+  var str = "Hello <%= include('file', {person: 'John'}); %>",
+      fn = ejs.compile(str, {client: true});
+  
+  fn(data, null, function(path, d){ // IncludeCallback
+    // path -> 'file'
+    // d -> {person: 'John'}
+    // Put your code here 
+    // Return the contents of file as a string
+  }); // returns rendered string
+  ```
+
+## Related projects
+
+There are a number of implementations of EJS:
+
+ * TJ's implementation, the v1 of this library: https://github.com/tj/ejs
+ * Jupiter Consulting's EJS: http://www.embeddedjs.com/
+ * EJS Embedded JavaScript Framework on Google Code: https://code.google.com/p/embeddedjavascript/
+ * Sam Stephenson's Ruby implementation: https://rubygems.org/gems/ejs
+ * Erubis, an ERB implementation which also runs JavaScript: http://www.kuwata-lab.com/erubis/users-guide.04.html#lang-javascript
+
+## License
+
+Licensed under the Apache License, Version 2.0
+(<http://www.apache.org/licenses/LICENSE-2.0>)
+
+- - -
+EJS Embedded JavaScript templates copyright 2112
+mde at fleegix.org.
+
+
diff --git a/docs/jsdoc/cache.jsdoc b/docs/jsdoc/cache.jsdoc
new file mode 100644
index 0000000..caf8866
--- /dev/null
+++ b/docs/jsdoc/cache.jsdoc
@@ -0,0 +1,37 @@
+/**
+ * A JavaScript function cache. This is implemented by the lru-cache module
+ * on NPM, so you can simply do `ejs.cache = LRU(10)` to get a
+ * least-recently-used cache.
+ *
+ * @interface Cache
+ * @global
+ */
+
+/**
+ * Cache the intermediate JavaScript function for a template.
+ *
+ * @function
+ * @name Cache#set
+ * @param {String}   key key for caching
+ * @param {Function} val cached function
+ */
+
+/**
+ * Get the cached intermediate JavaScript function for a template.
+ *
+ * If the cache does not contain the specified key, `null` shall be returned.
+ *
+ * @function
+ * @name Cache#get
+ * @param {String}   key key for caching
+ * @return {null|Function}
+ */
+
+/**
+ * Reset the entire cache.
+ *
+ * Erases the entire cache. Called by {@link module:ejs.clearCache}
+ *
+ * @function
+ * @name Cache#reset
+ */
diff --git a/docs/jsdoc/callbacks.jsdoc b/docs/jsdoc/callbacks.jsdoc
new file mode 100644
index 0000000..c962e2c
--- /dev/null
+++ b/docs/jsdoc/callbacks.jsdoc
@@ -0,0 +1,9 @@
+/**
+ * Callback for receiving data from {@link module:ejs.renderFile}.
+ *
+ * @callback RenderFileCallback
+ * @param {?Error}  err error, if any resulted from the rendering process
+ * @param {?String} str output string, is `null` if there is an error
+ * @static
+ * @global
+ */
diff --git a/docs/jsdoc/options.jsdoc b/docs/jsdoc/options.jsdoc
new file mode 100644
index 0000000..3a531a3
--- /dev/null
+++ b/docs/jsdoc/options.jsdoc
@@ -0,0 +1,59 @@
+/**
+ * Compilation and rendering options.
+ *
+ * @typedef Options
+ * @type {Object}
+ *
+ * @property {Boolean} [debug=false]
+ * Log generated JavaScript source for the EJS template to the console.
+ *
+ * @property {Boolean} [compileDebug=true]
+ * Include additional runtime debugging information in generated template
+ * functions.
+ *
+ * @property {Boolean} [_with=true]
+ * Whether or not to use `with () {}` construct in the generated template
+ * functions. If set to `false`, data is still accessible through the object
+ * whose name is specified by {@link module:ejs.localsName} (default to
+ * `locals`).
+ *
+ * @property {Boolean} [rmWhitespace=false]
+ * Remove all safe-to-remove whitespace, including leading and trailing
+ * whitespace. It also enables a safer version of `-%>` line slurping for all
+ * scriptlet tags (it does not strip new lines of tags in the middle of a
+ * 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} 
+ * 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 
+ * you are using {@link module:ejs.renderFile}. Also used for error reporting.
+ * 
+ * @property {String}  [delimiter='%']
+ * The delimiter used in template compilation.
+ *
+ * @property {Boolean} [cache=false]
+ * Whether or not to enable caching of template functions. Beware that
+ * the options of compilation are not checked as being the same, so
+ * special handling is required if, for example, you want to cache client
+ * and regular functions of the same file.
+ *
+ * Requires `filename` to be set. Only works with rendering function.
+ *
+ * @property {Object}  [context=this]
+ * The Object to which `this` is set during rendering.
+ *
+ * @property {Object}  [scope=this]
+ * Alias of `context`. Deprecated.
+ *
+ * @static
+ * @global
+ */
+
diff --git a/docs/jsdoc/template-functions.jsdoc b/docs/jsdoc/template-functions.jsdoc
new file mode 100644
index 0000000..a1b422a
--- /dev/null
+++ b/docs/jsdoc/template-functions.jsdoc
@@ -0,0 +1,70 @@
+/**
+ * This type of function is returned from {@link module:ejs.compile}, when
+ * {@link Options}`.client` is false.
+ *
+ * @callback TemplateFunction
+ * @param {Object}          [locals={}]
+ * an object of data to be passed into the template.
+ * @static
+ * @global
+ */
+
+/**
+ * This type of function is returned from {@link module:ejs.compile}, when
+ * {@link Options}`.client` is true.
+ *
+ * This is also used internally to generate a
+ * {@link TemplateFunction}.
+ *
+ * @callback ClientFunction
+ * @param {Object}          [locals={}]
+ * an object of data to be passed into the template. The name of this variable
+ * is adjustable through {@link module:ejs.localsName}.
+ *
+ * @param {EscapeCallback}  [escape={@link Options}.escape]
+ * callback used to escape variables
+ *
+ * @param {IncludeCallback} [include]
+ * callback used to include files at runtime with `include()`
+ *
+ * @param {RethrowCallback} [rethrow={@link module:ejs-internal.rethrow}]
+ * callback used to handle and rethrow errors
+ *
+ * @static
+ * @global
+ */
+
+/**
+ * Escapes a string using HTML/XML escaping rules.
+ *
+ * @callback EscapeCallback
+ * @param  {String} markup Input string
+ * @return {String}        Escaped string
+ * @static
+ * @global
+ */
+
+/**
+ * This type of callback is used when {@link Options}`.compileDebug`
+ * is true, and an error in the template is thrown. By default it is used to
+ * rethrow an error in a better-formatted way.
+ *
+ * @callback RethrowCallback
+ * @param {Error}  err      Error object
+ * @param {String} str      full EJS source
+ * @param {String} filename file name of the EJS file
+ * @param {String} lineno   line number of the error
+ * @static
+ * @global
+ */
+
+/**
+ * The callback called by {@link ClientFunction} to include files at runtime with `include()`
+ *
+ * @callback IncludeCallback
+ * @param  {String} path      Path to be included
+ * @param  {Object} [data]    Data passed to the template
+ * @return {String}           Contents of the file requested
+ * @static
+ * @global
+ */
diff --git a/docs/syntax.md b/docs/syntax.md
new file mode 100644
index 0000000..a77c87d
--- /dev/null
+++ b/docs/syntax.md
@@ -0,0 +1,603 @@
+EJS Syntax Reference
+====================
+
+EJS is designed to be flexible and easy-to-write, but without too much
+abstractions to cover up the HTML base.
+
+Table of contents
+-----------------
+
+- Basic format
+- Delimiters
+- Starting tags
+  - `<%=`: Escaped output
+  - `<%-`: Unescaped output
+  - `<%#`: Comments
+  - `<%`: Scriptlet
+  - `<%_`: Scriptlet, removes all preceeding whitespace
+- Ending tags
+  - `%>`: Regular ending tag
+  - `-%>`: Removes trailing newline
+  - `_%>`: Removes all trailing whitespace
+- Literal tag
+- Including other files
+  - “Preprocessor” directive
+  - JavaScript `include()` function
+- Copyright
+
+
+Basic format
+------------
+
+An EJS “tag” is the primary functioning unit in an EJS template. With the
+exception of the literal tag, all tags are formed by the following format:
+
+<pre><<em>starting</em> <em>content</em> <em>closing</em>></pre>
+
+The spaces between *starting* and *content*, and *content* and *closing* are
+not required; they are recommended though for readability.
+
+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 
+change it.
+
+Starting tags
+-------------
+
+### `<%=`: Escaped output
+
+The most important thing for a template language is the ability to pass
+variables to the template. In EJS, this is done with the `<%=` and `<%-` tags.
+
+`<%=` is the starting tag to use for variables that need to be escaped. If the
+specified string contains forbidden characters like `<` and `&`, they are
+escaped automatically with HTML codes.
+
+The content of the tag can be any valid JavaScript operators, so tags like
+`<%= name ? name : (lastName || 'John Doe') %>` would work as intended.
+
+#### Example
+
+##### EJS
+
+```html
+<p>Hello, <%= name %>.</p>
+<p>Hello, <%= 'the Most Honorable ' + name %>.</p>
+```
+
+##### Locals
+
+```json
+{
+  "name": "Timoth<y>"
+}
+```
+
+##### HTML
+
+```html
+<p>Hello, Timoth<y>.</p>
+<p>Hello, the Most Honorable Timoth<y>.</p>
+```
+
+### `<%-`: Unescaped output
+
+If your local contains preformatted HTML, you might not want to escape it. In
+this case, use the `<%-` tag.
+
+However, always **be 100% sure** the rendered local is sanitized, to prevent
+cross-site scripting (XSS) attacks.
+
+#### Example
+
+##### EJS
+
+```html
+<p>Hello, <%- myHtml %>.</p>
+<p>Hello, <%= myHtml %>.</p>
+
+<p>Hello, <%- myMaliciousHtml %>.</p>
+<p>Hello, <%= myMaliciousHtml %>.</p>
+```
+
+##### Locals
+
+```json
+{
+  "myHtml": "<strong>Timothy</strong>"
+, "myMaliciousHtml": "</p><script>document.write()</script><p>"
+}
+```
+
+##### HTML
+
+```html
+<p>Hello, <strong>Timothy</strong>.</p>
+<p>Hello, <strong>Timothy</strong>.</p>
+
+<p>Hello, </p><script>document.write()</script><p>.</p>
+<p>Hello, </p><script>document.write()</script><p>.</p>
+```
+
+### `<%#`: Comments
+
+The `<%#` starting tag denotes that the statement is a comment that is not to
+be executed or rendered in the resulting HTML.
+
+#### Whitespace
+
+The use of `<%#` might cause some useless whitespace, as illustrated by the
+example below. You can trim it using the `-%>` ending tag.
+
+#### Example
+
+##### EJS
+
+```html
+<div>
+<%# comment %>
+</div>
+
+<div>
+<%# comment -%>
+</div>
+```
+
+##### HTML
+
+```html
+<div>
+
+</div>
+
+<div>
+</div>
+```
+
+### `<%`: Scriptlets
+
+Scriptlets in the `<%` tag allows logic to be embedded in an EJS template. You
+are free to use *any* JavaScript syntax in this tag, and to mix JavaScript
+with EJS. You can also put multiple statements in one tag.
+
+#### Comments
+
+All types of JavaScript comments are allowed, although it is preferable to use
+the `<%#` tag for comments. For example, the following three code blocks are
+equivalent, though `<%#` is the shortest.
+
+```js
+<%# comment %>
+<%/* comment */%>
+<%// comment %>
+```
+
+#### Curly brackets
+
+Always use brackets in loops and conditionals that involves mixing EJS
+template and JavaScript scriptlets. Omitting brackets might work for some
+statements, but the behavior is undefined and subject to change.
+
+It is not necessary to use curly brackets for scriptlet-only code.
+
+```html
+<%# Bad practice %>
+<% if (true) %>
+  <p>Yay it's true!</p>
+
+<%# Good practice %>
+<% if (true) { %>
+  <p>Yay it's true!</p>
+<% } %>
+```
+
+```js
+<%# These are all valid statements %>
+<% var output
+     , exclamation = ''
+     , shouldOutput = false
+
+   if (true)
+     output = 'true!'
+
+   if (true) {
+     exclamation = 'Yay! ';
+   }
+
+   if (true) shouldOutput = true; %>
+
+<% if (shouldOutput) { %>
+  <%= exclamation + 'It\'s ' + output %>
+<% } %>
+```
+
+#### Line breaks inside a tag
+
+Line breaks are allowed in `<%` tags.
+
+Unless the statement involves mixing EJS and JavaScript scriptlet, always put
+complete statements in a tag. For example, the following works:
+
+```js
+<% var stringToShow = thisIsABooleanVariableWithAVeryLongName
+                    ? 'OK'
+                    : 'not OK' %>
+```
+
+While the following does not:
+
+```js
+<% var stringToShow = thisIsABooleanVariableWithAVeryLongName %>
+<%                  ? 'OK'                                    %>
+<%                  : 'not OK'                                %>
+```
+
+#### Semicolons
+
+As is in JavaScript, semicolons are not required if proper line breaks are
+preserved.
+
+#### Whitespace
+
+The use of scriptlets might cause some useless whitespace, as illustrated by
+the example below. You can trim it by
+
+1. using the `-%>` ending tag, and
+2. using the `<%_` starting tag or starting the tag in the beginning of a line.
+
+#### Example
+
+In the following example, several different coding styles are used
+simultaneously, to show that EJS is flexible with regards to personal habits.
+It does *not* mean that we recommend mixing coding styles in your own project.
+
+##### EJS
+
+```html
+<dl>
+<%for (var i = 0; i < users.length; i++) {    %><%
+  var user = users[i]
+      , name = user.name // the name of the user
+    %><%# comment %>
+  <%var age  = user.age; /* the age of the user */%>
+  <dt><%= name %></dt>
+  <dd><%= age %></dd>
+<%}-%>
+</dl>
+```
+
+##### Locals
+
+```json
+{
+  "users": [
+    {
+      "name": "Timothy"
+    , "age":  15
+    }
+  , {
+      "name": "Juan"
+    , "age":  51
+    }
+  ]
+}
+```
+
+##### HTML
+
+```html
+<dl>
+
+
+
+  <dt>Timothy</dt>
+  <dd>15</dd>
+
+
+
+  <dt>Juan</dt>
+  <dd>51</dd>
+
+</dl>
+```
+
+### `<%_` "Whitespace Slurping" Scriptlet
+
+This tag is the same as a Scriptlet, except that it removes all whitespace before it.
+
+#### Example
+
+##### EJS
+
+```html
+<ul>
+  <% users.forEach(function(user, i, arr){ -%>
+    <li><%= user %></li>
+  <% }); -%>
+</ul>
+
+<ul>
+  <%_ users.forEach(function(user, i, arr){ -%>
+    <li><%= user %></li>
+  <%_ }); -%>
+</ul>
+```
+
+##### HTML
+
+```html
+<ul>
+      <li>Anne</li>
+      <li>Bob</li>
+  </ul>
+
+<ul>
+    <li>Anne</li>
+    <li>Bob</li>
+</ul>
+```
+
+Ending tags
+-----------
+
+There are three flavors of ending tags: the regular one, the
+newline-trimming one, and the whitespace-slurping one.
+
+### `%>`: Regular ending tag
+
+As used in all of the examples above, `%>` is the standard tag used to end an
+EJS expression.
+
+### `-%>`: Newline-trimming ending tag
+
+`-%>` trims all extra newlines a scriptlet or a comment might cause. It does
+not have any effect on output tags.
+
+#### Example
+
+##### EJS
+
+```html
+Beginning of template
+<%  'this is a statement'
+ + ' that is long'
+ + ' and long'
+ + ' and long' %>
+End of template
+---
+Beginning of template
+<%  'this is a statement'
+ + ' that is long'
+ + ' and long'
+ + ' and long' -%>
+End of template
+```
+
+##### Output
+
+```html
+Beginning of template
+
+End of template
+---
+Beginning of template
+End of template
+```
+
+### `_%>`: Whitespace-slurping ending tag
+
+`_%>` removes all whitespace after it.
+
+Literal tag
+-----------
+
+To output a literal `<%`, use `<%%`. If a customized delimiter is used, use
+the same syntax. E.g. use `<$$` to get `<$` if the delimiter is `$`.
+
+In regards to all the other tags, the literal tag is special as it does not
+need a closing tag to function.
+
+However, think twice before you use this tag because the `<` character might
+need to be escaped as `<`.
+
+#### Example
+
+The following example wrap `<%` in a `<pre>`, where it is not necessary to
+escape `<`.
+
+##### EJS
+
+```html
+<pre>This is literal: <%%</pre>
+<pre>This is literal too: <%% %></pre>
+```
+
+##### HTML
+
+```html
+<pre>This is literal: <%</pre>
+<pre>This is literal too: <% %></pre>
+```
+
+## Including other files
+
+EJS offer two ways of including other files. You can even include files that
+are not EJS templates, as the case is for CSS stylesheets.
+
+For both flavors, if the file specified does not have an extension, `.ejs` is
+automatically appended. If it is an absolute path, that file is included.
+Otherwise, the file is assumed to be in the same directory as the parent
+template.
+
+The behavior of resolving included file path can be overridden using the
+`ejs.resolveInclude` function.
+
+### “Preprocessor” directive
+
+As a compatibility layer with EJS version 1, it is possible to use the
+`include` directive in an unescaped output tag to directly “yank” the
+text from another file, just like including a C header. However, as it is done
+as a simple inclusion, you cannot pass arguments to the included template. You
+can however make variables available in the parent template, that will be
+visible to the child template as well.
+
+This flavor of `include` is **static**, which means that the resulting
+function contains the copy of the included file as when it was compiled, so
+if you changed the file after compilation, the changes are not reflected.
+
+#### Whitespace control
+
+You most probably should not use the `-%>` ending tag on an `include`
+directive, as it trims the whitespace after the included file.
+
+#### Example
+
+##### included.ejs
+
+```html
+<li><%= pet.name %></li>
+```
+
+##### main.ejs
+
+```html
+<ul>
+<% pets.forEach(function (pet) { -%>
+  <% include included %>
+<% }) -%>
+</ul>
+```
+
+##### Locals
+
+```json
+{
+  "pets": [
+    {
+      "name": "Hazel"
+    }
+  , {
+      "name": "Crystal"
+    }
+  , {
+      "name": "Catcher"
+    }
+  ]
+}
+```
+
+##### “Preprocessor" output
+
+```js
+<ul>
+<% pets.forEach(function (pet) { -%>
+  <li><%= pet.name %></li>
+<% }) -%>
+</ul>
+```
+
+##### HTML
+
+```html
+<ul>
+  <li>Hazel</li>
+  <li>Crystal</li>
+  <li>Catcher</li>
+</ul>
+```
+
+### JavaScript `include()` function
+
+With the release of EJS version 2, we have added a new way of including files
+that is more intuitive. The `include()` function is available to the
+templates, with the following signature:
+
+```js
+include(filename, [locals])
+```
+
+One major difference with the method described above is that the variables in
+the parent function are not visible to the child template, unless it is
+explicitly declared in the `locals` object, or is passed as a local to the
+parent template.
+
+Also, the included file is compiled upon execution of the script, which means
+performance might be theoretically lower than the “preprocessor” flavor. In
+practice however, caching can make this difference negligible.
+
+Some cautions **MUST** to be taken if the included filename is fetched from a
+user during rendering time, as one could easily use private files as the file
+name, like `/etc/passwd` or `../api-keys`.
+
+#### Example
+
+This has the exact same effect as the example for the `include` directive.
+
+##### included.ejs
+
+```html
+<li><%= pet.name %></li>
+```
+
+##### main.ejs
+
+```html
+<ul>
+<%  pets.forEach(function (pet) { -%>
+  <%- include('included', {
+        pet: pet
+      }) %>
+<%  }) -%>
+</ul>
+```
+
+##### Locals
+
+```json
+{
+  "pets": [
+    {
+      "name": "Hazel"
+    }
+  , {
+      "name": "Crystal"
+    }
+  , {
+      "name": "Catcher"
+    }
+  ]
+}
+```
+
+##### HTML
+
+```html
+<ul>
+  <li>Hazel</li>
+  <li>Crystal</li>
+  <li>Catcher</li>
+</ul>
+```
+
+## Copyright
+
+This document is under the following license:
+
+Copyright © 2015 Tiancheng “Timothy” Gu
+
+Licensed under the Apache License, Version 2.0 (the “License”); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
diff --git a/examples/client-compilation.html b/examples/client-compilation.html
new file mode 100644
index 0000000..51c4140
--- /dev/null
+++ b/examples/client-compilation.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<!--
+  This example demonstrates how to compile EJS templates in the browser
+  from a <textarea>.
+
+  To use this example, first run `jake build` in the EJS root directory to
+  produce the client-side ejs.js and ejs.min.js. Alternatively, you can
+  download the prebuilt client scripts (both minified and not) in GitHub
+  releases.
+  -->
+<html>
+  <head>
+    <title>EJS compilation demo</title>
+    <style>
+      * {
+        box-sizing: border-box;
+      }
+      /* Preformatted <textarea> */
+      textarea.pre {
+        font-family: monospace;
+        font-size: 1em;
+        height: auto;
+        width: 100%;
+      }
+      pre {
+        border: 2px solid gray;
+        border-radius: 2px;
+        padding: 8px;
+        max-height: 600px;
+        min-height: 120px;
+        overflow: auto;
+        width: auto;
+      }
+      /* Prettier button stolen from Bootstrap */
+      button {
+        padding: 8px 16px;
+        font-size: 19px;
+        border: 1px solid transparent;
+        border-radius: 5px;
+        background-color: #fff;
+        border-color: #ccc;
+        cursor: pointer;
+      }
+      button:hover,
+      button:focus,
+      button:active {
+        color: #333;
+        background-color: #e6e6e6;
+        border-color: #adadad;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>
+      <button type="submit" onclick="comp()">
+        Submit
+      </button>
+    </h1>
+
+    <h2>Input template</h2>
+    <textarea rows="8" class="pre" id="input"></textarea>
+
+    <h2>Compilation options (JSON)</h2>
+    <textarea rows="8" class="pre" id="options"></textarea>
+
+    <h2>Locals (JSON)</h2>
+    <textarea rows="8" class="pre" id="locals"></textarea>
+
+    <h2 id="output-title">Output</h2>
+    <pre id="output"></pre>
+
+    <script src="../ejs.js"></script>
+    <script>
+      function comp() {
+        // Get the elements
+        var // input EJS template
+            templ = document.getElementById('input')
+            // JSON locals
+          , locals = document.getElementById('locals')
+            // JSON options
+          , options = document.getElementById('options')
+            // output text area
+          , output = document.getElementById('output')
+            // for dynamic output heading
+          , outputTitle = document.getElementById('output-title')
+          , fn;
+
+        // Handle errors by catching it, print the message to the output HTML
+        // box, and throwing it again so that it's visible on the JS console.
+
+        // `stage` sets the corresponding error context.
+        function handleError(func, stage) {
+          try {
+            // Try executing callback
+            func();
+          } catch (e) {
+            // If there is an exception:
+            // 1. Change output heading to "Error when ..."
+            outputTitle.innerHTML = 'Error ' + stage;
+            // 2. Put the stack trace into the output text area
+            output.textContent = e.stack || e.message || e.toString();
+            // 3. Rethrow it
+            throw e;
+          }
+        }
+
+        // Parse options as JSON
+        handleError(function () {
+          options = JSON.parse(options.value || '{}');
+        }, 'when parsing options');
+
+        // Parse locals as JSON
+        handleError(function () {
+          locals = JSON.parse(locals.value || '{}');
+        }, 'when parsing locals');
+
+        // Compile template with specified template and options
+        handleError(function () {
+          fn = ejs.compile(templ.value, options);
+        }, 'when compiling');
+
+        // Put HTML/client JavaScript into the output textarea
+        if (options.client) {
+          // Put generated JavaScript function into the text area
+
+          // In EJS v3 the returned function will be in string form, so that
+          // you don't need to `.toString()` anymore.
+          output.textContent = fn.toString();
+          outputTitle.innerHTML = 'Output JS';
+        }
+        else {
+          // Put the rendered HTML into the text area
+          handleError(function () {
+            output.textContent = fn(locals);
+            outputTitle.innerHTML = 'Output HTML';
+          }, 'when executing template function');
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/examples/client-injection.html b/examples/client-injection.html
new file mode 100644
index 0000000..aab0347
--- /dev/null
+++ b/examples/client-injection.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+  This example demonstrates how to compile and render EJS templates in the
+  browser. It is usually recommended to always embed a precompiled function
+  in the HTML, rather than compile it on-the-fly, but this demonstrates that
+  it is possible.
+
+  To use this example, first run `jake build` in the EJS root directory to
+  produce the client-side ejs.js and ejs.min.js. Alternatively, you can
+  download the prebuilt client scripts (both minified and not) in GitHub
+  releases.
+  -->
+<html>
+  <head>
+    <script src="../ejs.js"></script>
+    <!-- `type` can be anything but application/javascript -->
+    <script id="users" type="text/template">
+      <% if (names.length) { %>
+        <ul>
+          <% names.forEach(function(name){ %>
+            <li><%= name %></li>
+          <% }) %>
+        </ul>
+      <% } %>
+    </script>
+    <script>
+      onload = function () {
+        // get the EJS template as a string
+        var templ = document.getElementById('users').innerHTML;
+        console.log('EJS template:');
+        console.log(templ);
+        // data to output to the template function
+        var data = { names: ['loki', 'tobi', 'jane'] };
+        // stores the rendered HTML
+        var html = ejs.compile(templ)(data);
+        console.log('Rendered HTML:');
+        console.log(html);
+        // inject the rendered data to <body>
+        document.body.innerHTML = html;
+        console.log('HTML injected to <body>');
+      }
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/examples/functions.ejs b/examples/functions.ejs
new file mode 100644
index 0000000..08d274a
--- /dev/null
+++ b/examples/functions.ejs
@@ -0,0 +1,9 @@
+<h1>Users</h1>
+
+<% function user(user) { %>
+  <li><strong><%= user.name %></strong> is a <%= user.age %> year old <%= user.species %>.</li>
+<% } %>
+
+<ul>
+  <% users.map(user) %>
+</ul>
diff --git a/examples/functions.js b/examples/functions.js
new file mode 100644
index 0000000..976819c
--- /dev/null
+++ b/examples/functions.js
@@ -0,0 +1,19 @@
+/*
+ * 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 ret = ejs.compile(read(path, 'utf8'), {filename: path})(data);
+
+console.log(ret);
diff --git a/examples/list.ejs b/examples/list.ejs
new file mode 100644
index 0000000..4d74987
--- /dev/null
+++ b/examples/list.ejs
@@ -0,0 +1,8 @@
+<% if (names.length) { %>
+  <ul>
+    <% names.forEach(function (name) { %>
+      <%# Notice how the single quotation mark is escaped in the output HTML %>
+      <li foo='<%= name + "'" %>'><%= name %></li>
+    <% }) %>
+  </ul>
+<% } %>
diff --git a/examples/list.js b/examples/list.js
new file mode 100644
index 0000000..c9839d8
--- /dev/null
+++ b/examples/list.js
@@ -0,0 +1,15 @@
+/*
+ * This example demonstrates how to use Array.prototype.forEach() in an EJS
+ * template.
+ */
+
+var ejs = require('../')
+  , read = require('fs').readFileSync
+  , join = require('path').join
+  , str = read(join(__dirname, '/list.ejs'), 'utf8');
+
+var ret = ejs.compile(str)({
+  names: ['foo', 'bar', 'baz']
+});
+
+console.log(ret);
diff --git a/jsdoc.json b/jsdoc.json
new file mode 100644
index 0000000..ca4b60f
--- /dev/null
+++ b/jsdoc.json
@@ -0,0 +1,15 @@
+{
+  "_comment": "Configuration file for JSDoc."
+, "tags": {
+    "allowUnknownTags": true
+  }
+, "source": {
+      "includePattern": ".+\\.js(doc)?$"
+    , "excludePattern": "(^|\\/|\\\\)_"
+  }
+, "plugins": [ "plugins/markdown" ]
+, "templates": {
+    "cleverLinks": true
+  , "monospaceLinks": false
+  }
+}
diff --git a/lib/ejs.js b/lib/ejs.js
new file mode 100644
index 0000000..c0566a6
--- /dev/null
+++ b/lib/ejs.js
@@ -0,0 +1,759 @@
+/*
+ * EJS Embedded JavaScript templates
+ * Copyright 2112 Matthew Eernisse (mde at fleegix.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+*/
+
+'use strict';
+
+/**
+ * @file Embedded JavaScript templating engine.
+ * @author Matthew Eernisse <mde at fleegix.org>
+ * @author Tiancheng "Timothy" Gu <timothygu99 at gmail.com>
+ * @project EJS
+ * @license {@link http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0}
+ */
+
+/**
+ * EJS internal functions.
+ *
+ * Technically this "module" lies in the same file as {@link module:ejs}, for
+ * the sake of organization all the private functions re grouped into this
+ * module.
+ *
+ * @module ejs-internal
+ * @private
+ */
+
+/**
+ * Embedded JavaScript templating engine.
+ *
+ * @module ejs
+ * @public
+ */
+
+var fs = require('fs')
+  , utils = require('./utils')
+  , scopeOptionWarned = false
+  , _VERSION_STRING = require('../package.json').version
+  , _DEFAULT_DELIMITER = '%'
+  , _DEFAULT_LOCALS_NAME = 'locals'
+  , _REGEX_STRING = '(<%%|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)'
+  , _OPTS = [ 'cache', 'filename', 'delimiter', 'scope', 'context'
+            , 'debug', 'compileDebug', 'client', '_with', 'rmWhitespace'
+            , 'strict', 'localsName'
+            ]
+  , _TRAILING_SEMCOL = /;\s*$/
+  , _BOM = /^\uFEFF/;
+
+/**
+ * EJS template function cache. This can be a LRU object from lru-cache NPM
+ * module. By default, it is {@link module:utils.cache}, a simple in-process
+ * cache that grows continuously.
+ *
+ * @type {Cache}
+ */
+
+exports.cache = utils.cache;
+
+/**
+ * Name of the object containing the locals.
+ *
+ * This variable is overriden by {@link Options}`.localsName` if it is not
+ * `undefined`.
+ *
+ * @type {String}
+ * @public
+ */
+
+exports.localsName = _DEFAULT_LOCALS_NAME;
+
+/**
+ * Get the path to the included file from the parent file path and the
+ * specified path.
+ *
+ * @param {String} name     specified path
+ * @param {String} filename parent file path
+ * @return {String}
+ */
+
+exports.resolveInclude = function(name, filename) {
+  var path = require('path')
+    , dirname = path.dirname
+    , extname = path.extname
+    , resolve = path.resolve
+    , includePath = resolve(dirname(filename), name)
+    , ext = extname(name);
+  if (!ext) {
+    includePath += '.ejs';
+  }
+  return includePath;
+};
+
+/**
+ * Get the template from a string or a file, either compiled on-the-fly or
+ * read from cache (if enabled), and cache the template if needed.
+ *
+ * If `template` is not set, the file specified in `options.filename` will be
+ * read.
+ *
+ * If `options.cache` is true, this function reads the file from
+ * `options.filename` so it must be set prior to calling this function.
+ *
+ * @memberof module:ejs-internal
+ * @param {Options} options   compilation options
+ * @param {String} [template] template source
+ * @return {(TemplateFunction|ClientFunction)}
+ * Depending on the value of `options.client`, either type might be returned.
+ * @static
+ */
+
+function handleCache(options, template) {
+  var fn
+    , path = options.filename
+    , hasTemplate = arguments.length > 1;
+
+  if (options.cache) {
+    if (!path) {
+      throw new Error('cache option requires a filename');
+    }
+    fn = exports.cache.get(path);
+    if (fn) {
+      return fn;
+    }
+    if (!hasTemplate) {
+      template = fs.readFileSync(path).toString().replace(_BOM, '');
+    }
+  }
+  else if (!hasTemplate) {
+    // istanbul ignore if: should not happen at all
+    if (!path) {
+      throw new Error('Internal EJS error: no file name or template '
+                    + 'provided');
+    }
+    template = fs.readFileSync(path).toString().replace(_BOM, '');
+  }
+  fn = exports.compile(template, options);
+  if (options.cache) {
+    exports.cache.set(path, fn);
+  }
+  return fn;
+}
+
+/**
+ * Get the template function.
+ *
+ * If `options.cache` is `true`, then the template is cached.
+ *
+ * @memberof module:ejs-internal
+ * @param {String}  path    path for the specified file
+ * @param {Options} options compilation options
+ * @return {(TemplateFunction|ClientFunction)}
+ * Depending on the value of `options.client`, either type might be returned
+ * @static
+ */
+
+function includeFile(path, options) {
+  var opts = utils.shallowCopy({}, options);
+  if (!opts.filename) {
+    throw new Error('`include` requires the \'filename\' option.');
+  }
+  opts.filename = exports.resolveInclude(path, opts.filename);
+  return handleCache(opts);
+}
+
+/**
+ * Get the JavaScript source of an included file.
+ *
+ * @memberof module:ejs-internal
+ * @param {String}  path    path for the specified file
+ * @param {Options} options compilation options
+ * @return {String}
+ * @static
+ */
+
+function includeSource(path, options) {
+  var opts = utils.shallowCopy({}, options)
+    , includePath
+    , template;
+  if (!opts.filename) {
+    throw new Error('`include` requires the \'filename\' option.');
+  }
+  includePath = exports.resolveInclude(path, opts.filename);
+  template = fs.readFileSync(includePath).toString().replace(_BOM, '');
+
+  opts.filename = includePath;
+  var templ = new Template(template, opts);
+  templ.generateSource();
+  return templ.source;
+}
+
+/**
+ * Re-throw the given `err` in context to the `str` of ejs, `filename`, and
+ * `lineno`.
+ *
+ * @implements RethrowCallback
+ * @memberof module:ejs-internal
+ * @param {Error}  err      Error object
+ * @param {String} str      EJS source
+ * @param {String} filename file name of the EJS file
+ * @param {String} lineno   line number of the error
+ * @static
+ */
+
+function rethrow(err, str, filename, lineno){
+  var lines = str.split('\n')
+    , start = Math.max(lineno - 3, 0)
+    , end = Math.min(lines.length, lineno + 3);
+
+  // Error context
+  var context = lines.slice(start, end).map(function (line, i){
+    var curr = i + start + 1;
+    return (curr == lineno ? ' >> ' : '    ')
+      + curr
+      + '| '
+      + line;
+  }).join('\n');
+
+  // Alter exception message
+  err.path = filename;
+  err.message = (filename || 'ejs') + ':'
+    + lineno + '\n'
+    + context + '\n\n'
+    + err.message;
+
+  throw err;
+}
+
+/**
+ * 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];
+    }
+  });
+}
+
+/**
+ * Compile the given `str` of ejs into a template function.
+ *
+ * @param {String}  template EJS template
+ *
+ * @param {Options} opts     compilation options
+ *
+ * @return {(TemplateFunction|ClientFunction)}
+ * Depending on the value of `opts.client`, either type might be returned.
+ * @public
+ */
+
+exports.compile = function compile(template, opts) {
+  var templ;
+
+  // v1 compat
+  // 'scope' is 'context'
+  // FIXME: Remove this in a future version
+  if (opts && opts.scope) {
+    if (!scopeOptionWarned){
+      console.warn('`scope` option is deprecated and will be removed in EJS 3');
+      scopeOptionWarned = true;
+    }
+    if (!opts.context) {
+      opts.context = opts.scope;
+    }
+    delete opts.scope;
+  }
+  templ = new Template(template, opts);
+  return templ.compile();
+};
+
+/**
+ * Render the given `template` of ejs.
+ *
+ * If you would like to include options but not data, you need to explicitly
+ * call this function with `data` being an empty object or `null`.
+ *
+ * @param {String}   template EJS template
+ * @param {Object}  [data={}] template data
+ * @param {Options} [opts={}] compilation and rendering options
+ * @return {String}
+ * @public
+ */
+
+exports.render = function (template, data, opts) {
+  data = data || {};
+  opts = opts || {};
+  var fn;
+
+  // No options object -- if there are optiony names
+  // in the data, copy them to options
+  if (arguments.length == 2) {
+    cpOptsInData(data, opts);
+  }
+
+  return handleCache(opts, template)(data);
+};
+
+/**
+ * Render an EJS file at the given `path` and callback `cb(err, str)`.
+ *
+ * If you would like to include options but not data, you need to explicitly
+ * call this function with `data` being an empty object or `null`.
+ *
+ * @param {String}             path     path to the EJS file
+ * @param {Object}            [data={}] template data
+ * @param {Options}           [opts={}] compilation and rendering options
+ * @param {RenderFileCallback} cb callback
+ * @public
+ */
+
+exports.renderFile = function () {
+  var args = Array.prototype.slice.call(arguments)
+    , path = args.shift()
+    , cb = args.pop()
+    , data = args.shift() || {}
+    , opts = args.pop() || {}
+    , 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);
+    }
+    // Express 3 and lower
+    else {
+      cpOptsInData(data, opts);
+    }
+  }
+  opts.filename = path;
+
+  try {
+    result = handleCache(opts)(data);
+  }
+  catch(err) {
+    return cb(err);
+  }
+  return cb(null, result);
+};
+
+/**
+ * Clear intermediate JavaScript cache. Calls {@link Cache#reset}.
+ * @public
+ */
+
+exports.clearCache = function () {
+  exports.cache.reset();
+};
+
+function Template(text, opts) {
+  opts = opts || {};
+  var options = {};
+  this.templateText = text;
+  this.mode = null;
+  this.truncate = false;
+  this.currentLine = 1;
+  this.source = '';
+  this.dependencies = [];
+  options.client = opts.client || false;
+  options.escapeFunction = opts.escape || utils.escapeXML;
+  options.compileDebug = opts.compileDebug !== false;
+  options.debug = !!opts.debug;
+  options.filename = opts.filename;
+  options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;
+  options.strict = opts.strict || false;
+  options.context = opts.context;
+  options.cache = opts.cache || false;
+  options.rmWhitespace = opts.rmWhitespace;
+  options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME;
+
+  if (options.strict) {
+    options._with = false;
+  }
+  else {
+    options._with = typeof opts._with != 'undefined' ? opts._with : true;
+  }
+
+  this.opts = options;
+
+  this.regex = this.createRegex();
+}
+
+Template.modes = {
+  EVAL: 'eval'
+, ESCAPED: 'escaped'
+, RAW: 'raw'
+, COMMENT: 'comment'
+, LITERAL: 'literal'
+};
+
+Template.prototype = {
+  createRegex: function () {
+    var str = _REGEX_STRING
+      , delim = utils.escapeRegExpChars(this.opts.delimiter);
+    str = str.replace(/%/g, delim);
+    return new RegExp(str);
+  }
+
+, compile: function () {
+    var src
+      , fn
+      , opts = this.opts
+      , prepended = ''
+      , appended = ''
+      , escape = opts.escapeFunction;
+
+    if (opts.rmWhitespace) {
+      // Have to use two separate replace here as `^` and `$` operators don't
+      // work well with `\r`.
+      this.templateText =
+        this.templateText.replace(/\r/g, '').replace(/^\s+|\s+$/gm, '');
+    }
+
+    // Slurp spaces and tabs before <%_ and after _%>
+    this.templateText =
+      this.templateText.replace(/[ \t]*<%_/gm, '<%_').replace(/_%>[ \t]*/gm, '_%>');
+
+    if (!this.source) {
+      this.generateSource();
+      prepended += '  var __output = [], __append = __output.push.bind(__output);' + '\n';
+      if (opts._with !== false) {
+        prepended +=  '  with (' + opts.localsName + ' || {}) {' + '\n';
+        appended += '  }' + '\n';
+      }
+      appended += '  return __output.join("");' + '\n';
+      this.source = prepended + this.source + appended;
+    }
+
+    if (opts.compileDebug) {
+      src = 'var __line = 1' + '\n'
+          + '  , __lines = ' + JSON.stringify(this.templateText) + '\n'
+          + '  , __filename = ' + (opts.filename ?
+                JSON.stringify(opts.filename) : 'undefined') + ';' + '\n'
+          + 'try {' + '\n'
+          + this.source
+          + '} catch (e) {' + '\n'
+          + '  rethrow(e, __lines, __filename, __line);' + '\n'
+          + '}' + '\n';
+    }
+    else {
+      src = this.source;
+    }
+
+    if (opts.debug) {
+      console.log(src);
+    }
+
+    if (opts.client) {
+      src = 'escape = escape || ' + escape.toString() + ';' + '\n' + src;
+      if (opts.compileDebug) {
+        src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
+      }
+    }
+
+    if (opts.strict) {
+      src = '"use strict";\n' + src;
+    }
+
+    try {
+      fn = new Function(opts.localsName + ', escape, include, rethrow', src);
+    }
+    catch(e) {
+      // istanbul ignore else
+      if (e instanceof SyntaxError) {
+        if (opts.filename) {
+          e.message += ' in ' + opts.filename;
+        }
+        e.message += ' while compiling ejs';
+      }
+      throw e;
+    }
+
+    if (opts.client) {
+      fn.dependencies = this.dependencies;
+      return fn;
+    }
+
+    // Return a callable function which will execute the function
+    // created by the source-code, with the passed data as locals
+    // Adds a local `include` function which allows full recursive include
+    var returnedFn = function (data) {
+      var include = function (path, includeData) {
+        var d = utils.shallowCopy({}, data);
+        if (includeData) {
+          d = utils.shallowCopy(d, includeData);
+        }
+        return includeFile(path, opts)(d);
+      };
+      return fn.apply(opts.context, [data || {}, escape, include, rethrow]);
+    };
+    returnedFn.dependencies = this.dependencies;
+    return returnedFn;
+  }
+
+, generateSource: function () {
+    var self = this
+      , matches = this.parseTemplateText()
+      , d = this.opts.delimiter;
+
+    if (matches && matches.length) {
+      if (this.opts.compileDebug && this.opts.filename) {
+        this.source =  '    ; __lines = ' + JSON.stringify(this.templateText) + '\n';
+        this.source += '    ; __filename = "' + this.opts.filename.replace(/\\/g,  '/') + '"\n';
+      }
+      matches.forEach(function (line, index) {
+        var opening
+          , closing
+          , include
+          , includeOpts
+          , includeSrc;
+        // If this is an opening tag, check for closing tags
+        // FIXME: May end up with some false positives here
+        // Better to store modes as k/v with '<' + delimiter as key
+        // Then this can simply check against the map
+        if ( line.indexOf('<' + d) === 0        // If it is a tag
+          && line.indexOf('<' + d + d) !== 0) { // and is not escaped
+          closing = matches[index + 2];
+          if (!(closing == d + '>' || closing == '-' + d + '>' || closing == '_' + d + '>')) {
+            throw new Error('Could not find matching close tag for "' + line + '".');
+          }
+        }
+        // HACK: backward-compat `include` preprocessor directives
+        if ((include = line.match(/^\s*include\s+(\S+)/))) {
+          opening = matches[index - 1];
+          // Must be in EVAL or RAW mode
+          if (opening && (opening == '<' + d || opening == '<' + d + '-' || opening == '<' + d + '_')) {
+            includeOpts = utils.shallowCopy({}, self.opts);
+            includeSrc = includeSource(include[1], includeOpts);
+            includeSrc = '    ; (function(){' + '\n' + includeSrc +
+                '    ; })()' + '\n';
+            self.source += includeSrc;
+            self.dependencies.push(exports.resolveInclude(include[1],
+                includeOpts.filename));
+            return;
+          }
+        }
+        self.scanLine(line);
+      });
+    }
+
+  }
+
+, parseTemplateText: function () {
+    var str = this.templateText
+      , pat = this.regex
+      , result = pat.exec(str)
+      , arr = []
+      , firstPos
+      , lastPos;
+
+    while (result) {
+      firstPos = result.index;
+      lastPos = pat.lastIndex;
+
+      if (firstPos !== 0) {
+        arr.push(str.substring(0, firstPos));
+        str = str.slice(firstPos);
+      }
+
+      arr.push(result[0]);
+      str = str.slice(result[0].length);
+      result = pat.exec(str);
+    }
+
+    if (str) {
+      arr.push(str);
+    }
+
+    return arr;
+  }
+
+, scanLine: function (line) {
+    var self = this
+      , d = this.opts.delimiter
+      , newLineCount = 0;
+
+    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, '\\\\');
+
+      // 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, '\\"');
+      self.source += '    ; __append("' + line + '")' + '\n';
+    }
+
+    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 + '>':
+      case '-' + d + '>':
+      case '_' + d + '>':
+        if (this.mode == Template.modes.LITERAL) {
+          _addOutput();
+        }
+
+        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 '//' 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) {
+            // Just executing code
+            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;
+            // Exec and output
+            case Template.modes.RAW:
+              this.source += '    ; __append(' +
+                line.replace(_TRAILING_SEMCOL, '').trim() + ')' + '\n';
+              break;
+            case Template.modes.COMMENT:
+              // Do nothing
+              break;
+            // Literal <%% mode, append as raw output
+            case Template.modes.LITERAL:
+              _addOutput();
+              break;
+          }
+        }
+        // In string mode, just add the output
+        else {
+          _addOutput();
+        }
+    }
+
+    if (self.opts.compileDebug && newLineCount) {
+      this.currentLine += newLineCount;
+      this.source += '    ; __line = ' + this.currentLine + '\n';
+    }
+  }
+};
+
+/*
+ * Export the internal function for escaping XML so people
+ * can use for manual escaping if needed
+ * */
+exports.escapeXML = utils.escapeXML;
+
+/**
+ * Express.js support.
+ *
+ * This is an alias for {@link module:ejs.renderFile}, in order to support
+ * Express.js out-of-the-box.
+ *
+ * @func
+ */
+
+exports.__express = exports.renderFile;
+
+// Add require support
+/* istanbul ignore else */
+if (require.extensions) {
+  require.extensions['.ejs'] = function (module, filename) {
+    filename = filename || /* istanbul ignore next */ module.filename;
+    var options = {
+          filename: filename
+        , client: true
+        }
+      , template = fs.readFileSync(filename).toString()
+      , fn = exports.compile(template, options);
+    module._compile('module.exports = ' + fn.toString() + ';', filename);
+  };
+}
+
+/**
+ * Version of EJS.
+ *
+ * @readonly
+ * @type {String}
+ * @public
+ */
+
+exports.VERSION = _VERSION_STRING;
+
+/* istanbul ignore if */
+if (typeof window != 'undefined') {
+  window.ejs = exports;
+}
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..9e2c1d0
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,141 @@
+/*
+ * EJS Embedded JavaScript templates
+ * Copyright 2112 Matthew Eernisse (mde at fleegix.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+*/
+
+/**
+ * Private utility functions
+ * @module utils
+ * @private
+ */
+
+'use strict';
+
+var regExpChars = /[|\\{}()[\]^$+*?.]/g;
+
+/**
+ * Escape characters reserved in regular expressions.
+ *
+ * If `string` is `undefined` or `null`, the empty string is returned.
+ *
+ * @param {String} string Input string
+ * @return {String} Escaped string
+ * @static
+ * @private
+ */
+exports.escapeRegExpChars = function (string) {
+  // istanbul ignore if
+  if (!string) {
+    return '';
+  }
+  return String(string).replace(regExpChars, '\\$&');
+};
+
+var _ENCODE_HTML_RULES = {
+      '&': '&'
+    , '<': '<'
+    , '>': '>'
+    , '"': '"'
+    , "'": '''
+    }
+  , _MATCH_HTML = /[&<>\'"]/g;
+
+function encode_char(c) {
+  return _ENCODE_HTML_RULES[c] || c;
+};
+
+/**
+ * Stringified version of constants used by {@link module:utils.escapeXML}.
+ *
+ * It is used in the process of generating {@link ClientFunction}s.
+ *
+ * @readonly
+ * @type {String}
+ */
+
+var escapeFuncStr =
+  'var _ENCODE_HTML_RULES = {\n'
++ '      "&": "&"\n'
++ '    , "<": "<"\n'
++ '    , ">": ">"\n'
++ '    , \'"\': """\n'
++ '    , "\'": "'"\n'
++ '    }\n'
++ '  , _MATCH_HTML = /[&<>\'"]/g;\n'
++ 'function encode_char(c) {\n'
++ '  return _ENCODE_HTML_RULES[c] || c;\n'
++ '};\n';
+
+/**
+ * Escape characters reserved in XML.
+ *
+ * If `markup` is `undefined` or `null`, the empty string is returned.
+ *
+ * @implements {EscapeCallback}
+ * @param {String} markup Input string
+ * @return {String} Escaped string
+ * @static
+ * @private
+ */
+
+exports.escapeXML = function (markup) {
+  return markup == undefined
+    ? ''
+    : String(markup)
+        .replace(_MATCH_HTML, encode_char);
+};
+exports.escapeXML.toString = function () {
+  return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr
+};
+
+/**
+ * Copy all properties from one object to another, in a shallow fashion.
+ *
+ * @param  {Object} to   Destination object
+ * @param  {Object} from Source object
+ * @return {Object}      Destination object
+ * @static
+ * @private
+ */
+exports.shallowCopy = function (to, from) {
+  from = from || {};
+  for (var p in from) {
+    to[p] = from[p];
+  }
+  return to;
+};
+
+/**
+ * Simple in-process cache implementation. Does not implement limits of any
+ * sort.
+ *
+ * @implements Cache
+ * @static
+ * @private
+ */
+exports.cache = {
+  _data: {},
+  set: function (key, val) {
+    this._data[key] = val;
+  },
+  get: function (key) {
+    return this._data[key];
+  },
+  reset: function () {
+    this._data = {};
+  }
+};
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c311f1c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "ejs",
+  "description": "Embedded JavaScript templates",
+  "keywords": [
+    "template",
+    "engine",
+    "ejs"
+  ],
+  "version": "2.4.2",
+  "author": "Matthew Eernisse <mde at fleegix.org> (http://fleegix.org)",
+  "contributors": [
+    "Timothy Gu <timothygu99 at gmail.com> (https://timothygu.github.io)"
+  ],
+  "license": "Apache-2.0",
+  "main": "./lib/ejs.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/mde/ejs.git"
+  },
+  "bugs": "https://github.com/mde/ejs/issues",
+  "homepage": "https://github.com/mde/ejs",
+  "dependencies": {},
+  "devDependencies": {
+    "browserify": "^8.0.3",
+    "istanbul": "~0.3.5",
+    "jake": "^8.0.0",
+    "jsdoc": "^3.3.0-beta1",
+    "lru-cache": "^2.5.0",
+    "mocha": "^2.1.0",
+    "rimraf": "^2.2.8",
+    "uglify-js": "^2.4.16"
+  },
+  "engines": {
+    "node": ">=0.10.0"
+  },
+  "scripts": {
+    "test": "mocha",
+    "sample": "npm install express && node sample/index.js",
+    "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/*"
+  }
+}
diff --git a/sample/body.html b/sample/body.html
new file mode 100644
index 0000000..84b9905
--- /dev/null
+++ b/sample/body.html
@@ -0,0 +1,2 @@
+<!-- Execute an exception -->
+<p><%= this_variable_is_not_defined %></p>
\ No newline at end of file
diff --git a/sample/header.html b/sample/header.html
new file mode 100644
index 0000000..afc41bf
--- /dev/null
+++ b/sample/header.html
@@ -0,0 +1,3 @@
+<head>
+<title><%= header %></title>
+</head>
\ No newline at end of file
diff --git a/sample/index.html b/sample/index.html
new file mode 100644
index 0000000..f19ec67
--- /dev/null
+++ b/sample/index.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<% include header.html %>
+</head>
+<body>
+<!--
+	Without fix output is confusing
+
+	ReferenceError: ./ejs/test/sample/index.html:2
+   1| <html> 
+>> 2| <head> 
+   3| <% include header.html %> 
+   4| </head> 
+   5| <body> 
+
+this_variable_is_not_defined is not defined
+-->
+<% include body.html %>
+</body>
+</html>
\ No newline at end of file
diff --git a/sample/index.js b/sample/index.js
new file mode 100644
index 0000000..08284af
--- /dev/null
+++ b/sample/index.js
@@ -0,0 +1,11 @@
+var express = require('express'),
+    ejs = require('../lib/ejs.js');
+var app = express();
+app.engine('.html', ejs.__express);
+app.set('view engine', 'ejs');
+app.get('/', function(req, res) {
+	res.render(__dirname + '/index.html', {header: 'Hello EJS'});
+});
+var server = app.listen(3000, function () {
+  console.log('Example app listening at http://localhost:3000');
+});
\ No newline at end of file
diff --git a/test/ejs.js b/test/ejs.js
new file mode 100644
index 0000000..1baf07f
--- /dev/null
+++ b/test/ejs.js
@@ -0,0 +1,932 @@
+/* jshint mocha: true */
+
+/**
+ * Module dependencies.
+ */
+
+var ejs = require('..')
+  , fs = require('fs')
+  , read = fs.readFileSync
+  , assert = require('assert')
+  , path = require('path')
+  , LRU = require('lru-cache');
+
+try {
+  fs.mkdirSync(__dirname + '/tmp');
+} catch (ex) {
+  if (ex.code !== 'EEXIST') {
+    throw ex;
+  }
+}
+
+// From https://gist.github.com/pguillory/729616
+function hook_stdio(stream, callback) {
+  var old_write = stream.write;
+
+  stream.write = (function() {
+    return function(string, encoding, fd) {
+      callback(string, encoding, fd);
+    };
+  })(stream.write);
+
+  return function() {
+    stream.write = old_write;
+  };
+}
+
+/**
+ * Load fixture `name`.
+ */
+
+function fixture(name) {
+  return read('test/fixtures/' + name, 'utf8');
+}
+
+/**
+ * User fixtures.
+ */
+
+var users = [];
+users.push({name: 'geddy'});
+users.push({name: 'neil'});
+users.push({name: 'alex'});
+
+suite('ejs.compile(str, options)', function () {
+  test('compile to a function', function () {
+    var fn = ejs.compile('<p>yay</p>');
+    assert.equal(fn(), '<p>yay</p>');
+  });
+
+  test('empty input works', function () {
+    var fn = ejs.compile('');
+    assert.equal(fn(), '');
+  });
+
+  test('throw if there are syntax errors', function () {
+    try {
+      ejs.compile(fixture('fail.ejs'));
+    }
+    catch (err) {
+      assert.ok(err.message.indexOf('compiling ejs') > -1);
+
+      try {
+        ejs.compile(fixture('fail.ejs'), {filename: 'fail.ejs'});
+      }
+      catch (err) {
+        assert.ok(err.message.indexOf('fail.ejs') > -1);
+        return;
+      }
+    }
+    throw new Error('no error reported when there should be');
+  });
+
+  test('allow customizing delimiter local var', function () {
+    var fn;
+    fn = ejs.compile('<p><?= name ?></p>', {delimiter: '?'});
+    assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
+
+    fn = ejs.compile('<p><:= name :></p>', {delimiter: ':'});
+    assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
+
+    fn = ejs.compile('<p><$= name $></p>', {delimiter: '$'});
+    assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
+  });
+
+  test('default to using ejs.delimiter', function () {
+    var fn;
+    ejs.delimiter = '&';
+    fn = ejs.compile('<p><&= name &></p>');
+    assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
+
+    fn = ejs.compile('<p><|= name |></p>', {delimiter: '|'});
+    assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
+    delete ejs.delimiter;
+  });
+
+  test('have a working client option', function () {
+    var fn
+      , str
+      , preFn;
+    fn = ejs.compile('<p><%= foo %></p>', {client: true});
+    str = fn.toString();
+    if (!process.env.running_under_istanbul) {
+      eval('var preFn = ' + str);
+      assert.equal(preFn({foo: 'bar'}), '<p>bar</p>');
+    }
+  });
+
+  test('support client mode without locals', function () {
+    var fn
+      , str
+      , preFn;
+    fn = ejs.compile('<p><%= "foo" %></p>', {client: true});
+    str = fn.toString();
+    if (!process.env.running_under_istanbul) {
+      eval('var preFn = ' + str);
+      assert.equal(preFn(), '<p>foo</p>');
+    }
+  });
+
+  test('not include rethrow() in client mode if compileDebug is false', function () {
+    var fn = ejs.compile('<p><%= "foo" %></p>', {
+               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;
+    customEscape = function customEscape(str) {
+      return !str ? '' : str.toUpperCase();
+    };
+    fn = ejs.compile('HELLO <%= name %>', {escape: customEscape, client: true});
+    str = fn.toString();
+    if (!process.env.running_under_istanbul) {
+      eval('var preFn = ' + str);
+      assert.equal(preFn({name: 'world'}), 'HELLO WORLD');
+    }
+  });
+
+});
+
+suite('ejs.render(str, data, opts)', function () {
+  test('render the template', function () {
+    assert.equal(ejs.render('<p>yay</p>'), '<p>yay</p>');
+  });
+
+  test('empty input works', function () {
+    assert.equal(ejs.render(''), '');
+  });
+
+  test('undefined renders nothing escaped', function () {
+    assert.equal(ejs.render('<%= undefined %>'), '');
+  });
+
+  test('undefined renders nothing raw', function () {
+    assert.equal(ejs.render('<%- undefined %>'), '');
+  });
+
+  test('null renders nothing escaped', function () {
+    assert.equal(ejs.render('<%= null %>'), '');
+  });
+
+  test('null renders nothing raw', function () {
+    assert.equal(ejs.render('<%- null %>'), '');
+  });
+
+  test('zero-value data item renders something escaped', function () {
+    assert.equal(ejs.render('<%= 0 %>'), '0');
+  });
+
+  test('zero-value data object renders something raw', function () {
+    assert.equal(ejs.render('<%- 0 %>'), '0');
+  });
+
+  test('accept locals', function () {
+    assert.equal(ejs.render('<p><%= name %></p>', {name: 'geddy'}),
+        '<p>geddy</p>');
+  });
+
+  test('accept locals without using with() {}', function () {
+    assert.equal(ejs.render('<p><%= locals.name %></p>', {name: 'geddy'},
+                            {_with: false}),
+        '<p>geddy</p>');
+    assert.throws(function() {
+      ejs.render('<p><%= name %></p>', {name: 'geddy'},
+                 {_with: false});
+    }, /name is not defined/);
+  });
+
+  test('accept custom name for locals', function () {
+    ejs.localsName = 'it';
+    assert.equal(ejs.render('<p><%= it.name %></p>', {name: 'geddy'},
+                            {_with: false}),
+        '<p>geddy</p>');
+    assert.throws(function() {
+      ejs.render('<p><%= name %></p>', {name: 'geddy'},
+                 {_with: false});
+    }, /name is not defined/);
+    ejs.localsName = 'locals';
+  });
+
+  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>';
+    assert.equal(out, expected);
+    // Assert no change, still in cache
+    out = ejs.render('<p>New</p>', {}, options);
+    assert.equal(out, expected);
+  });
+
+  test('support LRU caching', function () {
+    var oldCache = ejs.cache
+      , file = __dirname + '/tmp/render.ejs'
+      , options = {cache: true, filename: file}
+      , out
+      , expected = '<p>Old</p>';
+
+    // Switch to LRU
+    ejs.cache = LRU();
+
+    out = ejs.render('<p>Old</p>', {}, options);
+    assert.equal(out, expected);
+    // Assert no change, still in cache
+    out = ejs.render('<p>New</p>', {}, options);
+    assert.equal(out, expected);
+
+    // Restore system cache
+    ejs.cache = oldCache;
+  });
+
+  test('opts.context', function () {
+    var ctxt = {foo: 'FOO'}
+      , out = ejs.render('<%= this.foo %>', {}, {context: ctxt});
+    assert.equal(out, ctxt.foo);
+  });
+});
+
+suite('ejs.renderFile(path, [data], [options], fn)', function () {
+  test('render a file', function(done) {
+    ejs.renderFile('test/fixtures/para.ejs', function(err, html) {
+      if (err) {
+        return done(err);
+      }
+      assert.equal(html, '<p>hey</p>\n');
+      done();
+    });
+  });
+
+  test('accept locals', function(done) {
+    var data =  {name: 'fonebone'}
+      , options = {delimiter: '$'};
+    ejs.renderFile('test/fixtures/user.ejs', data, options, function(err, html) {
+      if (err) {
+        return done(err);
+      }
+      assert.equal(html, '<h1>fonebone</h1>\n');
+      done();
+    });
+  });
+
+  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) {
+      if (err) {
+        if (doneCount === 2) {
+          return;
+        }
+        doneCount = 2;
+        return done(err);
+      }
+      assert.equal(html, '<h1>fonebone</h1>\n');
+      doneCount++;
+      if (doneCount === 2) {
+        done();
+      }
+    });
+    ejs.renderFile('test/fixtures/user.ejs', data, options, function(err) {
+      if (!err) {
+        if (doneCount === 2) {
+          return;
+        }
+        doneCount = 2;
+        return done(new Error('error not thrown'));
+      }
+      doneCount++;
+      if (doneCount === 2) {
+        done();
+      }
+    });
+  });
+
+  test('not catch err thrown by callback', function(done) {
+    var data =  {name: 'fonebone'}
+      , options = {delimiter: '$'}
+      , counter = 0;
+
+    var d = require('domain').create();
+    d.on('error', function (err) {
+      assert.equal(counter, 1);
+      assert.equal(err.message, 'Exception in callback');
+      done();
+    });
+    d.run(function () {
+      // process.nextTick() needed to work around mochajs/mocha#513
+      //
+      // tl;dr: mocha doesn't support synchronous exception throwing in
+      // 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) {
+          counter++;
+          if (err) {
+            assert.notEqual(err.message, 'Exception in callback');
+            return done(err);
+          }
+          throw new Error('Exception in callback');
+        });
+      });
+    });
+  });
+
+  test('support caching', function (done) {
+    var expected = '<p>Old</p>'
+      , file = __dirname + '/tmp/renderFile.ejs'
+      , options = {cache: true};
+    fs.writeFileSync(file, '<p>Old</p>');
+
+    ejs.renderFile(file, {}, options, function (err, out) {
+      if (err) {
+        done(err);
+      }
+      fs.writeFileSync(file, '<p>New</p>');
+      assert.equal(out, expected);
+
+      ejs.renderFile(file, {}, options, function (err, out) {
+        if (err) {
+          done(err);
+        }
+        // Assert no change, still in cache
+        assert.equal(out, expected);
+        done();
+      });
+    });
+  });
+
+  test('opts.context', function (done) {
+    var ctxt = {foo: 'FOO'};
+    ejs.renderFile('test/fixtures/with-context.ejs', {},
+          {context: ctxt}, function(err, html) {
+      if (err) {
+        return done(err);
+      }
+      assert.equal(html, ctxt.foo + '\n');
+      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);
+    assert.equal(out, expected);
+
+    ejs.clearCache();
+
+    expected = '<p>New</p>';
+    out = ejs.render('<p>New</p>', {}, options);
+    assert.equal(out, expected);
+  });
+
+  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;
+
+    ejs.cache = LRU();
+
+    out = ejs.render('<p>Old</p>', {}, options);
+    assert.equal(out, expected);
+    ejs.clearCache();
+    expected = '<p>New</p>';
+    out = ejs.render('<p>New</p>', {}, options);
+    assert.equal(out, expected);
+
+    ejs.cache = oldCache;
+  });
+
+  test('LRU with cache-size 1', function () {
+    var oldCache = ejs.cache
+      , options
+      , out
+      , expected
+      , file;
+
+    ejs.cache = LRU(1);
+
+    file = __dirname + '/tmp/render1.ejs';
+    options = {cache: true, filename: file};
+    out = ejs.render('<p>File1</p>', {}, options);
+    expected = '<p>File1</p>';
+    assert.equal(out, expected);
+
+    // Same filename, different template, but output
+    // should be the same because cache
+    file = __dirname + '/tmp/render1.ejs';
+    options = {cache: true, filename: file};
+    out = ejs.render('<p>ChangedFile1</p>', {}, options);
+    expected = '<p>File1</p>';
+    assert.equal(out, expected);
+
+    // Different filiename -- output should be different,
+    // and previous cache-entry should be evicted
+    file = __dirname + '/tmp/render2.ejs';
+    options = {cache: true, filename: file};
+    out = ejs.render('<p>File2</p>', {}, options);
+    expected = '<p>File2</p>';
+    assert.equal(out, expected);
+
+    // Entry with first filename should now be out of cache,
+    // results should be different
+    file = __dirname + '/tmp/render1.ejs';
+    options = {cache: true, filename: file};
+    out = ejs.render('<p>ChangedFile1</p>', {}, options);
+    expected = '<p>ChangedFile1</p>';
+    assert.equal(out, expected);
+
+    ejs.cache = oldCache;
+  });
+});
+
+suite('<%', function () {
+  test('without semicolons', function () {
+    assert.equal(ejs.render(fixture('no.semicolons.ejs')),
+        fixture('no.semicolons.html'));
+  });
+});
+
+suite('<%=', function () {
+  test('escape &<script>', function () {
+    assert.equal(ejs.render('<%= name %>', {name: ' <script>'}),
+        '&nbsp;<script>');
+  });
+
+  test('should escape \'', function () {
+    assert.equal(ejs.render('<%= name %>', {name: 'The Jones\'s'}),
+      'The Jones's');
+  });
+
+  test('should escape &foo_bar;', function () {
+    assert.equal(ejs.render('<%= name %>', {name: '&foo_bar;'}),
+      '&foo_bar;');
+  });
+
+  test('should accept custom function', function() {
+
+    var customEscape = function customEscape(str) {
+      return !str ? '' : str.toUpperCase();
+    };
+
+    assert.equal(
+      ejs.render('<%= name %>', {name: 'The Jones\'s'}, {escape: customEscape}),
+      'THE JONES\'S'
+    );
+  });
+});
+
+suite('<%-', function () {
+  test('not escape', function () {
+    assert.equal(ejs.render('<%- name %>', {name: '<script>'}),
+        '<script>');
+  });
+
+  test('terminate gracefully if no close tag is found', function () {
+    try {
+      ejs.compile('<h1>oops</h1><%- name ->');
+      throw new Error('Expected parse failure');
+    }
+    catch (err) {
+      assert.ok(err.message.indexOf('Could not find matching close tag for') > -1);
+    }
+  });
+});
+
+suite('%>', function () {
+  test('produce newlines', function () {
+    assert.equal(ejs.render(fixture('newlines.ejs'), {users: users}),
+      fixture('newlines.html'));
+  });
+  test('works with `-%>` interspersed', function () {
+    assert.equal(ejs.render(fixture('newlines.mixed.ejs'), {users: users}),
+      fixture('newlines.mixed.html'));
+  });
+  test('consecutive tags work', function () {
+    assert.equal(ejs.render(fixture('consecutive-tags.ejs')),
+      fixture('consecutive-tags.html'));
+  });
+});
+
+suite('-%>', function () {
+  test('not produce newlines', function () {
+    assert.equal(ejs.render(fixture('no.newlines.ejs'), {users: users}),
+      fixture('no.newlines.html'));
+  });
+  test('stack traces work', function () {
+    try {
+      ejs.render(fixture('no.newlines.error.ejs'));
+    }
+    catch (e) {
+      if (e.message.indexOf('>> 4| <%= qdata %>') > -1) {
+        return;
+      }
+      throw e;
+    }
+    throw new Error('Expected ReferenceError');
+  });
+
+  test('works with unix style', function () {
+    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 fn;
+    fn = ejs.compile(content);
+    assert.equal(fn({users: users}),
+      expectedResult);
+  });
+
+  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 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}),
+      expectedResult);
+  });
+});
+
+suite('<%%', function () {
+  test('produce literals', function () {
+    assert.equal(ejs.render('<%%- "foo" %>'),
+      '<%- "foo" %>');
+  });
+  test('work without an end tag', function () {
+    assert.equal(ejs.render('<%%'), '<%');
+    assert.equal(ejs.render(fixture('literal.ejs'), {}, {delimiter: ' '}),
+      fixture('literal.html'));
+  });
+});
+
+suite('<%_ and _%>', function () {
+  test('slurps spaces and tabs', function () {
+    assert.equal(ejs.render(fixture('space-and-tab-slurp.ejs'), {users: users}),
+      fixture('space-and-tab-slurp.html'));
+  });
+});
+
+suite('single quotes', function () {
+  test('not mess up the constructed function', function () {
+    assert.equal(ejs.render(fixture('single-quote.ejs')),
+      fixture('single-quote.html'));
+  });
+});
+
+suite('double quotes', function () {
+  test('not mess up the constructed function', function () {
+    assert.equal(ejs.render(fixture('double-quote.ejs')),
+      fixture('double-quote.html'));
+  });
+});
+
+suite('backslashes', function () {
+  test('escape', function () {
+    assert.equal(ejs.render(fixture('backslash.ejs')),
+      fixture('backslash.html'));
+  });
+});
+
+suite('messed up whitespace', function () {
+  test('work', function () {
+    assert.equal(ejs.render(fixture('messed.ejs'), {users: users}),
+      fixture('messed.html'));
+  });
+});
+
+suite('exceptions', function () {
+  test('produce useful stack traces', function () {
+    try {
+      ejs.render(fixture('error.ejs'), {}, {filename: 'error.ejs'});
+    }
+    catch (err) {
+      assert.equal(err.path, 'error.ejs');
+      assert.equal(err.stack.split('\n').slice(0, 8).join('\n'), fixture('error.out'));
+      return;
+    }
+    throw new Error('no error reported when there should be');
+  });
+
+  test('not include fancy stack info if compileDebug is false', function () {
+    try {
+      ejs.render(fixture('error.ejs'), {}, {
+        filename: 'error.ejs',
+        compileDebug: false
+      });
+    }
+    catch (err) {
+      assert.ok(!err.path);
+      assert.notEqual(err.stack.split('\n').slice(0, 8).join('\n'), fixture('error.out'));
+      return;
+    }
+    throw new Error('no error reported when there should be');
+  });
+
+  var unhook = null;
+  test('log JS source when debug is set', function (done) {
+    var out = ''
+      , needToExit = false;
+    unhook = hook_stdio(process.stdout, function (str) {
+      out += str;
+      if (needToExit) {
+        return;
+      }
+      if (out.indexOf('__output')) {
+        needToExit = true;
+        unhook();
+        unhook = null;
+        return done();
+      }
+    });
+    ejs.render(fixture('hello-world.ejs'), {}, {debug: true});
+  });
+  teardown(function() {
+    if (!unhook) {
+      return;
+    }
+    unhook();
+    unhook = null;
+  });
+});
+
+suite('rmWhitespace', function () {
+  test('works', function () {
+    assert.equal(ejs.render(fixture('rmWhitespace.ejs'), {}, {rmWhitespace: true}),
+        fixture('rmWhitespace.html'));
+  });
+});
+
+suite('include()', function () {
+  test('include ejs', function () {
+    var file = 'test/fixtures/include-simple.ejs';
+    assert.equal(ejs.render(fixture('include-simple.ejs'), {}, {filename: file}),
+        fixture('include-simple.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);
+      return;
+    }
+    throw new Error('expected inclusion error');
+  });
+
+  test('strips BOM', function () {
+    assert.equal(
+      ejs.render('<%- include("fixtures/includes/bom.ejs") %>',
+        {}, {filename: path.join(__dirname, 'f.ejs')}),
+      '<p>This is a file with BOM.</p>\n');
+  });
+
+  test('include ejs with locals', function () {
+    var file = 'test/fixtures/include.ejs';
+    assert.equal(ejs.render(fixture('include.ejs'), {pets: users}, {filename: file, delimiter: '@'}),
+        fixture('include.html'));
+  });
+
+  test('include ejs with absolute path and locals', function () {
+    var file = 'test/fixtures/include-abspath.ejs';
+    assert.equal(ejs.render(fixture('include-abspath.ejs'),
+      {dir: path.join(__dirname, 'fixtures'), pets: users, path: path},
+      {filename: file, delimiter: '@'}),
+        fixture('include.html'));
+  });
+
+  test('work when nested', function () {
+    var file = 'test/fixtures/menu.ejs';
+    assert.equal(ejs.render(fixture('menu.ejs'), {pets: users}, {filename: file}),
+        fixture('menu.html'));
+  });
+
+  test('work with a variable path', function () {
+    var file = 'test/fixtures/menu_var.ejs',
+        includePath = 'includes/menu-item';
+    assert.equal(ejs.render(fixture('menu.ejs'), {pets: users, varPath:  includePath}, {filename: file}),
+      fixture('menu.html'));
+  });
+
+  test('include arbitrary files as-is', function () {
+    var file = 'test/fixtures/include.css.ejs';
+    assert.equal(ejs.render(fixture('include.css.ejs'), {pets: users}, {filename: file}),
+        fixture('include.css.html'));
+  });
+
+  test('pass compileDebug to include', function () {
+    var file = 'test/fixtures/include.ejs'
+      , fn;
+    fn = ejs.compile(fixture('include.ejs'), {
+      filename: file
+    , delimiter: '@'
+    , compileDebug: false
+    });
+    try {
+      // Render without a required variable reference
+      fn({foo: 'asdf'});
+    }
+    catch(e) {
+      assert.equal(e.message, 'pets is not defined');
+      assert.ok(!e.path);
+      return;
+    }
+    throw new Error('no error reported when there should be');
+  });
+
+  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);
+    assert.equal(out(), '<p>Old</p>\n');
+
+    fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
+    assert.equal(out(), '<p>New</p>\n');
+  });
+
+  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');
+    assert.equal(out, expected);
+    out = ejs.render(fixture('include_cache.ejs'), {}, options);
+    // No change, still in cache
+    assert.equal(out, expected);
+    fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
+    out = ejs.render(fixture('include_cache.ejs'), {}, options);
+    assert.equal(out, expected);
+  });
+
+});
+
+suite('preprocessor include', function () {
+  test('work', function () {
+    var file = 'test/fixtures/include_preprocessor.ejs';
+    assert.equal(ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {filename: file, delimiter: '@'}),
+        fixture('include_preprocessor.html'));
+  });
+
+  test('no false positives', function () {
+    assert.equal(ejs.render('<% %> include foo <% %>'), ' include foo ');
+  });
+
+  test('fails without `filename`', function () {
+    try {
+      ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {delimiter: '@'});
+    }
+    catch (err) {
+      assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
+      return;
+    }
+    throw new Error('expected inclusion error');
+  });
+
+  test('strips BOM', function () {
+    assert.equal(
+      ejs.render('<% include fixtures/includes/bom.ejs %>',
+        {}, {filename: path.join(__dirname, 'f.ejs')}),
+      '<p>This is a file with BOM.</p>\n');
+  });
+
+  test('work when nested', function () {
+    var file = 'test/fixtures/menu_preprocessor.ejs';
+    assert.equal(ejs.render(fixture('menu_preprocessor.ejs'), {pets: users}, {filename: file}),
+        fixture('menu_preprocessor.html'));
+  });
+
+  test('tracks dependency correctly', function () {
+    var file = 'test/fixtures/menu_preprocessor.ejs'
+      , fn = ejs.compile(fixture('menu_preprocessor.ejs'), {filename: file});
+    assert(fn.dependencies.length);
+  });
+
+  test('include arbitrary files as-is', function () {
+    var file = 'test/fixtures/include_preprocessor.css.ejs';
+    assert.equal(ejs.render(fixture('include_preprocessor.css.ejs'), {pets: users}, {filename: file}),
+        fixture('include_preprocessor.css.html'));
+  });
+
+  test('pass compileDebug to include', function () {
+    var file = 'test/fixtures/include_preprocessor.ejs'
+      , fn;
+    fn = ejs.compile(fixture('include_preprocessor.ejs'), {
+      filename: file
+    , delimiter: '@'
+    , compileDebug: false
+    });
+    try {
+      // Render without a required variable reference
+      fn({foo: 'asdf'});
+    }
+    catch(e) {
+      assert.equal(e.message, 'pets is not defined');
+      assert.ok(!e.path);
+      return;
+    }
+    throw new Error('no error reported when there should be');
+  });
+
+  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);
+    assert.equal(out(), '<p>Old</p>\n');
+
+    fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
+    assert.equal(out(), '<p>Old</p>\n');
+  });
+
+  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');
+    assert.equal(out, expected);
+    fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
+    out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options);
+    assert.equal(out, expected);
+  });
+
+});
+
+suite('comments', function () {
+  test('fully render with comments removed', function () {
+    assert.equal(ejs.render(fixture('comments.ejs')),
+        fixture('comments.html'));
+  });
+});
+
+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}),
+          fixture('menu_preprocessor.html'));
+      }
+  });
+});
+
+suite('examples', function () {
+  function noop () {}
+  fs.readdirSync('examples').forEach(function (f) {
+    if (!/\.js$/.test(f)) {
+      return;
+    }
+    suite(f, function () {
+      test('doesn\'t throw any errors', function () {
+        var stderr = hook_stdio(process.stderr, noop)
+          , stdout = hook_stdio(process.stdout, noop);
+        try {
+          require('../examples/' + f);
+        }
+        catch (ex) {
+          stdout();
+          stderr();
+          throw ex;
+        }
+        stdout();
+        stderr();
+      });
+    });
+  });
+});
diff --git a/test/fixtures/backslash.ejs b/test/fixtures/backslash.ejs
new file mode 100644
index 0000000..2de78ae
--- /dev/null
+++ b/test/fixtures/backslash.ejs
@@ -0,0 +1 @@
+\foo
diff --git a/test/fixtures/backslash.html b/test/fixtures/backslash.html
new file mode 100644
index 0000000..2de78ae
--- /dev/null
+++ b/test/fixtures/backslash.html
@@ -0,0 +1 @@
+\foo
diff --git a/test/fixtures/comments.ejs b/test/fixtures/comments.ejs
new file mode 100644
index 0000000..48ece70
--- /dev/null
+++ b/test/fixtures/comments.ejs
@@ -0,0 +1,7 @@
+<li><a href="foo"><% // double-slash comment %>foo</li>
+<li><a href="bar"><% /* C-style comment */ %>bar</li>
+<li><a href="baz"><% // double-slash comment with newline
+    %>baz</li>
+<li><a href="qux"><% var x = 'qux'; // double-slash comment @ end of line %><%= x %></li>
+<li><a href="fee"><%# ERB style comment %>fee</li>
+<li><a href="bah"><%= 'not a ' + '//' + ' comment' %></a></li>
diff --git a/test/fixtures/comments.html b/test/fixtures/comments.html
new file mode 100644
index 0000000..63fcd25
--- /dev/null
+++ b/test/fixtures/comments.html
@@ -0,0 +1,6 @@
+<li><a href="foo">foo</li>
+<li><a href="bar">bar</li>
+<li><a href="baz">baz</li>
+<li><a href="qux">qux</li>
+<li><a href="fee">fee</li>
+<li><a href="bah">not a // comment</a></li>
diff --git a/test/fixtures/consecutive-tags.ejs b/test/fixtures/consecutive-tags.ejs
new file mode 100644
index 0000000..175f5c3
--- /dev/null
+++ b/test/fixtures/consecutive-tags.ejs
@@ -0,0 +1 @@
+<% var a = 'foo' %><% var b = 'bar' %><%= a %>
diff --git a/test/fixtures/consecutive-tags.html b/test/fixtures/consecutive-tags.html
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/test/fixtures/consecutive-tags.html
@@ -0,0 +1 @@
+foo
diff --git a/test/fixtures/double-quote.ejs b/test/fixtures/double-quote.ejs
new file mode 100644
index 0000000..e341be9
--- /dev/null
+++ b/test/fixtures/double-quote.ejs
@@ -0,0 +1 @@
+<p><%= "lo" + 'ki' %>'s "wheelchair"</p>
diff --git a/test/fixtures/double-quote.html b/test/fixtures/double-quote.html
new file mode 100644
index 0000000..9d94873
--- /dev/null
+++ b/test/fixtures/double-quote.html
@@ -0,0 +1 @@
+<p>loki's "wheelchair"</p>
diff --git a/test/fixtures/error.ejs b/test/fixtures/error.ejs
new file mode 100644
index 0000000..afe2d1b
--- /dev/null
+++ b/test/fixtures/error.ejs
@@ -0,0 +1,5 @@
+<ul>
+  <% if (users) { %>
+    <p>Has users</p>
+  <% } %>
+</ul>
diff --git a/test/fixtures/error.out b/test/fixtures/error.out
new file mode 100644
index 0000000..a2c9108
--- /dev/null
+++ b/test/fixtures/error.out
@@ -0,0 +1,8 @@
+ReferenceError: error.ejs:2
+    1| <ul>
+ >> 2|   <% if (users) { %>
+    3|     <p>Has users</p>
+    4|   <% } %>
+    5| </ul>
+
+users is not defined
\ No newline at end of file
diff --git a/test/fixtures/fail.ejs b/test/fixtures/fail.ejs
new file mode 100644
index 0000000..9dfb1f6
--- /dev/null
+++ b/test/fixtures/fail.ejs
@@ -0,0 +1 @@
+<% function foo() return 'foo'; %>
diff --git a/test/fixtures/hello-world.ejs b/test/fixtures/hello-world.ejs
new file mode 100644
index 0000000..e2e353e
--- /dev/null
+++ b/test/fixtures/hello-world.ejs
@@ -0,0 +1 @@
+<p>Hello world!</p>
diff --git a/test/fixtures/include-abspath.ejs b/test/fixtures/include-abspath.ejs
new file mode 100644
index 0000000..26b0522
--- /dev/null
+++ b/test/fixtures/include-abspath.ejs
@@ -0,0 +1,5 @@
+<ul>
+  <@ pets.forEach(function(pet){ @>
+    <@- include(path.join(dir, 'pet'), {pet: pet}); @>
+  <@ }); @>
+</ul>
diff --git a/test/fixtures/include-simple.ejs b/test/fixtures/include-simple.ejs
new file mode 100644
index 0000000..a874878
--- /dev/null
+++ b/test/fixtures/include-simple.ejs
@@ -0,0 +1,3 @@
+<ul>
+  <%- include('hello-world'); %>
+</ul>
diff --git a/test/fixtures/include-simple.html b/test/fixtures/include-simple.html
new file mode 100644
index 0000000..f4522c0
--- /dev/null
+++ b/test/fixtures/include-simple.html
@@ -0,0 +1,4 @@
+<ul>
+  <p>Hello world!</p>
+
+</ul>
diff --git a/test/fixtures/include.css.ejs b/test/fixtures/include.css.ejs
new file mode 100644
index 0000000..cd184b4
--- /dev/null
+++ b/test/fixtures/include.css.ejs
@@ -0,0 +1 @@
+<style><%- include('style.css', {value: 'bar'}); %></style>
diff --git a/test/fixtures/include.css.html b/test/fixtures/include.css.html
new file mode 100644
index 0000000..9e6e33e
--- /dev/null
+++ b/test/fixtures/include.css.html
@@ -0,0 +1,4 @@
+<style>body {
+  foo: 'bar';
+}
+</style>
diff --git a/test/fixtures/include.ejs b/test/fixtures/include.ejs
new file mode 100644
index 0000000..f2295ee
--- /dev/null
+++ b/test/fixtures/include.ejs
@@ -0,0 +1,5 @@
+<ul>
+  <@ pets.forEach(function(pet){ @>
+    <@- include('pet', {pet: pet}); @>
+  <@ }); @>
+</ul>
diff --git a/test/fixtures/include.html b/test/fixtures/include.html
new file mode 100644
index 0000000..5431d7c
--- /dev/null
+++ b/test/fixtures/include.html
@@ -0,0 +1,12 @@
+<ul>
+  
+    <li>geddy</li>
+
+  
+    <li>neil</li>
+
+  
+    <li>alex</li>
+
+  
+</ul>
diff --git a/test/fixtures/include_cache.ejs b/test/fixtures/include_cache.ejs
new file mode 100644
index 0000000..439193c
--- /dev/null
+++ b/test/fixtures/include_cache.ejs
@@ -0,0 +1 @@
+<%- include('../tmp/include') %>
diff --git a/test/fixtures/include_cache.html b/test/fixtures/include_cache.html
new file mode 100644
index 0000000..63bf29c
--- /dev/null
+++ b/test/fixtures/include_cache.html
@@ -0,0 +1 @@
+<p>Old</p>
diff --git a/test/fixtures/include_preprocessor.css.ejs b/test/fixtures/include_preprocessor.css.ejs
new file mode 100644
index 0000000..2535b6c
--- /dev/null
+++ b/test/fixtures/include_preprocessor.css.ejs
@@ -0,0 +1 @@
+<style><% var value = 'bar' %><% include style.css %></style>
diff --git a/test/fixtures/include_preprocessor.css.html b/test/fixtures/include_preprocessor.css.html
new file mode 100644
index 0000000..9e6e33e
--- /dev/null
+++ b/test/fixtures/include_preprocessor.css.html
@@ -0,0 +1,4 @@
+<style>body {
+  foo: 'bar';
+}
+</style>
diff --git a/test/fixtures/include_preprocessor.ejs b/test/fixtures/include_preprocessor.ejs
new file mode 100644
index 0000000..f2728c5
--- /dev/null
+++ b/test/fixtures/include_preprocessor.ejs
@@ -0,0 +1,5 @@
+<ul>
+  <@ pets.forEach(function(pet){ @>
+    <@ include pet @>
+  <@ }) @>
+</ul>
diff --git a/test/fixtures/include_preprocessor.html b/test/fixtures/include_preprocessor.html
new file mode 100644
index 0000000..5431d7c
--- /dev/null
+++ b/test/fixtures/include_preprocessor.html
@@ -0,0 +1,12 @@
+<ul>
+  
+    <li>geddy</li>
+
+  
+    <li>neil</li>
+
+  
+    <li>alex</li>
+
+  
+</ul>
diff --git a/test/fixtures/include_preprocessor_cache.ejs b/test/fixtures/include_preprocessor_cache.ejs
new file mode 100644
index 0000000..ae60923
--- /dev/null
+++ b/test/fixtures/include_preprocessor_cache.ejs
@@ -0,0 +1 @@
+<%- include ../tmp/include_preprocessor %>
diff --git a/test/fixtures/include_preprocessor_cache.html b/test/fixtures/include_preprocessor_cache.html
new file mode 100644
index 0000000..63bf29c
--- /dev/null
+++ b/test/fixtures/include_preprocessor_cache.html
@@ -0,0 +1 @@
+<p>Old</p>
diff --git a/test/fixtures/includes/bom.ejs b/test/fixtures/includes/bom.ejs
new file mode 100644
index 0000000..cc198ba
--- /dev/null
+++ b/test/fixtures/includes/bom.ejs
@@ -0,0 +1 @@
+<p>This is a file with BOM.</p>
diff --git a/test/fixtures/includes/menu-item.ejs b/test/fixtures/includes/menu-item.ejs
new file mode 100644
index 0000000..5a1e2ef
--- /dev/null
+++ b/test/fixtures/includes/menu-item.ejs
@@ -0,0 +1 @@
+<li><% include menu/item %></li>
diff --git a/test/fixtures/includes/menu/item.ejs b/test/fixtures/includes/menu/item.ejs
new file mode 100644
index 0000000..cd85b3d
--- /dev/null
+++ b/test/fixtures/includes/menu/item.ejs
@@ -0,0 +1 @@
+<a href="/<%= url %>"><%= title %></a>
diff --git a/test/fixtures/literal.ejs b/test/fixtures/literal.ejs
new file mode 100644
index 0000000..9f6bf81
--- /dev/null
+++ b/test/fixtures/literal.ejs
@@ -0,0 +1,3 @@
+<pre>There should be a space followed by a less-than sign and then two more
+spaces in the next line:
+ <   .</pre>
diff --git a/test/fixtures/literal.html b/test/fixtures/literal.html
new file mode 100644
index 0000000..95acd44
--- /dev/null
+++ b/test/fixtures/literal.html
@@ -0,0 +1,3 @@
+<pre>There should be a space followed by a less-than sign and then two more
+spaces in the next line:
+ <  .</pre>
diff --git a/test/fixtures/menu.ejs b/test/fixtures/menu.ejs
new file mode 100644
index 0000000..0309990
--- /dev/null
+++ b/test/fixtures/menu.ejs
@@ -0,0 +1,15 @@
+<%- include('includes/menu-item', {
+  url: '/foo'
+, title: 'Foo'
+}); -%>
+
+<%- include('includes/menu-item', {
+  url: '/bar'
+, title: 'Bar'
+}); -%>
+
+<%- include('includes/menu-item', {
+  url: '/baz'
+, title: 'Baz'
+}); -%>
+
diff --git a/test/fixtures/menu.html b/test/fixtures/menu.html
new file mode 100644
index 0000000..eef5777
--- /dev/null
+++ b/test/fixtures/menu.html
@@ -0,0 +1,9 @@
+<li><a href="//foo">Foo</a>
+</li>
+
+<li><a href="//bar">Bar</a>
+</li>
+
+<li><a href="//baz">Baz</a>
+</li>
+
diff --git a/test/fixtures/menu_preprocessor.ejs b/test/fixtures/menu_preprocessor.ejs
new file mode 100644
index 0000000..cca1d02
--- /dev/null
+++ b/test/fixtures/menu_preprocessor.ejs
@@ -0,0 +1,11 @@
+<% var url = '/foo' -%>
+<% var title = 'Foo' -%>
+<% include includes/menu-item -%>
+
+<% var url = '/bar' -%>
+<% var title = 'Bar' -%>
+<% include includes/menu-item -%>
+
+<% var url = '/baz' -%>
+<% var title = 'Baz' -%>
+<% include includes/menu-item -%>
diff --git a/test/fixtures/menu_preprocessor.html b/test/fixtures/menu_preprocessor.html
new file mode 100644
index 0000000..651b60e
--- /dev/null
+++ b/test/fixtures/menu_preprocessor.html
@@ -0,0 +1,8 @@
+<li><a href="//foo">Foo</a>
+</li>
+
+<li><a href="//bar">Bar</a>
+</li>
+
+<li><a href="//baz">Baz</a>
+</li>
diff --git a/test/fixtures/menu_var.ejs b/test/fixtures/menu_var.ejs
new file mode 100644
index 0000000..a421dcb
--- /dev/null
+++ b/test/fixtures/menu_var.ejs
@@ -0,0 +1,15 @@
+<%- include(varPath, {
+  url: '/foo'
+, title: 'Foo'
+}); -%>
+
+<%- include(varPath, {
+  url: '/bar'
+, title: 'Bar'
+}); -%>
+
+<%- include(varPath, {
+  url: '/baz'
+, title: 'Baz'
+}); -%>
+
diff --git a/test/fixtures/messed.ejs b/test/fixtures/messed.ejs
new file mode 100644
index 0000000..37aca11
--- /dev/null
+++ b/test/fixtures/messed.ejs
@@ -0,0 +1 @@
+<ul><%users.forEach(function(user){%><li><%=user.name%></li><%})%></ul>
diff --git a/test/fixtures/messed.html b/test/fixtures/messed.html
new file mode 100644
index 0000000..52d3562
--- /dev/null
+++ b/test/fixtures/messed.html
@@ -0,0 +1 @@
+<ul><li>geddy</li><li>neil</li><li>alex</li></ul>
diff --git a/test/fixtures/newlines.ejs b/test/fixtures/newlines.ejs
new file mode 100644
index 0000000..83ffe98
--- /dev/null
+++ b/test/fixtures/newlines.ejs
@@ -0,0 +1,5 @@
+<ul>
+  <% users.forEach(function(user){ %>
+    <li><%= user.name %></li>
+  <% }) %>
+</ul>
diff --git a/test/fixtures/newlines.html b/test/fixtures/newlines.html
new file mode 100644
index 0000000..4557edb
--- /dev/null
+++ b/test/fixtures/newlines.html
@@ -0,0 +1,9 @@
+<ul>
+  
+    <li>geddy</li>
+  
+    <li>neil</li>
+  
+    <li>alex</li>
+  
+</ul>
diff --git a/test/fixtures/newlines.mixed.ejs b/test/fixtures/newlines.mixed.ejs
new file mode 100644
index 0000000..5895057
--- /dev/null
+++ b/test/fixtures/newlines.mixed.ejs
@@ -0,0 +1,6 @@
+<ul>
+  <% var unused1 = 'blah' -%>
+  <% var unused2 = 'bleh'  %>
+  <% var unused3 = 'bloh' -%>
+  <% var unused4 = 'bluh'  %>
+</ul>
diff --git a/test/fixtures/newlines.mixed.html b/test/fixtures/newlines.mixed.html
new file mode 100644
index 0000000..cd88a97
--- /dev/null
+++ b/test/fixtures/newlines.mixed.html
@@ -0,0 +1,4 @@
+<ul>
+    
+    
+</ul>
diff --git a/test/fixtures/no.newlines.ejs b/test/fixtures/no.newlines.ejs
new file mode 100644
index 0000000..c7a6dc9
--- /dev/null
+++ b/test/fixtures/no.newlines.ejs
@@ -0,0 +1,5 @@
+<ul>
+  <% users.forEach(function(user){ -%>
+  <li><%= user.name %></li>
+  <% }) -%>
+</ul>
diff --git a/test/fixtures/no.newlines.error.ejs b/test/fixtures/no.newlines.error.ejs
new file mode 100644
index 0000000..17bca4e
--- /dev/null
+++ b/test/fixtures/no.newlines.error.ejs
@@ -0,0 +1,5 @@
+AAA
+<% data = "test"; -%>
+BBB
+<%= qdata %>
+CCC
diff --git a/test/fixtures/no.newlines.html b/test/fixtures/no.newlines.html
new file mode 100644
index 0000000..2285700
--- /dev/null
+++ b/test/fixtures/no.newlines.html
@@ -0,0 +1,5 @@
+<ul>
+    <li>geddy</li>
+    <li>neil</li>
+    <li>alex</li>
+  </ul>
diff --git a/test/fixtures/no.semicolons.ejs b/test/fixtures/no.semicolons.ejs
new file mode 100644
index 0000000..d583134
--- /dev/null
+++ b/test/fixtures/no.semicolons.ejs
@@ -0,0 +1,8 @@
+This document does not use semicolons in scriptlets.
+<%
+  var a = 'b'
+  var b = 'c'
+  var c
+  c = b
+%>
+The value of c is: <%= c %>
diff --git a/test/fixtures/no.semicolons.html b/test/fixtures/no.semicolons.html
new file mode 100644
index 0000000..524f8c6
--- /dev/null
+++ b/test/fixtures/no.semicolons.html
@@ -0,0 +1,3 @@
+This document does not use semicolons in scriptlets.
+
+The value of c is: c
diff --git a/test/fixtures/para.ejs b/test/fixtures/para.ejs
new file mode 100644
index 0000000..28f5df8
--- /dev/null
+++ b/test/fixtures/para.ejs
@@ -0,0 +1 @@
+<p>hey</p>
diff --git a/test/fixtures/pet.ejs b/test/fixtures/pet.ejs
new file mode 100644
index 0000000..09dd230
--- /dev/null
+++ b/test/fixtures/pet.ejs
@@ -0,0 +1 @@
+<li><@= pet.name @></li>
diff --git a/test/fixtures/rmWhitespace.ejs b/test/fixtures/rmWhitespace.ejs
new file mode 100644
index 0000000..5da0d89
--- /dev/null
+++ b/test/fixtures/rmWhitespace.ejs
@@ -0,0 +1,14 @@
+   <tag1>  
+<tag2>  
+A very long piece of text very long piece of text very long piece of
+text very long piece <% var f = 'f' %>of text very long piece of
+tex
t very long piece of<% %>text very long 
+adsffadsfadsfad<%= f %>   
+   
+piece of text.
+<% var a = 'a' %>  
+Text again.
+<% var b = 'b' %>
+<% var c = 'c'
+var d = 'd' %>
+Another text. <%= c %>  
diff --git a/test/fixtures/rmWhitespace.html b/test/fixtures/rmWhitespace.html
new file mode 100644
index 0000000..4beb5fa
--- /dev/null
+++ b/test/fixtures/rmWhitespace.html
@@ -0,0 +1,8 @@
+<tag1>
+<tag2>
+A very long piece of text very long piece of text very long piece of
+text very long piece of text very long piece of
+text very long piece oftext very long
+adsffadsfadsfadfpiece of text.
+Text again.
+Another text. c
\ No newline at end of file
diff --git a/test/fixtures/single-quote.ejs b/test/fixtures/single-quote.ejs
new file mode 100644
index 0000000..b520c84
--- /dev/null
+++ b/test/fixtures/single-quote.ejs
@@ -0,0 +1 @@
+<p><%= 'loki' %>'s wheelchair</p>
diff --git a/test/fixtures/single-quote.html b/test/fixtures/single-quote.html
new file mode 100644
index 0000000..d642750
--- /dev/null
+++ b/test/fixtures/single-quote.html
@@ -0,0 +1 @@
+<p>loki's wheelchair</p>
diff --git a/test/fixtures/space-and-tab-slurp.ejs b/test/fixtures/space-and-tab-slurp.ejs
new file mode 100644
index 0000000..7622457
--- /dev/null
+++ b/test/fixtures/space-and-tab-slurp.ejs
@@ -0,0 +1,5 @@
+<ul>
+	 <%_ users.forEach(function(user){ _%>	 
+    <li><%= user.name %></li>
+ 	<%_ }) _%> 	
+</ul>
diff --git a/test/fixtures/space-and-tab-slurp.html b/test/fixtures/space-and-tab-slurp.html
new file mode 100644
index 0000000..cfb6582
--- /dev/null
+++ b/test/fixtures/space-and-tab-slurp.html
@@ -0,0 +1,5 @@
+<ul>
+    <li>geddy</li>
+    <li>neil</li>
+    <li>alex</li>
+</ul>
diff --git a/test/fixtures/style.css b/test/fixtures/style.css
new file mode 100644
index 0000000..f9c16e6
--- /dev/null
+++ b/test/fixtures/style.css
@@ -0,0 +1,3 @@
+body {
+  foo: '<%= value %>';
+}
diff --git a/test/fixtures/user-no-with.ejs b/test/fixtures/user-no-with.ejs
new file mode 100644
index 0000000..1822e24
--- /dev/null
+++ b/test/fixtures/user-no-with.ejs
@@ -0,0 +1 @@
+<h1><$= locals.name $></h1>
diff --git a/test/fixtures/user.ejs b/test/fixtures/user.ejs
new file mode 100644
index 0000000..66f9adc
--- /dev/null
+++ b/test/fixtures/user.ejs
@@ -0,0 +1 @@
+<h1><$= name $></h1>
diff --git a/test/fixtures/with-context.ejs b/test/fixtures/with-context.ejs
new file mode 100644
index 0000000..412a9be
--- /dev/null
+++ b/test/fixtures/with-context.ejs
@@ -0,0 +1 @@
+<%= this.foo %>
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..e30264f
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,3 @@
+--ui tdd
+--reporter spec
+--check-leaks

-- 
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