first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// @ts-nocheck
|
||||
// Minimal YAML front-matter parser. Handles the subset used by SKILL.md in
|
||||
// our examples: scalar strings/numbers/booleans, block-literal (|) strings,
|
||||
// and flat arrays ("- foo"). Keeps the daemon dep-free. If you need real
|
||||
// YAML (nested objects, flow-style, anchors), swap for `yaml` or `js-yaml`.
|
||||
|
||||
export function parseFrontmatter(src) {
|
||||
const text = src.replace(/^/, '');
|
||||
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
|
||||
if (!match) return { data: {}, body: text };
|
||||
const [, yaml, body] = match;
|
||||
return { data: parseYamlSubset(yaml), body };
|
||||
}
|
||||
|
||||
function parseYamlSubset(src) {
|
||||
const lines = src.split(/\r?\n/);
|
||||
const root = {};
|
||||
const stack = [{ indent: -1, container: root, key: null }];
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const raw = lines[i];
|
||||
if (/^\s*(#.*)?$/.test(raw)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
const indent = raw.match(/^\s*/)[0].length;
|
||||
|
||||
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
||||
stack.pop();
|
||||
}
|
||||
const top = stack[stack.length - 1];
|
||||
const line = raw.slice(indent);
|
||||
|
||||
// Array item
|
||||
if (line.startsWith('- ')) {
|
||||
const value = line.slice(2).trim();
|
||||
let container = top.container;
|
||||
if (!Array.isArray(container)) {
|
||||
// Convert the pending key's value to an array on first `-`.
|
||||
const parent = stack[stack.length - 2];
|
||||
if (parent && top.key) {
|
||||
parent.container[top.key] = [];
|
||||
container = parent.container[top.key];
|
||||
top.container = container;
|
||||
} else {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (value.includes(':')) {
|
||||
const obj = {};
|
||||
const colonIdx = value.indexOf(':');
|
||||
const key = value.slice(0, colonIdx).trim();
|
||||
const valRaw = value.slice(colonIdx + 1).trim();
|
||||
if (valRaw) obj[key] = coerce(valRaw);
|
||||
container.push(obj);
|
||||
stack.push({ indent, container: obj, key: null });
|
||||
} else {
|
||||
container.push(coerce(value));
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// key: value or key: |
|
||||
const kv = /^([^:]+):\s*(.*)$/.exec(line);
|
||||
if (!kv) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
const key = kv[1].trim();
|
||||
const val = kv[2];
|
||||
|
||||
if (val === '' || val === undefined) {
|
||||
top.container[key] = {};
|
||||
stack.push({ indent, container: top.container[key], key });
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (val === '|' || val === '|-' || val === '>' || val === '>-') {
|
||||
const collected = [];
|
||||
const childIndent = indent + 2;
|
||||
i++;
|
||||
while (i < lines.length) {
|
||||
const next = lines[i];
|
||||
if (/^\s*$/.test(next)) {
|
||||
collected.push('');
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
const nIndent = next.match(/^\s*/)[0].length;
|
||||
if (nIndent < childIndent) break;
|
||||
collected.push(next.slice(childIndent));
|
||||
i++;
|
||||
}
|
||||
top.container[key] = collected.join('\n').trimEnd();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (val === '[]') {
|
||||
top.container[key] = [];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (val.startsWith('[') && val.endsWith(']')) {
|
||||
top.container[key] = val
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map((s) => coerce(s.trim()))
|
||||
.filter((v) => v !== '');
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
top.container[key] = coerce(val);
|
||||
i++;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
function coerce(raw) {
|
||||
if (raw === undefined) return '';
|
||||
let v = raw.trim();
|
||||
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
||||
return v.slice(1, -1);
|
||||
}
|
||||
if (v === 'true') return true;
|
||||
if (v === 'false') return false;
|
||||
if (v === 'null' || v === '~') return null;
|
||||
if (/^-?\d+$/.test(v)) return Number(v);
|
||||
if (/^-?\d*\.\d+$/.test(v)) return Number(v);
|
||||
return v;
|
||||
}
|
||||
Reference in New Issue
Block a user