1'use strict';
2
3function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
4function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
5var asap = require('asap');
6var _waterfall = require('a-sync-waterfall');
7var lib = require('./lib');
8var compiler = require('./compiler');
9var filters = require('./filters');
10var _require = require('./loaders'),
11  FileSystemLoader = _require.FileSystemLoader,
12  WebLoader = _require.WebLoader,
13  PrecompiledLoader = _require.PrecompiledLoader;
14var tests = require('./tests');
15var globals = require('./globals');
16var _require2 = require('./object'),
17  Obj = _require2.Obj,
18  EmitterObj = _require2.EmitterObj;
19var globalRuntime = require('./runtime');
20var handleError = globalRuntime.handleError,
21  Frame = globalRuntime.Frame;
22var expressApp = require('./express-app');
23
24// If the user is using the async API, *always* call it
25// asynchronously even if the template was synchronous.
26function callbackAsap(cb, err, res) {
27  asap(function () {
28    cb(err, res);
29  });
30}
31
32/**
33 * A no-op template, for use with {% include ignore missing %}
34 */
35var noopTmplSrc = {
36  type: 'code',
37  obj: {
38    root: function root(env, context, frame, runtime, cb) {
39      try {
40        cb(null, '');
41      } catch (e) {
42        cb(handleError(e, null, null));
43      }
44    }
45  }
46};
47var Environment = /*#__PURE__*/function (_EmitterObj) {
48  _inheritsLoose(Environment, _EmitterObj);
49  function Environment() {
50    return _EmitterObj.apply(this, arguments) || this;
51  }
52  var _proto = Environment.prototype;
53  _proto.init = function init(loaders, opts) {
54    var _this = this;
55    // The dev flag determines the trace that'll be shown on errors.
56    // If set to true, returns the full trace from the error point,
57    // otherwise will return trace starting from Template.render
58    // (the full trace from within nunjucks may confuse developers using
59    //  the library)
60    // defaults to false
61    opts = this.opts = opts || {};
62    this.opts.dev = !!opts.dev;
63
64    // The autoescape flag sets global autoescaping. If true,
65    // every string variable will be escaped by default.
66    // If false, strings can be manually escaped using the `escape` filter.
67    // defaults to true
68    this.opts.autoescape = opts.autoescape != null ? opts.autoescape : true;
69
70    // If true, this will make the system throw errors if trying
71    // to output a null or undefined value
72    this.opts.throwOnUndefined = !!opts.throwOnUndefined;
73    this.opts.trimBlocks = !!opts.trimBlocks;
74    this.opts.lstripBlocks = !!opts.lstripBlocks;
75    this.loaders = [];
76    if (!loaders) {
77      // The filesystem loader is only available server-side
78      if (FileSystemLoader) {
79        this.loaders = [new FileSystemLoader('views')];
80      } else if (WebLoader) {
81        this.loaders = [new WebLoader('/views')];
82      }
83    } else {
84      this.loaders = lib.isArray(loaders) ? loaders : [loaders];
85    }
86
87    // It's easy to use precompiled templates: just include them
88    // before you configure nunjucks and this will automatically
89    // pick it up and use it
90    if (typeof window !== 'undefined' && window.nunjucksPrecompiled) {
91      this.loaders.unshift(new PrecompiledLoader(window.nunjucksPrecompiled));
92    }
93    this._initLoaders();
94    this.globals = globals();
95    this.filters = {};
96    this.tests = {};
97    this.asyncFilters = [];
98    this.extensions = {};
99    this.extensionsList = [];
100    lib._entries(filters).forEach(function (_ref) {
101      var name = _ref[0],
102        filter = _ref[1];
103      return _this.addFilter(name, filter);
104    });
105    lib._entries(tests).forEach(function (_ref2) {
106      var name = _ref2[0],
107        test = _ref2[1];
108      return _this.addTest(name, test);
109    });
110  };
111  _proto._initLoaders = function _initLoaders() {
112    var _this2 = this;
113    this.loaders.forEach(function (loader) {
114      // Caching and cache busting
115      loader.cache = {};
116      if (typeof loader.on === 'function') {
117        loader.on('update', function (name, fullname) {
118          loader.cache[name] = null;
119          _this2.emit('update', name, fullname, loader);
120        });
121        loader.on('load', function (name, source) {
122          _this2.emit('load', name, source, loader);
123        });
124      }
125    });
126  };
127  _proto.invalidateCache = function invalidateCache() {
128    this.loaders.forEach(function (loader) {
129      loader.cache = {};
130    });
131  };
132  _proto.addExtension = function addExtension(name, extension) {
133    extension.__name = name;
134    this.extensions[name] = extension;
135    this.extensionsList.push(extension);
136    return this;
137  };
138  _proto.removeExtension = function removeExtension(name) {
139    var extension = this.getExtension(name);
140    if (!extension) {
141      return;
142    }
143    this.extensionsList = lib.without(this.extensionsList, extension);
144    delete this.extensions[name];
145  };
146  _proto.getExtension = function getExtension(name) {
147    return this.extensions[name];
148  };
149  _proto.hasExtension = function hasExtension(name) {
150    return !!this.extensions[name];
151  };
152  _proto.addGlobal = function addGlobal(name, value) {
153    this.globals[name] = value;
154    return this;
155  };
156  _proto.getGlobal = function getGlobal(name) {
157    if (typeof this.globals[name] === 'undefined') {
158      throw new Error('global not found: ' + name);
159    }
160    return this.globals[name];
161  };
162  _proto.addFilter = function addFilter(name, func, async) {
163    var wrapped = func;
164    if (async) {
165      this.asyncFilters.push(name);
166    }
167    this.filters[name] = wrapped;
168    return this;
169  };
170  _proto.getFilter = function getFilter(name) {
171    if (!this.filters[name]) {
172      throw new Error('filter not found: ' + name);
173    }
174    return this.filters[name];
175  };
176  _proto.addTest = function addTest(name, func) {
177    this.tests[name] = func;
178    return this;
179  };
180  _proto.getTest = function getTest(name) {
181    if (!this.tests[name]) {
182      throw new Error('test not found: ' + name);
183    }
184    return this.tests[name];
185  };
186  _proto.resolveTemplate = function resolveTemplate(loader, parentName, filename) {
187    var isRelative = loader.isRelative && parentName ? loader.isRelative(filename) : false;
188    return isRelative && loader.resolve ? loader.resolve(parentName, filename) : filename;
189  };
190  _proto.getTemplate = function getTemplate(name, eagerCompile, parentName, ignoreMissing, cb) {
191    var _this3 = this;
192    var that = this;
193    var tmpl = null;
194    if (name && name.raw) {
195      // this fixes autoescape for templates referenced in symbols
196      name = name.raw;
197    }
198    if (lib.isFunction(parentName)) {
199      cb = parentName;
200      parentName = null;
201      eagerCompile = eagerCompile || false;
202    }
203    if (lib.isFunction(eagerCompile)) {
204      cb = eagerCompile;
205      eagerCompile = false;
206    }
207    if (name instanceof Template) {
208      tmpl = name;
209    } else if (typeof name !== 'string') {
210      throw new Error('template names must be a string: ' + name);
211    } else {
212      for (var i = 0; i < this.loaders.length; i++) {
213        var loader = this.loaders[i];
214        tmpl = loader.cache[this.resolveTemplate(loader, parentName, name)];
215        if (tmpl) {
216          break;
217        }
218      }
219    }
220    if (tmpl) {
221      if (eagerCompile) {
222        tmpl.compile();
223      }
224      if (cb) {
225        cb(null, tmpl);
226        return undefined;
227      } else {
228        return tmpl;
229      }
230    }
231    var syncResult;
232    var createTemplate = function createTemplate(err, info) {
233      if (!info && !err && !ignoreMissing) {
234        err = new Error('template not found: ' + name);
235      }
236      if (err) {
237        if (cb) {
238          cb(err);
239          return;
240        } else {
241          throw err;
242        }
243      }
244      var newTmpl;
245      if (!info) {
246        newTmpl = new Template(noopTmplSrc, _this3, '', eagerCompile);
247      } else {
248        newTmpl = new Template(info.src, _this3, info.path, eagerCompile);
249        if (!info.noCache) {
250          info.loader.cache[name] = newTmpl;
251        }
252      }
253      if (cb) {
254        cb(null, newTmpl);
255      } else {
256        syncResult = newTmpl;
257      }
258    };
259    lib.asyncIter(this.loaders, function (loader, i, next, done) {
260      function handle(err, src) {
261        if (err) {
262          done(err);
263        } else if (src) {
264          src.loader = loader;
265          done(null, src);
266        } else {
267          next();
268        }
269      }
270
271      // Resolve name relative to parentName
272      name = that.resolveTemplate(loader, parentName, name);
273      if (loader.async) {
274        loader.getSource(name, handle);
275      } else {
276        handle(null, loader.getSource(name));
277      }
278    }, createTemplate);
279    return syncResult;
280  };
281  _proto.express = function express(app) {
282    return expressApp(this, app);
283  };
284  _proto.render = function render(name, ctx, cb) {
285    if (lib.isFunction(ctx)) {
286      cb = ctx;
287      ctx = null;
288    }
289
290    // We support a synchronous API to make it easier to migrate
291    // existing code to async. This works because if you don't do
292    // anything async work, the whole thing is actually run
293    // synchronously.
294    var syncResult = null;
295    this.getTemplate(name, function (err, tmpl) {
296      if (err && cb) {
297        callbackAsap(cb, err);
298      } else if (err) {
299        throw err;
300      } else {
301        syncResult = tmpl.render(ctx, cb);
302      }
303    });
304    return syncResult;
305  };
306  _proto.renderString = function renderString(src, ctx, opts, cb) {
307    if (lib.isFunction(opts)) {
308      cb = opts;
309      opts = {};
310    }
311    opts = opts || {};
312    var tmpl = new Template(src, this, opts.path);
313    return tmpl.render(ctx, cb);
314  };
315  _proto.waterfall = function waterfall(tasks, callback, forceAsync) {
316    return _waterfall(tasks, callback, forceAsync);
317  };
318  return Environment;
319}(EmitterObj);
320var Context = /*#__PURE__*/function (_Obj) {
321  _inheritsLoose(Context, _Obj);
322  function Context() {
323    return _Obj.apply(this, arguments) || this;
324  }
325  var _proto2 = Context.prototype;
326  _proto2.init = function init(ctx, blocks, env) {
327    var _this4 = this;
328    // Has to be tied to an environment so we can tap into its globals.
329    this.env = env || new Environment();
330
331    // Make a duplicate of ctx
332    this.ctx = lib.extend({}, ctx);
333    this.blocks = {};
334    this.exported = [];
335    lib.keys(blocks).forEach(function (name) {
336      _this4.addBlock(name, blocks[name]);
337    });
338  };
339  _proto2.lookup = function lookup(name) {
340    // This is one of the most called functions, so optimize for
341    // the typical case where the name isn't in the globals
342    if (name in this.env.globals && !(name in this.ctx)) {
343      return this.env.globals[name];
344    } else {
345      return this.ctx[name];
346    }
347  };
348  _proto2.setVariable = function setVariable(name, val) {
349    this.ctx[name] = val;
350  };
351  _proto2.getVariables = function getVariables() {
352    return this.ctx;
353  };
354  _proto2.addBlock = function addBlock(name, block) {
355    this.blocks[name] = this.blocks[name] || [];
356    this.blocks[name].push(block);
357    return this;
358  };
359  _proto2.getBlock = function getBlock(name) {
360    if (!this.blocks[name]) {
361      throw new Error('unknown block "' + name + '"');
362    }
363    return this.blocks[name][0];
364  };
365  _proto2.getSuper = function getSuper(env, name, block, frame, runtime, cb) {
366    var idx = lib.indexOf(this.blocks[name] || [], block);
367    var blk = this.blocks[name][idx + 1];
368    var context = this;
369    if (idx === -1 || !blk) {
370      throw new Error('no super block available for "' + name + '"');
371    }
372    blk(env, context, frame, runtime, cb);
373  };
374  _proto2.addExport = function addExport(name) {
375    this.exported.push(name);
376  };
377  _proto2.getExported = function getExported() {
378    var _this5 = this;
379    var exported = {};
380    this.exported.forEach(function (name) {
381      exported[name] = _this5.ctx[name];
382    });
383    return exported;
384  };
385  return Context;
386}(Obj);
387var Template = /*#__PURE__*/function (_Obj2) {
388  _inheritsLoose(Template, _Obj2);
389  function Template() {
390    return _Obj2.apply(this, arguments) || this;
391  }
392  var _proto3 = Template.prototype;
393  _proto3.init = function init(src, env, path, eagerCompile) {
394    this.env = env || new Environment();
395    if (lib.isObject(src)) {
396      switch (src.type) {
397        case 'code':
398          this.tmplProps = src.obj;
399          break;
400        case 'string':
401          this.tmplStr = src.obj;
402          break;
403        default:
404          throw new Error("Unexpected template object type " + src.type + "; expected 'code', or 'string'");
405      }
406    } else if (lib.isString(src)) {
407      this.tmplStr = src;
408    } else {
409      throw new Error('src must be a string or an object describing the source');
410    }
411    this.path = path;
412    if (eagerCompile) {
413      try {
414        this._compile();
415      } catch (err) {
416        throw lib._prettifyError(this.path, this.env.opts.dev, err);
417      }
418    } else {
419      this.compiled = false;
420    }
421  };
422  _proto3.render = function render(ctx, parentFrame, cb) {
423    var _this6 = this;
424    if (typeof ctx === 'function') {
425      cb = ctx;
426      ctx = {};
427    } else if (typeof parentFrame === 'function') {
428      cb = parentFrame;
429      parentFrame = null;
430    }
431
432    // If there is a parent frame, we are being called from internal
433    // code of another template, and the internal system
434    // depends on the sync/async nature of the parent template
435    // to be inherited, so force an async callback
436    var forceAsync = !parentFrame;
437
438    // Catch compile errors for async rendering
439    try {
440      this.compile();
441    } catch (e) {
442      var err = lib._prettifyError(this.path, this.env.opts.dev, e);
443      if (cb) {
444        return callbackAsap(cb, err);
445      } else {
446        throw err;
447      }
448    }
449    var context = new Context(ctx || {}, this.blocks, this.env);
450    var frame = parentFrame ? parentFrame.push(true) : new Frame();
451    frame.topLevel = true;
452    var syncResult = null;
453    var didError = false;
454    this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err, res) {
455      // TODO: this is actually a bug in the compiled template (because waterfall
456      // tasks are both not passing errors up the chain of callbacks AND are not
457      // causing a return from the top-most render function). But fixing that
458      // will require a more substantial change to the compiler.
459      if (didError && cb && typeof res !== 'undefined') {
460        // prevent multiple calls to cb
461        return;
462      }
463      if (err) {
464        err = lib._prettifyError(_this6.path, _this6.env.opts.dev, err);
465        didError = true;
466      }
467      if (cb) {
468        if (forceAsync) {
469          callbackAsap(cb, err, res);
470        } else {
471          cb(err, res);
472        }
473      } else {
474        if (err) {
475          throw err;
476        }
477        syncResult = res;
478      }
479    });
480    return syncResult;
481  };
482  _proto3.getExported = function getExported(ctx, parentFrame, cb) {
483    // eslint-disable-line consistent-return
484    if (typeof ctx === 'function') {
485      cb = ctx;
486      ctx = {};
487    }
488    if (typeof parentFrame === 'function') {
489      cb = parentFrame;
490      parentFrame = null;
491    }
492
493    // Catch compile errors for async rendering
494    try {
495      this.compile();
496    } catch (e) {
497      if (cb) {
498        return cb(e);
499      } else {
500        throw e;
501      }
502    }
503    var frame = parentFrame ? parentFrame.push() : new Frame();
504    frame.topLevel = true;
505
506    // Run the rootRenderFunc to populate the context with exported vars
507    var context = new Context(ctx || {}, this.blocks, this.env);
508    this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err) {
509      if (err) {
510        cb(err, null);
511      } else {
512        cb(null, context.getExported());
513      }
514    });
515  };
516  _proto3.compile = function compile() {
517    if (!this.compiled) {
518      this._compile();
519    }
520  };
521  _proto3._compile = function _compile() {
522    var props;
523    if (this.tmplProps) {
524      props = this.tmplProps;
525    } else {
526      var source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, this.path, this.env.opts);
527      var func = new Function(source); // eslint-disable-line no-new-func
528      props = func();
529    }
530    this.blocks = this._getBlocks(props);
531    this.rootRenderFunc = props.root;
532    this.compiled = true;
533  };
534  _proto3._getBlocks = function _getBlocks(props) {
535    var blocks = {};
536    lib.keys(props).forEach(function (k) {
537      if (k.slice(0, 2) === 'b_') {
538        blocks[k.slice(2)] = props[k];
539      }
540    });
541    return blocks;
542  };
543  return Template;
544}(Obj);
545module.exports = {
546  Environment: Environment,
547  Template: Template
548};