Fix license issues
[sdnc/oam.git] / dgbuilder / dgeflows / node_modules / ejs / lib / ejs.js
1 /*
2  * EJS Embedded JavaScript templates
3  * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *         http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17 */
18
19 'use strict';
20
21 var fs = require('fs')
22   , utils = require('./utils')
23   , jsCache = {}
24   , _VERSION_STRING = require('../package.json').version
25   , _DEFAULT_DELIMITER = '%'
26   , _DEFAULT_LOCALS_NAME = 'locals'
27   , _REGEX_STRING = '(<%%|<%=|<%-|<%#|<%|%>|-%>)'
28   , _OPTS = [ 'cache', 'filename', 'delimiter', 'scope', 'context'
29             , 'debug', 'compileDebug', 'client', '_with'
30             ]
31   , _TRAILING_SEMCOL = /;\s*$/;
32
33 exports.localsName = _DEFAULT_LOCALS_NAME;
34
35 exports.resolveInclude = function(name, filename) {
36   var path = require('path')
37     , dirname = path.dirname
38     , extname = path.extname
39     , resolve = path.resolve
40     , includePath = resolve(dirname(filename), name)
41     , ext = extname(name);
42   if (!ext) {
43     includePath += '.ejs';
44   }
45   return includePath;
46 }
47
48
49 // Returns a possibly cached template function, set by options.cache.
50 // `template` is the string of EJS to compile.
51 // If template is undefined then the file specified in options.filename is
52 // read.
53 function handleCache(options, template) {
54   var fn
55     , path = options.filename
56     , hasTemplate = template !== undefined;
57
58   if (options.cache) {
59     if (!path) {
60       throw new Error('cache option requires a filename');
61     }
62     fn = jsCache[path];
63     if (fn) {
64       return fn;
65     }
66     if (!hasTemplate) {
67       template = fs.readFileSync(path, {encoding: 'utf8'});
68     }
69   }
70   else if (!hasTemplate) {
71     if (!path) {
72       throw new Error('Internal EJS error: no file name or template '
73                     + 'provided');
74     }
75     template = fs.readFileSync(path, {encoding: 'utf8'});
76   }
77   fn = exports.compile(template.trim(), options);
78   if (options.cache) {
79     jsCache[path] = fn;
80   }
81   return fn;
82 }
83
84 function includeFile(path, options) {
85   var opts = utils.shallowCopy({}, options || /* istanbul ignore next */ {});
86   if (!opts.filename) {
87     throw new Error('`include` requires the \'filename\' option.');
88   }
89   opts.filename = exports.resolveInclude(path, opts.filename);
90   return handleCache(opts);
91 }
92
93 function includeSource(path, options) {
94   var opts = utils.shallowCopy({}, options || {})
95     , includePath
96     , template;
97   if (!opts.filename) {
98     throw new Error('`include` requires the \'filename\' option.');
99   }
100   includePath = exports.resolveInclude(path, opts.filename);
101   template = fs.readFileSync(includePath).toString().trim();
102
103   opts.filename = includePath;
104   var templ = new Template(template, opts);
105   templ.generateSource();
106   return templ.source;
107 }
108
109 function rethrow(err, str, filename, lineno){
110   var lines = str.split('\n')
111     , start = Math.max(lineno - 3, 0)
112     , end = Math.min(lines.length, lineno + 3);
113
114   // Error context
115   var context = lines.slice(start, end).map(function (line, i){
116     var curr = i + start + 1;
117     return (curr == lineno ? ' >> ' : '    ')
118       + curr
119       + '| '
120       + line;
121   }).join('\n');
122
123   // Alter exception message
124   err.path = filename;
125   err.message = (filename || 'ejs') + ':'
126     + lineno + '\n'
127     + context + '\n\n'
128     + err.message;
129
130   throw err;
131 }
132
133 function cpOptsInData(data, opts) {
134   _OPTS.forEach(function (p) {
135     if (typeof data[p] != 'undefined') {
136       opts[p] = data[p];
137     }
138   });
139   delete data.__expressRender__;
140 }
141
142 function compile(template, opts) {
143   var templ;
144
145   // v1 compat
146   // 'scope' is 'context'
147   // FIXME: Remove this in a future version
148   if (opts && opts.scope) {
149     if (!opts.context) {
150       opts.context = opts.scope;
151     }
152     delete opts.scope;
153   }
154   templ = new Template(template, opts);
155   return templ.compile();
156 }
157 exports.compile = compile;
158
159 // template, [data], [opts]
160 // Have to include an empty data object if you want opts and no data
161 exports.render = function (template, data, opts) {
162   data = data || {};
163   opts = opts || {};
164   var fn;
165
166   // No options object -- if there are optiony names
167   // in the data, copy them to options
168   if (arguments.length == 2) {
169     cpOptsInData(data, opts);
170   }
171
172   fn = handleCache(opts, template);
173   return fn.call(opts.context, data);
174 };
175
176 // path, [data], [opts], cb
177 // Have to include an empty data object if you want opts and no data
178 exports.renderFile = function () {
179   var args = Array.prototype.slice.call(arguments)
180     , path = args.shift()
181     , cb = args.pop()
182     , data = args.shift() || {}
183     , opts = args.pop() || {}
184     , result
185     , failed = false;
186
187   // No options object -- if there are optiony names
188   // in the data, copy them to options
189   if (arguments.length == 3) {
190     cpOptsInData(data, opts);
191   }
192   opts.filename = path;
193
194   try {
195     result = handleCache(opts)(data);
196   }
197   catch(err) {
198     return process.nextTick(function () {
199       cb(err);
200     });
201   }
202   process.nextTick(function () {
203     cb(null, result);
204   });
205 };
206
207 exports.clearCache = function () {
208   jsCache = {};
209 };
210
211 function Template(text, opts) {
212   opts = opts || {};
213   var options = {};
214   this.templateText = text;
215   this.mode = null;
216   this.truncate = false;
217   this.currentLine = 1;
218   this.source = '';
219   options.client = opts.client || false;
220   options.escapeFunction = opts.escape || utils.escapeXML;
221   options.compileDebug = opts.compileDebug !== false;
222   options.debug = !!opts.debug;
223   options.filename = opts.filename;
224   options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;
225   options._with = typeof opts._with != 'undefined' ? opts._with : true;
226   options.cache = opts.cache || false;
227   this.opts = options;
228
229   this.regex = this.createRegex();
230 }
231
232 Template.modes = {
233     EVAL: 'eval'
234   , ESCAPED: 'escaped'
235   , RAW: 'raw'
236   , COMMENT: 'comment'
237   , LITERAL: 'literal'
238 };
239
240 Template.prototype = new function () {
241   this.createRegex = function () {
242     var str = _REGEX_STRING
243       , delim = utils.escapeRegExpChars(this.opts.delimiter);
244     str = str.replace(/%/g, delim);
245     return new RegExp(str);
246   };
247
248   this.compile = function () {
249     var src
250       , fn
251       , opts = this.opts
252       , escape = opts.escapeFunction;
253
254     if (!this.source) {
255       this.generateSource();
256       var prepended = 'var __output = [];';
257       if (opts._with !== false) {
258         prepended +=  ' with (' + exports.localsName + ' || {}) { ';
259       }
260       this.source  = prepended + this.source;
261       if (opts._with !== false) {
262         this.source += '}';
263       }
264       this.source += ';return __output.join("").trim();';
265     }
266
267     if (opts.compileDebug) {
268       src = 'var __line = 1' +
269           ', __lines = ' + JSON.stringify(this.templateText) +
270           ', __filename = ' + (opts.filename ?
271                 JSON.stringify(opts.filename) : 'undefined') +
272           '; try {' +
273           this.source + '} catch (e) { rethrow(e, __lines, __filename, __line); }';
274     }
275     else {
276       src = this.source;
277     }
278
279     if (opts.debug) {
280       console.log(src);
281     }
282
283     if (opts.client) {
284       if (escape !== utils.escapeXML) {
285         src = 'escape = escape || ' + escape.toString() + ';\n' + src;
286       }
287       else {
288         src = utils.escapeFuncStr
289             + 'escape = escape || '
290             + escape.toString() + ';\n'
291             + src;
292       }
293       if (opts.compileDebug) {
294         src = 'rethrow = rethrow || ' + rethrow.toString() + ';\n' + src;
295       }
296     }
297
298     try {
299       fn = new Function(exports.localsName + ', escape, include, rethrow', src);
300     }
301     catch(e) {
302       if (e instanceof SyntaxError) {
303         if (opts.filename) {
304           e.message += ' in ' + opts.filename;
305         }
306         e.message += ' while compiling ejs';
307         throw e;
308       }
309     }
310
311     if (opts.client) {
312       return fn;
313     }
314
315     // Return a callable function which will execute the function
316     // created by the source-code, with the passed data as locals
317     return function (data) {
318       var include = function (path, includeData) {
319         var d = utils.shallowCopy({}, data);
320         if (includeData) {
321           d = utils.shallowCopy(d, includeData);
322         }
323         return includeFile(path, opts)(d);
324       };
325       return fn(data || {}, escape, include, rethrow);
326     };
327
328   };
329
330   this.generateSource = function () {
331     var self = this
332       , matches = this.parseTemplateText()
333       , d = this.opts.delimiter;
334
335     if (matches && matches.length) {
336       matches.forEach(function (line, index) {
337         var closing
338           , include
339           , includeOpts
340           , includeSrc;
341         // If this is an opening tag, check for closing tags
342         // FIXME: May end up with some false positives here
343         // Better to store modes as k/v with '<' + delimiter as key
344         // Then this can simply check against the map
345         if ( line.indexOf('<' + d) === 0        // If it is a tag
346           && line.indexOf('<' + d + d) !== 0) { // and is not escaped
347           closing = matches[index + 2];
348           if (!(closing == d + '>' || closing == '-' + d + '>')) {
349             throw new Error('Could not find matching close tag for "' + line + '".');
350           }
351         }
352         // HACK: backward-compat `include` preprocessor directives
353         if ((include = line.match(/^\s*include\s+(\S+)/))) {
354           includeOpts = utils.shallowCopy({}, self.opts);
355           includeSrc = includeSource(include[1], includeOpts);
356           includeSrc = ';(function(){' + includeSrc + '})();';
357           self.source += includeSrc;
358         }
359         else {
360           self.scanLine(line);
361         }
362       });
363     }
364
365   };
366
367   this.parseTemplateText = function () {
368     var str = this.templateText
369       , pat = this.regex
370       , result = pat.exec(str)
371       , arr = []
372       , firstPos
373       , lastPos;
374
375     while (result) {
376       firstPos = result.index;
377       lastPos = pat.lastIndex;
378
379       if (firstPos !== 0) {
380         arr.push(str.substring(0, firstPos));
381         str = str.slice(firstPos);
382       }
383
384       arr.push(result[0]);
385       str = str.slice(result[0].length);
386       result = pat.exec(str);
387     }
388
389     if (str) {
390       arr.push(str);
391     }
392
393     return arr;
394   };
395
396   this.scanLine = function (line) {
397     var self = this
398       , d = this.opts.delimiter
399       , newLineCount = 0;
400
401     function _addOutput() {
402       if (self.truncate) {
403         line = line.replace('\n', '');
404       }
405
406       // Preserve literal slashes
407       line = line.replace(/\\/g, '\\\\');
408
409       // Convert linebreaks
410       line = line.replace(/\n/g, '\\n');
411       line = line.replace(/\r/g, '\\r');
412
413       // Escape double-quotes
414       // - this will be the delimiter during execution
415       line = line.replace(/"/g, '\\"');
416       self.source += ';__output.push("' + line + '");';
417     }
418
419     newLineCount = (line.split('\n').length - 1);
420
421     switch (line) {
422       case '<' + d:
423         this.mode = Template.modes.EVAL;
424         break;
425       case '<' + d + '=':
426         this.mode = Template.modes.ESCAPED;
427         break;
428       case '<' + d + '-':
429         this.mode = Template.modes.RAW;
430         break;
431       case '<' + d + '#':
432         this.mode = Template.modes.COMMENT;
433         break;
434       case '<' + d + d:
435         this.mode = Template.modes.LITERAL;
436         this.source += ';__output.push("' + line.replace('<' + d + d, '<' + d) + '");';
437         break;
438       case d + '>':
439       case '-' + d + '>':
440         if (this.mode == Template.modes.LITERAL) {
441           _addOutput();
442         }
443
444         this.mode = null;
445         this.truncate = line.indexOf('-') === 0;
446         break;
447       default:
448         // In script mode, depends on type of tag
449         if (this.mode) {
450           // If '//' is found without a line break, add a line break.
451           switch (this.mode) {
452             case Template.modes.EVAL:
453             case Template.modes.ESCAPED:
454             case Template.modes.RAW:
455               if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
456                 line += '\n';
457               }
458           }
459           switch (this.mode) {
460             // Just executing code
461             case Template.modes.EVAL:
462               this.source += ';' + line;
463               break;
464             // Exec, esc, and output
465             case Template.modes.ESCAPED:
466               // Add the exec'd, escaped result to the output
467               // Have to prevent the string-coercion of `undefined` and `null`
468               // in the `escape` function -- making a `join` call like below unnecessary
469               this.source += ';__output.push(escape(' +
470                 line.replace(_TRAILING_SEMCOL, '').trim() + '))';
471               break;
472             // Exec and output
473             case Template.modes.RAW:
474               // Add the exec'd result to the output
475               // Using `join` here prevents string-coercion of `undefined` and `null`
476               // without filtering out falsey values like zero
477               this.source += ';__output.push(' +
478                 line.replace(_TRAILING_SEMCOL, '').trim() + ')';
479               break;
480             case Template.modes.COMMENT:
481               // Do nothing
482               break;
483             // Literal <%% mode, append as raw output
484             case Template.modes.LITERAL:
485               _addOutput();
486               break;
487           }
488         }
489         // In string mode, just add the output
490         else {
491           _addOutput();
492         }
493     }
494
495     if (self.opts.compileDebug && newLineCount) {
496       this.currentLine += newLineCount;
497       this.source += ';__line = ' + this.currentLine + ';';
498     }
499   };
500 };
501
502 // Express support
503 exports.__express = exports.renderFile;
504
505 // Add require support
506 /* istanbul ignore else */
507 if (require.extensions) {
508   require.extensions['.ejs'] = function (module, filename) {
509     filename = filename || /* istanbul ignore next */ module.filename;
510     var options = {
511           filename: filename
512         , client: true
513         }
514       , template = fs.readFileSync(filename).toString().trim()
515       , fn = compile(template, options);
516     module._compile('module.exports = ' + fn.toString() + ';', filename);
517   };
518 }
519
520 exports.VERSION = _VERSION_STRING;
521
522 /* istanbul ignore if */
523 if (typeof window != 'undefined') {
524   window.ejs = exports;
525 }