From d1e79f3a9c91dc1e397dcdda0a94bf80db710de0 Mon Sep 17 00:00:00 2001 From: panda Date: Tue, 12 Nov 2024 17:49:59 +0800 Subject: [PATCH] core: fix icpc problem format import (#904) --- packages/hydrooj/src/model/problem.ts | 33 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 2c80800d9..818e318c3 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -42,12 +42,16 @@ function sortable(source: string, namespaces: Record) { function findOverrideContent(dir: string, base: string) { let files = fs.readdirSync(dir); if (files.includes(`${base}.md`)) return fs.readFileSync(path.join(dir, `${base}.md`), 'utf8'); + if (files.includes(`${base}.pdf`)) return `@[PDF](file://${base}.pdf)`; const languages = {}; - files = files.filter((i) => new RegExp(`^${base}_[a-zA-Z_]+\\.md$`).test(i)); + files = files.filter((i) => new RegExp(`^${base}(?:_|.)([a-zA-Z_]+)\\.(md|pdf)$`).test(i)); if (!files.length) return null; for (const file of files) { - const lang = file.slice(8, -3); - languages[lang] = fs.readFileSync(path.join(dir, file), 'utf8'); + const match = file.match(`^${base}(?:_|.)([a-zA-Z_]+)\\.(md|pdf)$`); + const lang = match[1]; + const ext = match[2]; + if (ext === 'pdf') languages[lang] = `@[PDF](file://${file})`; + else languages[lang] = fs.readFileSync(path.join(dir, file), 'utf8'); } return JSON.stringify(languages); } @@ -511,13 +515,16 @@ export class ProblemModel { } if (!await isValidPid(pid)) pid = undefined; } - const overrideContent = findOverrideContent(path.join(tmpdir, i), 'problem'); + let overrideContent = findOverrideContent(path.join(tmpdir, i), 'problem'); + overrideContent ||= findOverrideContent(path.join(tmpdir, i, 'statement'), 'problem'); + overrideContent ||= findOverrideContent(path.join(tmpdir, i, 'problem_statement'), 'problem'); if (pdoc.difficulty && !Number.isSafeInteger(pdoc.difficulty)) delete pdoc.difficulty; - if (typeof pdoc.title !== 'string') throw new ValidationError('title', null, 'Invalid title'); + const title = pdoc.title || (pdoc as any).name; + if (typeof title !== 'string') throw new ValidationError('title', null, 'Invalid title'); const allFiles = await getFiles( 'testdata', 'additional_file', // The following is from https://icpc.io/problem-package-format/spec/2023-07-draft.html - 'attachments', 'generators', 'include', 'data', 'statement', + 'attachments', 'generators', 'include', 'data', 'statement', 'problem_statement', ); const totalSize = allFiles.map((f) => fs.statSync(f[1]).size).reduce((a, b) => a + b, 0); if (allFiles.length > SystemModel.get('limit.problem_files')) throw new ValidationError('files', null, 'Too many files'); @@ -532,15 +539,19 @@ export class ProblemModel { // TODO: report this as a warning } } + if ((pdoc as any).limits) { + config.time = (pdoc as any).limits.time_limit; + config.memory = (pdoc as any).limits.memory; + } const docId = overridePid ? (await ProblemModel.edit(domainId, overridePid, { - title: pdoc.title.trim(), - content: overrideContent || pdoc.content.toString() || 'No content', + title: title.trim(), + content: overrideContent || pdoc.content?.toString() || 'No content', tag, difficulty: pdoc.difficulty, })).docId : await ProblemModel.add( - domainId, pid, pdoc.title.trim(), overrideContent || pdoc.content.toString() || 'No content', + domainId, pid, title.trim(), overrideContent || pdoc.content?.toString() || 'No content', operator || pdoc.owner, tag, { hidden: pdoc.hidden, difficulty: pdoc.difficulty }, ); // TODO delete unused file when updating pdoc @@ -559,7 +570,7 @@ export class ProblemModel { : null)?.(domainId, docId, file, path.join(loc, file)); } } - for (const [f, loc] of await getFiles('additional_file', 'attachments', 'statement')) { + for (const [f, loc] of await getFiles('additional_file', 'attachments', 'statement', 'problem_statement')) { if (!f.isFile()) continue; await ProblemModel.addAdditionalFile(domainId, docId, f.name, loc); } @@ -583,7 +594,7 @@ export class ProblemModel { await RecordModel.add(domainId, docId, operator, f.name.split('.')[1], await fs.readFile(loc, 'utf-8'), true); } if (configChanged) await ProblemModel.addTestdata(domainId, docId, 'config.yaml', yaml.dump(config)); - const message = `${overridePid ? 'Updated' : 'Imported'} problem ${pdoc.pid} (${pdoc.title})`; + const message = `${overridePid ? 'Updated' : 'Imported'} problem ${pdoc.pid || docId} (${title})`; (process.env.HYDRO_CLI ? logger.info : progress)?.(message); } catch (e) { (process.env.HYDRO_CLI ? logger.info : progress)?.(`Error importing problem ${i}: ${e.message}`);