// Minimaler YAML-Leser fuer unsere eigenen Konfigurationsdateien. // // Deckt genau das ab, was config/*.yaml benutzt: verschachtelte Maps ueber // Einrueckung, Skalare, Inline-Maps `{a: 1, b: 2}`, Listen aus Skalaren und // Blockskalare (`>` / `|`). Bewusst kein vollstaendiges YAML - sobald in S1 // echte Abhaengigkeiten dazukommen, wird das durch `yaml` aus npm ersetzt. function scalar(raw) { const s = raw.trim(); if (s === '' || s === '~' || s === 'null') return null; if (s === 'true') return true; if (s === 'false') return false; if (/^-?\d+$/.test(s)) return Number.parseInt(s, 10); if (/^-?\d*\.\d+$/.test(s)) return Number.parseFloat(s); if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) { return s.slice(1, -1); } return s; } function flowMap(raw) { const out = {}; for (const part of raw.slice(1, -1).split(',')) { if (!part.trim()) continue; const i = part.indexOf(':'); if (i === -1) continue; out[part.slice(0, i).trim()] = scalar(part.slice(i + 1)); } return out; } function stripComment(line) { let quote = null; for (let i = 0; i < line.length; i++) { const c = line[i]; if (quote) { if (c === quote) quote = null; } else if (c === '"' || c === "'") { quote = c; } else if (c === '#' && (i === 0 || /\s/.test(line[i - 1]))) { return line.slice(0, i); } } return line; } export function parseYaml(text) { const lines = text.split(/\r?\n/); const root = {}; // Stapel aus [Einrueckung, Zielobjekt] const stack = [[-1, root]]; let i = 0; while (i < lines.length) { const rawLine = lines[i]; const line = stripComment(rawLine); if (!line.trim()) { i++; continue; } const indent = line.length - line.trimStart().length; const body = line.trim(); while (stack.length > 1 && indent <= stack[stack.length - 1][0]) stack.pop(); const parent = stack[stack.length - 1][1]; // Listeneintrag if (body.startsWith('- ')) { const key = stack[stack.length - 1][2]; if (key && Array.isArray(parent[key])) parent[key].push(scalar(body.slice(2))); i++; continue; } const colon = body.indexOf(':'); if (colon === -1) { i++; continue; } const key = body.slice(0, colon).trim(); const rest = body.slice(colon + 1).trim(); if (rest === '') { // Verschachtelte Map, leere Liste oder leerer Wert - anhand der // naechsten inhaltstragenden Zeile entscheiden let j = i + 1; while (j < lines.length && !stripComment(lines[j]).trim()) j++; const next = j < lines.length ? stripComment(lines[j]) : ''; const nextIndent = next ? next.length - next.trimStart().length : -1; if (next && nextIndent > indent && next.trim().startsWith('- ')) { parent[key] = []; stack.push([indent, parent, key]); } else if (next && nextIndent > indent) { parent[key] = {}; stack.push([indent, parent[key]]); } else { parent[key] = null; } } else if (rest === '>' || rest === '|' || rest === '>-' || rest === '|-') { // Blockskalar: alle staerker eingerueckten Zeilen einsammeln const parts = []; let j = i + 1; for (; j < lines.length; j++) { if (!lines[j].trim()) { parts.push(''); continue; } const ind = lines[j].length - lines[j].trimStart().length; if (ind <= indent) break; parts.push(lines[j].trim()); } parent[key] = rest.startsWith('>') ? parts.join(' ').trim() : parts.join('\n'); i = j; continue; } else if (rest.startsWith('{')) { parent[key] = flowMap(rest); } else if (rest.startsWith('[')) { parent[key] = rest.slice(1, -1).split(',').map(scalar).filter((v) => v !== null); } else { parent[key] = scalar(rest); } i++; } return root; }