import-hoj: add import support for HOJ (#680)

pull/675/head
panda 11 months ago committed by GitHub
parent c008639242
commit 65a503b3bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,142 @@
/* eslint-disable no-await-in-loop */
import os from 'os';
import path from 'path';
import {
AdmZip, buildContent, Context, fs, Handler, PERM,
ProblemConfigFile, ProblemModel, ValidationError, yaml,
} from 'hydrooj';
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-hoj');
fs.ensureDirSync(tmpdir);
class ImportHojHandler extends Handler {
async fromFile(domainId: string, zipfile: string) {
let zip: AdmZip;
try {
zip = new AdmZip(zipfile);
} catch (e) {
throw new ValidationError('zip', null, e.message);
}
const tmp = path.resolve(tmpdir, String.random(32));
await new Promise((resolve, reject) => {
zip.extractAllToAsync(tmp, true, (err) => (err ? reject(err) : resolve(null)));
});
let cnt = 0;
try {
const folders = await fs.readdir(tmp, { withFileTypes: true });
for (const { name: folder } of folders.filter((i) => i.isDirectory())) {
if (!fs.existsSync(path.join(tmp, `${folder}.json`))) continue;
const buf = await fs.readFile(path.join(tmp, `${folder}.json`));
const doc = JSON.parse(buf.toString());
const pdoc = doc.problem;
const content = {
description: pdoc.description,
input: pdoc.input,
output: pdoc.output,
samples: [],
hint: pdoc.hint,
source: pdoc.source,
};
if (pdoc.examples) {
const re = /<input>([\s\S]*?)<\/input><output>([\s\S]*?)<\/output>/g;
const examples = pdoc.examples.match(re).map((i) => {
const m = i.match(/<input>([\s\S]*?)<\/input><output>([\s\S]*?)<\/output>/);
return { input: m[1], output: m[2] };
});
content.samples = examples.map((sample) => ([sample.input, sample.output]));
}
const isValidPid = async (id: string) => {
if (!(/^[A-Za-z]+[0-9A-Za-z]*$/.test(id))) return false;
if (await ProblemModel.get(domainId, id)) return false;
return true;
};
if (!await isValidPid(pdoc.problemId)) pdoc.display_id = null;
const pid = await ProblemModel.add(
domainId, pdoc.display_id, pdoc.title, buildContent(content, 'markdown'),
this.user._id, doc.tags || [],
);
const config: ProblemConfigFile = {
time: `${pdoc.timeLimit}ms`,
memory: `${pdoc.memoryLimit}m`,
subtasks: [],
};
if (pdoc.isFileIO && pdoc.ioReadFileName && pdoc.ioWriteFileName) {
config.filename = pdoc.ioReadFileName.split('.')[0].trim();
}
const tasks = [];
for (const tc of doc.samples) {
tasks.push(ProblemModel.addTestdata(
domainId, pid, tc.input,
path.join(tmp, folder, tc.input),
));
tasks.push(ProblemModel.addTestdata(
domainId, pid, tc.output,
path.join(tmp, folder, tc.output),
));
config.subtasks.push({
...(tc.score ? { score: tc.score } : {}),
cases: [{
input: tc.input,
output: tc.output,
}],
});
}
if (pdoc.spjLanguage === 'C++') {
tasks.push(ProblemModel.addTestdata(
domainId, pid, 'checker.cc',
Buffer.from(pdoc.spjCode),
));
config.checker = 'checker.cc';
config.checker_type = 'testlib';
}
if (pdoc.userExtraFile) {
for (const file of Object.keys(pdoc.userExtraFile)) {
if (file === 'testlib.h') continue;
tasks.push(ProblemModel.addTestdata(
domainId, pid, file, Buffer.from(pdoc.userExtraFile[file]),
));
config.user_extra_files ||= [];
config.user_extra_files.push(file);
}
}
if (pdoc.judgeExtraFile) {
for (const file of Object.keys(pdoc.judgeExtraFile)) {
tasks.push(ProblemModel.addTestdata(
domainId, pid, file, Buffer.from(pdoc.judgeExtraFile[file]),
));
config.judge_extra_files ||= [];
config.judge_extra_files.push(file);
}
}
tasks.push(ProblemModel.addTestdata(domainId, pid, 'config.yaml', Buffer.from(yaml.dump(config))));
await Promise.all(tasks);
cnt++;
}
} finally {
await fs.remove(tmp);
}
if (!cnt) throw new ValidationError('zip', 'No problemset imported');
}
async get() {
this.response.body = { type: 'HOJ' };
this.response.template = 'problem_import.html';
}
async post({ domainId }) {
if (!this.request.files.file) throw new ValidationError('file');
const stat = await fs.stat(this.request.files.file.filepath);
if (stat.size > 128 * 1024 * 1024) throw new ValidationError('file', 'File too large');
await this.fromFile(domainId, this.request.files.file.filepath);
this.response.redirect = this.url('problem_main');
}
}
export const name = 'import-hoj';
export async function apply(ctx: Context) {
ctx.Route('problem_import_hoj', '/problem/import/hoj', ImportHojHandler, PERM.PERM_CREATE_PROBLEM);
ctx.inject('ProblemAdd', 'problem_import_hoj', { icon: 'copy', text: 'From HOJ Export' });
ctx.i18n.load('zh', {
'From HOJ Export': '从 HOJ 导入',
});
}

@ -0,0 +1,10 @@
{
"name": "@hydrooj/import-hoj",
"version": "0.0.1",
"description": "Import HOJ problem export",
"main": "index.ts",
"repository": "https://github.com/hydro-dev/Hydro.git",
"author": "panda <panda_dtdyy@outlook.com>",
"license": "AGPL-3.0-or-later",
"preferUnplugged": true
}
Loading…
Cancel
Save