aubade
Version:
markdown, orchestrated.
80 lines (79 loc) • 3.2 kB
JavaScript
import { util } from './context.js';
import { annotate, compose } from './engine.js';
import { base, standard } from './resolver.js';
import { escape } from './utils.js';
export const engrave = forge({
transform: { 'inline:text': ({ type, text }) => ({ type, text: typography(text) }) },
});
export function forge({ directive = {}, renderer = {}, transform = {} } = {}) {
const resolver = {
...standard,
...renderer,
'aubade:directive'({ token: { meta }, render, sanitize }) {
const transform = { ...base, ...directive }[meta.type];
if (!transform)
throw new Error(`Unknown directive type: ${meta.type}`);
return transform({
data: meta.data,
annotate: (source) => annotate(source).map(render).join(''),
print: (...lines) => lines.flatMap((l) => (!l ? [] : l)).join('\n'),
sanitize,
});
},
};
function walk(token, visitors = {}) {
if ('children' in token) {
const visited = token.children.map((child) => walk(child, visitors));
token.children = visited;
}
const visitor = visitors[token.type];
return visitor ? visitor(token) : token;
}
return (input) => {
let { children: stream } = compose(input.replace(/\r\n?/g, '\n'));
if (Object.keys(transform).length)
stream = stream.map((t) => walk(t, transform));
return {
get tokens() {
return stream;
},
set tokens(v) {
stream = v;
},
html(overrides = {}) {
delete overrides['aubade:directive']; // prevent override of directives
function html(token) {
const resolve = { ...resolver, ...overrides }[token.type];
if (!resolve)
throw new Error(`Unknown token type: ${token.type}`);
const visited = transform[token.type] ? walk(token, transform) : token;
return resolve({ token: visited, render: html, sanitize: escape });
}
return stream.map(html).join('\n');
},
visit(visitors) {
return stream.map((token) => walk(token, visitors));
},
};
};
}
export function typography(text) {
let output = '';
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (char !== "'" && char !== '"') {
output += char;
continue;
}
const prev = text[i - 1];
const next = text[i + 1];
const prime = /\d/.test(prev);
const left = util.is['left-flanking'](prev || ' ', next || ' ');
const right = util.is['right-flanking'](prev || ' ', next || ' ');
const double = right ? (prime ? '″' : '”') : left ? '“' : char;
const single = right ? (prime ? '′' : '’') : left ? '‘' : char;
output += char === '"' ? double : single;
}
output = output.replace(/---/g, '—').replace(/--/g, '–');
return output.replace(/\.{3,}/g, '…');
}