aubade
Version:
filesystem-based content processor
51 lines (50 loc) • 1.87 kB
JavaScript
export function matter(raw, memo = {}) {
if (!/[:\-\[\]|#]/gm.test(raw))
return coerce(raw.trim());
if (/^(".*"|'.*')$/.test(raw.trim()))
return raw.trim().slice(1, -1);
const PATTERN = /(^[^:\s]+):(?!\/)\r?\n?([\s\S]*?(?=^\S)|[\s\S]*$)/gm;
let match;
while ((match = PATTERN.exec(raw))) {
const [, key, value] = match;
const data = matter(outdent(value), memo[key]);
if (Array.isArray(data) || typeof data !== 'object')
memo[key] = data;
else
memo[key] = { ...memo[key], ...data };
}
if (Object.keys(memo).length)
return memo;
const cleaned = raw.replace(/#.*$/gm, '').trim();
switch (cleaned[0]) {
case '-': {
const sequence = cleaned.split(/^- /gm).filter((v) => v);
const tabbed = sequence.map((v) => v.replace(/\n( +)/g, (_, s) => '\n' + '\t'.repeat(s.length / 2)));
// @ts-expect-error - `FrontMatter` is assignable to itself
return tabbed.map((v) => matter(outdent(` ${v}`)));
}
case '[': {
const pruned = cleaned.slice(1, -1);
return pruned.split(',').map(coerce);
}
case '|': {
return outdent(cleaned.slice(1).replace('\n', ''));
}
default: {
return coerce(cleaned.trim());
}
}
}
function coerce(u) {
const v = u.trim(); // argument can be passed as-is
const map = { true: true, false: false, null: null };
if (v in map)
return map[v];
// if (!Number.isNaN(Number(v))) return Number(v);
return /^(".*"|'.*')$/.test(v) ? v.slice(1, -1) : v;
}
function outdent(input) {
const lines = input.split(/\r?\n/).filter((l) => l.trim());
const { length } = (/^\s*/.exec(lines[0]) || [''])[0];
return lines.map((l) => l.slice(length)).join('\n');
}