import { mkdir, writeFile } from 'node:fs/promises'; import { spawn } from 'node:child_process'; import path from 'node:path'; import { catalogFromCard, normaliseProgress, readJson, statusLabel, teamsMarkdown } from './lib/card_progress.mjs'; const root = path.resolve(process.cwd()); const card = await readJson(path.join(root, 'json/card_source.json')); const progressFile = path.resolve(process.env.CARD_PROGRESS_FILE ?? path.join(root, 'json/lesson_progress.json')); let savedProgress; try { savedProgress = await readJson(progressFile); } catch (error) { if (error.code !== 'ENOENT') throw error; savedProgress = {}; } const progress = normaliseProgress(card, savedProgress, { repository: process.env.CARD_SOURCE_REPOSITORY, revision: process.env.CARD_SOURCE_REVISION }); const reportDir = path.join(root, 'doc/session-reports'); const basename = `${card.card.id}-${progress.session.id}`; const markdownFile = path.join(reportDir, `${basename}.md`); const texFile = path.join(reportDir, `${basename}.tex`); const pdfFile = path.join(reportDir, `${basename}.pdf`); function tex(value) { return String(value ?? '') .replaceAll('\\', '\\textbackslash{}') .replaceAll('&', '\\&').replaceAll('%', '\\%').replaceAll('$', '\\$') .replaceAll('#', '\\#').replaceAll('_', '\\_').replaceAll('{', '\\{') .replaceAll('}', '\\}').replaceAll('~', '\\textasciitilde{}') .replaceAll('^', '\\textasciicircum{}'); } function reportTex() { const catalog = catalogFromCard(card); const rows = catalog.map(item => { const current = progress.items[item.id]; return `${tex(item.id)} & ${tex(statusLabel[current.status])} & ${tex(current.updated_at || '—')} & ${tex(item.title)} \\\\ \\hline`; }).join('\n'); const events = progress.events.length ? progress.events.map(event => `${tex(event.at)} & ${tex(event.item_id)} & ${tex(`${statusLabel[event.from]} → ${statusLabel[event.to]}`)} & ${tex(event.note || '—')} \\\\ \\hline`).join('\n') : `— & — & Brak zmian & — \\\\ \\hline`; return `\\documentclass[11pt,a4paper]{article} \\usepackage[utf8]{inputenc} \\usepackage[T1]{fontenc} \\usepackage[polish]{babel} \\usepackage[margin=18mm]{geometry} \\usepackage{longtable} \\usepackage{array} \\usepackage{booktabs} \\begin{document} \\section*{${tex(card.card.title)} — zapis zajęć} \\begin{tabular}{ll} Karta & ${tex(card.card.id)} (${tex(card.card.version)}) \\\\ Materiały & ${tex(progress.card.repository || 'nieuzupełniono')} \\\\ Rewizja & ${tex(progress.card.revision || 'nieuzupełniono')} \\\\ Sesja & ${tex(progress.session.id)} \\\\ Prowadzący & ${tex(progress.session.teacher || 'nieuzupełniono')} \\\\ Rozpoczęcie & ${tex(progress.session.started_at || 'brak')} \\\\ \\end{tabular} \\subsection*{Zakres i status} \\begin{longtable}{p{0.13\\textwidth}p{0.18\\textwidth}p{0.22\\textwidth}p{0.38\\textwidth}} \\toprule Element & Status & Ostatnia zmiana & Zakres \\\\ \\midrule ${rows} \\bottomrule \\end{longtable} \\subsection*{Historia zatwierdzeń} \\begin{longtable}{p{0.22\\textwidth}p{0.13\\textwidth}p{0.25\\textwidth}p{0.30\\textwidth}} \\toprule Czas & Element & Zmiana & Notatka \\\\ \\midrule ${events} \\bottomrule \\end{longtable} \\end{document} `; } function run(command, args, options) { return new Promise((resolve, reject) => { const child = spawn(command, args, options); child.on('error', reject); child.on('exit', code => code === 0 ? resolve() : reject(new Error(`${command} zakończył się kodem ${code}`))); }); } await mkdir(reportDir, { recursive: true }); await writeFile(markdownFile, teamsMarkdown(card, progress), 'utf8'); await writeFile(texFile, reportTex(), 'utf8'); console.log(`Markdown: ${path.relative(root, markdownFile)}`); console.log(`TeX: ${path.relative(root, texFile)}`); if (!process.argv.includes('--no-pdf')) { try { await run('latexmk', ['-pdf', '-interaction=nonstopmode', '-outdir=' + reportDir, texFile], { cwd: root, stdio: 'inherit' }); console.log(`PDF: ${path.relative(root, pdfFile)}`); } catch (error) { console.warn(`Nie utworzono PDF (${error.message}). Pliki Markdown i TeX są gotowe.`); } }