gridsome-plugin-remark-markdown-snippets
Version:
Gridsome markdown remark transformer plugin to inject external markdown files into another file, allowing use with variables as well.
105 lines (85 loc) • 3.75 kB
JavaScript
const fs = require("fs");
const path = require("path");
const visit = require("unist-util-visit-parents");
const normalize = require("normalize-path");
const unified = require("unified");
const toVFile = require("to-vfile");
const remarkParse = require("remark-parse");
const remarkAttr = require("remark-attr");
const frontMatter = require("remark-frontmatter");
const appRoot = require('app-root-path');
const DEFAULT_SNIPPET_IDENTIFIER = "snippet:",
DEFAULT_FILENAME_MATCH = /:([^{]*[^{])/g,
DEFAULT_ATTRIBUTES_MATCH = /([^{]+?)(?=}+?)/g,
DEFAULT_ATTRIBUTE_SEARCH = /([^"={]+?)="([^"]+?)"|(data-[^"= ]+?)(?=[" ])/gm;
module.exports = options => {
return async (tree, file) => {
const parentFileDir = file.data.node ? formatSlideDirectory(file.data.node.fileInfo) : "content";
const snippetsDir = options ? options.path : parentFileDir;
const snippetPath = path.resolve(appRoot.toString(), snippetsDir);
try {
if (!fs.existsSync(snippetPath)) {
throw Error(`Invalid directory specified "${snippetPath}"`);
}
visit (tree,
node => node.type === "paragraph" &&
node.children[0].type === "inlineCode" &&
node.children[0].value.startsWith(DEFAULT_SNIPPET_IDENTIFIER),
(node, ancestors) => {
const parent = ancestors[ancestors.length - 1];
const nodeIndex = parent.children.indexOf(node);
const { value } = node.children[0];
const snippetFilename = extractFilename(value);
const attributesArray = value.match(DEFAULT_ATTRIBUTES_MATCH);
let attributesString;
if(attributesArray) {
attributesString = attributesArray[0]
} else {
attributesString = null;
}
const astNodes = fileToAST(node, snippetPath, snippetFilename, attributesString);
parent.children.splice(nodeIndex, 1, ...astNodes);
return nodeIndex + (astNodes.length - 1);
});
} catch (error) {
console.log(error)
throw error
}
}
}
function extractAttributes (attributeString) {
const regexMatches = attributeString.matchAll(DEFAULT_ATTRIBUTE_SEARCH);
const attributeObject = {};
for (const match of regexMatches) {
attributeObject[match[1].trim()] = match[2].trim();
}
return attributeObject
}
function fileToAST (node, directory, filename, attributes) {
let filePath = normalize(path.join(directory, filename));
if (!fs.existsSync(filePath)) {
throw Error(`Invalid snippet specified; no such file "${filePath}"`);
}
let code = toVFile.readSync(filePath, "utf8");
if(attributes) {
const attributesObject = extractAttributes(attributes);
const variableKeys = Object.keys(attributesObject);
variableKeys.forEach(variable => {
code.contents = code.contents.replaceAll(`$${variable}`, attributesObject[variable]);
})
}
const tree = unified()
.use(remarkParse)
.use(remarkAttr)
.use(frontMatter, ["yaml"])
.parse(code);
return [...tree.children];
}
function extractFilename (snippetString) {
const regexMatches = [...snippetString.matchAll(DEFAULT_FILENAME_MATCH)];
return regexMatches[0][1];
}
function formatSlideDirectory (fileInfo) {
const { path, name, extension } = fileInfo;
return path.split(`${name}${extension}`)[0]
}