UNPKG

honkit

Version:

HonKit is building beautiful books using Markdown.

231 lines (230 loc) 8.88 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const is_1 = __importDefault(require("is")); const extend_1 = __importDefault(require("extend")); const immutable_1 = __importDefault(require("immutable")); const promise_1 = __importDefault(require("../utils/promise")); const genKey_1 = __importDefault(require("../utils/genKey")); const templateShortcut_1 = __importDefault(require("./templateShortcut")); const NODE_ENDARGS = "%%endargs%%"; class TemplateBlock extends immutable_1.default.Record({ // Name of block, also the start tag name: String(), // End tag, default to "end<name>" end: String(), // Function to process the block content process: Function(), // List of String, for inner block tags blocks: immutable_1.default.List(), // List of shortcuts to replace with this block shortcuts: immutable_1.default.Map() }, "TemplateBlock") { getName() { return this.get("name"); } getEndTag() { return this.get("end") || `end${this.getName()}`; } getProcess() { return this.get("process"); } getBlocks() { return this.get("blocks"); } /** * Return shortcuts associated with this block or undefined * @return {TemplateShortcut|undefined} */ getShortcuts() { const shortcuts = this.get("shortcuts"); if (shortcuts.size === 0) { return undefined; } return templateShortcut_1.default.createForBlock(this, shortcuts); } /** * Return name for the nunjucks extension * @return {string} */ getExtensionName() { return `Block${this.getName()}Extension`; } /** * Return a nunjucks extension to represents this block * @return {Nunjucks.Extension} */ toNunjucksExt(mainContext, blocksOutput) { blocksOutput = blocksOutput || {}; const that = this; const name = this.getName(); const endTag = this.getEndTag(); const blocks = this.getBlocks().toJS(); function Ext() { this.tags = [name]; this.parse = function (parser, nodes) { let lastBlockName = null; let lastBlockArgs = null; const allBlocks = blocks.concat([endTag]); // Parse first block const tok = parser.nextToken(); lastBlockArgs = parser.parseSignature(null, true); parser.advanceAfterBlockEnd(tok.value); const args = new nodes.NodeList(); const bodies = []; const blockNamesNode = new nodes.Array(tok.lineno, tok.colno); const blockArgCounts = new nodes.Array(tok.lineno, tok.colno); // Parse while we found "end<block>" do { // Read body const currentBody = parser.parseUntilBlocks.apply(parser, allBlocks); // Handle body with previous block name and args blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName)); blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length)); bodies.push(currentBody); // Append arguments of this block as arguments of the run function lastBlockArgs.children.forEach((child) => { args.addChild(child); }); // Read new block lastBlockName = parser.nextToken().value; // Parse signature and move to the end of the block if (lastBlockName != endTag) { lastBlockArgs = parser.parseSignature(null, true); } parser.advanceAfterBlockEnd(lastBlockName); } while (lastBlockName != endTag); args.addChild(blockNamesNode); args.addChild(blockArgCounts); args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS)); return new nodes.CallExtensionAsync(this, "run", args, bodies); }; this.run = function (context) { const fnArgs = Array.prototype.slice.call(arguments, 1); let args; const blocks = []; let bodies = []; // Extract callback const callback = fnArgs.pop(); // Detect end of arguments const endArgIndex = fnArgs.indexOf(NODE_ENDARGS); // Extract arguments and bodies args = fnArgs.slice(0, endArgIndex); bodies = fnArgs.slice(endArgIndex + 1); // Extract block counts const blockArgCounts = args.pop(); const blockNames = args.pop(); // Recreate list of blocks blockNames.forEach((name, i) => { const countArgs = blockArgCounts[i]; const blockBody = bodies.shift(); const blockArgs = countArgs > 0 ? args.slice(0, countArgs) : []; args = args.slice(countArgs); const blockKwargs = extractKwargs(blockArgs); blocks.push({ name: name, body: blockBody(), args: blockArgs, kwargs: blockKwargs }); }); const mainBlock = blocks.shift(); mainBlock.blocks = blocks; (0, promise_1.default)() .then(() => { const ctx = (0, extend_1.default)({ ctx: context }, mainContext || {}); return that.applyBlock(mainBlock, ctx); }) .then((result) => { return that.blockResultToHtml(result, blocksOutput); }) .nodeify(callback); }; } // @ts-expect-error: nunjucks.Extension return Ext; } /** * Apply a block to a content * @param {Object} inner * @param {Object} context * @return {Promise<String>|String} */ applyBlock(inner, context) { const processFn = this.getProcess(); inner = inner || {}; inner.args = inner.args || []; inner.kwargs = inner.kwargs || {}; inner.blocks = inner.blocks || []; const r = processFn.call(context, inner); if (promise_1.default.isPromiseAlike(r)) { return r.then(this.normalizeBlockResult.bind(this)); } else { return this.normalizeBlockResult(r); } } /** * Normalize result from a block process function * @param {Object|String} result * @return {Object} */ normalizeBlockResult(result) { if (is_1.default.string(result)) { result = { body: result }; } result.name = this.getName(); return result; } /** * Convert a block result to HTML * @param {Object} result * @param {Object} blocksOutput: stored post processing blocks in this object * @return {string} */ blockResultToHtml(result, blocksOutput) { let indexedKey; const toIndex = !result.parse || result.post !== undefined; if (toIndex) { indexedKey = (0, genKey_1.default)(); blocksOutput[indexedKey] = result; } // Parsable block, just return it if (result.parse) { return result.body; } // Return it as a position marker return `{{-%${indexedKey}%-}}`; } /** * Create a template block from a function or an object * @param {string} blockName * @param {Object} block * @return {TemplateBlock} */ static create(blockName, block) { if (is_1.default.fn(block)) { // @ts-expect-error ts-migrate(2350) FIXME: Only a void function can be called with the 'new' ... Remove this comment to see the full error message block = new immutable_1.default.Map({ process: block }); } block = new TemplateBlock(block); block = block.set("name", blockName); return block; } } /** * Extract kwargs from an arguments array * @param {Array} args * @return {Object} */ function extractKwargs(args) { const last = args[args.length - 1]; return is_1.default.object(last) && last.__keywords ? args.pop() : {}; } exports.default = TemplateBlock;