UNPKG

@thi.ng/hiccup-markdown

Version:

Markdown parser & serializer from/to Hiccup format

156 lines (155 loc) 5.35 kB
import { implementsFunction } from "@thi.ng/checks/implements-function"; import { isFunction } from "@thi.ng/checks/is-function"; import { isNotStringAndIterable } from "@thi.ng/checks/is-not-string-iterable"; import { isString } from "@thi.ng/checks/is-string"; import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import { normalize } from "@thi.ng/hiccup/normalize"; import { repeat } from "@thi.ng/strings/repeat"; import { wrap } from "@thi.ng/strings/wrap"; import { Border } from "@thi.ng/text-canvas/api"; import { formatCanvas } from "@thi.ng/text-canvas/format"; import { tableCanvas } from "@thi.ng/text-canvas/table"; const serialize = (tree, ctx) => __serialize(tree, ctx, { indent: 0, sep: "" }).replace(/\n{3,}/g, "\n\n").trim(); const __serialize = (tree, ctx, state) => tree == null ? "" : Array.isArray(tree) ? __serializeArray(tree, ctx, state) : isFunction(tree) ? __serialize(tree(ctx), ctx, state) : implementsFunction(tree, "toHiccup") ? __serialize(tree.toHiccup(ctx), ctx, state) : implementsFunction(tree, "deref") ? __serialize(tree.deref(), ctx, state) : isNotStringAndIterable(tree) ? __serializeIter(tree, ctx, state) : tree.toString(); const __serializeArray = (tree, ctx, state) => { if (!tree.length) return ""; let tag = tree[0]; return isFunction(tag) ? __serialize(tag.apply(null, [ctx, ...tree.slice(1)]), ctx, state) : implementsFunction(tag, "render") ? __serialize( tag.render.apply(null, [ctx, ...tree.slice(1)]), ctx, state ) : isString(tag) ? __serializeTreeElement(tree, ctx, state) : isNotStringAndIterable(tree) ? __serializeIter(tree, ctx, state) : illegalArgs(`invalid tree node: ${tree}`); }; const __serializeTreeElement = (tree, ctx, state) => { tree = normalize(tree); const attribs = tree[1]; return attribs.__skip || attribs.__serialize === false ? "" : serializeElement(tree, ctx, state); }; const __serializeIter = (iter, ctx, state) => { if (!iter) return ""; const res = []; for (const i of iter) { res.push(__serialize(i, ctx, state)); } return res.join(state.sep); }; const __heading = (level) => (el, ctx, state) => ` ${repeat("#", level)} ${__body(el, ctx, state)} `; const __body = (el, ctx, state) => __serializeIter(el[2], ctx, state); const __resolve = (x) => isFunction(x) ? x() : x; const serializeElement = defmulti( (el) => el[0], { th: "strong" }, { [DEFAULT]: __body, h1: __heading(1), h2: __heading(2), h3: __heading(3), h4: __heading(4), h5: __heading(5), h6: __heading(6), p: (el, ctx, state) => ` ${__body(el, ctx, state)} `, img: (el) => `![${el[1].alt || ""}](${el[1].src})`, a: (el, ctx, state) => { let { href, title } = el[1]; title = __resolve(title); return `[${__body(el, ctx, state)}](${__resolve(href)}${title ? ` "${title}"` : ""})`; }, em: (el, ctx, state) => `_${__body(el, ctx, state)}_`, strong: (el, ctx, state) => `**${__body(el, ctx, state)}**`, pre: (el, ctx, state) => ` \`\`\`${el[1].lang || ""} ${__body(el, ctx, { ...state, pre: true, sep: "\n" })} \`\`\` `, code: (el, ctx, state) => state.pre ? el[2][0] : `\`${__body(el, ctx, state)}\``, ul: (el, ctx, state) => { const cstate = { ...state, indent: state.indent + 4, sep: "\n" }; return wrap(state.indent === 0 ? "\n" : "")( __body(el, ctx, cstate) ); }, ol: (el, ctx, state) => { const cstate = { ...state, indent: state.indent + 4, id: 0, sep: "\n" }; return wrap(state.indent === 0 ? "\n" : "")( __body(el, ctx, cstate) ); }, li: (el, ctx, state) => repeat(" ", state.indent - 4) + (state.id != null ? ++state.id + "." : "-") + " " + __body(el, ctx, { ...state, sep: "" }), blockquote: (el, ctx, state) => ` ${repeat(">", state.indent + 1)} ${__body(el, ctx, { ...state, indent: state.indent + 1, blockquote: true })} `, br: (_, __, state) => state.blockquote ? `\\ ${repeat(">", state.indent)} ` : "\\\n", hr: () => "\n---\n", table: (el, ctx, state) => { let caption = ""; let thead = []; let tbody = []; let colWidths = []; const rows = (rows2) => rows2.map( (x) => normalize(x)[2].map((td, i) => { const cell = serialize(td, ctx); colWidths[i] = Math.max(colWidths[i] || 3, cell.length); return cell; }) ); for (let child of el[2]) { child = normalize(child); switch (child[0]) { case "thead": thead = rows(child[2]); break; case "tbody": tbody = rows(child[2]); break; case "caption": caption = __body(child, ctx, state); break; default: } } return "\n" + formatCanvas( tableCanvas( { cols: colWidths.map((width) => ({ width })), padding: [1, 0], border: Border.V }, [ ...thead, colWidths.map((w) => repeat("-", w)), ...tbody ] ) ) + "\n" + caption + "\n"; } } ); export { serialize, serializeElement };