mystjs
Version:
Markdown parser for MyST markdown in JavaScript
607 lines • 21.1 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.tokensToMyst = void 0;
const fromMarkdown_1 = require("./fromMarkdown");
const types_1 = require("./types");
const unist_util_visit_1 = require("unist-util-visit");
const unist_util_remove_1 = require("unist-util-remove");
const unist_builder_1 = require("unist-builder");
const he_1 = __importDefault(require("he"));
const utils_1 = require("./utils");
const unist_util_select_1 = require("unist-util-select");
const transforms_1 = require("./transforms");
const NUMBERED_CLASS = /^numbered$/;
const ALIGN_CLASS = /(?:(?:align-)|^)(left|right|center)/;
function getClassName(token, exclude) {
var _a, _b, _c;
const allClasses = new Set([
// Grab the trimmed classes from the token
...((_a = token.attrGet('class')) !== null && _a !== void 0 ? _a : '')
.split(' ')
.map((c) => c.trim())
.filter((c) => c),
// Add any from the meta information (these are often repeated)
...((_c = (_b = token.meta) === null || _b === void 0 ? void 0 : _b.class) !== null && _c !== void 0 ? _c : []),
]);
const className = [...allClasses].join(' ');
if (!className)
return undefined;
return (className
.split(' ')
.map((c) => c.trim())
.filter((c) => {
if (!c)
return false;
if (!exclude)
return true;
return !exclude.reduce((doExclude, test) => doExclude || !!c.match(test), false);
})
.join(' ') || undefined);
}
function hasClassName(token, matcher) {
const className = getClassName(token);
if (!className)
return false;
const matches = className
.split(' ')
.map((c) => c.match(matcher))
.filter((c) => c);
if (matches.length === 0)
return false;
return matches[0];
}
function getLang(t) {
return he_1.default.decode(t.info).trim().split(' ')[0].replace('\\', '');
}
function getColAlign(t) {
var _a;
if ((_a = t.attrs) === null || _a === void 0 ? void 0 : _a.length) {
for (const attrPair of t.attrs) {
if (attrPair[0] === 'style') {
const match = attrPair[1].match(/text-align:(left|right|center)/);
if (match) {
return match[1];
}
}
}
}
}
const defaultMdast = {
heading: {
type: 'heading',
getAttrs(token) {
var _a;
return {
depth: Number(token.tag[1]),
enumerated: (_a = token.meta) === null || _a === void 0 ? void 0 : _a.enumerated,
};
},
},
hr: {
type: 'thematicBreak',
noCloseToken: true,
isLeaf: true,
},
paragraph: {
type: 'paragraph',
},
blockquote: {
type: 'blockquote',
},
ordered_list: {
type: 'list',
getAttrs(token, tokens, index) {
var _a, _b;
const info = (_a = tokens[index + 1]) === null || _a === void 0 ? void 0 : _a.info;
const start = Number((_b = tokens[index + 1]) === null || _b === void 0 ? void 0 : _b.info);
return {
ordered: true,
start: isNaN(start) || !info ? 1 : start,
spread: false,
};
},
},
bullet_list: {
type: 'list',
attrs: {
ordered: false,
spread: false,
},
},
list_item: {
type: 'listItem',
attrs: {
spread: true,
},
},
em: {
type: 'emphasis',
},
strong: {
type: 'strong',
},
colon_fence: {
type: 'code',
isLeaf: true,
noCloseToken: true,
getAttrs(t) {
return { lang: getLang(t), value: (0, utils_1.withoutTrailingNewline)(t.content) };
},
},
fence: {
type: 'code',
isLeaf: true,
getAttrs(t) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const name = ((_a = t.meta) === null || _a === void 0 ? void 0 : _a.name) || undefined;
const showLineNumbers = !!(((_b = t.meta) === null || _b === void 0 ? void 0 : _b.linenos) ||
((_c = t.meta) === null || _c === void 0 ? void 0 : _c.linenos) === null || // Weird docutils implementation
((_d = t.meta) === null || _d === void 0 ? void 0 : _d['number-lines']) ||
(
// If lineno-start is present, linenos option is also automatically activated
// https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-option-code-block-lineno-start
(_e = t.meta) === null || _e === void 0 ? void 0 : _e['lineno-start']));
const lineno = (_g = (_f = t.meta) === null || _f === void 0 ? void 0 : _f['lineno-start']) !== null && _g !== void 0 ? _g : (_h = t.meta) === null || _h === void 0 ? void 0 : _h['number-lines'];
const startingLineNumber = lineno && lineno !== 1 && !isNaN(Number(lineno)) ? Number(lineno) : undefined;
const emphasizeLines = ((_j = t.meta) === null || _j === void 0 ? void 0 : _j['emphasize-lines'])
? (_k = t.meta) === null || _k === void 0 ? void 0 : _k['emphasize-lines'].split(',').map((n) => Number(n.trim())).filter((n) => !isNaN(n) && n > 0)
: undefined;
return Object.assign(Object.assign({ lang: getLang(t) }, (0, utils_1.normalizeLabel)(name)), { class: getClassName(t), showLineNumbers: showLineNumbers || undefined, startingLineNumber: showLineNumbers ? startingLineNumber : undefined, // Only if showing line numbers!
emphasizeLines, value: (0, utils_1.withoutTrailingNewline)(t.content) });
},
},
code_block: {
type: 'code',
isLeaf: true,
getAttrs(t) {
return { lang: getLang(t), value: (0, utils_1.withoutTrailingNewline)(t.content) };
},
},
code_inline: {
type: 'inlineCode',
noCloseToken: true,
isText: true,
},
hardbreak: {
type: 'break',
noCloseToken: true,
isLeaf: true,
},
link: {
type: 'link',
getAttrs(token) {
var _a;
return {
url: token.attrGet('href'),
title: (_a = token.attrGet('title')) !== null && _a !== void 0 ? _a : undefined,
};
},
},
image: {
type: 'image',
noCloseToken: true,
isLeaf: true,
getAttrs(token) {
var _a;
const alt = token.attrGet('alt') || ((_a = token.children) === null || _a === void 0 ? void 0 : _a.reduce((i, t) => i + (t === null || t === void 0 ? void 0 : t.content), ''));
const alignMatch = hasClassName(token, ALIGN_CLASS);
const align = alignMatch ? alignMatch[1] : undefined;
return {
url: token.attrGet('src'),
alt: alt || undefined,
title: token.attrGet('title') || undefined,
class: getClassName(token, [ALIGN_CLASS]),
width: token.attrGet('width') || undefined,
height: token.attrGet('height') || undefined,
align,
};
},
},
abbr: {
type: 'abbreviation',
getAttrs(token) {
var _a, _b, _c;
const value = (_b = (_a = token.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.content;
return {
title: (_c = token.attrGet('title')) !== null && _c !== void 0 ? _c : undefined,
value,
};
},
},
sub: {
type: 'subscript',
},
sup: {
type: 'superscript',
},
dl: {
type: 'definitionList',
},
dt: {
type: 'definitionTerm',
},
dd: {
type: 'definitionDescription',
},
admonition: {
type: 'admonition',
getAttrs(token) {
var _a;
const kind = ((_a = token.meta) === null || _a === void 0 ? void 0 : _a.kind) || undefined;
return {
kind,
class: getClassName(token, [new RegExp(`admonition|${kind}`)]),
};
},
},
admonition_title: {
type: 'admonitionTitle',
},
figure: {
type: 'container',
getAttrs(token) {
var _a, _b;
const name = ((_a = token.meta) === null || _a === void 0 ? void 0 : _a.name) || undefined;
return Object.assign(Object.assign({ kind: 'figure' }, (0, utils_1.normalizeLabel)(name)), { enumerated: (_b = token.meta) === null || _b === void 0 ? void 0 : _b.enumerated, class: getClassName(token, [NUMBERED_CLASS]) });
},
},
figure_caption: {
type: 'caption',
},
figure_legend: {
type: 'legend',
},
table: {
type: 'table',
getAttrs(token) {
var _a, _b, _c;
const name = ((_a = token.meta) === null || _a === void 0 ? void 0 : _a.name) || undefined;
return Object.assign(Object.assign({ kind: undefined }, (0, utils_1.normalizeLabel)(name)), { enumerated: (_b = token.meta) === null || _b === void 0 ? void 0 : _b.enumerated, class: getClassName(token, [NUMBERED_CLASS, ALIGN_CLASS]), align: ((_c = token.meta) === null || _c === void 0 ? void 0 : _c.align) || undefined });
},
},
table_caption: {
type: 'caption',
},
thead: {
type: '_lift',
},
tbody: {
type: '_lift',
},
tr: {
type: 'tableRow',
},
th: {
type: 'tableCell',
getAttrs(t) {
return { header: true, align: getColAlign(t) || undefined };
},
},
td: {
type: 'tableCell',
getAttrs(t) {
return { align: getColAlign(t) || undefined };
},
},
math_inline: {
type: 'inlineMath',
noCloseToken: true,
isText: true,
},
math_inline_double: {
type: 'math',
noCloseToken: true,
isText: true,
getAttrs(t) {
var _a;
return {
enumerated: (_a = t.meta) === null || _a === void 0 ? void 0 : _a.enumerated,
};
},
},
math_block: {
type: 'math',
noCloseToken: true,
isText: true,
getAttrs(t) {
var _a;
const name = t.info || undefined;
return Object.assign(Object.assign({}, (0, utils_1.normalizeLabel)(name)), { enumerated: (_a = t.meta) === null || _a === void 0 ? void 0 : _a.enumerated });
},
},
math_block_label: {
type: 'math',
noCloseToken: true,
isText: true,
getAttrs(t) {
var _a;
const name = t.info || undefined;
return Object.assign(Object.assign({}, (0, utils_1.normalizeLabel)(name)), { enumerated: (_a = t.meta) === null || _a === void 0 ? void 0 : _a.enumerated });
},
},
amsmath: {
type: 'math',
noCloseToken: true,
isText: true,
getAttrs(t) {
var _a;
return {
enumerated: (_a = t.meta) === null || _a === void 0 ? void 0 : _a.enumerated,
};
},
},
ref: {
type: 'crossReference',
isLeaf: true,
getAttrs(t) {
var _a, _b, _c;
return Object.assign(Object.assign({ kind: (_a = t.meta) === null || _a === void 0 ? void 0 : _a.kind }, (0, utils_1.normalizeLabel)((_b = t.meta) === null || _b === void 0 ? void 0 : _b.label)), { value: ((_c = t.meta) === null || _c === void 0 ? void 0 : _c.value) || undefined });
},
},
footnote_ref: {
type: 'footnoteReference',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
var _a;
return Object.assign({}, (0, utils_1.normalizeLabel)((_a = t === null || t === void 0 ? void 0 : t.meta) === null || _a === void 0 ? void 0 : _a.label));
},
},
footnote_anchor: {
type: '_remove',
noCloseToken: true,
},
footnote_block: {
// The footnote block is a view concern, not AST
// Lift footnotes out of the tree
type: '_lift',
},
footnote: {
type: 'footnoteDefinition',
getAttrs(t) {
var _a;
return Object.assign({}, (0, utils_1.normalizeLabel)((_a = t === null || t === void 0 ? void 0 : t.meta) === null || _a === void 0 ? void 0 : _a.label));
},
},
directive: {
type: 'mystDirective',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
var _a;
return {
name: t.info,
args: ((_a = t === null || t === void 0 ? void 0 : t.meta) === null || _a === void 0 ? void 0 : _a.arg) || undefined,
value: t.content.trim(),
};
},
},
parsed_directive: {
type: 'mystDirective',
getAttrs(t) {
var _a, _b;
let opts = (_a = t.meta) === null || _a === void 0 ? void 0 : _a.opts;
if (!opts || !Object.keys(opts).length) {
opts = undefined;
}
else {
if (opts.class)
opts.class = opts.class.join(' ');
Object.keys(opts).forEach((k) => {
// Handle flags, where option is simply present.
// This simple solution is very unlikely to be sufficient long term.
if (opts[k] === null)
opts[k] = true;
});
}
return {
name: t.info,
args: ((_b = t.meta) === null || _b === void 0 ? void 0 : _b.arg) || undefined,
options: opts,
value: t.content.trim() || undefined,
};
},
},
directive_error: {
type: 'mystDirectiveError',
noCloseToken: true,
},
role: {
type: 'mystRole',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
var _a;
return {
name: (_a = t.meta) === null || _a === void 0 ? void 0 : _a.name,
value: t.content,
};
},
},
parsed_role: {
type: 'mystRole',
getAttrs(t) {
return {
name: t.meta.name,
value: t.content,
};
},
},
role_error: {
type: 'mystRoleError',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
return {
value: t.content,
};
},
},
myst_target: {
type: 'mystTarget',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
return {
label: t.content,
};
},
},
html_inline: {
type: 'html',
noCloseToken: true,
isText: true,
},
html_block: {
type: 'html',
noCloseToken: true,
isText: true,
},
myst_block_break: {
type: 'blockBreak',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
return {
meta: t.content || undefined,
};
},
},
myst_line_comment: {
type: 'mystComment',
noCloseToken: true,
isLeaf: true,
getAttrs(t) {
return {
value: t.content.trim() || undefined,
};
},
},
};
function hoistSingleImagesOutofParagraphs(tree) {
// Hoist up all paragraphs with a single image
(0, unist_util_visit_1.visit)(tree, 'paragraph', (node) => {
var _a, _b;
if (!(((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) === 1 && ((_b = node.children) === null || _b === void 0 ? void 0 : _b[0].type) === 'image'))
return;
const child = node.children[0];
Object.keys(node).forEach((k) => {
delete node[k];
});
Object.assign(node, child);
});
}
function nestSingleImagesIntoParagraphs(tree) {
tree.children = tree.children.map((node) => {
if (node.type === 'image') {
return { type: 'paragraph', children: [node] };
}
else {
return node;
}
});
}
const defaultOptions = {
handlers: defaultMdast,
hoistSingleImagesOutofParagraphs: true,
nestBlocks: true,
};
function tokensToMyst(tokens, options = defaultOptions) {
var _a;
const opts = Object.assign(Object.assign(Object.assign({}, defaultOptions), options), { handlers: Object.assign(Object.assign({}, defaultOptions.handlers), options === null || options === void 0 ? void 0 : options.handlers) });
const state = new fromMarkdown_1.MarkdownParseState(opts.handlers);
state.parseTokens(tokens);
let tree;
do {
tree = state.closeNode();
} while (state.stack.length);
// Remove all redundant nodes marked for removal
(0, unist_util_remove_1.remove)(tree, '_remove');
// Lift up all nodes that are named "lift"
(0, transforms_1.liftChildren)(tree, '_lift');
// Remove unnecessary admonition titles from AST
// These are up to the serializer to put in
(0, unist_util_visit_1.visit)(tree, 'admonition', (node) => {
var _a, _b;
const { kind, children } = node;
if (!kind || !children || kind === types_1.AdmonitionKind.admonition)
return;
const expectedTitle = (0, utils_1.admonitionKindToTitle)(kind);
const titleNode = children[0];
if (titleNode.type === 'admonitionTitle' && ((_b = (_a = titleNode.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) === expectedTitle)
node.children = children.slice(1);
});
// Move crossReference text value to children
(0, unist_util_visit_1.visit)(tree, 'crossReference', (node) => {
delete node.children;
if (node.value) {
(0, utils_1.setTextAsChild)(node, node.value);
delete node.value;
}
});
// Nest block content inside of a block
if (opts.nestBlocks) {
const newTree = (0, unist_builder_1.u)('root', []);
let lastBlock;
const pushBlock = () => {
var _a;
if (!lastBlock)
return;
if (((_a = lastBlock.children) === null || _a === void 0 ? void 0 : _a.length) === 0) {
delete lastBlock.children;
}
newTree.children.push(lastBlock);
};
(_a = tree.children) === null || _a === void 0 ? void 0 : _a.forEach((node) => {
var _a, _b;
if (node.type === 'blockBreak') {
pushBlock();
lastBlock = node;
node.type = 'block';
node.children = (_a = node.children) !== null && _a !== void 0 ? _a : [];
return;
}
const stack = lastBlock ? lastBlock : newTree;
(_b = stack.children) === null || _b === void 0 ? void 0 : _b.push(node);
});
pushBlock();
tree = newTree;
}
(0, transforms_1.ensureCaptionIsParagraph)(tree);
// Replace "table node with caption" with "figure node with table and caption"
// TODO: Clean up when we update to typescript > 4.6.2 and we have access to
// parent in visitor function.
// i.e. visit(tree, 'table', (node, index parent) => {...})
// https://github.com/microsoft/TypeScript/issues/46900
(0, unist_util_select_1.selectAll)('table', tree).forEach((node) => {
var _a, _b;
const captionChildren = (_a = node.children) === null || _a === void 0 ? void 0 : _a.filter((n) => n.type === 'caption');
if (captionChildren === null || captionChildren === void 0 ? void 0 : captionChildren.length) {
const tableChildren = (_b = node.children) === null || _b === void 0 ? void 0 : _b.filter((n) => n.type !== 'caption');
const newTableNode = {
type: 'table',
align: node.align,
children: tableChildren,
};
node.type = 'container';
node.kind = 'table';
node.children = [...captionChildren, newTableNode];
delete node.align;
}
else {
delete node.enumerated;
}
});
if (opts.hoistSingleImagesOutofParagraphs) {
hoistSingleImagesOutofParagraphs(tree);
}
else {
nestSingleImagesIntoParagraphs(tree);
}
return tree;
}
exports.tokensToMyst = tokensToMyst;
//# sourceMappingURL=tokensToMyst.js.map
;