File size: 2,795 Bytes
5448d8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**

 * Minimal YAML frontmatter reader/writer for idea files.

 * Supports: scalars (string/number/bool), nested object (one level), inline arrays [a, b].

 * Not a general YAML lib — kept simple intentionally.

 */

const DELIM = "---";

function parseScalar(raw) {
  const s = raw.trim();
  if (s === "true") return true;
  if (s === "false") return false;
  if (s === "null") return null;
  if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
  if (s.startsWith("[") && s.endsWith("]")) {
    const inner = s.slice(1, -1).trim();
    if (!inner) return [];
    return inner.split(",").map((x) => parseScalar(x.trim()));
  }
  if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
    return s.slice(1, -1);
  }
  return s;
}

export function parseFrontmatter(text) {
  if (typeof text !== "string") return null;
  if (!text.startsWith(`${DELIM}\n`)) return null;
  const rest = text.slice(DELIM.length + 1);
  const closeIdx = rest.indexOf(`\n${DELIM}`);
  if (closeIdx < 0) return null;
  const body = rest.slice(0, closeIdx);

  const out = {};
  let currentNested = null;

  for (const line of body.split("\n")) {
    if (!line.trim()) continue;
    if (line.startsWith("  ") && currentNested) {
      const m = line.match(/^\s{2}([\w_-]+):\s*(.*)$/);
      if (m) currentNested[m[1]] = parseScalar(m[2]);
      continue;
    }
    const m = line.match(/^([\w_-]+):\s*(.*)$/);
    if (!m) continue;
    const key = m[1];
    const val = m[2];
    if (val === "") {
      currentNested = {};
      out[key] = currentNested;
    } else {
      currentNested = null;
      out[key] = parseScalar(val);
    }
  }
  return out;
}

function serializeScalar(v) {
  if (v === null) return "null";
  if (typeof v === "boolean") return String(v);
  if (typeof v === "number") return String(v);
  if (Array.isArray(v)) return `[${v.map(serializeScalar).join(", ")}]`;
  return String(v);
}

export function serializeFrontmatter(meta, body) {
  const lines = [DELIM];
  for (const [k, v] of Object.entries(meta)) {
    if (v && typeof v === "object" && !Array.isArray(v)) {
      lines.push(`${k}:`);
      for (const [nk, nv] of Object.entries(v)) {
        lines.push(`  ${nk}: ${serializeScalar(nv)}`);
      }
    } else {
      lines.push(`${k}: ${serializeScalar(v)}`);
    }
  }
  lines.push(DELIM, "", body);
  return lines.join("\n");
}

export function stripFrontmatter(text) {
  if (typeof text !== "string") return text;
  if (!text.startsWith(`${DELIM}\n`)) return text;
  const rest = text.slice(DELIM.length + 1);
  const closeIdx = rest.indexOf(`\n${DELIM}`);
  if (closeIdx < 0) return text;
  return rest.slice(closeIdx + DELIM.length + 1).replace(/^\n+/, "");
}