hexo/node_modules/nunjucks/src/environment.js

548 lines
17 KiB
JavaScript

'use strict';
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
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) {
// TODO: this is actually a bug in the compiled template (because waterfall
// tasks are both not passing errors up the chain of callbacks AND are not
// causing a return from the top-most render function). But fixing that
// will require a more substantial change to the compiler.
if (didError && cb && typeof res !== 'undefined') {
// prevent multiple calls to cb
return;
}
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
};