UNPKG

remark-prism

Version:

Syntax highlighter for markdown code blocks - with support for plugins

139 lines (119 loc) 3.3 kB
const classNames = require('classnames'); const { CssSelectorParser } = require('css-selector-parser'); const map = require('unist-util-map'); const rangeParser = require('parse-numeric-range'); const createHighlighter = require('./highlight'); const h = (type, attrs = {}, children = []) => { return { type: 'element', tagName: type, data: { hName: type, hProperties: attrs, hChildren: children, }, properties: attrs, children, }; }; const selectorToAttrs = (selector) => { const parser = new CssSelectorParser(); const { rule } = parser.parse(selector); const { attrs = [] } = rule; return attrs.reduce((memo, { name, operator, value }) => { if (operator !== '=') { return memo; } const isClass = name === 'class'; const newValue = isClass ? [memo[name] || ''].concat(value).join(' ') : value; return Object.assign(memo, { [name]: newValue, }); }, {}); }; const parseLang = (str) => { const match = (regexp) => { const m = (str || '').match(regexp); return Array.isArray(m) ? m.filter(Boolean) : []; }; const [lang = 'unknown'] = match(/^[a-zA-Z\d-]*/g); const selectors = match(/\[(.*?)\]/g).join(''); const attrs = selectors.length ? selectorToAttrs(selectors) : {}; const className = classNames(`language-${lang}`, attrs.class); const { legend = '', ...restAttrs } = attrs; const range = rangeParser( match(/\{(.*?)\}$/g) .join(',') .replace(/^\{/, '') .replace(/\}$/, ''), ); return { lang, legend, range, attrs: { ...restAttrs, class: className, }, }; }; module.exports = (options = {}) => (tree) => { const highlight = createHighlighter(options); const { transformInlineCode = false } = options; return map(tree, (node) => { const { type, tagName } = node; if ( !['code', 'inlineCode'].includes(type) && !['code', 'inlineCode'].includes(tagName) ) { return node; } if ( (type === 'inlineCode' && !transformInlineCode) || (type === 'tagName' && !transformInlineCode) ) { return node; } const { value, properties = {}, children = [] } = node; const { className: classNameArr = [] } = properties; const langClassName = classNameArr .map((lang) => lang.replace(/language-/, '')) .join(' '); const langToken = node.lang || langClassName; const { lang = 'unknown', attrs, legend, range } = parseLang(langToken); const { class: className = '', ...restAttrs } = attrs; const code = h( 'code', { className: `language-${lang}` }, highlight({ lang, value: value || children .filter(({ type }) => type === 'text') .map(({ value }) => value) .pop(), attrs, range, }), ); const pre = h( 'div', { className: `remark-highlight` }, [ h( 'pre', { ...restAttrs, className: className.split(/\s/), }, [code], ), legend ? h('legend', {}, [{ type: 'text', value: legend }]) : null, ].filter(Boolean), ); return /^inline/.test(type) ? code : pre; }); };