oicontest
Version:
OI Contest Management Tool
160 lines (159 loc) • 8.07 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.exportExamCommand = void 0;
const commander_1 = require("commander");
const config_1 = require("../lib/config");
const chalk_1 = __importDefault(require("chalk"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const archiver_1 = __importDefault(require("archiver"));
// 工具函数:递归复制目录
function copyDir(src, dest) {
return __awaiter(this, void 0, void 0, function* () {
if (yield fs_extra_1.default.pathExists(src)) {
yield fs_extra_1.default.ensureDir(dest);
yield fs_extra_1.default.copy(src, dest);
}
});
}
// 工具函数:处理html中的本地图片/链接为相对路径
function fixHtmlLinks(htmlPath, problemId) {
return __awaiter(this, void 0, void 0, function* () {
let html = yield fs_extra_1.default.readFile(htmlPath, 'utf-8');
// 替换绝对路径为 ./additional/problemid/xxx
html = html.replace(/(["'])((?:\/|[A-Za-z]:\\)[^"']*additional_file[\/\\][^"']+)/g, (match, quote, url) => {
// 只保留 additional_file 及后面的部分
const idx = url.indexOf('additional_file');
if (idx !== -1) {
const rel = `./additional/${problemId}/` + url.substring(idx + 'additional_file'.length + 1).replace(/\\/g, '/');
return quote + rel;
}
return match;
});
yield fs_extra_1.default.writeFile(htmlPath, html, 'utf-8');
});
}
exports.exportExamCommand = new commander_1.Command('exportexam')
.description('Export student.zip and teacher.zip for exam use')
.action(() => __awaiter(void 0, void 0, void 0, function* () {
const contestDir = process.cwd();
const config = yield (0, config_1.loadConfig)(contestDir);
const htmlDir = path_1.default.join(contestDir, 'html');
const pdfDir = path_1.default.join(contestDir, 'pdf');
const outputStudentZip = path_1.default.join(contestDir, 'student.zip');
const outputTeacherZip = path_1.default.join(contestDir, 'teacher.zip');
const tempStudentDir = path_1.default.join(contestDir, '__student_tmp__');
const tempTeacherDir = path_1.default.join(contestDir, '__teacher_tmp__');
try {
// 1. 检查 PDF/HTML 是否存在
let pdfFound = false;
for (const p of config.problems) {
const pdfPath1 = path_1.default.join(pdfDir, `${p.id}.pdf`);
const pdfPath2 = path_1.default.join(contestDir, `${p.id}.pdf`);
if ((yield fs_extra_1.default.pathExists(pdfPath1)) || (yield fs_extra_1.default.pathExists(pdfPath2))) {
pdfFound = true;
break;
}
}
if (!pdfFound) {
console.log(chalk_1.default.red('未找到任何题目的 PDF 文件。请先用 oicontest genhtml 生成 HTML 并打印 PDF 到 pdf/ 目录或当前目录。'));
return;
}
// 清理临时目录
yield fs_extra_1.default.remove(tempStudentDir);
yield fs_extra_1.default.remove(tempTeacherDir);
yield fs_extra_1.default.ensureDir(tempStudentDir);
yield fs_extra_1.default.ensureDir(tempTeacherDir);
// 2. 复制 additional_file
for (const p of config.problems) {
const addiSrc = path_1.default.join(contestDir, 'problem', p.id, 'additional_file');
const addiDest = path_1.default.join(tempStudentDir, 'additional', p.id);
yield copyDir(addiSrc, addiDest);
}
// 3. 复制 html
if (yield fs_extra_1.default.pathExists(htmlDir)) {
yield fs_extra_1.default.copy(htmlDir, path_1.default.join(tempStudentDir, 'html'));
// 修正 html 路径
for (const p of config.problems) {
const htmlPath = path_1.default.join(tempStudentDir, 'html', `${p.id}.html`);
if (yield fs_extra_1.default.pathExists(htmlPath)) {
yield fixHtmlLinks(htmlPath, p.id);
}
}
}
// 4. 复制 pdf
yield fs_extra_1.default.ensureDir(path_1.default.join(tempStudentDir, 'pdf'));
for (const p of config.problems) {
const pdfPath1 = path_1.default.join(pdfDir, `${p.id}.pdf`);
const pdfPath2 = path_1.default.join(contestDir, `${p.id}.pdf`);
let found = false;
if (yield fs_extra_1.default.pathExists(pdfPath1)) {
yield fs_extra_1.default.copy(pdfPath1, path_1.default.join(tempStudentDir, 'pdf', `${p.id}.pdf`));
found = true;
}
else if (yield fs_extra_1.default.pathExists(pdfPath2)) {
yield fs_extra_1.default.copy(pdfPath2, path_1.default.join(tempStudentDir, 'pdf', `${p.id}.pdf`));
found = true;
}
if (!found) {
console.log(chalk_1.default.yellow(`题目 ${p.id} 未找到 PDF 文件。`));
}
}
// 5. 打包 student.zip
yield zipDir(tempStudentDir, outputStudentZip);
console.log(chalk_1.default.green(`student.zip 已生成: ${outputStudentZip}`));
// 6. 生成 teacher.zip
// 先复制 student.zip 内容
yield fs_extra_1.default.copy(tempStudentDir, tempTeacherDir);
// 复制 solution 和 std.cpp
for (const p of config.problems) {
// solution
const solSrc = path_1.default.join(contestDir, 'problem', p.id, 'solution');
const solDest = path_1.default.join(tempTeacherDir, 'solution', p.id, 'solution');
yield copyDir(solSrc, solDest);
// std.cpp
const stdSrc = path_1.default.join(contestDir, 'problem', p.id, 'src', 'std.cpp');
const stdDest = path_1.default.join(tempTeacherDir, 'solution', p.id, 'std');
if (yield fs_extra_1.default.pathExists(stdSrc)) {
yield fs_extra_1.default.ensureDir(stdDest);
yield fs_extra_1.default.copy(stdSrc, path_1.default.join(stdDest, 'std.cpp'));
}
}
yield zipDir(tempTeacherDir, outputTeacherZip);
console.log(chalk_1.default.green(`teacher.zip 已生成: ${outputTeacherZip}`));
// 清理临时目录
yield fs_extra_1.default.remove(tempStudentDir);
yield fs_extra_1.default.remove(tempTeacherDir);
}
catch (e) {
console.error(chalk_1.default.red('导出失败:'), e.message);
yield fs_extra_1.default.remove(tempStudentDir);
yield fs_extra_1.default.remove(tempTeacherDir);
}
}));
// 工具函数:zip 目录
function zipDir(srcDir, zipPath) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const output = fs_extra_1.default.createWriteStream(zipPath);
const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
output.on('close', () => resolve());
archive.on('error', err => reject(err));
archive.pipe(output);
archive.directory(srcDir, false);
archive.finalize();
});
});
}