This commit is contained in:
2024-11-03 17:16:20 +01:00
commit fd6412d6f2
8090 changed files with 778406 additions and 0 deletions

447
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js generated vendored Executable file
View File

@@ -0,0 +1,447 @@
import NunjucksLib from "nunjucks";
import { TemplatePath } from "@11ty/eleventy-utils";
import TemplateEngine from "./TemplateEngine.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import EventBusUtil from "../Util/EventBusUtil.js";
import { augmentObject } from "./Util/ContextAugmenter.js";
class EleventyNunjucksError extends EleventyBaseError {}
class Nunjucks extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.nunjucksEnvironmentOptions = this.config.nunjucksEnvironmentOptions || { dev: true };
this.nunjucksPrecompiledTemplates = this.config.nunjucksPrecompiledTemplates || {};
this._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0;
this.setLibrary(this.config.libraryOverrides.njk);
this.cacheable = true;
}
_setEnv(override) {
if (override) {
this.njkEnv = override;
} else if (this._usingPrecompiled) {
// Precompiled templates to avoid eval!
const NodePrecompiledLoader = function () {};
NodePrecompiledLoader.prototype.getSource = (name) => {
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/precompiled-loader.js#L5
return {
src: {
type: "code",
obj: this.nunjucksPrecompiledTemplates[name],
},
// Maybe add this?
// path,
// noCache: true
};
};
this.njkEnv = new NunjucksLib.Environment(
new NodePrecompiledLoader(),
this.nunjucksEnvironmentOptions,
);
} else {
let paths = new Set();
paths.add(super.getIncludesDir());
paths.add(TemplatePath.getWorkingDir());
// Filter out undefined paths
let fsLoader = new NunjucksLib.FileSystemLoader(Array.from(paths).filter(Boolean));
this.njkEnv = new NunjucksLib.Environment(fsLoader, this.nunjucksEnvironmentOptions);
}
this.config.events.emit("eleventy.engine.njk", {
nunjucks: NunjucksLib,
environment: this.njkEnv,
});
}
setLibrary(override) {
this._setEnv(override);
// Correct, but overbroad. Better would be to evict more granularly, but
// resolution from paths isn't straightforward.
EventBusUtil.soloOn("eleventy.templateModified", (/*path*/) => {
this.njkEnv.invalidateCache();
});
this.setEngineLib(this.njkEnv);
this.addFilters(this.config.nunjucksFilters);
this.addFilters(this.config.nunjucksAsyncFilters, true);
// TODO these all go to the same place (addTag), add warnings for overwrites
// TODO(zachleat): variableName should work with quotes or without quotes (same as {% set %})
this.addPairedShortcode("setAsync", function (content, variableName) {
this.ctx[variableName] = content;
return "";
});
this.addCustomTags(this.config.nunjucksTags);
this.addAllShortcodes(this.config.nunjucksShortcodes);
this.addAllShortcodes(this.config.nunjucksAsyncShortcodes, true);
this.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes);
this.addAllPairedShortcodes(this.config.nunjucksAsyncPairedShortcodes, true);
this.addGlobals(this.config.nunjucksGlobals);
}
addFilters(filters, isAsync) {
for (let name in filters) {
this.njkEnv.addFilter(name, Nunjucks.wrapFilter(name, filters[name]), isAsync);
}
}
static wrapFilter(name, fn) {
return function (...args) {
try {
augmentObject(this, {
source: this.ctx,
lazy: false, // context.env?.opts.throwOnUndefined,
});
return fn.call(this, ...args);
} catch (e) {
throw new EleventyNunjucksError(
`Error in Nunjucks Filter \`${name}\`${this.page ? ` (${this.page.inputPath})` : ""}`,
e,
);
}
};
}
// Shortcodes
static normalizeContext(context) {
let obj = {};
if (context.ctx) {
obj.ctx = context.ctx;
obj.env = context.env;
augmentObject(obj, {
source: context.ctx,
lazy: false, // context.env?.opts.throwOnUndefined,
});
}
return obj;
}
addCustomTags(tags) {
for (let name in tags) {
this.addTag(name, tags[name]);
}
}
addTag(name, tagFn) {
let tagObj;
if (typeof tagFn === "function") {
tagObj = tagFn(NunjucksLib, this.njkEnv);
} else {
throw new Error(
"Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})",
);
}
this.njkEnv.addExtension(name, tagObj);
}
addGlobals(globals) {
for (let name in globals) {
this.addGlobal(name, globals[name]);
}
}
addGlobal(name, globalFn) {
this.njkEnv.addGlobal(name, globalFn);
}
addAllShortcodes(shortcodes, isAsync = false) {
for (let name in shortcodes) {
this.addShortcode(name, shortcodes[name], isAsync);
}
}
addAllPairedShortcodes(shortcodes, isAsync = false) {
for (let name in shortcodes) {
this.addPairedShortcode(name, shortcodes[name], isAsync);
}
}
_getShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
return function ShortcodeFunction() {
this.tags = [shortcodeName];
this.parse = function (parser, nodes) {
let args;
let tok = parser.nextToken();
args = parser.parseSignature(true, true);
// Nunjucks bug with non-paired custom tags bug still exists even
// though this issue is closed. Works fine for paired.
// https://github.com/mozilla/nunjucks/issues/158
if (args.children.length === 0) {
args.addChild(new nodes.Literal(0, 0, ""));
}
parser.advanceAfterBlockEnd(tok.value);
if (isAsync) {
return new nodes.CallExtensionAsync(this, "run", args);
}
return new nodes.CallExtension(this, "run", args);
};
this.run = function (...args) {
let resolve;
if (isAsync) {
resolve = args.pop();
}
let [context, ...argArray] = args;
if (isAsync) {
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
// #3286 error messaging when the shortcode is not a promise
if (!ret?.then) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
),
);
}
ret.then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString("" + returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(`Error with Nunjucks shortcode \`${shortcodeName}\``, e),
);
},
);
} else {
try {
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
return new NunjucksLib.runtime.SafeString("" + ret);
} catch (e) {
throw new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\``,
e,
);
}
}
};
};
}
_getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
return function PairedShortcodeFunction() {
this.tags = [shortcodeName];
this.parse = function (parser, nodes) {
var tok = parser.nextToken();
var args = parser.parseSignature(true, true);
parser.advanceAfterBlockEnd(tok.value);
var body = parser.parseUntilBlocks("end" + shortcodeName);
parser.advanceAfterBlockEnd();
return new nodes.CallExtensionAsync(this, "run", args, [body]);
};
this.run = function (...args) {
let resolve = args.pop();
let body = args.pop();
let [context, ...argArray] = args;
body(function (e, bodyContent) {
if (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
}
if (isAsync) {
let ret = shortcodeFn.call(
Nunjucks.normalizeContext(context),
bodyContent,
...argArray,
);
// #3286 error messaging when the shortcode is not a promise
if (!ret?.then) {
throw new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
);
}
ret.then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
},
);
} else {
try {
resolve(
null,
new NunjucksLib.runtime.SafeString(
shortcodeFn.call(Nunjucks.normalizeContext(context), bodyContent, ...argArray),
),
);
} catch (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
}
}
});
};
};
}
addShortcode(shortcodeName, shortcodeFn, isAsync = false) {
let fn = this._getShortcodeFn(shortcodeName, shortcodeFn, isAsync);
this.njkEnv.addExtension(shortcodeName, new fn());
}
addPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {
let fn = this._getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync);
this.njkEnv.addExtension(shortcodeName, new fn());
}
// Dont return a boolean if permalink is a function (see TemplateContent->renderPermalink)
permalinkNeedsCompilation(str) {
if (typeof str === "string") {
return this.needsCompilation(str);
}
}
needsCompilation(str) {
// Defend against syntax customisations:
// https://mozilla.github.io/nunjucks/api.html#customizing-syntax
let optsTags = this.njkEnv.opts.tags || {};
let blockStart = optsTags.blockStart || "{%";
let variableStart = optsTags.variableStart || "{{";
let commentStart = optsTags.variableStart || "{#";
return (
str.indexOf(blockStart) !== -1 ||
str.indexOf(variableStart) !== -1 ||
str.indexOf(commentStart) !== -1
);
}
_getParseExtensions() {
if (this._parseExtensions) {
return this._parseExtensions;
}
// add extensions so the parser knows about our custom tags/blocks
let ext = [];
for (let name in this.config.nunjucksTags) {
let fn = this._getShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksShortcodes) {
let fn = this._getShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksAsyncShortcodes) {
let fn = this._getShortcodeFn(name, () => {}, true);
ext.push(new fn());
}
for (let name in this.config.nunjucksPairedShortcodes) {
let fn = this._getPairedShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksAsyncPairedShortcodes) {
let fn = this._getPairedShortcodeFn(name, () => {}, true);
ext.push(new fn());
}
this._parseExtensions = ext;
return ext;
}
/* Outputs an Array of lodash get selectors */
parseForSymbols(str) {
const { parser, nodes } = NunjucksLib;
let obj = parser.parse(str, this._getParseExtensions());
let linesplit = str.split("\n");
let values = obj.findAll(nodes.Value);
let symbols = obj.findAll(nodes.Symbol).map((entry) => {
let name = [entry.value];
let nestedIndex = -1;
for (let val of values) {
if (nestedIndex > -1) {
/* deep.object.syntax */
if (linesplit[val.lineno].charAt(nestedIndex) === ".") {
name.push(val.value);
nestedIndex += val.value.length + 1;
} else {
nestedIndex = -1;
}
} else if (
val.lineno === entry.lineno &&
val.colno === entry.colno &&
val.value === entry.value
) {
nestedIndex = entry.colno + entry.value.length;
}
}
return name.join(".");
});
let uniqueSymbols = Array.from(new Set(symbols));
return uniqueSymbols;
}
async compile(str, inputPath) {
let tmpl;
// *All* templates are precompiled to avoid runtime eval
if (this._usingPrecompiled) {
tmpl = this.njkEnv.getTemplate(str, true);
} else if (!inputPath || inputPath === "njk" || inputPath === "md") {
tmpl = new NunjucksLib.Template(str, this.njkEnv, null, false);
} else {
tmpl = new NunjucksLib.Template(str, this.njkEnv, inputPath, false);
}
return function (data) {
return new Promise(function (resolve, reject) {
tmpl.render(data, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
};
}
}
export default Nunjucks;