UNPKG

oimp

Version:

A CLI tool for generating OI problem and packages

313 lines (298 loc) 12.5 kB
const path = require("path"); const fs = require("fs"); const { getTemplate, writeFileWithDir, writeYaml, updateChecklistStatus } = require("../utils"); const chalk = require("chalk"); const { rimraf } = require("rimraf"); const { mkdirp } = require("mkdirp"); const inquirer = require("inquirer"); module.exports = async function initCommand(problemName, options) { const problemDir = path.join(process.cwd(), problemName); const currentYear = new Date().getFullYear().toString(); // 检查目录是否存在 if (fs.existsSync(problemDir)) { if (options.force) { await rimraf(problemDir); } else { console.error( chalk.red( `Directory ${problemName} already exists. Use -f to overwrite.` ) ); process.exit(1); } } // 交互式询问题目信息 const answers = await inquirer.prompt([ { type: "input", name: "title", message: "请输入题目标题:", default: `${problemName}`, }, { type: "checkbox", name: "tags", message: "请选择题目标签:", choices: [ { name: "入门", value: "入门" }, { name: "普及-", value: "普及-" }, { name: "普及/提高-", value: "普及/提高-" }, { name: "普及+/提高", value: "普及+/提高" }, { name: "提高+/省选-", value: "提高+/省选-" }, { name: "省选/NOI-", value: "省选/NOI-" }, { name: "NOI/NOI+/CTSC", value: "NOI/NOI+/CTSC" }, new inquirer.Separator(), { name: "动态规划", value: "动态规划" }, { name: "图论", value: "图论" }, { name: "数据结构", value: "数据结构" }, { name: "数学", value: "数学" }, { name: "基础算法", value: "基础算法" }, { name: "字符串", value: "字符串" }, { name: "其他", value: "其他" }, ], default: ["入门"], }, { type: "input", name: "othertag", message: "其他分类标签(用空格或,分割多个):", default: "", }, { type: "input", name: "memory", message: "内存限制:", default: "256m", }, { type: "input", name: "time", message: "时间限制:", default: "1000ms", }, ]); // 选择checker类型 // const checkerType = await inquirer.prompt([ { type: "list", name: "selectedChecker", message: "请选择需要的Checker 类型:", choices: [ { name: "ncmp (标准比较器:逐字节对比用户输出和标准答案,但会忽略行末空格和文件末尾的多余换行)", value: "ncmp.cpp", }, { name: "wcmp (比较两个单词序列,按顺序逐个比较单词,若某对单词不同或序列长度不同,则判定为答案错误;否则判定为答案正确。)", value: "wcmp.cpp", }, { name: "fcmp (将文件按行作为字符串序列进行比较,若某行内容不同,则判定为答案错误;否则判定为答案正确。)", value: "fcmp.cpp", }, { name: "rcmp (比较两个双精度浮点数,允许最大绝对误差为 1.5E-6。若误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "rcmp.cpp", }, { name: "rcmp4 (比较两个双精度浮点数序列,允许最大绝对或相对误差为 1E-4。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "rcmp4.cpp", }, { name: "rcmp6 (比较两个双精度浮点数序列,允许最大绝对或相对误差为 1E-6。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "rcmp6.cpp", }, { name: "rcmp9 (比较两个双精度浮点数序列,允许最大绝对或相对误差为 1E-9。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "rcmp9.cpp", }, { name: "rncmp (比较两个双精度浮点数序列,允许最大绝对误差为 1.5E-5。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "rncmp.cpp", }, { name: "uncmp (比较两个无序的有符号长整型序列,会先排序再比较,若序列长度或元素不同,则判定为错误。)", value: "uncmp.cpp", }, { name: 'yesno (检查输入是否为 "YES" 或 "NO" (大小写不敏感),若输入不符合要求或与答案不一致,则判定为错误。)', value: "yesno.cpp", }, { name: "acmp (比较两个双精度浮点数,允许最大绝对误差为 1.5E-6。若误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "acmp.cpp", }, { name: "caseicmp (带有测试用例支持的单 int64 检查器,用于比较两个有序的 int64 序列,若序列长度不同或对应元素不同,则判定为错误。)", value: "caseicmp.cpp", }, { name: 'casencmp (用于比较输出和答案的格式为 "Case X: <number> <number> ..." 的情况,按测试用例逐行比较长整型序列。)', value: "casencmp.cpp", }, { name: 'casewcmp (用于比较输出和答案的格式为 "Case X: <token> <token> ..." 的情况,按测试用例逐行比较字符串序列。)', value: "casewcmp.cpp", }, { name: "dcmp (比较两个双精度浮点数,允许最大绝对或相对误差为 1E-6。若误差超过该值,判定为答案错误;否则判定为答案正确。)", value: "dcmp.cpp", }, { name: "hcmp (比较两个有符号的大整数,会先检查输入是否为有效的整数格式,若格式错误或数值不同,则判定为错误。)", value: "hcmp.cpp", }, { name: "icmp (比较两个有符号的整数,若两个整数不相等,则判定为答案错误;否则判定为答案正确。)", value: "icmp.cpp", }, { name: "lcmp (将文件按行拆分为单词序列进行比较,若某行的单词序列不同,则判定为答案错误;否则判定为答案正确。)", value: "lcmp.cpp", }, { name: "ncmp (比较两个有序的有符号长整型序列,会检查序列长度和对应元素是否相同,若不同则判定为错误。)", value: "ncmp.cpp", }, { name: 'nyesno (用于检查多个 "YES" 或 "NO" (大小写不敏感)的输入,会统计 "YES" 和 "NO" 的数量,若输入不符合要求或与答案不一致,则判定为错误。)', value: "nyesno.cpp", }, { name: "pointscmp (示例得分检查器,通过比较两个双精度浮点数的差值来给出得分。)", value: "pointscmp.cpp", }, { name: "pointsinfo (示例带有 points_info 的检查器,读取两个双精度浮点数,记录相关信息并退出。)", value: "pointsinfo.cpp", }, ], default: "", }, ]); // 创建目录结构 const dirs = ["testdata", "src", "sample", "solution", "additional_file", ".snapshots"]; for (const dir of dirs) { await mkdirp(path.join(problemDir, dir)); } // 初始化快照元数据和 mtime 文件 const snapshotDir = path.join(problemDir, '.snapshots'); const metaPath = path.join(snapshotDir, 'metadata.json'); let mtimePath = path.join(snapshotDir, 'file-mtime.json'); if (!fs.existsSync(metaPath)) fs.writeFileSync(metaPath, JSON.stringify({ snapshots: [] }, null, 2)); if (!fs.existsSync(mtimePath)) fs.writeFileSync(mtimePath, '{}'); // 生成题目状态文件 const problemStatus = { problem: { desc: "题面", status: "pending" }, std: { desc: "标准程序", status: "pending" }, generator: { desc: "数据生成器", status: "pending" }, validator: { desc: "数据验证器", status: "pending" }, testsample: { desc: "样例检测", status: "pending" }, data: { desc: "测评数据", status: "pending" }, check: { desc: "完整性检查", status: "pending" }, package: { desc: "打包", status: "pending" } }; try { const statusFileName = path.resolve(problemDir, "status.json",); console.log(statusFileName); fs.writeFileSync(statusFileName, JSON.stringify(problemStatus), "utf-8"); } catch (e) { console.error(chalk.red("写入题目状态文件失败:"), e.message); process.exit(1); } // 初始化后统一推进 checklist 状态 // 写入模板文件 const templates = { "src/std.cpp": "std.cpp", "problem_zh.md": "problem_zh.md", "src/generator.cpp": "generator.cpp", "src/validator.cpp": "validator.cpp", "src/testlib.h": "testlib.h", "solution/stdsol.md": "solution/stdsol.md", "src/checker.cpp": "checker.cpp", "src/std-error.cpp": "std-error.cpp", }; // 根据用户选择的checker if (checkerType.value) templates["src/checker.cpp"] = `checkers/${checkerType.value}`; else templates["src/checker.cpp"] = "checkers/wcmp.cpp"; // 根据用户选择过滤模板文件 answers.needGenerator = 1; answers.needSpecialJudge = 1; answers.needValidator = 1; const finalTemplates = Object.entries(templates).filter(([filePath]) => { if (filePath.includes("checker") && !answers.needSpecialJudge) return false; if (filePath.includes("generator") && !answers.needGenerator) return false; if (filePath.includes("validator") && !answers.needValidator) return false; return true; }); for (const [filePath, templateName] of finalTemplates) { writeFileWithDir( path.join(problemDir, filePath), getTemplate(templateName) ); } // 生成 problem.yaml answers.tags.filter((item) => item !== ""); if (answers.othertag) answers["othertag"].split(/[\s,,;]+/).forEach((el) => { answers.tags.push(el); }); const problemYaml = { tag: [...answers.tags], title: answers.title, }; if (answers.year) problemYaml.tag.push(answers.years); writeYaml(path.join(problemDir, "problem.yaml"), problemYaml); // 生成 testdata/config.yaml const configYaml = { memory: answers.memory, time: answers.time, }; writeYaml(path.join(problemDir, "testdata", "config.yaml"), configYaml); // 生成 sample 目录和空样例文件 const sampleDir = path.join(problemDir, 'sample'); if (!fs.existsSync(sampleDir)) fs.mkdirSync(sampleDir); const sampleIn = path.join(sampleDir, 'sample01.in'); const sampleAns = path.join(sampleDir, 'sample01.ans'); if (!fs.existsSync(sampleIn)) fs.writeFileSync(sampleIn, ''); if (!fs.existsSync(sampleAns)) fs.writeFileSync(sampleAns, ''); // 记录关键文件的 mtime // snapshotDir 已有定义 // mtimePath 只声明一次 const files = [ 'src/generator.cpp', 'src/validator.cpp', 'src/std.cpp', 'src/checker.cpp' ].map(f => path.join(problemDir, f)); let mtimeInfo = {}; for (const file of files) { if (fs.existsSync(file)) { mtimeInfo[path.basename(file)] = fs.statSync(file).mtimeMs; } } fs.writeFileSync(mtimePath, JSON.stringify(mtimeInfo, null, 2)); console.log(chalk.green('已更新 .snapshots/file-mtime.json')); console.log(chalk.green(`\n题目 "${problemName}" 初始化完成!`)); console.log(chalk.blue(`目录结构已创建在: ${problemDir}`)); console.log(chalk.yellow("接下来您可以:")); console.log("1. 编辑 problem_zh.md 编写题面"); console.log( "2. 编写 src/std.cpp 标准程序,可以在src中加入std开头的其他c++文件进行TLE、WA等的测评" ); if (answers.needSpecialJudge) { console.log("3. 编写 src/checker.cpp Special Judge程序"); } if (answers.needGenerator) { console.log("4. 编写 src/generator.cpp 数据生成器"); } if (answers.needValidator) { console.log("5. 编写 src/validator.cpp 输入验证器"); } console.log("6. 编写 solution/stdsol.md 题解"); console.log("7. 使用 oimp check 检查题目"); console.log("8. 使用 oimp package 打包题目,生成对应格式的zip压缩包"); };