text: 'use strict';
  
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
  
  var asap = require('asap');
  
  var _waterfall = require('a-sync-waterfall');
  
  var lib = require('./lib');
  
  var compiler = require('./compiler');
  
  var filters = require('./filters');
  
  var _require = require('./loaders'),
      FileSystemLoader = _require.FileSystemLoader,
      WebLoader = _require.WebLoader,
      PrecompiledLoader = _require.PrecompiledLoader;
  
  var tests = require('./tests');
  
  var globals = require('./globals');
  
  var _require2 = require('./object'),
      Obj = _require2.Obj,
      EmitterObj = _require2.EmitterObj;
  
  var globalRuntime = require('./runtime');
  
  var handleError = globalRuntime.handleError,
      Frame = globalRuntime.Frame;
  
  var expressApp = require('./express-app'); // If the user is using the async API, *always* call it
  // asynchronously even if the template was synchronous.
  
  
  function callbackAsap(cb, err, res) {
    asap(function () {
      cb(err, res);
    });
  }
  /**
   * A no-op template, for use with {% include ignore missing %}
   */
  
  
  var noopTmplSrc = {
    type: 'code',
    obj: {
      root: function root(env, context, frame, runtime, cb) {
        try {
          cb(null, '');
        } catch (e) {
          cb(handleError(e, null, null));
        }
      }
    }
  };
  
  var Environment =
  /*#__PURE__*/
  function (_EmitterObj) {
    _inheritsLoose(Environment, _EmitterObj);
  
    function Environment() {
      return _EmitterObj.apply(this, arguments) || this;
    }
  
    var _proto = Environment.prototype;
  
    _proto.init = function init(loaders, opts) {
      var _this = this;
  
      // The dev flag determines the trace that'll be shown on errors.
      // If set to true, returns the full trace from the error point,
      // otherwise will return trace starting from Template.render
      // (the full trace from within nunjucks may confuse developers using
      //  the library)
      // defaults to false
      opts = this.opts = opts || {};
      this.opts.dev = !!opts.dev; // The autoescape flag sets global autoescaping. If true,
      // every string variable will be escaped by default.
      // If false, strings can be manually escaped using the `escape` filter.
      // defaults to true
  
      this.opts.autoescape = opts.autoescape != null ? opts.autoescape : true; // If true, this will make the system throw errors if trying
      // to output a null or undefined value
  
      this.opts.throwOnUndefined = !!opts.throwOnUndefined;
      this.opts.trimBlocks = !!opts.trimBlocks;
      this.opts.lstripBlocks = !!opts.lstripBlocks;
      this.loaders = [];
  
      if (!loaders) {
        // The filesystem loader is only available server-side
        if (FileSystemLoader) {
          this.loaders = [new FileSystemLoader('views')];
        } else if (WebLoader) {
          this.loaders = [new WebLoader('/views')];
        }
      } else {
        this.loaders = lib.isArray(loaders) ? loaders : [loaders];
      } // It's easy to use precompiled templates: just include them
      // before you configure nunjucks and this will automatically
      // pick it up and use it
  
  
      if (typeof window !== 'undefined' && window.nunjucksPrecompiled) {
        this.loaders.unshift(new PrecompiledLoader(window.nunjucksPrecompiled));
      }
  
      this._initLoaders();
  
      this.globals = globals();
      this.filters = {};
      this.tests = {};
      this.asyncFilters = [];
      this.extensions = {};
      this.extensionsList = [];
  
      lib._entries(filters).forEach(function (_ref) {
        var name = _ref[0],
            filter = _ref[1];
        return _this.addFilter(name, filter);
      });
  
      lib._entries(tests).forEach(function (_ref2) {
        var name = _ref2[0],
            test = _ref2[1];
        return _this.addTest(name, test);
      });
    };
  
    _proto._initLoaders = function _initLoaders() {
      var _this2 = this;
  
      this.loaders.forEach(function (loader) {
        // Caching and cache busting
        loader.cache = {};
  
        if (typeof loader.on === 'function') {
          loader.on('update', function (name, fullname) {
            loader.cache[name] = null;
  
            _this2.emit('update', name, fullname, loader);
          });
          loader.on('load', function (name, source) {
            _this2.emit('load', name, source, loader);
          });
        }
      });
    };
  
    _proto.invalidateCache = function invalidateCache() {
      this.loaders.forEach(function (loader) {
        loader.cache = {};
      });
    };
  
    _proto.addExtension = function addExtension(name, extension) {
      extension.__name = name;
      this.extensions[name] = extension;
      this.extensionsList.push(extension);
      return this;
    };
  
    _proto.removeExtension = function removeExtension(name) {
      var extension = this.getExtension(name);
  
      if (!extension) {
        return;
      }
  
      this.extensionsList = lib.without(this.extensionsList, extension);
      delete this.extensions[name];
    };
  
    _proto.getExtension = function getExtension(name) {
      return this.extensions[name];
    };
  
    _proto.hasExtension = function hasExtension(name) {
      return !!this.extensions[name];
    };
  
    _proto.addGlobal = function addGlobal(name, value) {
      this.globals[name] = value;
      return this;
    };
  
    _proto.getGlobal = function getGlobal(name) {
      if (typeof this.globals[name] === 'undefined') {
        throw new Error('global not found: ' + name);
      }
  
      return this.globals[name];
    };
  
    _proto.addFilter = function addFilter(name, func, async) {
      var wrapped = func;
  
      if (async) {
        this.asyncFilters.push(name);
      }
  
      this.filters[name] = wrapped;
      return this;
    };
  
    _proto.getFilter = function getFilter(name) {
      if (!this.filters[name]) {
        throw new Error('filter not found: ' + name);
      }
  
      return this.filters[name];
    };
  
    _proto.addTest = function addTest(name, func) {
      this.tests[name] = func;
      return this;
    };
  
    _proto.getTest = function getTest(name) {
      if (!this.tests[name]) {
        throw new Error('test not found: ' + name);
      }
  
      return this.tests[name];
    };
  
    _proto.resolveTemplate = function resolveTemplate(loader, parentName, filename) {
      var isRelative = loader.isRelative && parentName ? loader.isRelative(filename) : false;
      return isRelative && loader.resolve ? loader.resolve(parentName, filename) : filename;
    };
  
    _proto.getTemplate = function getTemplate(name, eagerCompile, parentName, ignoreMissing, cb) {
      var _this3 = this;
  
      var that = this;
      var tmpl = null;
  
      if (name && name.raw) {
        // this fixes autoescape for templates referenced in symbols
        name = name.raw;
      }
  
      if (lib.isFunction(parentName)) {
        cb = parentName;
        parentName = null;
        eagerCompile = eagerCompile || false;
      }
  
      if (lib.isFunction(eagerCompile)) {
        cb = eagerCompile;
        eagerCompile = false;
      }
  
      if (name instanceof Template) {
        tmpl = name;
      } else if (typeof name !== 'string') {
        throw new Error('template names must be a string: ' + name);
      } else {
        for (var i = 0; i < this.loaders.length; i++) {
          var loader = this.loaders[i];
          tmpl = loader.cache[this.resolveTemplate(loader, parentName, name)];
  
          if (tmpl) {
            break;
          }
        }
      }
  
      if (tmpl) {
        if (eagerCompile) {
          tmpl.compile();
        }
  
        if (cb) {
          cb(null, tmpl);
          return undefined;
        } else {
          return tmpl;
        }
      }
  
      var syncResult;
  
      var createTemplate = function createTemplate(err, info) {
        if (!info && !err && !ignoreMissing) {
          err = new Error('template not found: ' + name);
        }
  
        if (err) {
          if (cb) {
            cb(err);
            return;
          } else {
            throw err;
          }
        }
  
        var newTmpl;
  
        if (!info) {
          newTmpl = new Template(noopTmplSrc, _this3, '', eagerCompile);
        } else {
          newTmpl = new Template(info.src, _this3, info.path, eagerCompile);
  
          if (!info.noCache) {
            info.loader.cache[name] = newTmpl;
          }
        }
  
        if (cb) {
          cb(null, newTmpl);
        } else {
          syncResult = newTmpl;
        }
      };
  
      lib.asyncIter(this.loaders, function (loader, i, next, done) {
        function handle(err, src) {
          if (err) {
            done(err);
          } else if (src) {
            src.loader = loader;
            done(null, src);
          } else {
            next();
          }
        } // Resolve name relative to parentName
  
  
        name = that.resolveTemplate(loader, parentName, name);
  
        if (loader.async) {
          loader.getSource(name, handle);
        } else {
          handle(null, loader.getSource(name));
        }
      }, createTemplate);
      return syncResult;
    };
  
    _proto.express = function express(app) {
      return expressApp(this, app);
    };
  
    _proto.render = function render(name, ctx, cb) {
      if (lib.isFunction(ctx)) {
        cb = ctx;
        ctx = null;
      } // We support a synchronous API to make it easier to migrate
      // existing code to async. This works because if you don't do
      // anything async work, the whole thing is actually run
      // synchronously.
  
  
      var syncResult = null;
      this.getTemplate(name, function (err, tmpl) {
        if (err && cb) {
          callbackAsap(cb, err);
        } else if (err) {
          throw err;
        } else {
          syncResult = tmpl.render(ctx, cb);
        }
      });
      return syncResult;
    };
  
    _proto.renderString = function renderString(src, ctx, opts, cb) {
      if (lib.isFunction(opts)) {
        cb = opts;
        opts = {};
      }
  
      opts = opts || {};
      var tmpl = new Template(src, this, opts.path);
      return tmpl.render(ctx, cb);
    };
  
    _proto.waterfall = function waterfall(tasks, callback, forceAsync) {
      return _waterfall(tasks, callback, forceAsync);
    };
  
    return Environment;
  }(EmitterObj);
  
  var Context =
  /*#__PURE__*/
  function (_Obj) {
    _inheritsLoose(Context, _Obj);
  
    function Context() {
      return _Obj.apply(this, arguments) || this;
    }
  
    var _proto2 = Context.prototype;
  
    _proto2.init = function init(ctx, blocks, env) {
      var _this4 = this;
  
      // Has to be tied to an environment so we can tap into its globals.
      this.env = env || new Environment(); // Make a duplicate of ctx
  
      this.ctx = lib.extend({}, ctx);
      this.blocks = {};
      this.exported = [];
      lib.keys(blocks).forEach(function (name) {
        _this4.addBlock(name, blocks[name]);
      });
    };
  
    _proto2.lookup = function lookup(name) {
      // This is one of the most called functions, so optimize for
      // the typical case where the name isn't in the globals
      if (name in this.env.globals && !(name in this.ctx)) {
        return this.env.globals[name];
      } else {
        return this.ctx[name];
      }
    };
  
    _proto2.setVariable = function setVariable(name, val) {
      this.ctx[name] = val;
    };
  
    _proto2.getVariables = function getVariables() {
      return this.ctx;
    };
  
    _proto2.addBlock = function addBlock(name, block) {
      this.blocks[name] = this.blocks[name] || [];
      this.blocks[name].push(block);
      return this;
    };
  
    _proto2.getBlock = function getBlock(name) {
      if (!this.blocks[name]) {
        throw new Error('unknown block "' + name + '"');
      }
  
      return this.blocks[name][0];
    };
  
    _proto2.getSuper = function getSuper(env, name, block, frame, runtime, cb) {
      var idx = lib.indexOf(this.blocks[name] || [], block);
      var blk = this.blocks[name][idx + 1];
      var context = this;
  
      if (idx === -1 || !blk) {
        throw new Error('no super block available for "' + name + '"');
      }
  
      blk(env, context, frame, runtime, cb);
    };
  
    _proto2.addExport = function addExport(name) {
      this.exported.push(name);
    };
  
    _proto2.getExported = function getExported() {
      var _this5 = this;
  
      var exported = {};
      this.exported.forEach(function (name) {
        exported[name] = _this5.ctx[name];
      });
      return exported;
    };
  
    return Context;
  }(Obj);
  
  var Template =
  /*#__PURE__*/
  function (_Obj2) {
    _inheritsLoose(Template, _Obj2);
  
    function Template() {
      return _Obj2.apply(this, arguments) || this;
    }
  
    var _proto3 = Template.prototype;
  
    _proto3.init = function init(src, env, path, eagerCompile) {
      this.env = env || new Environment();
  
      if (lib.isObject(src)) {
        switch (src.type) {
          case 'code':
            this.tmplProps = src.obj;
            break;
  
          case 'string':
            this.tmplStr = src.obj;
            break;
  
          default:
            throw new Error("Unexpected template object type " + src.type + "; expected 'code', or 'string'");
        }
      } else if (lib.isString(src)) {
        this.tmplStr = src;
      } else {
        throw new Error('src must be a string or an object describing the source');
      }
  
      this.path = path;
  
      if (eagerCompile) {
        try {
          this._compile();
        } catch (err) {
          throw lib._prettifyError(this.path, this.env.opts.dev, err);
        }
      } else {
        this.compiled = false;
      }
    };
  
    _proto3.render = function render(ctx, parentFrame, cb) {
      var _this6 = this;
  
      if (typeof ctx === 'function') {
        cb = ctx;
        ctx = {};
      } else if (typeof parentFrame === 'function') {
        cb = parentFrame;
        parentFrame = null;
      } // If there is a parent frame, we are being called from internal
      // code of another template, and the internal system
      // depends on the sync/async nature of the parent template
      // to be inherited, so force an async callback
  
  
      var forceAsync = !parentFrame; // Catch compile errors for async rendering
  
      try {
        this.compile();
      } catch (e) {
        var err = lib._prettifyError(this.path, this.env.opts.dev, e);
  
        if (cb) {
          return callbackAsap(cb, err);
        } else {
          throw err;
        }
      }
  
      var context = new Context(ctx || {}, this.blocks, this.env);
      var frame = parentFrame ? parentFrame.push(true) : new Frame();
      frame.topLevel = true;
      var syncResult = null;
      var didError = false;
      this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err, res) {
        if (didError) {
          // prevent multiple calls to cb
          if (cb) {
            return;
          } else {
            throw err;
          }
        }
  
        if (err) {
          err = lib._prettifyError(_this6.path, _this6.env.opts.dev, err);
          didError = true;
        }
  
        if (cb) {
          if (forceAsync) {
            callbackAsap(cb, err, res);
          } else {
            cb(err, res);
          }
        } else {
          if (err) {
            throw err;
          }
  
          syncResult = res;
        }
      });
      return syncResult;
    };
  
    _proto3.getExported = function getExported(ctx, parentFrame, cb) {
      // eslint-disable-line consistent-return
      if (typeof ctx === 'function') {
        cb = ctx;
        ctx = {};
      }
  
      if (typeof parentFrame === 'function') {
        cb = parentFrame;
        parentFrame = null;
      } // Catch compile errors for async rendering
  
  
      try {
        this.compile();
      } catch (e) {
        if (cb) {
          return cb(e);
        } else {
          throw e;
        }
      }
  
      var frame = parentFrame ? parentFrame.push() : new Frame();
      frame.topLevel = true; // Run the rootRenderFunc to populate the context with exported vars
  
      var context = new Context(ctx || {}, this.blocks, this.env);
      this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err) {
        if (err) {
          cb(err, null);
        } else {
          cb(null, context.getExported());
        }
      });
    };
  
    _proto3.compile = function compile() {
      if (!this.compiled) {
        this._compile();
      }
    };
  
    _proto3._compile = function _compile() {
      var props;
  
      if (this.tmplProps) {
        props = this.tmplProps;
      } else {
        var source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, this.path, this.env.opts);
        var func = new Function(source); // eslint-disable-line no-new-func
  
        props = func();
      }
  
      this.blocks = this._getBlocks(props);
      this.rootRenderFunc = props.root;
      this.compiled = true;
    };
  
    _proto3._getBlocks = function _getBlocks(props) {
      var blocks = {};
      lib.keys(props).forEach(function (k) {
        if (k.slice(0, 2) === 'b_') {
          blocks[k.slice(2)] = props[k];
        }
      });
      return blocks;
    };
  
    return Template;
  }(Obj);
  
  module.exports = {
    Environment: Environment,
    Template: Template
  };