├── .gitignore
├── .gitmodules
├── .travis.yml
├── History.md
├── Makefile
├── Readme.md
├── benchmark.js
├── ejs.js
├── ejs.min.js
├── examples
├── client.html
├── functions.ejs
├── functions.js
├── list.ejs
└── list.js
├── index.js
├── lib
├── ejs.js
├── filters.js
└── utils.js
├── package.json
├── support
└── compile.js
└── test
├── ejs.js
└── fixtures
├── backslash.ejs
├── backslash.html
├── comments.ejs
├── comments.html
├── double-quote.ejs
├── double-quote.html
├── error.ejs
├── error.out
├── fail.ejs
├── include.css.ejs
├── include.css.html
├── include.ejs
├── include.html
├── includes
├── menu-item.ejs
└── menu
│ └── item.ejs
├── menu.ejs
├── menu.html
├── messed.ejs
├── messed.html
├── newlines.ejs
├── newlines.html
├── no.newlines.ejs
├── no.newlines.html
├── para.ejs
├── pet.ejs
├── single-quote.ejs
├── single-quote.html
├── style.css
└── user.ejs
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore any vim files:
2 | *.sw[a-z]
3 | vim/.netrwhist
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tj/ejs/259aa234e43fcd19e4038cd87c8f259c92f2583a/.gitmodules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.11
4 | - 0.10
5 | - 0.9
6 | - 0.6
7 | - 0.8
8 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 |
2 | 1.0.0 / 2014-03-24
3 | ==================
4 |
5 | * change: escape & even if it looks like an HTML entity. Don't try to prevent double-escaping.
6 |
7 | 0.8.6 / 2014-03-21
8 | ==================
9 |
10 | * fix: Escape & even if it looks like an HTML entity. Don't try to prevent double-escaping.
11 |
12 | 0.8.5 / 2013-11-21
13 | ==================
14 |
15 | * fix: Escape apostrophe & don't over-match existing entities
16 | * fix function name changed by uglify
17 | * fixes require, closes #78
18 |
19 | 0.8.4 / 2013-05-08
20 | ==================
21 |
22 | * fix support for colons in filter arguments
23 | * fix double callback when the callback throws
24 | * rename escape option
25 |
26 | 0.8.3 / 2012-09-13
27 | ==================
28 |
29 | * allow pre-compiling into a standalone function [seanmonstar]
30 |
31 | 0.8.2 / 2012-08-16
32 | ==================
33 |
34 | * fix include "open" / "close" options. Closes #64
35 |
36 | 0.8.1 / 2012-08-11
37 | ==================
38 |
39 | * fix comments. Closes #62 [Nate Silva]
40 |
41 | 0.8.0 / 2012-07-25
42 | ==================
43 |
44 | * add `<% include file %>` support
45 | * fix wrapping of custom require in build step. Closes #57
46 |
47 | 0.7.3 / 2012-04-25
48 | ==================
49 |
50 | * Added repository to package.json [isaacs]
51 |
52 | 0.7.1 / 2012-03-26
53 | ==================
54 |
55 | * Fixed exception when using express in production caused by typo. [slaskis]
56 |
57 | 0.7.0 / 2012-03-24
58 | ==================
59 |
60 | * Added newline consumption support (`-%>`) [whoatemydomain]
61 |
62 | 0.6.1 / 2011-12-09
63 | ==================
64 |
65 | * Fixed `ejs.renderFile()`
66 |
67 | 0.6.0 / 2011-12-09
68 | ==================
69 |
70 | * Changed: you no longer need `{ locals: {} }`
71 |
72 | 0.5.0 / 2011-11-20
73 | ==================
74 |
75 | * Added express 3.x support
76 | * Added ejs.renderFile()
77 | * Added 'json' filter
78 | * Fixed tests for 0.5.x
79 |
80 | 0.4.3 / 2011-06-20
81 | ==================
82 |
83 | * Fixed stacktraces line number when used multiline js expressions [Octave]
84 |
85 | 0.4.2 / 2011-05-11
86 | ==================
87 |
88 | * Added client side support
89 |
90 | 0.4.1 / 2011-04-21
91 | ==================
92 |
93 | * Fixed error context
94 |
95 | 0.4.0 / 2011-04-21
96 | ==================
97 |
98 | * Added; ported jade's error reporting to ejs. [slaskis]
99 |
100 | 0.3.1 / 2011-02-23
101 | ==================
102 |
103 | * Fixed optional `compile()` options
104 |
105 | 0.3.0 / 2011-02-14
106 | ==================
107 |
108 | * Added 'json' filter [Yuriy Bogdanov]
109 | * Use exported version of parse function to allow monkey-patching [Anatoliy Chakkaev]
110 |
111 | 0.2.1 / 2010-10-07
112 | ==================
113 |
114 | * Added filter support
115 | * Fixed _cache_ option. ~4x performance increase
116 |
117 | 0.2.0 / 2010-08-05
118 | ==================
119 |
120 | * Added support for global tag config
121 | * Added custom tag support. Closes #5
122 | * Fixed whitespace bug. Closes #4
123 |
124 | 0.1.0 / 2010-08-04
125 | ==================
126 |
127 | * Faster implementation [ashleydev]
128 |
129 | 0.0.4 / 2010-08-02
130 | ==================
131 |
132 | * Fixed single quotes for content outside of template tags. [aniero]
133 | * Changed; `exports.compile()` now expects only "locals"
134 |
135 | 0.0.3 / 2010-07-15
136 | ==================
137 |
138 | * Fixed single quotes
139 |
140 | 0.0.2 / 2010-07-09
141 | ==================
142 |
143 | * Fixed newline preservation
144 |
145 | 0.0.1 / 2010-07-09
146 | ==================
147 |
148 | * Initial release
149 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | SRC = $(shell find lib -name "*.js" -type f)
3 | UGLIFY_FLAGS = --no-mangle
4 |
5 | all: ejs.min.js
6 |
7 | test:
8 | @./node_modules/.bin/mocha \
9 | --reporter spec
10 |
11 | ejs.js: $(SRC)
12 | @node support/compile.js $^
13 |
14 | ejs.min.js: ejs.js
15 | @uglifyjs $(UGLIFY_FLAGS) $< > $@ \
16 | && du ejs.min.js \
17 | && du ejs.js
18 |
19 | clean:
20 | rm -f ejs.js
21 | rm -f ejs.min.js
22 |
23 | .PHONY: test
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # EJS
2 |
3 | Embedded JavaScript templates.
4 |
5 | [](https://travis-ci.org/visionmedia/ejs)
6 |
7 | - - -
8 | NOTE: Version 2 of EJS makes some breaking changes with this version (notably,
9 | removal of the filters feature). Work on v2 is happening here:
10 | https://github.com/mde/ejs
11 |
12 | File issues for EJS v2 here: https://github.com/mde/ejs/issues
13 | - - -
14 |
15 | ## Installation
16 |
17 | $ npm install ejs
18 |
19 | ## Features
20 |
21 | * Complies with the [Express](http://expressjs.com) view system
22 | * Static caching of intermediate JavaScript
23 | * Unbuffered code for conditionals etc `<% code %>`
24 | * Escapes html by default with `<%= code %>`
25 | * Unescaped buffering with `<%- code %>`
26 | * Supports tag customization
27 | * Filter support for designer-friendly templates
28 | * Includes
29 | * Client-side support
30 | * Newline slurping with `<% code -%>` or `<% -%>` or `<%= code -%>` or `<%- code -%>`
31 |
32 | ## Example
33 |
34 | <% if (user) { %>
35 |
<%= user.name %>
36 | <% } %>
37 |
38 | ## Try out a live example now
39 |
40 |
41 |
42 | ## Usage
43 |
44 | ejs.compile(str, options);
45 | // => Function
46 |
47 | ejs.render(str, options);
48 | // => str
49 |
50 | ## Options
51 |
52 | - `cache` Compiled functions are cached, requires `filename`
53 | - `filename` Used by `cache` to key caches
54 | - `scope` Function execution context
55 | - `debug` Output generated function body
56 | - `compileDebug` When `false` no debug instrumentation is compiled
57 | - `client` Returns standalone compiled function
58 | - `open` Open tag, defaulting to "<%"
59 | - `close` Closing tag, defaulting to "%>"
60 | - * All others are template-local variables
61 |
62 | ## Includes
63 |
64 | Includes are relative to the template with the `include` statement,
65 | for example if you have "./views/users.ejs" and "./views/user/show.ejs"
66 | you would use `<% include user/show %>`. The included file(s) are literally
67 | included into the template, _no_ IO is performed after compilation, thus
68 | local variables are available to these included templates.
69 |
70 | ```
71 |
72 | <% users.forEach(function(user){ %>
73 | <% include user/show %>
74 | <% }) %>
75 |
76 | ```
77 |
78 | ## Custom delimiters
79 |
80 | Custom delimiters can also be applied globally:
81 |
82 | var ejs = require('ejs');
83 | ejs.open = '{{';
84 | ejs.close = '}}';
85 |
86 | Which would make the following a valid template:
87 |
88 | {{= title }}
89 |
90 | ## Filters
91 |
92 | EJS conditionally supports the concept of "filters". A "filter chain"
93 | is a designer friendly api for manipulating data, without writing JavaScript.
94 |
95 | Filters can be applied by supplying the _:_ modifier, so for example if we wish to take the array `[{ name: 'tj' }, { name: 'mape' }, { name: 'guillermo' }]` and output a list of names we can do this simply with filters:
96 |
97 | Template:
98 |
99 | <%=: users | map:'name' | join %>
100 |
101 | Output:
102 |
103 | Tj, Mape, Guillermo
104 |
105 | Render call:
106 |
107 | ejs.render(str, {
108 | users: [
109 | { name: 'tj' },
110 | { name: 'mape' },
111 | { name: 'guillermo' }
112 | ]
113 | });
114 |
115 | Or perhaps capitalize the first user's name for display:
116 |
117 | <%=: users | first | capitalize %>
118 |
119 | ## Filter list
120 |
121 | Currently these filters are available:
122 |
123 | - first
124 | - last
125 | - capitalize
126 | - downcase
127 | - upcase
128 | - sort
129 | - sort_by:'prop'
130 | - size
131 | - length
132 | - plus:n
133 | - minus:n
134 | - times:n
135 | - divided_by:n
136 | - join:'val'
137 | - truncate:n
138 | - truncate_words:n
139 | - replace:pattern,substitution
140 | - prepend:val
141 | - append:val
142 | - map:'prop'
143 | - reverse
144 | - get:'prop'
145 |
146 | ## Adding filters
147 |
148 | To add a filter simply add a method to the `.filters` object:
149 |
150 | ```js
151 | ejs.filters.last = function(obj) {
152 | return obj[obj.length - 1];
153 | };
154 | ```
155 |
156 | ## Layouts
157 |
158 | Currently EJS has no notion of blocks, only compile-time `include`s,
159 | however you may still utilize this feature to implement "layouts" by
160 | simply including a header and footer like so:
161 |
162 | ```html
163 | <% include head %>
164 | Title
165 | My page
166 | <% include foot %>
167 | ```
168 |
169 | ## client-side support
170 |
171 | include `./ejs.js` or `./ejs.min.js` and `require("ejs").compile(str)`.
172 |
173 | ## License
174 |
175 | (The MIT License)
176 |
177 | Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca>
178 |
179 | Permission is hereby granted, free of charge, to any person obtaining
180 | a copy of this software and associated documentation files (the
181 | 'Software'), to deal in the Software without restriction, including
182 | without limitation the rights to use, copy, modify, merge, publish,
183 | distribute, sublicense, and/or sell copies of the Software, and to
184 | permit persons to whom the Software is furnished to do so, subject to
185 | the following conditions:
186 |
187 | The above copyright notice and this permission notice shall be
188 | included in all copies or substantial portions of the Software.
189 |
190 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
191 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
192 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
193 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
194 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
195 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
196 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
197 |
--------------------------------------------------------------------------------
/benchmark.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | var ejs = require('./lib/ejs'),
4 | str = '<% if (foo) { %><%= foo %>
<% } %>',
5 | times = 50000;
6 |
7 | console.log('rendering ' + times + ' times');
8 |
9 | var start = new Date;
10 | while (times--) {
11 | ejs.render(str, { cache: true, filename: 'test', locals: { foo: 'bar' }});
12 | }
13 |
14 | console.log('took ' + (new Date - start) + 'ms');
--------------------------------------------------------------------------------
/ejs.js:
--------------------------------------------------------------------------------
1 | ejs = (function(){
2 |
3 | // CommonJS require()
4 |
5 | function require(p){
6 | if ('fs' == p) return {};
7 | if ('path' == p) return {};
8 | var path = require.resolve(p)
9 | , mod = require.modules[path];
10 | if (!mod) throw new Error('failed to require "' + p + '"');
11 | if (!mod.exports) {
12 | mod.exports = {};
13 | mod.call(mod.exports, mod, mod.exports, require.relative(path));
14 | }
15 | return mod.exports;
16 | }
17 |
18 | require.modules = {};
19 |
20 | require.resolve = function (path){
21 | var orig = path
22 | , reg = path + '.js'
23 | , index = path + '/index.js';
24 | return require.modules[reg] && reg
25 | || require.modules[index] && index
26 | || orig;
27 | };
28 |
29 | require.register = function (path, fn){
30 | require.modules[path] = fn;
31 | };
32 |
33 | require.relative = function (parent) {
34 | return function(p){
35 | if ('.' != p.substr(0, 1)) return require(p);
36 |
37 | var path = parent.split('/')
38 | , segs = p.split('/');
39 | path.pop();
40 |
41 | for (var i = 0; i < segs.length; i++) {
42 | var seg = segs[i];
43 | if ('..' == seg) path.pop();
44 | else if ('.' != seg) path.push(seg);
45 | }
46 |
47 | return require(path.join('/'));
48 | };
49 | };
50 |
51 |
52 | require.register("ejs.js", function(module, exports, require){
53 |
54 | /*!
55 | * EJS
56 | * Copyright(c) 2012 TJ Holowaychuk
57 | * MIT Licensed
58 | */
59 |
60 | /**
61 | * Module dependencies.
62 | */
63 |
64 | var utils = require('./utils')
65 | , path = require('path')
66 | , dirname = path.dirname
67 | , extname = path.extname
68 | , join = path.join
69 | , fs = require('fs')
70 | , read = fs.readFileSync;
71 |
72 | /**
73 | * Filters.
74 | *
75 | * @type Object
76 | */
77 |
78 | var filters = exports.filters = require('./filters');
79 |
80 | /**
81 | * Intermediate js cache.
82 | *
83 | * @type Object
84 | */
85 |
86 | var cache = {};
87 |
88 | /**
89 | * Clear intermediate js cache.
90 | *
91 | * @api public
92 | */
93 |
94 | exports.clearCache = function(){
95 | cache = {};
96 | };
97 |
98 | /**
99 | * Translate filtered code into function calls.
100 | *
101 | * @param {String} js
102 | * @return {String}
103 | * @api private
104 | */
105 |
106 | function filtered(js) {
107 | return js.substr(1).split('|').reduce(function(js, filter){
108 | var parts = filter.split(':')
109 | , name = parts.shift()
110 | , args = parts.join(':') || '';
111 | if (args) args = ', ' + args;
112 | return 'filters.' + name + '(' + js + args + ')';
113 | });
114 | };
115 |
116 | /**
117 | * Re-throw the given `err` in context to the
118 | * `str` of ejs, `filename`, and `lineno`.
119 | *
120 | * @param {Error} err
121 | * @param {String} str
122 | * @param {String} filename
123 | * @param {String} lineno
124 | * @api private
125 | */
126 |
127 | function rethrow(err, str, filename, lineno){
128 | var lines = str.split('\n')
129 | , start = Math.max(lineno - 3, 0)
130 | , end = Math.min(lines.length, lineno + 3);
131 |
132 | // Error context
133 | var context = lines.slice(start, end).map(function(line, i){
134 | var curr = i + start + 1;
135 | return (curr == lineno ? ' >> ' : ' ')
136 | + curr
137 | + '| '
138 | + line;
139 | }).join('\n');
140 |
141 | // Alter exception message
142 | err.path = filename;
143 | err.message = (filename || 'ejs') + ':'
144 | + lineno + '\n'
145 | + context + '\n\n'
146 | + err.message;
147 |
148 | throw err;
149 | }
150 |
151 | /**
152 | * Parse the given `str` of ejs, returning the function body.
153 | *
154 | * @param {String} str
155 | * @return {String}
156 | * @api public
157 | */
158 |
159 | var parse = exports.parse = function(str, options){
160 | var options = options || {}
161 | , open = options.open || exports.open || '<%'
162 | , close = options.close || exports.close || '%>'
163 | , filename = options.filename
164 | , compileDebug = options.compileDebug !== false
165 | , buf = "";
166 |
167 | buf += 'var buf = [];';
168 | if (false !== options._with) buf += '\nwith (locals || {}) { (function(){ ';
169 | buf += '\n buf.push(\'';
170 |
171 | var lineno = 1;
172 |
173 | var consumeEOL = false;
174 | for (var i = 0, len = str.length; i < len; ++i) {
175 | var stri = str[i];
176 | if (str.slice(i, open.length + i) == open) {
177 | i += open.length
178 |
179 | var prefix, postfix, line = (compileDebug ? '__stack.lineno=' : '') + lineno;
180 | switch (str[i]) {
181 | case '=':
182 | prefix = "', escape((" + line + ', ';
183 | postfix = ")), '";
184 | ++i;
185 | break;
186 | case '-':
187 | prefix = "', (" + line + ', ';
188 | postfix = "), '";
189 | ++i;
190 | break;
191 | default:
192 | prefix = "');" + line + ';';
193 | postfix = "; buf.push('";
194 | }
195 |
196 | var end = str.indexOf(close, i);
197 |
198 | if (end < 0){
199 | throw new Error('Could not find matching close tag "' + close + '".');
200 | }
201 |
202 | var js = str.substring(i, end)
203 | , start = i
204 | , include = null
205 | , n = 0;
206 |
207 | if ('-' == js[js.length-1]){
208 | js = js.substring(0, js.length - 2);
209 | consumeEOL = true;
210 | }
211 |
212 | if (0 == js.trim().indexOf('include')) {
213 | var name = js.trim().slice(7).trim();
214 | if (!filename) throw new Error('filename option is required for includes');
215 | var path = resolveInclude(name, filename);
216 | include = read(path, 'utf8');
217 | include = exports.parse(include, { filename: path, _with: false, open: open, close: close, compileDebug: compileDebug });
218 | buf += "' + (function(){" + include + "})() + '";
219 | js = '';
220 | }
221 |
222 | while (~(n = js.indexOf("\n", n))) n++, lineno++;
223 | if (js.substr(0, 1) == ':') js = filtered(js);
224 | if (js) {
225 | if (js.lastIndexOf('//') > js.lastIndexOf('\n')) js += '\n';
226 | buf += prefix;
227 | buf += js;
228 | buf += postfix;
229 | }
230 | i += end - start + close.length - 1;
231 |
232 | } else if (stri == "\\") {
233 | buf += "\\\\";
234 | } else if (stri == "'") {
235 | buf += "\\'";
236 | } else if (stri == "\r") {
237 | // ignore
238 | } else if (stri == "\n") {
239 | if (consumeEOL) {
240 | consumeEOL = false;
241 | } else {
242 | buf += "\\n";
243 | lineno++;
244 | }
245 | } else {
246 | buf += stri;
247 | }
248 | }
249 |
250 | if (false !== options._with) buf += "'); })();\n} \nreturn buf.join('');";
251 | else buf += "');\nreturn buf.join('');";
252 | return buf;
253 | };
254 |
255 | /**
256 | * Compile the given `str` of ejs into a `Function`.
257 | *
258 | * @param {String} str
259 | * @param {Object} options
260 | * @return {Function}
261 | * @api public
262 | */
263 |
264 | var compile = exports.compile = function(str, options){
265 | options = options || {};
266 | var escape = options.escape || utils.escape;
267 |
268 | var input = JSON.stringify(str)
269 | , compileDebug = options.compileDebug !== false
270 | , client = options.client
271 | , filename = options.filename
272 | ? JSON.stringify(options.filename)
273 | : 'undefined';
274 |
275 | if (compileDebug) {
276 | // Adds the fancy stack trace meta info
277 | str = [
278 | 'var __stack = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };',
279 | rethrow.toString(),
280 | 'try {',
281 | exports.parse(str, options),
282 | '} catch (err) {',
283 | ' rethrow(err, __stack.input, __stack.filename, __stack.lineno);',
284 | '}'
285 | ].join("\n");
286 | } else {
287 | str = exports.parse(str, options);
288 | }
289 |
290 | if (options.debug) console.log(str);
291 | if (client) str = 'escape = escape || ' + escape.toString() + ';\n' + str;
292 |
293 | try {
294 | var fn = new Function('locals, filters, escape, rethrow', str);
295 | } catch (err) {
296 | if ('SyntaxError' == err.name) {
297 | err.message += options.filename
298 | ? ' in ' + filename
299 | : ' while compiling ejs';
300 | }
301 | throw err;
302 | }
303 |
304 | if (client) return fn;
305 |
306 | return function(locals){
307 | return fn.call(this, locals, filters, escape, rethrow);
308 | }
309 | };
310 |
311 | /**
312 | * Render the given `str` of ejs.
313 | *
314 | * Options:
315 | *
316 | * - `locals` Local variables object
317 | * - `cache` Compiled functions are cached, requires `filename`
318 | * - `filename` Used by `cache` to key caches
319 | * - `scope` Function execution context
320 | * - `debug` Output generated function body
321 | * - `open` Open tag, defaulting to "<%"
322 | * - `close` Closing tag, defaulting to "%>"
323 | *
324 | * @param {String} str
325 | * @param {Object} options
326 | * @return {String}
327 | * @api public
328 | */
329 |
330 | exports.render = function(str, options){
331 | var fn
332 | , options = options || {};
333 |
334 | if (options.cache) {
335 | if (options.filename) {
336 | fn = cache[options.filename] || (cache[options.filename] = compile(str, options));
337 | } else {
338 | throw new Error('"cache" option requires "filename".');
339 | }
340 | } else {
341 | fn = compile(str, options);
342 | }
343 |
344 | options.__proto__ = options.locals;
345 | return fn.call(options.scope, options);
346 | };
347 |
348 | /**
349 | * Render an EJS file at the given `path` and callback `fn(err, str)`.
350 | *
351 | * @param {String} path
352 | * @param {Object|Function} options or callback
353 | * @param {Function} fn
354 | * @api public
355 | */
356 |
357 | exports.renderFile = function(path, options, fn){
358 | var key = path + ':string';
359 |
360 | if ('function' == typeof options) {
361 | fn = options, options = {};
362 | }
363 |
364 | options.filename = path;
365 |
366 | var str;
367 | try {
368 | str = options.cache
369 | ? cache[key] || (cache[key] = read(path, 'utf8'))
370 | : read(path, 'utf8');
371 | } catch (err) {
372 | fn(err);
373 | return;
374 | }
375 | fn(null, exports.render(str, options));
376 | };
377 |
378 | /**
379 | * Resolve include `name` relative to `filename`.
380 | *
381 | * @param {String} name
382 | * @param {String} filename
383 | * @return {String}
384 | * @api private
385 | */
386 |
387 | function resolveInclude(name, filename) {
388 | var path = join(dirname(filename), name);
389 | var ext = extname(name);
390 | if (!ext) path += '.ejs';
391 | return path;
392 | }
393 |
394 | // express support
395 |
396 | exports.__express = exports.renderFile;
397 |
398 | /**
399 | * Expose to require().
400 | */
401 |
402 | if (require.extensions) {
403 | require.extensions['.ejs'] = function (module, filename) {
404 | filename = filename || module.filename;
405 | var options = { filename: filename, client: true }
406 | , template = fs.readFileSync(filename).toString()
407 | , fn = compile(template, options);
408 | module._compile('module.exports = ' + fn.toString() + ';', filename);
409 | };
410 | } else if (require.registerExtension) {
411 | require.registerExtension('.ejs', function(src) {
412 | return compile(src, {});
413 | });
414 | }
415 |
416 | }); // module: ejs.js
417 |
418 | require.register("filters.js", function(module, exports, require){
419 | /*!
420 | * EJS - Filters
421 | * Copyright(c) 2010 TJ Holowaychuk
422 | * MIT Licensed
423 | */
424 |
425 | /**
426 | * First element of the target `obj`.
427 | */
428 |
429 | exports.first = function(obj) {
430 | return obj[0];
431 | };
432 |
433 | /**
434 | * Last element of the target `obj`.
435 | */
436 |
437 | exports.last = function(obj) {
438 | return obj[obj.length - 1];
439 | };
440 |
441 | /**
442 | * Capitalize the first letter of the target `str`.
443 | */
444 |
445 | exports.capitalize = function(str){
446 | str = String(str);
447 | return str[0].toUpperCase() + str.substr(1, str.length);
448 | };
449 |
450 | /**
451 | * Downcase the target `str`.
452 | */
453 |
454 | exports.downcase = function(str){
455 | return String(str).toLowerCase();
456 | };
457 |
458 | /**
459 | * Uppercase the target `str`.
460 | */
461 |
462 | exports.upcase = function(str){
463 | return String(str).toUpperCase();
464 | };
465 |
466 | /**
467 | * Sort the target `obj`.
468 | */
469 |
470 | exports.sort = function(obj){
471 | return Object.create(obj).sort();
472 | };
473 |
474 | /**
475 | * Sort the target `obj` by the given `prop` ascending.
476 | */
477 |
478 | exports.sort_by = function(obj, prop){
479 | return Object.create(obj).sort(function(a, b){
480 | a = a[prop], b = b[prop];
481 | if (a > b) return 1;
482 | if (a < b) return -1;
483 | return 0;
484 | });
485 | };
486 |
487 | /**
488 | * Size or length of the target `obj`.
489 | */
490 |
491 | exports.size = exports.length = function(obj) {
492 | return obj.length;
493 | };
494 |
495 | /**
496 | * Add `a` and `b`.
497 | */
498 |
499 | exports.plus = function(a, b){
500 | return Number(a) + Number(b);
501 | };
502 |
503 | /**
504 | * Subtract `b` from `a`.
505 | */
506 |
507 | exports.minus = function(a, b){
508 | return Number(a) - Number(b);
509 | };
510 |
511 | /**
512 | * Multiply `a` by `b`.
513 | */
514 |
515 | exports.times = function(a, b){
516 | return Number(a) * Number(b);
517 | };
518 |
519 | /**
520 | * Divide `a` by `b`.
521 | */
522 |
523 | exports.divided_by = function(a, b){
524 | return Number(a) / Number(b);
525 | };
526 |
527 | /**
528 | * Join `obj` with the given `str`.
529 | */
530 |
531 | exports.join = function(obj, str){
532 | return obj.join(str || ', ');
533 | };
534 |
535 | /**
536 | * Truncate `str` to `len`.
537 | */
538 |
539 | exports.truncate = function(str, len, append){
540 | str = String(str);
541 | if (str.length > len) {
542 | str = str.slice(0, len);
543 | if (append) str += append;
544 | }
545 | return str;
546 | };
547 |
548 | /**
549 | * Truncate `str` to `n` words.
550 | */
551 |
552 | exports.truncate_words = function(str, n){
553 | var str = String(str)
554 | , words = str.split(/ +/);
555 | return words.slice(0, n).join(' ');
556 | };
557 |
558 | /**
559 | * Replace `pattern` with `substitution` in `str`.
560 | */
561 |
562 | exports.replace = function(str, pattern, substitution){
563 | return String(str).replace(pattern, substitution || '');
564 | };
565 |
566 | /**
567 | * Prepend `val` to `obj`.
568 | */
569 |
570 | exports.prepend = function(obj, val){
571 | return Array.isArray(obj)
572 | ? [val].concat(obj)
573 | : val + obj;
574 | };
575 |
576 | /**
577 | * Append `val` to `obj`.
578 | */
579 |
580 | exports.append = function(obj, val){
581 | return Array.isArray(obj)
582 | ? obj.concat(val)
583 | : obj + val;
584 | };
585 |
586 | /**
587 | * Map the given `prop`.
588 | */
589 |
590 | exports.map = function(arr, prop){
591 | return arr.map(function(obj){
592 | return obj[prop];
593 | });
594 | };
595 |
596 | /**
597 | * Reverse the given `obj`.
598 | */
599 |
600 | exports.reverse = function(obj){
601 | return Array.isArray(obj)
602 | ? obj.reverse()
603 | : String(obj).split('').reverse().join('');
604 | };
605 |
606 | /**
607 | * Get `prop` of the given `obj`.
608 | */
609 |
610 | exports.get = function(obj, prop){
611 | return obj[prop];
612 | };
613 |
614 | /**
615 | * Packs the given `obj` into json string
616 | */
617 | exports.json = function(obj){
618 | return JSON.stringify(obj);
619 | };
620 |
621 | }); // module: filters.js
622 |
623 | require.register("utils.js", function(module, exports, require){
624 |
625 | /*!
626 | * EJS
627 | * Copyright(c) 2010 TJ Holowaychuk
628 | * MIT Licensed
629 | */
630 |
631 | /**
632 | * Escape the given string of `html`.
633 | *
634 | * @param {String} html
635 | * @return {String}
636 | * @api private
637 | */
638 |
639 | exports.escape = function(html){
640 | return String(html)
641 | .replace(/&/g, '&')
642 | .replace(//g, '>')
644 | .replace(/'/g, ''')
645 | .replace(/"/g, '"');
646 | };
647 |
648 |
649 | }); // module: utils.js
650 |
651 | return require("ejs");
652 | })();
--------------------------------------------------------------------------------
/ejs.min.js:
--------------------------------------------------------------------------------
1 | ejs=function(){function require(p){if("fs"==p)return{};if("path"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');if(!mod.exports){mod.exports={};mod.call(mod.exports,mod,mod.exports,require.relative(path))}return mod.exports}require.modules={};require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig};require.register=function(path,fn){require.modules[path]=fn};require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}var parse=exports.parse=function(str,options){var options=options||{},open=options.open||exports.open||"<%",close=options.close||exports.close||"%>",filename=options.filename,compileDebug=options.compileDebug!==false,buf="";buf+="var buf = [];";if(false!==options._with)buf+="\nwith (locals || {}) { (function(){ ";buf+="\n buf.push('";var lineno=1;var consumeEOL=false;for(var i=0,len=str.length;ijs.lastIndexOf("\n"))js+="\n";buf+=prefix;buf+=js;buf+=postfix}i+=end-start+close.length-1}else if(stri=="\\"){buf+="\\\\"}else if(stri=="'"){buf+="\\'"}else if(stri=="\r"){}else if(stri=="\n"){if(consumeEOL){consumeEOL=false}else{buf+="\\n";lineno++}}else{buf+=stri}}if(false!==options._with)buf+="'); })();\n} \nreturn buf.join('');";else buf+="');\nreturn buf.join('');";return buf};var compile=exports.compile=function(str,options){options=options||{};var escape=options.escape||utils.escape;var input=JSON.stringify(str),compileDebug=options.compileDebug!==false,client=options.client,filename=options.filename?JSON.stringify(options.filename):"undefined";if(compileDebug){str=["var __stack = { lineno: 1, input: "+input+", filename: "+filename+" };",rethrow.toString(),"try {",exports.parse(str,options),"} catch (err) {"," rethrow(err, __stack.input, __stack.filename, __stack.lineno);","}"].join("\n")}else{str=exports.parse(str,options)}if(options.debug)console.log(str);if(client)str="escape = escape || "+escape.toString()+";\n"+str;try{var fn=new Function("locals, filters, escape, rethrow",str)}catch(err){if("SyntaxError"==err.name){err.message+=options.filename?" in "+filename:" while compiling ejs"}throw err}if(client)return fn;return function(locals){return fn.call(this,locals,filters,escape,rethrow)}};exports.render=function(str,options){var fn,options=options||{};if(options.cache){if(options.filename){fn=cache[options.filename]||(cache[options.filename]=compile(str,options))}else{throw new Error('"cache" option requires "filename".')}}else{fn=compile(str,options)}options.__proto__=options.locals;return fn.call(options.scope,options)};exports.renderFile=function(path,options,fn){var key=path+":string";if("function"==typeof options){fn=options,options={}}options.filename=path;var str;try{str=options.cache?cache[key]||(cache[key]=read(path,"utf8")):read(path,"utf8")}catch(err){fn(err);return}fn(null,exports.render(str,options))};function resolveInclude(name,filename){var path=join(dirname(filename),name);var ext=extname(name);if(!ext)path+=".ejs";return path}exports.__express=exports.renderFile;if(require.extensions){require.extensions[".ejs"]=function(module,filename){filename=filename||module.filename;var options={filename:filename,client:true},template=fs.readFileSync(filename).toString(),fn=compile(template,options);module._compile("module.exports = "+fn.toString()+";",filename)}}else if(require.registerExtension){require.registerExtension(".ejs",function(src){return compile(src,{})})}});require.register("filters.js",function(module,exports,require){exports.first=function(obj){return obj[0]};exports.last=function(obj){return obj[obj.length-1]};exports.capitalize=function(str){str=String(str);return str[0].toUpperCase()+str.substr(1,str.length)};exports.downcase=function(str){return String(str).toLowerCase()};exports.upcase=function(str){return String(str).toUpperCase()};exports.sort=function(obj){return Object.create(obj).sort()};exports.sort_by=function(obj,prop){return Object.create(obj).sort(function(a,b){a=a[prop],b=b[prop];if(a>b)return 1;if(alen){str=str.slice(0,len);if(append)str+=append}return str};exports.truncate_words=function(str,n){var str=String(str),words=str.split(/ +/);return words.slice(0,n).join(" ")};exports.replace=function(str,pattern,substitution){return String(str).replace(pattern,substitution||"")};exports.prepend=function(obj,val){return Array.isArray(obj)?[val].concat(obj):val+obj};exports.append=function(obj,val){return Array.isArray(obj)?obj.concat(val):obj+val};exports.map=function(arr,prop){return arr.map(function(obj){return obj[prop]})};exports.reverse=function(obj){return Array.isArray(obj)?obj.reverse():String(obj).split("").reverse().join("")};exports.get=function(obj,prop){return obj[prop]};exports.json=function(obj){return JSON.stringify(obj)}});require.register("utils.js",function(module,exports,require){exports.escape=function(html){return String(html).replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")}});return require("ejs")}();
--------------------------------------------------------------------------------
/examples/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/functions.ejs:
--------------------------------------------------------------------------------
1 | Users
2 |
3 | <% function user(user) { %>
4 | <%= user.name %> is a <%= user.age %> year old <%= user.species %>.
5 | <% } %>
6 |
7 |
8 | <% users.map(user) %>
9 |
--------------------------------------------------------------------------------
/examples/functions.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var ejs = require('../')
7 | , fs = require('fs')
8 | , path = __dirname + '/functions.ejs'
9 | , str = fs.readFileSync(path, 'utf8');
10 |
11 | var users = [];
12 |
13 | users.push({ name: 'Tobi', age: 2, species: 'ferret' })
14 | users.push({ name: 'Loki', age: 2, species: 'ferret' })
15 | users.push({ name: 'Jane', age: 6, species: 'ferret' })
16 |
17 | var ret = ejs.render(str, {
18 | users: users,
19 | filename: path
20 | });
21 |
22 | console.log(ret);
--------------------------------------------------------------------------------
/examples/list.ejs:
--------------------------------------------------------------------------------
1 | <% if (names.length) { %>
2 |
3 | <% names.forEach(function(name){ %>
4 | - '><%= name %>
5 | <% }) %>
6 |
7 | <% } %>
--------------------------------------------------------------------------------
/examples/list.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var ejs = require('../')
7 | , fs = require('fs')
8 | , str = fs.readFileSync(__dirname + '/list.ejs', 'utf8');
9 |
10 | var ret = ejs.render(str, {
11 | names: ['foo', 'bar', 'baz']
12 | });
13 |
14 | console.log(ret);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = require('./lib/ejs');
--------------------------------------------------------------------------------
/lib/ejs.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * EJS
3 | * Copyright(c) 2012 TJ Holowaychuk
4 | * MIT Licensed
5 | */
6 |
7 | /**
8 | * Module dependencies.
9 | */
10 |
11 | var utils = require('./utils')
12 | , path = require('path')
13 | , dirname = path.dirname
14 | , extname = path.extname
15 | , join = path.join
16 | , fs = require('fs')
17 | , read = fs.readFileSync;
18 |
19 | /**
20 | * Filters.
21 | *
22 | * @type Object
23 | */
24 |
25 | var filters = exports.filters = require('./filters');
26 |
27 | /**
28 | * Intermediate js cache.
29 | *
30 | * @type Object
31 | */
32 |
33 | var cache = {};
34 |
35 | /**
36 | * Clear intermediate js cache.
37 | *
38 | * @api public
39 | */
40 |
41 | exports.clearCache = function(){
42 | cache = {};
43 | };
44 |
45 | /**
46 | * Translate filtered code into function calls.
47 | *
48 | * @param {String} js
49 | * @return {String}
50 | * @api private
51 | */
52 |
53 | function filtered(js) {
54 | return js.substr(1).split('|').reduce(function(js, filter){
55 | var parts = filter.split(':')
56 | , name = parts.shift()
57 | , args = parts.join(':') || '';
58 | if (args) args = ', ' + args;
59 | return 'filters.' + name + '(' + js + args + ')';
60 | });
61 | };
62 |
63 | /**
64 | * Re-throw the given `err` in context to the
65 | * `str` of ejs, `filename`, and `lineno`.
66 | *
67 | * @param {Error} err
68 | * @param {String} str
69 | * @param {String} filename
70 | * @param {String} lineno
71 | * @api private
72 | */
73 |
74 | function rethrow(err, str, filename, lineno){
75 | var lines = str.split('\n')
76 | , start = Math.max(lineno - 3, 0)
77 | , end = Math.min(lines.length, lineno + 3);
78 |
79 | // Error context
80 | var context = lines.slice(start, end).map(function(line, i){
81 | var curr = i + start + 1;
82 | return (curr == lineno ? ' >> ' : ' ')
83 | + curr
84 | + '| '
85 | + line;
86 | }).join('\n');
87 |
88 | // Alter exception message
89 | err.path = filename;
90 | err.message = (filename || 'ejs') + ':'
91 | + lineno + '\n'
92 | + context + '\n\n'
93 | + err.message;
94 |
95 | throw err;
96 | }
97 |
98 | /**
99 | * Parse the given `str` of ejs, returning the function body.
100 | *
101 | * @param {String} str
102 | * @return {String}
103 | * @api public
104 | */
105 |
106 | var parse = exports.parse = function(str, options){
107 | var options = options || {}
108 | , open = options.open || exports.open || '<%'
109 | , close = options.close || exports.close || '%>'
110 | , filename = options.filename
111 | , compileDebug = options.compileDebug !== false
112 | , buf = "";
113 |
114 | buf += 'var buf = [];';
115 | if (false !== options._with) buf += '\nwith (locals || {}) { (function(){ ';
116 | buf += '\n buf.push(\'';
117 |
118 | var lineno = 1;
119 |
120 | var consumeEOL = false;
121 | for (var i = 0, len = str.length; i < len; ++i) {
122 | var stri = str[i];
123 | if (str.slice(i, open.length + i) == open) {
124 | i += open.length
125 |
126 | var prefix, postfix, line = (compileDebug ? '__stack.lineno=' : '') + lineno;
127 | switch (str[i]) {
128 | case '=':
129 | prefix = "', escape((" + line + ', ';
130 | postfix = ")), '";
131 | ++i;
132 | break;
133 | case '-':
134 | prefix = "', (" + line + ', ';
135 | postfix = "), '";
136 | ++i;
137 | break;
138 | default:
139 | prefix = "');" + line + ';';
140 | postfix = "; buf.push('";
141 | }
142 |
143 | var end = str.indexOf(close, i);
144 |
145 | if (end < 0){
146 | throw new Error('Could not find matching close tag "' + close + '".');
147 | }
148 |
149 | var js = str.substring(i, end)
150 | , start = i
151 | , include = null
152 | , n = 0;
153 |
154 | if ('-' == js[js.length-1]){
155 | js = js.substring(0, js.length - 2);
156 | consumeEOL = true;
157 | }
158 |
159 | if (0 == js.trim().indexOf('include')) {
160 | var name = js.trim().slice(7).trim();
161 | if (!filename) throw new Error('filename option is required for includes');
162 | var path = resolveInclude(name, filename);
163 | include = read(path, 'utf8');
164 | include = exports.parse(include, { filename: path, _with: false, open: open, close: close, compileDebug: compileDebug });
165 | buf += "' + (function(){" + include + "})() + '";
166 | js = '';
167 | }
168 |
169 | while (~(n = js.indexOf("\n", n))) n++, lineno++;
170 |
171 | switch(js.substr(0, 1)) {
172 | case ':':
173 | js = filtered(js);
174 | break;
175 | case '%':
176 | js = " buf.push('<%" + js.substring(1).replace(/'/g, "\\'") + "%>');";
177 | break;
178 | case '#':
179 | js = "";
180 | break;
181 | }
182 |
183 | if (js) {
184 | if (js.lastIndexOf('//') > js.lastIndexOf('\n')) js += '\n';
185 | buf += prefix;
186 | buf += js;
187 | buf += postfix;
188 | }
189 | i += end - start + close.length - 1;
190 |
191 | } else if (stri == "\\") {
192 | buf += "\\\\";
193 | } else if (stri == "'") {
194 | buf += "\\'";
195 | } else if (stri == "\r") {
196 | // ignore
197 | } else if (stri == "\n") {
198 | if (consumeEOL) {
199 | consumeEOL = false;
200 | } else {
201 | buf += "\\n";
202 | lineno++;
203 | }
204 | } else {
205 | buf += stri;
206 | }
207 | }
208 |
209 | if (false !== options._with) buf += "'); })();\n} \nreturn buf.join('');";
210 | else buf += "');\nreturn buf.join('');";
211 | return buf;
212 | };
213 |
214 | /**
215 | * Compile the given `str` of ejs into a `Function`.
216 | *
217 | * @param {String} str
218 | * @param {Object} options
219 | * @return {Function}
220 | * @api public
221 | */
222 |
223 | var compile = exports.compile = function(str, options){
224 | options = options || {};
225 | var escape = options.escape || utils.escape;
226 |
227 | var input = JSON.stringify(str)
228 | , compileDebug = options.compileDebug !== false
229 | , client = options.client
230 | , filename = options.filename
231 | ? JSON.stringify(options.filename)
232 | : 'undefined';
233 |
234 | if (compileDebug) {
235 | // Adds the fancy stack trace meta info
236 | str = [
237 | 'var __stack = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };',
238 | rethrow.toString(),
239 | 'try {',
240 | exports.parse(str, options),
241 | '} catch (err) {',
242 | ' rethrow(err, __stack.input, __stack.filename, __stack.lineno);',
243 | '}'
244 | ].join("\n");
245 | } else {
246 | str = exports.parse(str, options);
247 | }
248 |
249 | if (options.debug) console.log(str);
250 | if (client) str = 'escape = escape || ' + escape.toString() + ';\n' + str;
251 |
252 | try {
253 | var fn = new Function('locals, filters, escape, rethrow', str);
254 | } catch (err) {
255 | if ('SyntaxError' == err.name) {
256 | err.message += options.filename
257 | ? ' in ' + filename
258 | : ' while compiling ejs';
259 | }
260 | throw err;
261 | }
262 |
263 | if (client) return fn;
264 |
265 | return function(locals){
266 | return fn.call(this, locals, filters, escape, rethrow);
267 | }
268 | };
269 |
270 | /**
271 | * Render the given `str` of ejs.
272 | *
273 | * Options:
274 | *
275 | * - `locals` Local variables object
276 | * - `cache` Compiled functions are cached, requires `filename`
277 | * - `filename` Used by `cache` to key caches
278 | * - `scope` Function execution context
279 | * - `debug` Output generated function body
280 | * - `open` Open tag, defaulting to "<%"
281 | * - `close` Closing tag, defaulting to "%>"
282 | *
283 | * @param {String} str
284 | * @param {Object} options
285 | * @return {String}
286 | * @api public
287 | */
288 |
289 | exports.render = function(str, options){
290 | var fn
291 | , options = options || {};
292 |
293 | if (options.cache) {
294 | if (options.filename) {
295 | fn = cache[options.filename] || (cache[options.filename] = compile(str, options));
296 | } else {
297 | throw new Error('"cache" option requires "filename".');
298 | }
299 | } else {
300 | fn = compile(str, options);
301 | }
302 |
303 | options.__proto__ = options.locals;
304 | return fn.call(options.scope, options);
305 | };
306 |
307 | /**
308 | * Render an EJS file at the given `path` and callback `fn(err, str)`.
309 | *
310 | * @param {String} path
311 | * @param {Object|Function} options or callback
312 | * @param {Function} fn
313 | * @api public
314 | */
315 |
316 | exports.renderFile = function(path, options, fn){
317 | var key = path + ':string';
318 |
319 | if ('function' == typeof options) {
320 | fn = options, options = {};
321 | }
322 |
323 | options.filename = path;
324 |
325 | var str;
326 | try {
327 | str = options.cache
328 | ? cache[key] || (cache[key] = read(path, 'utf8'))
329 | : read(path, 'utf8');
330 | } catch (err) {
331 | fn(err);
332 | return;
333 | }
334 | fn(null, exports.render(str, options));
335 | };
336 |
337 | /**
338 | * Resolve include `name` relative to `filename`.
339 | *
340 | * @param {String} name
341 | * @param {String} filename
342 | * @return {String}
343 | * @api private
344 | */
345 |
346 | function resolveInclude(name, filename) {
347 | var path = join(dirname(filename), name);
348 | var ext = extname(name);
349 | if (!ext) path += '.ejs';
350 | return path;
351 | }
352 |
353 | // express support
354 |
355 | exports.__express = exports.renderFile;
356 |
357 | /**
358 | * Expose to require().
359 | */
360 |
361 | if (require.extensions) {
362 | require.extensions['.ejs'] = function (module, filename) {
363 | filename = filename || module.filename;
364 | var options = { filename: filename, client: true }
365 | , template = fs.readFileSync(filename).toString()
366 | , fn = compile(template, options);
367 | module._compile('module.exports = ' + fn.toString() + ';', filename);
368 | };
369 | } else if (require.registerExtension) {
370 | require.registerExtension('.ejs', function(src) {
371 | return compile(src, {});
372 | });
373 | }
374 |
--------------------------------------------------------------------------------
/lib/filters.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * EJS - Filters
3 | * Copyright(c) 2010 TJ Holowaychuk
4 | * MIT Licensed
5 | */
6 |
7 | /**
8 | * First element of the target `obj`.
9 | */
10 |
11 | exports.first = function(obj) {
12 | return obj[0];
13 | };
14 |
15 | /**
16 | * Last element of the target `obj`.
17 | */
18 |
19 | exports.last = function(obj) {
20 | return obj[obj.length - 1];
21 | };
22 |
23 | /**
24 | * Capitalize the first letter of the target `str`.
25 | */
26 |
27 | exports.capitalize = function(str){
28 | str = String(str);
29 | return str[0].toUpperCase() + str.substr(1, str.length);
30 | };
31 |
32 | /**
33 | * Downcase the target `str`.
34 | */
35 |
36 | exports.downcase = function(str){
37 | return String(str).toLowerCase();
38 | };
39 |
40 | /**
41 | * Uppercase the target `str`.
42 | */
43 |
44 | exports.upcase = function(str){
45 | return String(str).toUpperCase();
46 | };
47 |
48 | /**
49 | * Sort the target `obj`.
50 | */
51 |
52 | exports.sort = function(obj){
53 | return Object.create(obj).sort();
54 | };
55 |
56 | /**
57 | * Sort the target `obj` by the given `prop` ascending.
58 | */
59 |
60 | exports.sort_by = function(obj, prop){
61 | return Object.create(obj).sort(function(a, b){
62 | a = a[prop], b = b[prop];
63 | if (a > b) return 1;
64 | if (a < b) return -1;
65 | return 0;
66 | });
67 | };
68 |
69 | /**
70 | * Size or length of the target `obj`.
71 | */
72 |
73 | exports.size = exports.length = function(obj) {
74 | return obj.length;
75 | };
76 |
77 | /**
78 | * Add `a` and `b`.
79 | */
80 |
81 | exports.plus = function(a, b){
82 | return Number(a) + Number(b);
83 | };
84 |
85 | /**
86 | * Subtract `b` from `a`.
87 | */
88 |
89 | exports.minus = function(a, b){
90 | return Number(a) - Number(b);
91 | };
92 |
93 | /**
94 | * Multiply `a` by `b`.
95 | */
96 |
97 | exports.times = function(a, b){
98 | return Number(a) * Number(b);
99 | };
100 |
101 | /**
102 | * Divide `a` by `b`.
103 | */
104 |
105 | exports.divided_by = function(a, b){
106 | return Number(a) / Number(b);
107 | };
108 |
109 | /**
110 | * Join `obj` with the given `str`.
111 | */
112 |
113 | exports.join = function(obj, str){
114 | return obj.join(str || ', ');
115 | };
116 |
117 | /**
118 | * Truncate `str` to `len`.
119 | */
120 |
121 | exports.truncate = function(str, len, append){
122 | str = String(str);
123 | if (str.length > len) {
124 | str = str.slice(0, len);
125 | if (append) str += append;
126 | }
127 | return str;
128 | };
129 |
130 | /**
131 | * Truncate `str` to `n` words.
132 | */
133 |
134 | exports.truncate_words = function(str, n){
135 | var str = String(str)
136 | , words = str.split(/ +/);
137 | return words.slice(0, n).join(' ');
138 | };
139 |
140 | /**
141 | * Replace `pattern` with `substitution` in `str`.
142 | */
143 |
144 | exports.replace = function(str, pattern, substitution){
145 | return String(str).replace(pattern, substitution || '');
146 | };
147 |
148 | /**
149 | * Prepend `val` to `obj`.
150 | */
151 |
152 | exports.prepend = function(obj, val){
153 | return Array.isArray(obj)
154 | ? [val].concat(obj)
155 | : val + obj;
156 | };
157 |
158 | /**
159 | * Append `val` to `obj`.
160 | */
161 |
162 | exports.append = function(obj, val){
163 | return Array.isArray(obj)
164 | ? obj.concat(val)
165 | : obj + val;
166 | };
167 |
168 | /**
169 | * Map the given `prop`.
170 | */
171 |
172 | exports.map = function(arr, prop){
173 | return arr.map(function(obj){
174 | return obj[prop];
175 | });
176 | };
177 |
178 | /**
179 | * Reverse the given `obj`.
180 | */
181 |
182 | exports.reverse = function(obj){
183 | return Array.isArray(obj)
184 | ? obj.reverse()
185 | : String(obj).split('').reverse().join('');
186 | };
187 |
188 | /**
189 | * Get `prop` of the given `obj`.
190 | */
191 |
192 | exports.get = function(obj, prop){
193 | return obj[prop];
194 | };
195 |
196 | /**
197 | * Packs the given `obj` into json string
198 | */
199 | exports.json = function(obj){
200 | return JSON.stringify(obj);
201 | };
202 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 |
2 | /*!
3 | * EJS
4 | * Copyright(c) 2010 TJ Holowaychuk
5 | * MIT Licensed
6 | */
7 |
8 | /**
9 | * Escape the given string of `html`.
10 | *
11 | * @param {String} html
12 | * @return {String}
13 | * @api private
14 | */
15 |
16 | exports.escape = function(html){
17 | return String(html)
18 | .replace(/&/g, '&')
19 | .replace(//g, '>')
21 | .replace(/'/g, ''')
22 | .replace(/"/g, '"');
23 | };
24 |
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ejs",
3 | "description": "Embedded JavaScript templates",
4 | "version": "1.0.0",
5 | "author": "TJ Holowaychuk ",
6 | "keywords": ["template", "engine", "ejs"],
7 | "devDependencies": {
8 | "mocha": "*",
9 | "should": "*"
10 | },
11 | "main": "./lib/ejs.js",
12 | "repository": "git://github.com/visionmedia/ejs.git",
13 | "scripts": {
14 | "test": "mocha --require should --reporter spec"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/support/compile.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var fs = require('fs');
7 |
8 | /**
9 | * Arguments.
10 | */
11 |
12 | var args = process.argv.slice(2)
13 | , pending = args.length
14 | , files = {};
15 |
16 | console.log('');
17 |
18 | // parse arguments
19 |
20 | args.forEach(function(file){
21 | var mod = file.replace('lib/', '');
22 | fs.readFile(file, 'utf8', function(err, js){
23 | if (err) throw err;
24 | console.log(' \033[90mcompile : \033[0m\033[36m%s\033[0m', file);
25 | files[file] = parse(js);
26 | --pending || compile();
27 | });
28 | });
29 |
30 | /**
31 | * Parse the given `js`.
32 | */
33 |
34 | function parse(js) {
35 | return parseInheritance(parseConditionals(js));
36 | }
37 |
38 | /**
39 | * Parse __proto__.
40 | */
41 |
42 | function parseInheritance(js) {
43 | return js
44 | .replace(/^ *(\w+)\.prototype\.__proto__ * = *(\w+)\.prototype *;?/gm, function(_, child, parent){
45 | return child + '.prototype = new ' + parent + ';\n'
46 | + child + '.prototype.constructor = '+ child + ';\n';
47 | });
48 | }
49 |
50 | /**
51 | * Parse the given `js`, currently supporting:
52 | *
53 | * 'if' ['node' | 'browser']
54 | * 'end'
55 | *
56 | */
57 |
58 | function parseConditionals(js) {
59 | var lines = js.split('\n')
60 | , len = lines.length
61 | , buffer = true
62 | , browser = false
63 | , buf = []
64 | , line
65 | , cond;
66 |
67 | for (var i = 0; i < len; ++i) {
68 | line = lines[i];
69 | if (/^ *\/\/ *if *(node|browser)/gm.exec(line)) {
70 | cond = RegExp.$1;
71 | buffer = browser = 'browser' == cond;
72 | } else if (/^ *\/\/ *end/.test(line)) {
73 | buffer = true;
74 | browser = false;
75 | } else if (browser) {
76 | buf.push(line.replace(/^( *)\/\//, '$1'));
77 | } else if (buffer) {
78 | buf.push(line);
79 | }
80 | }
81 |
82 | return buf.join('\n');
83 | }
84 |
85 | /**
86 | * Compile the files.
87 | */
88 |
89 | function compile() {
90 | var buf = '';
91 | buf += 'ejs = (function(){\n';
92 | buf += '\n// CommonJS require()\n\n';
93 | buf += browser.require + '\n\n';
94 | buf += 'require.modules = {};\n\n';
95 | buf += 'require.resolve = ' + browser.resolve + ';\n\n';
96 | buf += 'require.register = ' + browser.register + ';\n\n';
97 | buf += 'require.relative = ' + browser.relative + ';\n\n';
98 | args.forEach(function(file){
99 | var js = files[file];
100 | file = file.replace('lib/', '');
101 | buf += '\nrequire.register("' + file + '", function(module, exports, require){\n';
102 | buf += js;
103 | buf += '\n}); // module: ' + file + '\n';
104 | });
105 | buf += '\n return require("ejs");\n})();';
106 | fs.writeFile('ejs.js', buf, function(err){
107 | if (err) throw err;
108 | console.log(' \033[90m create : \033[0m\033[36m%s\033[0m', 'ejs.js');
109 | console.log();
110 | });
111 | }
112 |
113 | // refactored version of weepy's
114 | // https://github.com/weepy/brequire/blob/master/browser/brequire.js
115 |
116 | var browser = {
117 |
118 | /**
119 | * Require a module.
120 | */
121 |
122 | require: function require(p){
123 | if ('fs' == p) return {};
124 | if ('path' == p) return {};
125 | var path = require.resolve(p)
126 | , mod = require.modules[path];
127 | if (!mod) throw new Error('failed to require "' + p + '"');
128 | if (!mod.exports) {
129 | mod.exports = {};
130 | mod.call(mod.exports, mod, mod.exports, require.relative(path));
131 | }
132 | return mod.exports;
133 | },
134 |
135 | /**
136 | * Resolve module path.
137 | */
138 |
139 | resolve: function(path){
140 | var orig = path
141 | , reg = path + '.js'
142 | , index = path + '/index.js';
143 | return require.modules[reg] && reg
144 | || require.modules[index] && index
145 | || orig;
146 | },
147 |
148 | /**
149 | * Return relative require().
150 | */
151 |
152 | relative: function(parent) {
153 | return function(p){
154 | if ('.' != p.substr(0, 1)) return require(p);
155 |
156 | var path = parent.split('/')
157 | , segs = p.split('/');
158 | path.pop();
159 |
160 | for (var i = 0; i < segs.length; i++) {
161 | var seg = segs[i];
162 | if ('..' == seg) path.pop();
163 | else if ('.' != seg) path.push(seg);
164 | }
165 |
166 | return require(path.join('/'));
167 | };
168 | },
169 |
170 | /**
171 | * Register a module.
172 | */
173 |
174 | register: function(path, fn){
175 | require.modules[path] = fn;
176 | }
177 | };
--------------------------------------------------------------------------------
/test/ejs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | var ejs = require('..')
6 | , fs = require('fs')
7 | , read = fs.readFileSync
8 | , assert = require('should');
9 |
10 | /**
11 | * Apparently someone wrote a test depending on a method
12 | * not included in the specified version of Should
13 | */
14 | assert.Assertion.add('include', function (param) {
15 | if (this.obj.indexOf(param) > -1) {
16 | }
17 | else {
18 | throw new Error('Substring "' + param +
19 | '" not found in string "' + this.obj + '"');
20 | }
21 | });
22 |
23 |
24 | /**
25 | * Load fixture `name`.
26 | */
27 |
28 | function fixture(name) {
29 | return read('test/fixtures/' + name, 'utf8').replace(/\r/g, '');
30 | }
31 |
32 | /**
33 | * User fixtures.
34 | */
35 |
36 | var users = [];
37 | users.push({ name: 'tobi' });
38 | users.push({ name: 'loki' });
39 | users.push({ name: 'jane' });
40 |
41 | describe('ejs.compile(str, options)', function(){
42 | it('should compile to a function', function(){
43 | var fn = ejs.compile('yay
');
44 | fn().should.equal('yay
');
45 | })
46 |
47 | it('should throw if there are syntax errors', function(){
48 | try {
49 | ejs.compile(fixture('fail.ejs'));
50 | } catch (err) {
51 | err.message.should.include('compiling ejs');
52 |
53 | try {
54 | ejs.compile(fixture('fail.ejs'), { filename: 'fail.ejs' });
55 | } catch (err) {
56 | err.message.should.include('fail.ejs');
57 | return;
58 | }
59 | }
60 |
61 | assert(false, 'compiling a file with invalid syntax should throw an exception');
62 | })
63 |
64 | it('should allow customizing delimiters', function(){
65 | var fn = ejs.compile('{= name }
', { open: '{', close: '}' });
66 | fn({ name: 'tobi' }).should.equal('tobi
');
67 |
68 | var fn = ejs.compile('::= name ::
', { open: '::', close: '::' });
69 | fn({ name: 'tobi' }).should.equal('tobi
');
70 |
71 | var fn = ejs.compile('(= name )
', { open: '(', close: ')' });
72 | fn({ name: 'tobi' }).should.equal('tobi
');
73 | })
74 |
75 | it('should default to using ejs.open and ejs.close', function(){
76 | ejs.open = '{';
77 | ejs.close = '}';
78 | var fn = ejs.compile('{= name }
');
79 | fn({ name: 'tobi' }).should.equal('tobi
');
80 |
81 | var fn = ejs.compile('|= name |
', { open: '|', close: '|' });
82 | fn({ name: 'tobi' }).should.equal('tobi
');
83 | delete ejs.open;
84 | delete ejs.close;
85 | })
86 |
87 | it('should have a working client option', function(){
88 | var fn = ejs.compile('<%= foo %>
', { client: true });
89 | var str = fn.toString();
90 | eval('var preFn = ' + str);
91 | preFn({ foo: 'bar' }).should.equal('bar
');
92 | })
93 | })
94 |
95 | describe('ejs.render(str, options)', function(){
96 | it('should render the template', function(){
97 | ejs.render('yay
')
98 | .should.equal('yay
');
99 | })
100 |
101 | it('should accept locals', function(){
102 | ejs.render('<%= name %>
', { name: 'tobi' })
103 | .should.equal('tobi
');
104 | })
105 | })
106 |
107 | describe('ejs.renderFile(path, options, fn)', function(){
108 | it('should render a file', function(done){
109 | ejs.renderFile('test/fixtures/para.ejs', function(err, html){
110 | if (err) return done(err);
111 | html.should.equal('hey
');
112 | done();
113 | });
114 | })
115 |
116 | it('should accept locals', function(done){
117 | var options = { name: 'tj', open: '{', close: '}' };
118 | ejs.renderFile('test/fixtures/user.ejs', options, function(err, html){
119 | if (err) return done(err);
120 | html.should.equal('tj
');
121 | done();
122 | });
123 | })
124 |
125 | it('should not catch err threw by callback', function(done){
126 | var options = { name: 'tj', open: '{', close: '}' };
127 | var counter = 0;
128 | try {
129 | ejs.renderFile('test/fixtures/user.ejs', options, function(err, html){
130 | counter++;
131 | if (err) {
132 | err.message.should.not.equal('Exception in callback');
133 | return done(err);
134 | }
135 | throw new Error('Exception in callback');
136 | });
137 | } catch (err) {
138 | counter.should.equal(1);
139 | err.message.should.equal('Exception in callback');
140 | done();
141 | }
142 | })
143 | })
144 |
145 | describe('<%=', function(){
146 |
147 | it('should escape &