commit pi sessions
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Minimal YAML frontmatter parser tailored to the prompt workflow files
|
||||
* in this directory. Mirrors the subset of YAML that the existing `c-*.md`
|
||||
* and `p-*.md` files actually use:
|
||||
*
|
||||
* key: value -> string
|
||||
* key: "quoted value" -> string (quotes stripped)
|
||||
* key: -> block scalar (everything more-indented is
|
||||
* collected, de-indented, and returned as one
|
||||
* string with newlines)
|
||||
*
|
||||
* Comments (`# ...`) and blank lines are skipped. Quoted strings have
|
||||
* their quotes stripped, matching the convention used in the rest of the
|
||||
* repo (no nested quoting or escapes are needed by the fixtures).
|
||||
*/
|
||||
export interface ParsedDoc {
|
||||
frontmatter: Record<string, string>;
|
||||
body: string;
|
||||
}
|
||||
|
||||
function stripComment(line: string): string {
|
||||
const idx = line.indexOf("#");
|
||||
// Only treat `#` as a comment when it appears at start-of-line or
|
||||
// after whitespace; otherwise the `#` is part of a value (it never
|
||||
// is in this repo, but it keeps the parser robust).
|
||||
if (idx === -1) return line;
|
||||
const before = line.slice(0, idx);
|
||||
if (/^\s*$/.test(before)) return before;
|
||||
return line;
|
||||
}
|
||||
|
||||
function deIndentBlock(block: string): string {
|
||||
const lines = block.split("\n");
|
||||
const indents = lines
|
||||
.filter((l) => l.trim() !== "")
|
||||
.map((l) => l.match(/^[ \t]*/)?.[0].length ?? 0);
|
||||
const min = indents.length === 0 ? 0 : Math.min(...indents);
|
||||
if (min === 0) return block.replace(/^\n+/, "");
|
||||
return lines
|
||||
.map((l) => (l.length >= min ? l.slice(min) : l))
|
||||
.join("\n")
|
||||
.replace(/^\n+/, "");
|
||||
}
|
||||
|
||||
export function parseFrontmatter(content: string): ParsedDoc {
|
||||
const normalized = content.replace(/\r\n/g, "\n");
|
||||
if (!normalized.startsWith("---")) {
|
||||
return { frontmatter: {}, body: normalized };
|
||||
}
|
||||
|
||||
// The closing fence is `\n---` (optionally followed by whitespace/eol).
|
||||
const endIndex = normalized.indexOf("\n---", 3);
|
||||
if (endIndex === -1) {
|
||||
return { frontmatter: {}, body: normalized };
|
||||
}
|
||||
|
||||
const block = normalized.slice(3, endIndex);
|
||||
const body = normalized.slice(endIndex + 4).replace(/^\s+/, "");
|
||||
|
||||
const frontmatter: Record<string, string> = {};
|
||||
let currentKey: string | null = null;
|
||||
let currentBlock: string[] | null = null;
|
||||
let currentIndent = -1;
|
||||
|
||||
const flush = () => {
|
||||
if (currentKey === null || currentBlock === null) return;
|
||||
frontmatter[currentKey] = deIndentBlock(currentBlock.join("\n"));
|
||||
currentKey = null;
|
||||
currentBlock = null;
|
||||
currentIndent = -1;
|
||||
};
|
||||
|
||||
for (const rawLine of block.split("\n")) {
|
||||
const line = stripComment(rawLine);
|
||||
if (line.trim() === "") {
|
||||
// Preserve blank lines inside an open block scalar, otherwise
|
||||
// ignore them between keys.
|
||||
if (currentBlock !== null) currentBlock.push("");
|
||||
continue;
|
||||
}
|
||||
|
||||
const indent = line.match(/^[ \t]*/)?.[0].length ?? 0;
|
||||
if (currentKey !== null && currentBlock !== null && indent > currentIndent) {
|
||||
currentBlock.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
flush();
|
||||
|
||||
const match = line.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
|
||||
if (!match) continue;
|
||||
const key = match[1];
|
||||
const raw = match[2].trim();
|
||||
|
||||
if (raw === "" || raw === ">" || raw === "|-" || raw === ">-" || raw === "|") {
|
||||
currentKey = key;
|
||||
currentBlock = [];
|
||||
currentIndent = indent;
|
||||
} else {
|
||||
const quoted =
|
||||
(raw.startsWith('"') && raw.endsWith('"')) ||
|
||||
(raw.startsWith("'") && raw.endsWith("'"));
|
||||
frontmatter[key] = quoted ? raw.slice(1, -1) : raw;
|
||||
}
|
||||
}
|
||||
|
||||
flush();
|
||||
|
||||
return { frontmatter, body };
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a `chain:` frontmatter value into individual step names.
|
||||
* Mirrors the upstream convention of ` -> ` as the delimiter and trims
|
||||
* whitespace from each step.
|
||||
*/
|
||||
export function parseChain(value: string | undefined): string[] {
|
||||
if (!value) return [];
|
||||
return value
|
||||
.split(" -> ")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
}
|
||||
Reference in New Issue
Block a user