[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>'}),
+ ' <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