auspice
Version:
Web app for visualizing pathogen evolution
107 lines (94 loc) • 3.87 kB
JavaScript
const marked = require('marked');
const yamlFront = require('yaml-front-matter'); /* https://www.npmjs.com/package/yaml-front-matter */
const utils = require("../utils");
const blockProxyHandler = {
set: (target, key, value) => {
if (key === "url") {
const urlParts = value.match(/.*(nextstrain.org|localhost).*?\/+([^?\s]+)\??(\S*)/);
target.dataset = urlParts[2];
target.query = urlParts[3];
return true;
} else if (key === "contents") {
target.__html = marked(value, {sanitize: false, gfm: true});
return true;
}
return false;
}
};
const makeFrontMatterBlock = (frontMatter) => {
if (!frontMatter.title || !frontMatter.dataset) {
throw new Error("Incorrectly formatted frontmatter in narrative file");
}
/* create markdown to represent the title page */
const markdown = [];
markdown.push(`# ${frontMatter.title}`);
if (frontMatter.authors) {
if (typeof frontMatter.authors === 'object' && Array.isArray(frontMatter.authors)) {
utils.warn(`Narrative parsing -- can't do author arrays yet`);
} else if (typeof frontMatter.authors === 'string') {
if (frontMatter.authorLinks && typeof frontMatter.authorLinks === "string") {
markdown.push(`### Author: [${frontMatter.authors}](${frontMatter.authorLinks})`);
} else {
markdown.push(`### Author: ${frontMatter.authors}`);
}
if (frontMatter.affiliations && typeof frontMatter.affiliations === "string") {
markdown[markdown.length-1] += " <sup> 1 </sup>";
markdown.push(`<sup> 1 </sup> ${frontMatter.affiliations}`);
}
}
}
if (frontMatter.date && typeof frontMatter.date === "string") {
markdown.push(`### Created: ${frontMatter.date}`);
}
if (frontMatter.updated && typeof frontMatter.updated === "string") {
markdown.push(`### Updated: ${frontMatter.updated}`);
}
if (frontMatter.abstract && typeof frontMatter.abstract === "string") {
markdown.push(`#### ${frontMatter.abstract}`);
}
const block = new Proxy({}, blockProxyHandler);
block.url = frontMatter.dataset;
block.contents = markdown.join("\n");
return block;
};
/**
* parses text (from the narrative markdown file)
* into blocks.
* Returned object (blocks) has properties "__html", "dataset", "query"
* and will be sent (in JSON form) to the client
* @param {string} fileContents string representing the entire markdown file
* @return {Array} Array of Objects. See `blockProxyHandler` for shape of each object.
*/
const parseNarrativeFile = (fileContents) => {
const blocks = [];
const frontMatter = yamlFront.loadFront(fileContents);
const titlesAndParagraphs = frontMatter.__content
.split(/\n*[#\s]+(\[.+?\]\(.+?\))\n+/) // matches titles defined as: # [title](url)
.filter((e) => !e.match(/^\s*$/)); // remove empty paragraphs (often leading / trailing)
/* process the frontmatter content */
blocks.push(makeFrontMatterBlock(frontMatter));
/* process the markdown content */
const isTitle = (s) => s.match(/^\[.+?\]\(.+?\)$/);
let idx = 0;
while (idx < titlesAndParagraphs.length) {
if (!isTitle(titlesAndParagraphs[idx])) {
idx++; continue;
}
/* want to capture the following groups:
title text (1st element) and URL, possibly including query (2nd element) */
const matches = titlesAndParagraphs[idx].match(/\[(.+?)\]\((\S+)\)/);
let paragraphContents = `# ${matches[1]}\n`; // the title text
/* add in the next paragraph _if_ it's not itself a title */
if (!isTitle(titlesAndParagraphs[++idx])) {
paragraphContents += titlesAndParagraphs[idx++];
}
const block = new Proxy({}, blockProxyHandler);
block.url = matches[2];
block.contents = paragraphContents;
blocks.push(block);
}
return blocks;
};
module.exports = {
default: parseNarrativeFile
};