7baebebc09
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
4.6 KiB
JavaScript
116 lines
4.6 KiB
JavaScript
// 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 };
|
|
}
|