UNPKG

ecmarkup

Version:

Custom element definitions and core utilities for markup that specifies ECMAScript and related technologies.

220 lines (219 loc) 8.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.printAlgorithm = printAlgorithm; exports.printFragments = printFragments; const parse5_1 = require("parse5"); const line_builder_1 = require("./line-builder"); const ecmarkup_1 = require("./ecmarkup"); const text_1 = require("./text"); async function printAlgorithm(source, alg, indent) { return await printListNode(source, alg.contents, indent); } async function printListNode(source, node, indent) { const output = new line_builder_1.LineBuilder(indent); for (const item of node.contents) { output.append(await printStep(source, item, indent)); } return output; } function commonIndent(lines) { var _a; if (lines.length === 0) { return ''; } let common = (_a = lines[0].match(/^[ \t]+/)) === null || _a === void 0 ? void 0 : _a[0]; if (common == null) { return ''; } for (let i = 1; i < lines.length; ++i) { const line = lines[i]; let j = 0; for (; j < line.length && j < common.length; ++j) { if (common[j] !== line[j]) { break; } } if (j <= 0) { return ''; } if (j < common.length) { common = common.slice(0, j); } } return common; } async function printStep(source, item, indent) { const output = new line_builder_1.LineBuilder(indent); output.firstLineIsPartial = false; output.appendText(item.name === 'ordered-list-item' ? '1. ' : '* '); if (item.attrs.length > 0) { const joined = item.attrs .map(({ key, value }) => (value === '' ? key : `${key}=${JSON.stringify(value)}`)) .join(', '); output.appendText(`[${joined}] `); } const stepSource = source.substring(item.location.start.offset, item.contents[item.contents.length - 1].location.end.offset); const stepIndent = commonIndent(stepSource.split('\n').slice(1)); const contents = await printFragments(source, item.contents, indent + 1, stepIndent); // this is a bit gross, but whatever contents.lines[0] = contents.lines[0].trimStart(); output.append(contents); if (output.last !== '') { // it can be empty if, for example, the step ends in a block element output.linebreak(); } // TODO fix up `Repeat:` to `Repeat,` etc if (item.sublist == null) { return output; } output.append(await printListNode(source, item.sublist, indent + 1)); return output; } async function printFragments(source, contents, indent, // for steps which are split over multiple lines, this is the common indent among them commonIndent = '') { const output = new line_builder_1.LineBuilder(indent); let skipNextElement = false; for (let i = 0; i < contents.length; ++i) { const node = contents[i]; switch (node.name) { case 'underscore': case 'text': { // ecmarkdown has a very permissive parser // this means figuring out exactly which things were escaped is difficult // so don't even bother const { start, end } = node.location; const originalText = source.substring(start.offset, end.offset); output.append((0, text_1.printText)(originalText, indent, commonIndent)); break; } case 'comment': { // for some reason emd comment nodes include the comment tokens // and also leading whitespace (???) // TODO think about that / maybe pad with spaces // TODO use the main printer for these, maybe if (node.contents.match(/^\s*<!--\s*emu-format ignore/)) { skipNextElement = true; } output.appendText(node.contents, true); break; } case 'opaqueTag': { // TODO parse and format this properly // including causing it to be a block tag (if that doesn't break stuff) // needs tests too // TODO just collapse this with below case output.appendText(node.contents, true); break; } case 'tag': { let htmlBits = null; const tagMatch = node.contents.match(/^<([a-z]+)/i); if (tagMatch != null) { const name = tagMatch[1]; if (ecmarkup_1.VOID_ELEMENTS.has(name.toLowerCase())) { htmlBits = node.contents; } else { let depth = 1; for (let j = i + 1; j < contents.length; ++j) { const otherNode = contents[j]; if (otherNode.name !== 'tag') { continue; } const otherTagMatch = otherNode.contents.match(/^<\/?([a-z]+)/i); if (otherTagMatch != null) { const otherName = otherTagMatch[1]; if (name.toLowerCase() !== otherName.toLowerCase()) { continue; } const isOpen = otherNode.contents[1] !== '/'; if (isOpen) { ++depth; } else { --depth; } if (depth === 0) { htmlBits = source.substring(node.location.start.offset, otherNode.location.end.offset); i = j; break; } } } } } if (htmlBits == null) { output.appendText(node.contents); } else if (skipNextElement) { skipNextElement = false; output.appendText(htmlBits.trim(), true); } else { htmlBits = htmlBits.trim(); // ecmarkdown includes whitespace sometimes??? // TODO try/catch for better error reporting const fragment = (0, parse5_1.parseFragment)(htmlBits, { sourceCodeLocationInfo: true }); if (fragment.childNodes.length !== 1) { throw new Error('confusing parse - this should not be possible; please report it to ecmarkup'); } const element = fragment.childNodes[0]; output.append(await (0, ecmarkup_1.printElement)(htmlBits, element, indent)); } break; } case 'pipe': { output.appendText('|'); output.appendText(node.nonTerminal); if (node.params) { // TODO sort parameters? output.appendText(`[${node.params}]`); } if (node.optional) { output.appendText('?'); } output.appendText('|'); break; } case 'star': case 'tick': case 'tilde': { output.append(await printFormat(source, node, indent)); break; } case 'double-brackets': { output.appendText(`[[${node.contents}]]`); break; } default: { // @ts-expect-error throw new Error(`Unknown node type ${node.name}`); } } } return output; } async function printFormat(source, node, indent) { let tok; switch (node.name) { case 'star': { tok = '*'; break; } case 'tick': tok = '`'; break; case 'tilde': tok = '~'; break; default: { // @ts-expect-error throw new Error(`Unknown node type ${node.name}`); } } const output = new line_builder_1.LineBuilder(indent); output.appendText(tok); output.append(await printFragments(source, node.contents, indent)); output.appendText(tok); return output; }