Files
abdulive/docker/lib/yaml.js
T
ZeasyAlexandClaude Opus 5 d09e2b945a Struktur, Doku und S0-Toolchain fuer das abdulive-Toolkit
Monorepo-Geruest fuer neun Streamer-Tools plus den Ingest-Daemon, der
unter sechs davon liegt. Enthaelt:

- docs/: Ueberblick, Architektur, Twitch-Integration, Datenmodell,
  Roadmap, Setup, Hebel-Analyse und den MVP-Plan "Clip-Knopf"
- docs/projects/: je eine Spezifikation pro Tool
- docs/decisions/: vier ADRs (TS-Monorepo, SQLite, EventSub/IRC, Docker)
- docker/: Image mit ffmpeg und whisper.cpp, abhaengigkeitsfreier
  Entrypoint mit check-env, fetch-model und calibrate
- config/layout.yaml: aus einem VOD-Screenshot eingemessener
  Facecam-Ausschnitt (440x330 oben links, Chat-Overlay ab x=1270)

Noch kein Anwendungscode - Scheibe S0 aus docs/07-mvp-plan.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
2026-09-09 08:56:09 +02:00

119 lines
3.8 KiB
JavaScript

// 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;
}