Files
rustdocusaurus/actions/transform.js
2021-07-10 10:55:56 +02:00

199 lines
5.9 KiB
JavaScript

const jsdom = require("jsdom");
const { clone, itemsReference } = require("../common");
const pretty = require("pretty");
const { removeChildren, insertAfter } = require("../utils/dom");
const unified = require("unified");
const parse = require("rehype-parse");
const toMdast = require("hast-util-to-mdast");
const stringify = require("remark-stringify");
const keys = Object.keys(itemsReference);
const { JSDOM } = jsdom;
const isRelativeLink = (link) => !link.startsWith("http");
const serializeDOM = (dom) => pretty(dom.window.document.body.innerHTML);
const transformLinks = (dom, crate) => {
Array.from(dom.window.document.querySelectorAll("a")).forEach((anchor) => {
if (isRelativeLink(anchor.href)) {
anchor.href =
`/docs/api/rust/${crate}/` + anchor.href.replace(".html", "");
}
});
};
const getFilePathInCrate = (file) => {
const parts = file.match(/(?:\.\.\/)+src\/([^\/]*)/);
const targetCrate = parts[parts.length - 1].replace(/_/g, "-");
const filePath = file.replace(/(?:\.\.\/)+src\/(?:.*)\//, "");
return `core/${targetCrate}/src/${filePath}`;
};
const transformSourceLinks = (dom, crate, repositoryInfo) => {
const getGitHubURL = (fileName) => {
return [
`https://github.com`,
repositoryInfo.owner,
repositoryInfo.project,
"blob",
repositoryInfo.revision,
fileName,
].join("/");
};
const transformHeader = (header) => {
const srclink = header.querySelector("a.srclink");
if (!srclink) {
return;
}
const parts = srclink.href.split("/");
const file = srclink.href.replace(".html", "").replace("#", "#L");
srclink.textContent = parts[parts.length - 1]
.replace(".html", "")
.replace(/#(.+)/, ":$1");
const functionName = header.querySelector("code > a").textContent;
let functionPrototype = header
.querySelector("code")
.innerHTML.replace(/\s{4,5}/g, "\n ");
if (functionPrototype.match(/\(\n/)) {
functionPrototype = functionPrototype.replace(") ->", "\n) ->");
}
functionPrototype = functionPrototype
.replace("where", "\nwhere")
.replace(/(#\[(?:[^\]]*)])/, "$1\n");
const functionNameWrapper = dom.window.document.createElement("code");
const prototype = dom.window.document.createElement("pre");
const wrapper = dom.window.document.createElement("i");
const text = dom.window.document.createTextNode("Defined in: ");
functionNameWrapper.textContent = functionName;
prototype.innerHTML = functionPrototype;
srclink.href = isRelativeLink(srclink.href)
? getGitHubURL(getFilePathInCrate(file))
: srclink.href;
srclink.title = "";
removeChildren(header);
header.appendChild(functionNameWrapper);
wrapper.appendChild(text);
wrapper.appendChild(srclink);
insertAfter(dom.window.document, prototype, header);
insertAfter(
dom.window.document,
wrapper,
prototype.nextSibling.classList.contains("docblock")
? prototype.nextSibling
: prototype
);
};
Array.from(dom.window.document.querySelectorAll("h3")).forEach(
transformHeader
);
Array.from(dom.window.document.querySelectorAll("h4")).forEach(
transformHeader
);
};
const transformMainHeader = (dom) => {
const header = dom.window.document.querySelector("h1");
header.textContent = Array.from(header.querySelectorAll("span")).map(
(span) => span.textContent
);
};
const transformSecondaryHeaders = (dom) => {
Array.from(dom.window.document.querySelectorAll("h1:not(.fqn)")).forEach(
(header) => {
const newNode = dom.window.document.createElement("h2");
newNode.textContent = header.textContent;
header.parentNode.replaceChild(newNode, header);
}
);
};
const removeRustdocTools = (dom) => {
const selectors = [
"h1 .srclink",
"#render-detail",
"a.anchor",
".loading-content",
];
selectors.forEach((selector) => {
Array.from(
dom.window.document.querySelectorAll(selector)
).forEach((element) => element.parentNode.removeChild(element));
});
};
const transformCodeBlocks = (dom) => {
Array.from(dom.window.document.querySelectorAll("pre")).forEach((element) => {
element.innerHTML = element.innerHTML.replace(/(#\[(?:[^\]]*)])/, "$1\n");
element.prepend(dom.window.document.createTextNode("```rs\r"));
element.append(dom.window.document.createTextNode("\r```"));
});
};
const transform = async (contents, crate, repositoryInfo) => {
const transformedContents = clone(contents);
const promises = keys.map(async (key) => {
if (key === "module") {
for (const path in transformedContents.module) {
transformedContents.module[path] = await transform(
transformedContents.module[path],
crate,
repositoryInfo
);
}
return;
}
const res = transformedContents[key].map((item) => {
const dom = new JSDOM(item.content);
if (repositoryInfo) {
transformSourceLinks(dom, crate, repositoryInfo);
}
removeRustdocTools(dom);
transformMainHeader(dom);
transformSecondaryHeaders(dom);
transformCodeBlocks(dom);
transformLinks(dom, crate);
const serialized = serializeDOM(dom);
const hast = unified().use(parse).parse(serialized);
const mdast = toMdast(hast);
let doc = unified().use(stringify).stringify(mdast);
const docBlocks = doc.match(/ *```rs[\s\S]*```/g);
if (docBlocks) {
docBlocks
.map((codeblock) => [codeblock, codeblock.replace(/^ {4}/gm, "")])
.forEach(([original, replacement]) => {
doc = doc.replace(original, replacement);
});
}
return {
path: item.path,
content: doc.replace(/!\[/g, "![").replace(/\s*$/g, ""),
};
});
transformedContents[key] = await Promise.all(res);
return;
});
await Promise.all(promises);
return transformedContents;
};
module.exports = transform;