// Parsing delle guide di studio (*.studio.md) del corso Biohacking Campus. // Le intestazioni H1 sono eterogenee per tipo di guida: ogni regex qui sotto // riconosce una famiglia e ne estrae titolo, relatore ed eventuale numero. import { marked } from 'marked'; const H1_RE = /^#\s+Lezione\s+([0-9]+)\s+—\s+(.*?)\s+\(([^)]*)\)\s+—\s+Guida/u; const ACCEL_RE = /^#\s+Accelerator\s+([0-9]+)\s+—\s+(.*?)\s+\(([^)]*)\)\s+—\s+Guida/u; const LIVE_RE = /^#\s+Lezione\s+live\s+([0-9]+)\s+—\s+(.*?)\s+\(([^)]*)\)\s+—\s+Guida/u; const SLIDE_RE = /^#\s+Slide\s+—\s+(.*?)\s+—\s+Guida/u; const SPEAKER_RE = /^#\s+Intervento\s+—\s+(.*?)\s+\(([^)]*)\)\s+—\s+Guida/u; const CONSUL_RE = /^#\s+Consulenza\s+—\s+(.*?)\s+\(([^)]*)\)\s+—\s+Guida/u; const MASTER_RE = /^#\s+Masterclass\s+—\s+(.*?)\s+\(([^)]*)\)\s+—\s+Guida/u; // I titoli Q&A sono eterogenei: cattura tutto fino a "— Guida allo studio". const QA_RE = /^#\s+(.+?)\s+—\s+Guida allo studio\s*$/u; const BOLD_RE = /\*\*(.+?)\*\*/u; const PARTE_RE = /\((Parte\s+\d+)\)/u; export function kindOf(slug) { if (slug.startsWith('accelerator')) return 'accelerator'; if (slug.startsWith('lezione-live')) return 'live'; if (slug.startsWith('slide-')) return 'slide'; if (slug.startsWith('speaker-')) return 'speaker'; if (slug.startsWith('consulenza-')) return 'consulenza'; if (slug.startsWith('masterclass-')) return 'masterclass'; if (slug.startsWith('qa-')) return 'qa'; return 'lezione'; } const LABEL_PREFIX = { accelerator: 'A', live: 'LV', slide: 'SL', speaker: 'SP', consulenza: 'C', masterclass: 'MC', qa: 'QA', }; // Le immagini nei sorgenti puntano a "assets/..." relativo alla cartella del // sito statico; nella sezione campus sono servite dalla route protetta. export function rewriteAssets(html) { return html.replaceAll('src="assets/', 'src="/campus/assets/'); } function extractConcetti(bodyLines) { const out = []; let inSection = false; for (const ln of bodyLines) { if (ln.startsWith('## ')) { inSection = ln.trim().toLowerCase().startsWith('## concetti chiave'); continue; } if (!inSection || !ln.trimStart().startsWith('- ')) continue; const m = BOLD_RE.exec(ln); if (m) out.push(m[1].trim().replace(/[.:;,]+$/, '')); } return out; } /** * Parsa una guida. * @param {string} raw contenuto del file .studio.md * @param {string} slug nome file senza estensione * @param {number} pos posizione della guida nel capitolo (1-based) * @returns {{slug,kind,num,titleFull,titleShort,relatore,parte,label,concetti,html}} */ export function parseGuide(raw, slug, pos) { const lines = raw.split('\n'); const h1 = lines[0]; const kind = kindOf(slug); let num = pos, titleFull, titleShort, relatore, parte = ''; let m; if (kind === 'qa' && (m = QA_RE.exec(h1))) { titleFull = titleShort = m[1].trim(); relatore = 'Q&A del corso'; } else if (kind === 'masterclass' && (m = MASTER_RE.exec(h1))) { titleFull = titleShort = m[1].trim(); relatore = m[2].trim(); } else if (kind === 'consulenza' && (m = CONSUL_RE.exec(h1))) { titleFull = titleShort = m[1].trim(); relatore = m[2].trim(); } else if (kind === 'speaker' && (m = SPEAKER_RE.exec(h1))) { titleFull = titleShort = m[1].trim(); relatore = m[2].trim(); } else if (kind === 'slide' && (m = SLIDE_RE.exec(h1))) { titleFull = titleShort = m[1].trim(); relatore = 'Slide ufficiali del corso'; } else if (kind === 'live' && (m = LIVE_RE.exec(h1))) { num = Number(m[1]); const date = m[2].trim(); titleFull = `Lezione live ${num} — ${date}`; titleShort = `Lezione live ${num}`; relatore = `${m[3]} · ${date}`; } else if (kind === 'accelerator' && (m = ACCEL_RE.exec(h1))) { num = Number(m[1]); const date = m[2].trim(); titleFull = `Accelerator ${num} — ${date}`; titleShort = `Accelerator ${num}`; relatore = `${m[3]} · ${date}`; } else if (kind === 'lezione' && (m = H1_RE.exec(h1))) { num = Number(m[1]); titleFull = m[2]; relatore = m[3]; parte = PARTE_RE.exec(titleFull)?.[1] ?? ''; titleShort = titleFull.replace(/\s*\(Parte\s+\d+\)/u, '').trim(); } else { throw new Error(`H1 non riconosciuto in ${slug}: ${JSON.stringify(h1)}`); } const bodyLines = lines.slice(1).filter((ln) => !ln.startsWith('> Sintesi di studio basata')); const html = rewriteAssets(marked.parse(bodyLines.join('\n').trim(), { async: false })); const label = kind === 'lezione' ? `L${num}${parte ? ` · ${parte}` : ''}` : `${LABEL_PREFIX[kind]}${num}`; return { slug, kind, num, titleFull, titleShort, relatore, parte, label, concetti: extractConcetti(bodyLines), html }; }