UNPKG

myst-parser

Version:
135 lines (134 loc) 5.13 kB
import { u } from 'unist-builder'; const UNHIDDEN_TOKENS = new Set([ 'parsed_directive_open', 'parsed_directive_close', 'directive_arg_open', 'directive_arg_close', 'directive_body_open', 'directive_body_close', 'myst_option_open', 'myst_option_close', 'parsed_role_open', 'parsed_role_close', 'role_body_open', 'role_body_close', ]); export function withoutTrailingNewline(str) { return str[str.length - 1] == '\n' ? str.slice(0, str.length - 1) : str; } /** MarkdownParseState tracks the context of a running token stream. * * Loosely based on prosemirror-markdown */ export class MarkdownParseState { constructor(src, handlers) { this.src = src; this.stack = [u('root', [])]; this.handlers = getTokenHandlers(handlers); } top() { return this.stack[this.stack.length - 1]; } addNode(node) { var _a; const top = this.top(); if (this.stack.length && node && 'children' in top) (_a = top.children) === null || _a === void 0 ? void 0 : _a.push(node); return node; } addText(text, token, type = 'text', attrs) { var _a, _b; const top = this.top(); const value = text; if (!value || !this.stack.length || !type || !('children' in top)) return; const last = (_a = top.children) === null || _a === void 0 ? void 0 : _a[top.children.length - 1]; if (type === 'text' && (last === null || last === void 0 ? void 0 : last.type) === type) { // The last node is also text, merge it with a space last.value += `${value}`; return last; } const node = { type: type, ...attrs, value }; (_b = top.children) === null || _b === void 0 ? void 0 : _b.push(node); this.addPositionsToNode(node, token); return node; } openNode(type, token, attrs, isLeaf = false) { const node = { type, ...attrs }; this.addPositionsToNode(node, token); if (!isLeaf) node.children = []; this.stack.push(node); } closeNode() { const node = this.stack.pop(); return this.addNode(node); } parseTokens(tokens) { tokens === null || tokens === void 0 ? void 0 : tokens.forEach((token, index) => { if (token.hidden && !UNHIDDEN_TOKENS.has(token.type)) return; const handler = this.handlers[token.type]; if (!handler) { throw new Error(`Token type ${token.type} not supported by tokensToMyst parser`); } handler(this, token, tokens, index); }); } addPositionsToNode(node, token) { var _a; const col = (_a = token.col) !== null && _a !== void 0 ? _a : [0, 0]; if (token.map) { node.position = { start: { line: token.map[0] + 1, column: col[0] + 1 }, end: { line: token.map[1], column: col[1] + 1 }, }; } else if (this._lastPosition) { node.position = { start: { line: this._lastPosition.start.line, column: col[0] + 1 }, end: { line: this._lastPosition.start.line, column: col[1] + 1 }, }; } if (node.position) { this._lastPosition = node.position; } } } function getAttrs(state, spec, token, tokens, index) { var _a; const attrs = ((_a = spec.getAttrs) === null || _a === void 0 ? void 0 : _a.call(spec, token, tokens, index, state)) || spec.attrs || {}; if ('type' in attrs) throw new Error('You can not have "type" as attrs.'); if ('children' in attrs) throw new Error('You can not have "children" as attrs.'); return attrs; } function noCloseToken(spec, type) { return spec.noCloseToken || type == 'code_inline' || type == 'code_block' || type == 'fence'; } function getTokenHandlers(specHandlers) { const handlers = {}; Object.entries(specHandlers).forEach(([type, spec]) => { const nodeType = spec.type; if (noCloseToken(spec, type)) { handlers[type] = (state, tok, tokens, i) => { if (spec.isText) { state.addText(withoutTrailingNewline(tok.content), tok, spec.type, getAttrs(state, spec, tok, tokens, i)); return; } state.openNode(nodeType, tok, getAttrs(state, spec, tok, tokens, i), spec.isLeaf); state.addText(withoutTrailingNewline(tok.content), tok); state.closeNode(); }; } else { handlers[type + '_open'] = (state, tok, tokens, i) => state.openNode(nodeType, tok, getAttrs(state, spec, tok, tokens, i)); handlers[type + '_close'] = (state) => state.closeNode(); } }); handlers.text = (state, tok) => state.addText(tok.content, tok); handlers.inline = (state, tok) => state.parseTokens(tok.children); handlers.softbreak = handlers.softbreak || ((state, tok) => state.addText('\n', tok)); return handlers; }