feat(L06): add control flow and program counter card
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user