From 377ef55d49a721d090b3e926f78334f93374cbbf Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 20:00:04 +0800 Subject: [PATCH] =?UTF-8?q?feats::=20=E7=BB=9F=E4=B8=80=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BD=8D=E7=BD=AE=20=E6=94=AF=E6=8C=81langs?= =?UTF-8?q?=E7=BB=A7=E6=89=BF=20=E6=94=AF=E6=8C=81=E5=BD=92=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hydrojudge/package.json | 5 +- packages/hydrojudge/setting.yaml | 100 +---- packages/hydrojudge/src/check.ts | 4 +- packages/hydrojudge/src/compile.ts | 30 +- packages/hydrojudge/src/hosts/hydro.ts | 25 +- packages/hydrojudge/src/hosts/vj4.ts | 420 +----------------- packages/hydrojudge/src/judge/default.ts | 16 +- packages/hydrojudge/src/judge/hack.ts | 7 +- packages/hydrojudge/src/judge/interactive.ts | 15 +- packages/hydrojudge/src/judge/run.ts | 2 +- packages/hydrojudge/src/service.ts | 7 + packages/hydrooj/locales/zh.yaml | 4 +- packages/hydrooj/package.json | 4 +- packages/hydrooj/setting.yaml | 98 ++++ packages/hydrooj/src/handler/judge.ts | 17 +- packages/hydrooj/src/model/builtin.ts | 18 - packages/hydrooj/src/model/setting.ts | 24 +- packages/hydrooj/src/model/system.ts | 7 + .../scratchpad/ScratchpadEditorContainer.jsx | 2 +- .../scratchpad/ScratchpadToolbarContainer.jsx | 4 +- packages/ui-default/handler.js | 10 +- packages/ui-default/locales/zh.yaml | 1 - packages/ui-default/package.json | 4 +- packages/ui-default/setting.yaml | 38 -- .../ui-default/templates/record_detail.html | 2 +- packages/utils/lib/lang.ts | 45 ++ packages/utils/package.json | 2 +- 27 files changed, 253 insertions(+), 658 deletions(-) create mode 100644 packages/hydrooj/setting.yaml create mode 100644 packages/utils/lib/lang.ts diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 71a027912..252f2e1c4 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,13 +1,12 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.5.21", + "version": "2.6.0", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", "dependencies": { - "@hydrooj/utils": "^1.0.9", - "adm-zip": "^0.5.5", + "@hydrooj/utils": "^1.0.14", "axios": "^0.21.1", "bson": "^4.3.0", "fs-extra": "^9.1.0", diff --git a/packages/hydrojudge/setting.yaml b/packages/hydrojudge/setting.yaml index 6fede66ee..e549c01a7 100644 --- a/packages/hydrojudge/setting.yaml +++ b/packages/hydrojudge/setting.yaml @@ -51,102 +51,4 @@ disable: type: boolean name: disable desc: Disable builtin judge - default: false -langs: - type: textarea - name: langs - desc: Language file settings - default: | - ccWithoutO2: # Do not remove this entry - type: compiler - compile: /usr/bin/g++ -Wall -o ${name} foo.cc - code_file: foo.cc - execute: ${dir}/${name} - c: - type: compiler - compile: /usr/bin/gcc -O2 -Wall -std=c99 -o ${name} foo.c -lm - code_file: foo.c - execute: ${dir}/${name} - cc: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc98: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++98 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc11: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc17: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++17 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - pas: - type: compiler - compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas - code_file: foo.pas - execute: ${dir}/${name} - java: - type: compiler - compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java - code_file: Main.java - target: Main.class - execute: /usr/bin/java Main - time_limit_rate: 2 - py: - type: compiler - compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python ${name} - py2: - type: compiler - compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python ${name} - py3: - type: compiler - compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python3 ${name} - php: - type: interpreter - code_file: foo.php - execute: /usr/bin/php foo.php - rs: - type: compiler - compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs - code_file: foo.rs - execute: ${dir}/${name} - hs: - type: compiler - compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs - code_file: foo.hs - execute: ${dir}/${name} - js: - type: interpreter - code_file: foo.js - execute: /usr/bin/jsc ${dir}/foo.js - go: - type: compiler - compile: /usr/bin/go build -o ${name} foo.go - code_file: foo.go - execute: ${dir}/${name} - rb: - type: interpreter - code_file: foo.rb - execute: /usr/bin/ruby foo.rb - cs: - type: compiler - compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs - code_file: foo.cs - execute: /usr/bin/mono ${name} - jl: - type: interpreter - code_file: foo.jl - execute: /usr/bin/julia ${dir}/foo.jl + default: false \ No newline at end of file diff --git a/packages/hydrojudge/src/check.ts b/packages/hydrojudge/src/check.ts index 6085cba5a..85b8b6573 100644 --- a/packages/hydrojudge/src/check.ts +++ b/packages/hydrojudge/src/check.ts @@ -24,10 +24,10 @@ export async function check(config): Promise<[number, number, string]> { return [status, score, message]; } -export async function compileChecker(checkerType: string, checker: string, copyIn: any) { +export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: any) { if (!checkers[checkerType]) throw new SystemError('Unknown checker type {0}.', [checkerType]); if (checkerType === 'testlib') copyIn['testlib.h'] = { src: testlibSrc }; const file = await fs.readFile(checker); // TODO cache compiled checker - return await compile(parseFilename(checker).split('.')[1], file.toString(), 'checker', copyIn); + return await compile(getLang(parseFilename(checker).split('.')[1]), file.toString(), 'checker', copyIn); } diff --git a/packages/hydrojudge/src/compile.ts b/packages/hydrojudge/src/compile.ts index ea2d644f2..cd53fe7ed 100644 --- a/packages/hydrojudge/src/compile.ts +++ b/packages/hydrojudge/src/compile.ts @@ -1,25 +1,21 @@ -import yaml from 'js-yaml'; +import { LangConfig } from '@hydrooj/utils/lib/lang'; import * as STATUS from './status'; import { run, del } from './sandbox'; -import { CompileError, SystemError } from './error'; +import { CompileError } from './error'; import { compilerText } from './utils'; import { Execute } from './interface'; -import { getConfig } from './config'; export = async function compile( - lang: string, code: string, target: string, copyIn: any, next?: Function, + lang: LangConfig, code: string, target: string, copyIn: any, next?: Function, ): Promise { - const LANGS = yaml.load(getConfig('langs')); - if (!LANGS[lang]) throw new SystemError('Unsupported language {0}.', [lang]); - const info = LANGS[lang]; - target = info.target || target; + target = lang.target || target; const f = {}; - if (info.type === 'compiler') { - copyIn[info.code_file] = { content: code }; + if (lang.compile) { + copyIn[lang.code_file] = { content: code }; const { status, stdout, stderr, fileIds, } = await run( - info.compile.replace(/\$\{name\}/g, target), + lang.compile.replace(/\$\{name\}/g, target), { copyIn, copyOutCached: [target] }, ); if (status !== STATUS.STATUS_ACCEPTED) throw new CompileError({ status, stdout, stderr }); @@ -27,13 +23,11 @@ export = async function compile( if (next) next({ compiler_text: compilerText(stdout, stderr) }); f[target] = { fileId: fileIds[target] }; return { - execute: info.execute, copyIn: f, clean: () => del(fileIds[target]), time: info.time || 1, - }; - } if (info.type === 'interpreter') { - f[target] = { content: code }; - return { - execute: info.execute, copyIn: f, clean: () => Promise.resolve(null), time: info.time || 1, + execute: lang.execute, copyIn: f, clean: () => del(fileIds[target]), time: lang.time_limit_rate || 1, }; } - throw new SystemError('Unknown language type.'); + f[target] = { content: code }; + return { + execute: lang.execute, copyIn: f, clean: () => Promise.resolve(null), time: lang.time_limit_rate || 1, + }; }; diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index f9e87de13..734eb26b0 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -6,6 +6,7 @@ import WebSocket from 'ws'; import { argv } from 'yargs'; import { noop } from 'lodash'; import { ObjectID } from 'bson'; +import { LangConfig } from '@hydrooj/utils/lib/lang'; import * as tmpfs from '../tmpfs'; import log from '../log'; import { compilerText, Queue } from '../utils'; @@ -37,6 +38,7 @@ class JudgeTask { config: any; nextId = 1; nextWaiting = []; + getLang: (name: string) => LangConfig; constructor(session: Hydro, request, ws: WebSocket) { this.stat = {}; @@ -45,6 +47,7 @@ class JudgeTask { this.host = session.config.host; this.request = request; this.ws = ws; + this.getLang = session.getLang; } async handle() { @@ -160,10 +163,9 @@ class JudgeTask { export default class Hydro { config: any; - axios: any; - ws: WebSocket; + language: Record; constructor(config) { this.config = config; @@ -172,6 +174,7 @@ export default class Hydro { this.config.last_update_at = this.config.last_update_at || 0; if (!this.config.server_url.startsWith('http')) this.config.server_url = `http://${this.config.server_url}`; if (!this.config.server_url.endsWith('/')) this.config.server_url = `${this.config.server_url}/`; + this.getLang = this.getLang.bind(this); } async init() { @@ -224,6 +227,11 @@ export default class Hydro { return filePath; } + getLang(name: string) { + if (this.language[name]) return this.language[name]; + throw new SystemError('Unsupported language {0}', [name]); + } + async consume(queue: Queue) { log.info('正在连接 %sjudge/conn', this.config.server_url); const res = await this.axios.get('judge/conn/info'); @@ -238,19 +246,16 @@ export default class Hydro { }); this.ws.on('message', (data) => { const request = JSON.parse(data.toString()); + if (request.language) this.language = request.language; if (request.task) queue.push(new JudgeTask(this, request.task, this.ws)); }); this.ws.on('close', (data, reason) => { log.warn(`[${this.config.host}] Websocket 断开:`, data, reason); - setTimeout(() => { - this.retry(queue); - }, 30000); + setTimeout(() => this.retry(queue), 30000); }); this.ws.on('error', (e) => { log.error(`[${this.config.host}] Websocket 错误:`, e); - setTimeout(() => { - this.retry(queue); - }, 30000); + setTimeout(() => this.retry(queue), 30000); }); await new Promise((resolve) => { this.ws.once('open', async () => { @@ -327,9 +332,7 @@ export default class Hydro { async retry(queue: Queue) { this.consume(queue).catch(() => { - setTimeout(() => { - this.retry(queue); - }, 30000); + setTimeout(() => this.retry(queue), 30000); }); } } diff --git a/packages/hydrojudge/src/hosts/vj4.ts b/packages/hydrojudge/src/hosts/vj4.ts index c770f1980..c82a18b6f 100644 --- a/packages/hydrojudge/src/hosts/vj4.ts +++ b/packages/hydrojudge/src/hosts/vj4.ts @@ -1,419 +1,7 @@ -/* eslint-disable no-await-in-loop */ -import path from 'path'; -import axios from 'axios'; -import AdmZip from 'adm-zip'; -import fs from 'fs-extra'; -import WebSocket from 'ws'; -import { noop } from 'lodash'; -import * as tmpfs from '../tmpfs'; -import log from '../log'; -import { compilerText, Queue } from '../utils'; -import { getConfig } from '../config'; -import { FormatError, CompileError, SystemError } from '../error'; -import { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } from '../status'; -import readCases from '../cases'; -import judge from '../judge'; - -class JudgeTask { - stat: Record; - session: any; - host: string; - request: any; - ws: WebSocket; - tag: any; - type: any; - domain_id: string; - pid: string; - rid: string; - lang: string; - code: string; - tmpdir: string; - clean: Function[]; - folder: string; - config: any; - nextId = 1; - nextWaiting = []; - - constructor(session: VJ4, request, ws: WebSocket) { - this.stat = {}; - this.stat.receive = new Date(); - this.session = session; - this.host = session.config.host; - this.request = request; - this.ws = ws; - } - - async handle() { - this.next = this.next.bind(this); - this.end = this.end.bind(this); - this.stat.handle = new Date(); - this.tag = this.request.tag; - this.type = this.request.type; - this.domain_id = this.request.domain_id; - this.pid = this.request.pid; - this.rid = this.request.rid; - this.lang = this.request.lang; - this.code = this.request.code; - this.tmpdir = path.resolve(getConfig('tmp_dir'), 'tmp', this.host, this.rid); - this.clean = []; - fs.ensureDirSync(this.tmpdir); - tmpfs.mount(this.tmpdir, '512m'); - log.info('Submission: %s/%s/%s pid=%s', this.host, this.domain_id, this.rid, this.pid); - try { - if (this.type === 0) await this.doSubmission(); - else if (this.type === 1) await this.doPretest(); - else throw new SystemError(`Unsupported type: ${this.type}`); - } catch (e) { - if (e instanceof CompileError) { - this.next({ compiler_text: compilerText(e.stdout, e.stderr) }); - this.end({ - status: STATUS_COMPILE_ERROR, score: 0, time_ms: 0, memory_kb: 0, - }); - } else if (e instanceof FormatError) { - this.next({ judge_text: '题目配置出现错误。请联系题目上传者。' }); - this.next({ judge_text: `${e.message}\n${JSON.stringify(e.params)}` }); - this.end({ - status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0, - }); - } else { - log.error(e); - this.next({ judge_text: `${e.message}\n${e.stack}\n${JSON.stringify(e.params)}` }); - this.end({ - status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0, - }); - } - } - for (const clean of this.clean) await clean().catch(noop); - tmpfs.umount(this.tmpdir); - fs.removeSync(this.tmpdir); - } - - async doSubmission() { - this.stat.cache_start = new Date(); - this.folder = await this.session.cacheOpen(this.domain_id, this.pid, this.next); - this.stat.read_cases = new Date(); - this.config = await readCases( - this.folder, - { detail: this.session.config.detail }, - { next: this.next }, - ); - this.stat.judge = new Date(); - await judge[this.config.type || 'default'].judge(this); - } - - async doPretest() { - this.folder = path.resolve(this.tmpdir, 'data'); - await this.session.recordPretestData(this.rid, this.folder); - this.config = await readCases( - this.folder, - { detail: this.session.config.detail }, - { next: this.next }, - ); - await judge[this.config.type || 'default'].judge(this); - } - - next(data, id?: number) { - if (data.message !== undefined) { - data.judge_text = data.message; - delete data.message; - } - if (data.judge_text && typeof data.judge_text !== 'string') { - data.judge_text = data.judge_text.message.format(data.judge_text.params); - } - if (data.case?.message !== undefined) { - data.case.judge_text = data.case.message; - delete data.case.message; - } - if (data.case?.judge_text && typeof data.case?.judge_text !== 'string') { - data.case.judge_text = data.case.judge_text.message.format(data.case.judge_text.params); - } - data.key = 'next'; - data.tag = this.tag; - if (id) { - if (id === this.nextId) { - this.ws.send(JSON.stringify(data)); - this.nextId++; - let t = true; - while (t) { - t = false; - for (const i in this.nextWaiting) { - if (this.nextId === this.nextWaiting[i].id) { - this.ws.send(JSON.stringify(this.nextWaiting[i].data)); - this.nextId++; - this.nextWaiting.splice(+i, 1); - t = true; - } - } - } - } else this.nextWaiting.push({ data, id }); - } else this.ws.send(JSON.stringify(data)); - } - - end(data) { - this.ws.send(JSON.stringify({ - key: 'next', - tag: this.tag, - judge_text: '自豪地采用 HydroJudge(https://github.com/hydro-dev/Hydro) 进行评测。', - })); - if (data.message !== undefined) { - data.judge_text = data.message; - delete data.message; - } - data.key = 'end'; - data.tag = this.tag; - log.info('Status=%d Score=%s Time=%dms Memory=%dkb', data.status, data.score, data.time_ms, data.memory_kb); - this.ws.send(JSON.stringify(data)); - } -} - export default class VJ4 { - config: any; - axios: any; - ws: WebSocket; - - constructor(config) { - this.config = config; - this.config.detail = this.config.detail ?? true; - this.config.cookie = this.config.cookie || ''; - this.config.last_update_at = this.config.last_update_at || 0; - if (!this.config.server_url.startsWith('http')) this.config.server_url = `http://${this.config.server_url}`; - if (!this.config.server_url.endsWith('/')) this.config.server_url = `${this.config.server_url}/`; - } - - async init() { - await this.setCookie(this.config.cookie || ''); - await this.ensureLogin(); - setInterval(() => { this.axios.get('judge/noop'); }, 30000000); - } - - async problemDataVersion(domainId: string, pid: string, retry = 3) { - let location: string; - let err: Error; - await this.ensureLogin(); - try { - await this.axios.get(`d/${domainId}/p/${pid}/data`, { maxRedirects: 0 }); - } catch (res) { - res.response = res.response || {}; - if (res.response.status === 302) { - location = res.response.headers.location; - if (location.includes('/fs/')) return location; - } else if (res.response.status === 404) { - throw new FormatError(`没有找到测试数据 ${domainId}/${pid}`); - } else { - if (retry) return await this.problemDataVersion(domainId, pid, retry - 1); - res.config = res.request = null; - err = res; - log.error('%o', err); - } - } - if (!location) return 'unknown'; - try { - await this.axios.get(location, { maxRedirects: 0 }); - } catch (res) { - res.response = res.response || {}; - if (res.response.status === 302) return res.response.headers.location; - if (res.response.status === 404) throw new FormatError(`没有找到测试数据 ${domainId}/${pid}`); - else { - if (retry) return await this.problemDataVersion(domainId, pid, retry - 1); - res.config = res.request = null; - err = res; - log.error(err); - } - } - return 'unknown'; - } - - async problemData(domainId: string, pid: string, savePath: string, retry = 3, next?) { - log.info(`Getting problem data: ${this.config.host}/${domainId}/${pid}`); - await this.ensureLogin(); - if (next) next({ judge_text: '正在同步测试数据,请稍后' }); - const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${domainId}_${pid}`); - try { - const res = await this.axios.get( - `${this.config.server_url}d/${domainId}/p/${pid}/data`, - { responseType: 'stream' }, - ); - const w = fs.createWriteStream(tmpFilePath); - res.data.pipe(w); - await new Promise((resolve, reject) => { - w.on('finish', resolve); - w.on('error', reject); - }); - fs.ensureDirSync(path.dirname(savePath)); - const zip = new AdmZip(tmpFilePath); - const entries = zip.getEntries(); - if (entries.length > 512) throw new FormatError('Too many files'); - await new Promise((resolve, reject) => { - zip.extractAllToAsync(savePath, true, (e) => { - if (e) reject(e); - else resolve(null); - }); - }); - await fs.unlink(tmpFilePath); - await this.processData(savePath).catch(noop); - } catch (e) { - if (retry) await this.problemData(domainId, pid, savePath, retry - 1); - else throw e; - } - return savePath; - } - - async recordPretestData(rid: string, savePath: string) { - log.info(`Getting pretest data: ${this.config.host}/${rid}`); - const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${rid}`); - await this.ensureLogin(); - const res = await this.axios.get(`records/${rid}/data`, { responseType: 'stream' }); - const w = fs.createWriteStream(tmpFilePath); - res.data.pipe(w); - await new Promise((resolve, reject) => { - w.on('finish', resolve); - w.on('error', reject); - }); - fs.ensureDirSync(path.dirname(savePath)); - const zip = new AdmZip(tmpFilePath); - const entries = zip.getEntries(); - if (entries.length > 512) throw new FormatError('Too many files'); - await new Promise((resolve, reject) => { - zip.extractAllToAsync(savePath, true, (e) => { - if (e) reject(e); - else resolve(null); - }); - }); - await fs.unlink(tmpFilePath); - await this.processData(savePath); - return savePath; - } - - async consume(queue: Queue) { - log.info('正在连接 %sjudge/consume-conn', this.config.server_url); - const res = await this.axios.get('judge/consume-conn/info'); - this.ws = new WebSocket(`${this.config.server_url.replace(/^http/i, 'ws')}judge/consume-conn/websocket?t=${res.data.entropy}`, { - headers: { cookie: this.config.cookie }, - }); - this.ws.on('message', (data) => { - const request = JSON.parse(data.toString()); - if (!request.event) queue.push(new JudgeTask(this, request, this.ws)); - }); - this.ws.on('close', (data, reason) => { - log.warn(`[${this.config.host}] Websocket 断开:`, data, reason); - setTimeout(() => { - this.retry(queue); - }, 30000); - }); - this.ws.on('error', (e) => { - log.error(`[${this.config.host}] Websocket 错误:`, e); - setTimeout(() => { - this.retry(queue); - }, 30000); - }); - await new Promise((resolve) => { - this.ws.once('open', () => { resolve(null); }); - }); - log.info(`[${this.config.host}] 已连接`); - } - - async setCookie(cookie: string) { - this.config.cookie = cookie; - this.axios = axios.create({ - baseURL: this.config.server_url, - timeout: 30000, - headers: { - accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', - cookie: this.config.cookie, - }, - transformRequest: [ - (data) => { - let ret = ''; - for (const it in data) { - ret += `${encodeURIComponent(it)}=${encodeURIComponent(data[it])}&`; - } - return ret; - }, - ], - }); - } - - async login() { - log.info('[%s] Updating session', this.config.host); - const res = await this.axios.post('login', { - uname: this.config.uname, password: this.config.password, rememberme: 'on', - }); - await this.setCookie(res.headers['set-cookie'].join(';')); - } - - async ensureLogin() { - try { - await this.axios.get('judge/noop'); - } catch (e) { - await this.login(); - } - } - - async processData(folder: string) { // eslint-disable-line class-methods-use-this - let files = await fs.readdir(folder); - let ini = false; - if (files.length <= 2) { - if (files.length === 2) files.splice(files.indexOf('version'), 1); - const s = fs.statSync(path.resolve(folder, files[0])); - if (s.isDirectory()) folder = path.resolve(folder, files[0]); - } - files = await fs.readdir(folder); - for (const i of files) { - if (i.toLowerCase() === 'config.ini') { - ini = true; - await fs.rename(`${folder}/${i}`, `${folder}/config.ini`); - break; - } - } - if (ini) { - for (const i of files) { - if (i.toLowerCase() === 'input') await fs.rename(`${folder}/${i}`, `${folder}/input`); - else if (i.toLowerCase() === 'output') await fs.rename(`${folder}/${i}`, `${folder}/output`); - } - files = await fs.readdir(`${folder}/input`); - for (const i of files) { - if (fs.statSync(`${folder}/input/${i}`).size > 64 * 1024 * 1024) { - await fs.rename(`${folder}/input/${i}`, `${folder}/input/${i.toLowerCase()}`); - } else { - const buffer = await fs.readFile(`${folder}/input/${i}`); - const data = buffer.toString().replace(/\r/g, ''); - await fs.unlink(`${folder}/input/${i}`); - await fs.writeFile(`${folder}/input/${i.toLowerCase()}`, data); - } - } - files = await fs.readdir(`${folder}/output`); - for (const i of files) await fs.rename(`${folder}/output/${i}`, `${folder}/output/${i.toLowerCase()}`); - } - } - - async cacheOpen(domainId: string, pid: string, next) { - const domainDir = path.join(getConfig('cache_dir'), this.config.host, domainId); - const filePath = path.join(domainDir, pid); - const version = await this.problemDataVersion(domainId, pid); - if (fs.existsSync(filePath)) { - let ver; - try { - ver = fs.readFileSync(path.join(filePath, 'version')).toString(); - } catch (e) { /* ignore */ } - if (version === ver) { - fs.writeFileSync(path.join(filePath, 'lastUsage'), new Date().getTime().toString()); - return filePath; - } - fs.removeSync(filePath); - } - fs.ensureDirSync(domainDir); - await this.problemData(domainId, pid, filePath, 3, next); - fs.writeFileSync(path.join(filePath, 'version'), version); - fs.writeFileSync(path.join(filePath, 'lastUsage'), new Date().getTime().toString()); - return filePath; - } - - async retry(queue: Queue) { - this.consume(queue).catch(() => { - setTimeout(() => { - this.retry(queue); - }, 30000); - }); + constructor() { + console.log('新版本不再支持 VJ4。请降级至 hydrojudge@2.5.21 或迁移到 Hydro。'); + console.log('详细文档: https://hydro.js.org/ '); + process.exit(1); } } diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index f6f638aff..782affd44 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -1,10 +1,9 @@ import Queue from 'p-queue'; import path from 'path'; -import yaml from 'js-yaml'; import fs from 'fs-extra'; import { argv } from 'yargs'; import * as STATUS from '../status'; -import { CompileError, SystemError } from '../error'; +import { SystemError } from '../error'; import { parseFilename } from '../utils'; import { run } from '../sandbox'; import compile from '../compile'; @@ -128,15 +127,7 @@ function judgeSubtask(subtask, sid) { export const judge = async (ctx) => { if (!ctx.config.subtasks.length) throw new SystemError('Problem data not found.'); - if (ctx.config.template) { - if (ctx.config.template[ctx.lang]) { - const tpl = ctx.config.template[ctx.lang]; - ctx.code = tpl[0] + ctx.code + tpl[1]; - } else throw new CompileError('Language not supported by provided templates'); - } - const LANGS = yaml.load(getConfig('langs')); - if (!LANGS[ctx.lang]) throw new SystemError('Unsupported language {0}.', [ctx.lang]); - const info = LANGS[ctx.lang]; + const info = ctx.getLang(ctx.lang); ctx.time_limit_rate = info.time_limit_rate || 1; ctx.next({ status: STATUS.STATUS_COMPILING }); [ctx.execute, ctx.checker] = await Promise.all([ @@ -145,7 +136,7 @@ export const judge = async (ctx) => { for (const file of ctx.config.user_extra_files) { copyIn[parseFilename(file)] = { src: file }; } - return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next); + return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next); })(), (async () => { if (!ctx.config.checker_type || ctx.config.checker_type === 'default') { @@ -156,6 +147,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } return await compileChecker( + ctx.getLang, ctx.config.checker_type, ctx.config.checker, copyIn, diff --git a/packages/hydrojudge/src/judge/hack.ts b/packages/hydrojudge/src/judge/hack.ts index 8e81be8dc..da0992c31 100644 --- a/packages/hydrojudge/src/judge/hack.ts +++ b/packages/hydrojudge/src/judge/hack.ts @@ -25,7 +25,7 @@ export const judge = async (ctx) => { for (const file of ctx.config.user_extra_files) { copyIn[parseFilename(file)] = { src: file }; } - return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next); + return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next); })(), // Validator (async () => { @@ -34,7 +34,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } const file = await fs.readFile(ctx.config.validator); - return await compile(parseFilename(ctx.config.validator).split('.')[1], file.toString(), 'validator', copyIn); + return await compile(ctx.getLang(parseFilename(ctx.config.validator).split('.')[1]), file.toString(), 'validator', copyIn); })(), // Std (async () => { @@ -43,7 +43,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } const file = await fs.readFile(ctx.config.std); - return await compile(parseFilename(ctx.config.std).split('.')[1], file.toString(), 'std', copyIn); + return await compile(ctx.getLang(parseFilename(ctx.config.std).split('.')[1]), file.toString(), 'std', copyIn); })(), // Checker (async () => { @@ -53,6 +53,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } return await compileChecker( + ctx.getLang, ctx.config.checker_type, ctx.config.checker, copyIn, diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 1794c89b1..0344ca1ff 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -1,7 +1,6 @@ import Queue from 'p-queue'; import fs from 'fs-extra'; import { argv } from 'yargs'; -import yaml from 'js-yaml'; import * as STATUS from '../status'; import { parse } from '../testlib'; import { findFileSync, parseFilename } from '../utils'; @@ -9,7 +8,6 @@ import { run } from '../sandbox'; import compile from '../compile'; import signals from '../signals'; import { getConfig } from '../config'; -import { SystemError } from '../error'; const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h'); const Score = { @@ -93,27 +91,22 @@ function judgeSubtask(subtask) { export const judge = async (ctx) => { ctx.next({ status: STATUS.STATUS_COMPILING }); - const LANGS = yaml.load(getConfig('langs')); - if (!LANGS[ctx.lang]) throw new SystemError('Unsupported language {0}.', [ctx.lang]); - const info = LANGS[ctx.lang]; - ctx.time_limit_rate = info.time_limit_rate || 1; + ctx.time_limit_rate = ctx.getLang(ctx.lang).time_limit_rate; [ctx.executeUser, ctx.executeInteractor] = await Promise.all([ (async () => { const copyIn = {}; for (const file of ctx.config.user_extra_files) { copyIn[parseFilename(file)] = { src: file }; } - return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next); + return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next); })(), (async () => { - const copyIn = { - 'testlib.h': testlibSrc, - }; + const copyIn = { 'testlib.h': testlibSrc }; for (const file of ctx.config.judge_extra_files) { copyIn[parseFilename(file)] = { src: file }; } return await compile( - parseFilename(ctx.config.interactor).split('.')[1], + ctx.getLang(parseFilename(ctx.config.interactor).split('.')[1]), fs.readFileSync(ctx.config.interactor).toString(), 'interactor', copyIn, diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index f80fd4f3e..9066f9a35 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -12,7 +12,7 @@ export const judge = async (ctx) => { ctx.stat.judge = new Date(); ctx.next({ status: STATUS.STATUS_COMPILING }); try { - ctx.execute = await compile(ctx.lang, ctx.code, 'code', {}, ctx.next); + ctx.execute = await compile(ctx.getLang(ctx.lang), ctx.code, 'code', {}, ctx.next); } catch (e) { if (e instanceof CompileError) { ctx.next({ diff --git a/packages/hydrojudge/src/service.ts b/packages/hydrojudge/src/service.ts index 11c9e77c0..0a1170a02 100644 --- a/packages/hydrojudge/src/service.ts +++ b/packages/hydrojudge/src/service.ts @@ -153,6 +153,11 @@ async function postInit() { }; } + function getLang(lang: string) { + if (system.lang[lang]) return system.lang[lang]; + throw new SystemError('Unsupported language {0}.', [lang]); + } + class JudgeTask { stat: any; request: any; @@ -170,11 +175,13 @@ async function postInit() { tmpdir: string; clean: any[]; folder: string; + getLang: (lang: string) => any; constructor(request) { this.stat = {}; this.stat.receive = new Date(); this.request = request; + this.getLang = getLang; logger.debug('%o', request); } diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index f3e599a27..cd6ccff2d 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -125,6 +125,7 @@ Click here to chat with me: 点击这里与我聊天 click to add: 点击添加 CLOSE: 关闭 Code blocks: 代码区块 +Code language settings: 编程语言设置 Code language: 代码语言 Code: 代码 collapse: 收缩 @@ -416,9 +417,6 @@ Judged At: 评测时间 Judged By: 评测机 Judging Queue: 评测队列 Keep current expiration: 保持当前过期设置 -LANG_TEXTS: 编译语言代号 -> 语言名称 -Language file settings: 评测机语言(langs.yaml) -Language Highlight ID: 编译语言代号 -> 高亮语言代号 Language: 语言 last active at: 最后活动于 last login at: 最后登录于 diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index a492d6072..83b1e80aa 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.24.22", + "version": "2.25.0", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", @@ -11,7 +11,7 @@ "node": ">=14" }, "dependencies": { - "@hydrooj/utils": "^1.0.13", + "@hydrooj/utils": "^1.0.14", "adm-zip": "^0.5.5", "ansi_up": "^5.0.1", "cookies": "^0.8.0", diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml new file mode 100644 index 000000000..dcfabbcf1 --- /dev/null +++ b/packages/hydrooj/setting.yaml @@ -0,0 +1,98 @@ +langs: + type: yaml + desc: Code language settings + default: + __hint__: + - All keys started with _ will be ignored + - Entries named like [foo], [foo].[bar] [foo].[baz] will be merged into one category. + - A entry named [foo].[bar] will inhert all options defined in entry [foo] if not overwritten + - code_file option defaults to foo.[name] + - highlight option defaults to [name] + - if highlight option was set, monaco option defaults to [highlight], otherwise defaults to [name] + - Compile step will be ignored if compile option was not set + - Key marked with a ? means the option is optional + - (name): + compile?: Compile Command + code_file?: Where to write code file when compiling + execute?: Command to execute the program + highlight?: PrismJS highlight mode + monaco?: Monaco editor mode + time_limit_rate: Time limit rate + target: Compiler output file + display: Display name + c: + compile: /usr/bin/gcc -Wall --std=c99 -o ${name} foo.c -lm + highlight: c astyle-c + monaco: cpp + display: C + cc: + compile: /usr/bin/g++ -Wall -std=c++11 -o ${name} foo.cc -lm + code_file: foo.cc + highlight: cpp astyle-c + monaco: cpp + display: C++ + cc.std98: + compile: /usr/bin/g++ -Wall -std=c++98 -o ${name} foo.cc -lm + display: C++ 98 + cc.std11: + display: C++ 11 + cc.std17: + compile: /usr/bin/g++ -Wall -std=c++17 -o ${name} foo.cc -lm + display: C++ 17 + pas: + compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas + highlight: pascal + display: Pascal + java: + compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java + code_file: Main.java + target: Main.class + execute: /usr/bin/java Main + time_limit_rate: 2 + highlight: java astyle-java + monaco: java + display: Java + py: + compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + code_file: foo.py + execute: /usr/bin/python ${name} + highlight: python + display: Python + py.py2: + display: Python 2 + py.py3: + compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + execute: /usr/bin/python3 ${name} + display: Python 3 + php: + execute: /usr/bin/php foo.php + display: PHP + rs: + compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs + highlight: rust + display: Rust + hs: + compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs + highlight: haskell + display: Haskell + js: + execute: /usr/bin/jsc ${dir}/foo.js + highlight: javascript + display: Javascript (JSC) + go: + compile: /usr/bin/go build -o ${name} foo.go + display: Golang + rb: + execute: /usr/bin/ruby foo.rb + highlight: ruby + display: Ruby + cs: + compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs + execute: /usr/bin/mono ${name} + highlight: csharp astyle-cs + monaco: csharp + display: Csharp + jl: + execute: /usr/bin/julia ${dir}/foo.jl + highlight: julia + display: Julia diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 7f15106a4..8962a182f 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -1,4 +1,5 @@ import { ObjectID } from 'mongodb'; +import { parseLang } from '@hydrooj/utils/lib/lang'; import { JudgeResultBody, Rdoc, TestCase } from '../interface'; import { sleep } from '../utils'; import { Logger } from '../logger'; @@ -9,6 +10,7 @@ import * as builtin from '../model/builtin'; import * as contest from '../model/contest'; import domain from '../model/domain'; import task from '../model/task'; +import * as system from '../model/system'; import * as bus from '../service/bus'; import { Route, Handler, Connection, ConnectionHandler, post, Types, @@ -119,12 +121,22 @@ class JudgeConnectionHandler extends ConnectionHandler { processing: any = null; closed = false; query: any = { type: 'judge' }; + ip: string; async prepare() { - logger.info('Judge daemon connected from ', this.request.ip); + const xff = system.get('server.xff'); + this.ip = xff ? this.request.headers[xff] || this.request.ip : this.request.ip; + logger.info('Judge daemon connected from ', this.ip); + this.send({ language: system.lang }); + this.sendLanguageConfig = this.sendLanguageConfig.bind(this); + bus.on('system/setting', this.sendLanguageConfig); this.newTask(); } + async sendLanguageConfig() { + this.send({ language: system.lang }); + } + async newTask() { if (this.processing) return; let t; @@ -157,7 +169,8 @@ class JudgeConnectionHandler extends ConnectionHandler { } async cleanup() { - logger.info('Judge daemon disconnected from ', this.request.ip); + logger.info('Judge daemon disconnected from ', this.ip); + bus.off('system/setting', this.sendLanguageConfig); if (this.processing) { await record.reset(this.processing.domainId, this.processing.rid, false); await task.add(this.processing); diff --git a/packages/hydrooj/src/model/builtin.ts b/packages/hydrooj/src/model/builtin.ts index e37da03ef..7f39e27ba 100644 --- a/packages/hydrooj/src/model/builtin.ts +++ b/packages/hydrooj/src/model/builtin.ts @@ -519,23 +519,6 @@ export const CATEGORIES = { ], }; -export const LANG_TEXTS = { - c: 'C', - cc: 'C++', - cs: 'C#', - pas: 'Pascal', - java: 'Java', - py: 'Python', - py3: 'Python 3', - php: 'PHP', - rs: 'Rust', - hs: 'Haskell', - js: 'JavaScript', - go: 'Go', - rb: 'Ruby', - jl: 'Julia', -}; - global.Hydro.model.builtin = { Permission, PERM, @@ -547,7 +530,6 @@ global.Hydro.model.builtin = { BUILTIN_ROLES, DEFAULT_NODES, CATEGORIES, - LANG_TEXTS, STATUS, STATUS_TEXTS, STATUS_CODES, diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 0a1135ecb..f10f268e0 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -3,6 +3,7 @@ import moment from 'moment-timezone'; import { Dictionary } from 'lodash'; import yaml from 'js-yaml'; import { retry } from '@hydrooj/utils/lib/utils'; +import { parseLang } from '@hydrooj/utils/lib/lang'; import * as builtin from './builtin'; import { Setting as _Setting } from '../interface'; import { Logger } from '../logger'; @@ -98,10 +99,30 @@ export const SystemSetting = (...settings: _Setting[]) => { } }; +const LangSettingNode = { + family: 'setting_usage', + key: 'codeLang', + value: 'c', + name: 'codeLang', + desc: 'Default Code Language', + flag: 0, + subType: '', + type: 'select', + range: {}, +}; + +bus.on('system/setting', (args) => { + if (!args['hydrooj.lang']) return; + const lang = parseLang(args['hydrooj.lang']); + const range = {}; + for (const key in lang) range[key] = lang[key].display; + LangSettingNode.range = range; +}); + PreferenceSetting( Setting('setting_display', 'viewLang', null, langRange, 'UI Language'), Setting('setting_display', 'timeZone', 'Asia/Shanghai', timezones, 'Timezone'), - Setting('setting_usage', 'codeLang', 'c', builtin.LANG_TEXTS, 'Default Code Language'), + LangSettingNode, Setting('setting_usage', 'codeTemplate', '', 'textarea', 'Default Code Template', 'If left blank, the built-in template of the corresponding language will be used.'), ); @@ -165,7 +186,6 @@ SystemSetting( Setting('setting_limits', 'limit.problem_files_max', 100, 'number', 'limit.problem_files_max', 'Max files per problem'), Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege'), Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'), - Setting('setting_basic', 'lang.texts', builtin.LANG_TEXTS, 'yaml', 'lang.texts', 'LANG_TEXTS'), Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'), Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'), Setting('setting_basic', 'pagination.discussion', 50, 'number', 'pagination.discussion', 'Discussions per page'), diff --git a/packages/hydrooj/src/model/system.ts b/packages/hydrooj/src/model/system.ts index 9c1a41327..d005757ac 100644 --- a/packages/hydrooj/src/model/system.ts +++ b/packages/hydrooj/src/model/system.ts @@ -1,3 +1,4 @@ +import { parseLang } from '@hydrooj/utils/lib/lang'; import { SYSTEM_SETTINGS } from './setting'; import { NumberKeys } from '../typeutils'; import { SystemKeys } from '../interface'; @@ -70,8 +71,13 @@ export async function runConfig() { await bus.emit('database/config'); } +// eslint-disable-next-line import/no-mutable-exports +export let lang = {}; + bus.on('system/setting', (args) => { for (const key in args) set(key, args[key], false); + if (!args['hydrooj.lang']) return; + lang = parseLang(args['hydrooj.lang']); }); global.Hydro.model.system = { @@ -80,4 +86,5 @@ global.Hydro.model.system = { getMany, inc, set, + lang, }; diff --git a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx index 3689440da..967447902 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx @@ -4,7 +4,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; const mapStateToProps = (state) => ({ value: state.editor.code, - language: window.MONACO_MODES[state.editor.lang], + language: window.LANGS[state.editor.lang].monaco, theme: 'vs-light', mainSize: state.ui.main.size, pretestSize: state.ui.pretest.size, diff --git a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx index fd62aec7d..b7fef7b7f 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx @@ -136,8 +136,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadTool value={this.props.editorLang} onChange={(ev) => this.props.setEditorLanguage(ev.target.value)} > - {_.map(window.LANG_TEXTS, (val, key) => ( - + {_.map(window.LANGS, (val, key) => ( + ))} diff --git a/packages/ui-default/handler.js b/packages/ui-default/handler.js index 19946e000..eb12b7263 100644 --- a/packages/ui-default/handler.js +++ b/packages/ui-default/handler.js @@ -53,15 +53,7 @@ class UiConstantsHandler extends Handler { } async get() { - const [LANG_TEXTS, LANG_HIGHLIGHT_ID, MONACO_MODES] = system.getMany([ - 'lang.texts', - 'ui-default.lang_highlight_id', - 'ui-default.lang_monaco_modes', - ]); - this.response.body = `\ -window.LANG_TEXTS=${JSON.stringify(yaml.load(LANG_TEXTS))}; -window.LANG_HIGHLIGHT_ID=${JSON.stringify(yaml.load(LANG_HIGHLIGHT_ID))}; -window.MONACO_MODES=${JSON.stringify(yaml.load(MONACO_MODES))}`; + this.response.body = `window.LANGS=${JSON.stringify(system.langs)}`; this.response.type = 'text/javascript'; this.ctx.set('nolog', '1'); } diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 32cfeee63..962d2578d 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -433,7 +433,6 @@ Memory: 内存 Message {0} not found.: 消息 {0} 不存在。 Messages: 消息 Method: 方法 -Monaco language modes: 编译语言代号 -> Monaco 编辑器模式 Month: 月 Monthly Popular: 月度最受欢迎 Most Upvoted Solutions: 最被赞同的题解 diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 7de42bec6..4b8a2bf47 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.7.8", + "version": "4.8.0", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", @@ -22,7 +22,7 @@ "@babel/preset-react": "^7.13.13", "@babel/register": "^7.13.14", "@babel/runtime-corejs2": "^7.14.0", - "@hydrooj/utils": "^1.0.13", + "@hydrooj/utils": "^1.0.14", "ansi_up": "^5.0.1", "autoprefixer": "^9.8.6", "babel-loader": "^8.2.2", diff --git a/packages/ui-default/setting.yaml b/packages/ui-default/setting.yaml index a4d042bfa..a177e18bd 100644 --- a/packages/ui-default/setting.yaml +++ b/packages/ui-default/setting.yaml @@ -43,44 +43,6 @@ nav_logo_dark_2x: name: nav_logo_dark_2x desc: Nav Logo Dark@2x default: /components/navigation/nav-logo-small@2x_dark.png -lang_highlight_id: - type: yaml - name: lang_highlight_id - desc: Language Highlight ID - default: - c: c astyle-c - cc: cpp astyle-c - cs: csharp astyle-cs - pas: pascal - java: java astyle-java - py: python - py3: python - php: php - rs: rust - hs: haskell - js: javascript - go: go - rb: ruby - jl: julia -lang_monaco_modes: - type: yaml - name: lang_monaco_modes - desc: Monaco language modes - default: - c: cpp - cc: cpp - cs: csharp - pas: pascal - java: java - py: python - py3: python - php: php - rs: rust - hs: haskell - js: javascript - go: go - rb: ruby - jl: julia preferredEditorType: type: wysiwyg: WYSIWYG diff --git a/packages/ui-default/templates/record_detail.html b/packages/ui-default/templates/record_detail.html index 7726c31b2..4bddcf168 100644 --- a/packages/ui-default/templates/record_detail.html +++ b/packages/ui-default/templates/record_detail.html @@ -11,7 +11,7 @@

{{ _('Code') }}

-
{{ rdoc['code'] }}
+
{{ rdoc['code'] }}
{% endif %} diff --git a/packages/utils/lib/lang.ts b/packages/utils/lib/lang.ts new file mode 100644 index 000000000..3eabc3c6e --- /dev/null +++ b/packages/utils/lib/lang.ts @@ -0,0 +1,45 @@ +import yaml from 'js-yaml'; + +export interface LangConfig { + compile?: string; + execute: string; + code_file: string; + highlight: string; + monaco: string; + time_limit_rate: number; + display: string; + target?: string; + sub?: LangConfig[]; + key: string; +} + +export function parseLang(config: string): Record { + const file = yaml.load(config) as Record; + if (typeof file === 'undefined' || typeof file === 'string' || typeof file === 'number') throw new Error(); + Object.keys(file).filter((i) => i.startsWith('_')).forEach((k) => delete file[k]); + for (const key in file) { + const entry = file[key]; + if (key.includes('.')) { + const base = key.split('.')[0]; + const baseCfg = file[base]; + if (baseCfg) { + for (const bkey in baseCfg) { + if (!(bkey in entry)) entry[bkey] = baseCfg[bkey]; + } + if (baseCfg.sub) baseCfg.sub.push(entry); + else baseCfg.sub = [entry]; + } + } + } + for (const key in file) { + const entry = file[key]; + entry.highlight = entry.highlight || key; + entry.monaco = entry.monaco || entry.highlight; + entry.time_limit_rate = entry.time_limit_rate || 1; + entry.code_file = entry.code_file || `foo.${key}`; + // eslint-disable-next-line no-template-curly-in-string + entry.execute = entry.execute || '${dir}/${name}'; + entry.key = key; + } + return file; +} diff --git a/packages/utils/package.json b/packages/utils/package.json index edfb34436..355ff4605 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/utils", - "version": "1.0.13", + "version": "1.0.14", "description": "hydrooj utils", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git",