Initial commit
This commit is contained in:
326
node_modules/@11ty/eleventy/src/Engines/Liquid.js
generated
vendored
Normal file
326
node_modules/@11ty/eleventy/src/Engines/Liquid.js
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
import moo from "moo";
|
||||
import { Tokenizer, TokenKind, evalToken, Liquid as LiquidJs } from "liquidjs";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
// import debugUtil from "debug";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import { augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
// const debug = debugUtil("Eleventy:Liquid");
|
||||
|
||||
class Liquid extends TemplateEngine {
|
||||
static argumentLexerOptions = {
|
||||
number: /[0-9]+\.*[0-9]*/,
|
||||
doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/,
|
||||
singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/,
|
||||
keyword: /[a-zA-Z0-9.\-_]+/,
|
||||
"ignore:whitespace": /[, \t]+/, // includes comma separator
|
||||
};
|
||||
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.liquidOptions = this.config.liquidOptions || {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.liquid);
|
||||
|
||||
this.argLexer = moo.compile(Liquid.argumentLexerOptions);
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
setLibrary(override) {
|
||||
// warning, the include syntax supported here does not exactly match what Jekyll uses.
|
||||
this.liquidLib = override || new LiquidJs(this.getLiquidOptions());
|
||||
this.setEngineLib(this.liquidLib);
|
||||
|
||||
this.addFilters(this.config.liquidFilters);
|
||||
|
||||
// TODO these all go to the same place (addTag), add warnings for overwrites
|
||||
this.addCustomTags(this.config.liquidTags);
|
||||
this.addAllShortcodes(this.config.liquidShortcodes);
|
||||
this.addAllPairedShortcodes(this.config.liquidPairedShortcodes);
|
||||
}
|
||||
|
||||
getLiquidOptions() {
|
||||
let defaults = {
|
||||
root: [this.dirs.includes, this.dirs.input], // supplemented in compile with inputPath below
|
||||
extname: ".liquid",
|
||||
strictFilters: true,
|
||||
// TODO?
|
||||
// cache: true,
|
||||
};
|
||||
|
||||
let options = Object.assign(defaults, this.liquidOptions || {});
|
||||
// debug("Liquid constructor options: %o", options);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static wrapFilter(name, fn) {
|
||||
/**
|
||||
* @this {object}
|
||||
*/
|
||||
return function (...args) {
|
||||
// Set this.eleventy and this.page
|
||||
if (typeof this.context?.get === "function") {
|
||||
augmentObject(this, {
|
||||
source: this.context,
|
||||
getter: (key, context) => context.get([key]),
|
||||
|
||||
lazy: this.context.strictVariables,
|
||||
});
|
||||
}
|
||||
|
||||
// We *don’t* wrap this in an EleventyFilterError because Liquid has a better error message with line/column information in the template
|
||||
return fn.call(this, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Shortcodes
|
||||
static normalizeScope(context) {
|
||||
let obj = {};
|
||||
if (context) {
|
||||
obj.ctx = context; // Full context available on `ctx`
|
||||
|
||||
// Set this.eleventy and this.page
|
||||
augmentObject(obj, {
|
||||
source: context,
|
||||
getter: (key, context) => context.get([key]),
|
||||
lazy: context.strictVariables,
|
||||
});
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
addCustomTags(tags) {
|
||||
for (let name in tags) {
|
||||
this.addTag(name, tags[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addFilters(filters) {
|
||||
for (let name in filters) {
|
||||
this.addFilter(name, filters[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addFilter(name, filter) {
|
||||
this.liquidLib.registerFilter(name, Liquid.wrapFilter(name, filter));
|
||||
}
|
||||
|
||||
addTag(name, tagFn) {
|
||||
let tagObj;
|
||||
if (typeof tagFn === "function") {
|
||||
tagObj = tagFn(this.liquidLib);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: …, render: … } })",
|
||||
);
|
||||
}
|
||||
this.liquidLib.registerTag(name, tagObj);
|
||||
}
|
||||
|
||||
addAllShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addShortcode(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addAllPairedShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addPairedShortcode(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
static parseArguments(lexer, str) {
|
||||
let argArray = [];
|
||||
|
||||
if (!lexer) {
|
||||
lexer = moo.compile(Liquid.argumentLexerOptions);
|
||||
}
|
||||
|
||||
if (typeof str === "string") {
|
||||
lexer.reset(str);
|
||||
|
||||
let arg = lexer.next();
|
||||
while (arg) {
|
||||
/*{
|
||||
type: 'doubleQuoteString',
|
||||
value: '"test 2"',
|
||||
text: '"test 2"',
|
||||
toString: [Function: tokenToString],
|
||||
offset: 0,
|
||||
lineBreaks: 0,
|
||||
line: 1,
|
||||
col: 1 }*/
|
||||
if (arg.type.indexOf("ignore:") === -1) {
|
||||
// Push the promise into an array instead of awaiting it here.
|
||||
// This forces the promises to run in order with the correct scope value for each arg.
|
||||
// Otherwise they run out of order and can lead to undefined values for arguments in layout template shortcodes.
|
||||
// console.log( arg.value, scope, engine );
|
||||
argArray.push(arg.value);
|
||||
}
|
||||
arg = lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
return argArray;
|
||||
}
|
||||
|
||||
static parseArgumentsBuiltin(args) {
|
||||
let tokenizer = new Tokenizer(args);
|
||||
let parsedArgs = [];
|
||||
|
||||
let value = tokenizer.readValue();
|
||||
while (value) {
|
||||
parsedArgs.push(value);
|
||||
tokenizer.skipBlank();
|
||||
if (tokenizer.peek() === ",") {
|
||||
tokenizer.advance();
|
||||
}
|
||||
value = tokenizer.readValue();
|
||||
}
|
||||
tokenizer.end();
|
||||
|
||||
return parsedArgs;
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function (liquidEngine) {
|
||||
return {
|
||||
parse(tagToken) {
|
||||
this.name = tagToken.name;
|
||||
if (_t.config.liquidParameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
},
|
||||
render: function* (ctx) {
|
||||
let argArray = [];
|
||||
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function (liquidEngine) {
|
||||
return {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
|
||||
if (_t.config.liquidParameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
|
||||
this.templates = [];
|
||||
|
||||
var stream = liquidEngine.parser
|
||||
.parseStream(remainTokens)
|
||||
.on("template", (tpl) => this.templates.push(tpl))
|
||||
.on("tag:end" + shortcodeName, () => stream.stop())
|
||||
.on("end", () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
render: function* (ctx /*, emitter*/) {
|
||||
let argArray = [];
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
const html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);
|
||||
|
||||
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), html, ...argArray);
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
parseForSymbols(str) {
|
||||
let tokenizer = new Tokenizer(str);
|
||||
/** @type {Array} */
|
||||
let tokens = tokenizer.readTopLevelTokens();
|
||||
let symbols = tokens
|
||||
.filter((token) => token.kind === TokenKind.Output)
|
||||
.map((token) => {
|
||||
// manually remove filters 😅
|
||||
return token.content.split("|").map((entry) => entry.trim())[0];
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)
|
||||
/** @returns {boolean|undefined} */
|
||||
permalinkNeedsCompilation(str) {
|
||||
if (typeof str === "string") {
|
||||
return this.needsCompilation(str);
|
||||
}
|
||||
}
|
||||
|
||||
needsCompilation(str) {
|
||||
let options = this.liquidLib.options;
|
||||
|
||||
return (
|
||||
str.indexOf(options.tagDelimiterLeft) !== -1 ||
|
||||
str.indexOf(options.outputDelimiterLeft) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let engine = this.liquidLib;
|
||||
let tmplReady = engine.parse(str, inputPath);
|
||||
|
||||
// Required for relative includes
|
||||
let options = {};
|
||||
if (!inputPath || inputPath === "liquid" || inputPath === "md") {
|
||||
// do nothing
|
||||
} else {
|
||||
options.root = [TemplatePath.getDirFromFilePath(inputPath)];
|
||||
}
|
||||
|
||||
return async function (data) {
|
||||
let tmpl = await tmplReady;
|
||||
|
||||
return engine.render(tmpl, data, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Liquid;
|
Reference in New Issue
Block a user