From b82a3fc69f51da71ea57ece7cc6b41b604fe4a28 Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Mon, 22 Jul 2019 14:57:11 -0700 Subject: [PATCH] use ERB converts for octicon() and avatar_for() --- lib/erb-to-html.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++ lib/mdx-loader.js | 19 ++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 lib/erb-to-html.js diff --git a/lib/erb-to-html.js b/lib/erb-to-html.js new file mode 100644 index 00000000..6c8519bc --- /dev/null +++ b/lib/erb-to-html.js @@ -0,0 +1,84 @@ +const visit = require('unist-util-visit') +const cache = new Map() + +module.exports = options => { + const convert = converter(options) + return tree => { + visit(tree, 'code', (node, parent, index) => { + const match = node.lang ? node.lang.match(/^[a-z]+/) : null + const lang = match ? match[0] : null + if (lang === 'erb' || lang === 'html') { + const erb = node.value + const html = convert(node.value) + + if (html && !html.includes('<%')) { + node.value = html + node.lang = node.lang.replace(/^erb/, 'html') + } + } else { + // console.warn(`Unknown code block lang: ${node.lang}`) + } + }) + } +} + +function converter({converters = {}}) { + return erb => { + const blocks = erb.match(/<%=[^%]+%>/g) + if (blocks && blocks.length) { + let html = erb + console.warn(`Replacing ${blocks.length} ERB block(s)...`) + for (const input of blocks) { + const output = convert(input) + if (output !== input) { + html = replaceAll(html, input, output) + } + } + return html + } + } + + function convert(block) { + if (cache.has(block)) { + return cache.get(block) + } + + const [_, method, argString] = block.match(/<%= *(\w+)[ \(]([^\)]+)\)? *%>/) + const parts = argString.split(/, */) + const args = [] + const kwargs = {} + for (const part of parts) { + let match + if (match = part.match(/^:(.+) => (.+)$/)) { + kwargs[unquote(match[1])] = unquote(match[2]) + } else if (match = part.match(/^(.+): (.+)$/)) { + kwargs[unquote(match[1])] = unquote(match[2]) + } else { + args.push(unquote(part)) + } + } + + if (typeof converters[method] === 'function') { + let output = converters[method](args, kwargs) + output = `\n${output}` + cache.set(block, output) + return output + } else { + console.warn(`Unable to convert: ${block}`) + return block + } + } +} + +function replaceAll(str, input, output) { + let result = str + while (result.includes(input)) { + result = result.replace(input, output) + } + return result +} + + +function unquote(str) { + return str.replace(/^\s*"([^"]+)"\s*$/, (_, value) => value) +} diff --git a/lib/mdx-loader.js b/lib/mdx-loader.js index 835c8434..7fa1f723 100644 --- a/lib/mdx-loader.js +++ b/lib/mdx-loader.js @@ -1,11 +1,13 @@ const {getOptions} = require('loader-utils') const mdx = require('@mdx-js/mdx') +const octicons = require('@primer/octicons') const emoji = require('remark-emoji') const images = require('remark-images') const rehypePrism = require('./rehype-prism') const textr = require('remark-textr') const toc = require('remark-toc') +const erbToHTML = require('./erb-to-html') const mdxExportJSONByDefault = require('mdx-constant') const grayMatter = require('gray-matter') @@ -22,7 +24,22 @@ module.exports = async function(source) { [toc, {heading: '(table of|section)? contents'}], images, emoji, - [textr, {plugins: [typographicBase]}] + [textr, {plugins: [typographicBase]}], + [erbToHTML, { + converters: { + octicon: ([icon], attrs) => { + if (octicons[icon]) { + return octicons[icon].toSVG(attrs) + } else { + throw new Error(`No such octicon: "${icon}"`) + } + }, + avatar_for: ([username, size], kwargs) => { + const attrs = Object.entries(kwargs).map(([key, value]) => ` ${key}="${value}"`).join(' ') + return `` + } + } + }] ], hastPlugins: [rehypePrism], compilers: [mdxExportJSONByDefault('frontMatter', data)]