@halooj/common 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cases.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { findFileSync, parseMemoryMB, parseTimeMS } from '@halooj/utils';
2
+ import { CompilableSource, ProblemConfigFile } from './types';
3
+
4
+ // eslint-disable-next-line ts/no-unused-vars
5
+ export async function readYamlCases(cfg: ProblemConfigFile = {}, checkFile = (s: CompilableSource, errMsg: string) => s) {
6
+ const config: any = {
7
+ checker_type: cfg.checker_type || 'default',
8
+ judge_extra_files: [],
9
+ user_extra_files: [],
10
+ };
11
+ const checkFileWithLang = (s: CompilableSource, errMsg: string) => {
12
+ if (typeof s === 'string') return { lang: 'auto', file: checkFile(s, errMsg) };
13
+ return { lang: s.lang, file: checkFile(s.file, errMsg) };
14
+ };
15
+ if (cfg.type === 'objective') {
16
+ if (cfg.checker || cfg.interactor || cfg.validator) {
17
+ throw new Error('You cannot use checker, interactor or validator for objective questions');
18
+ }
19
+ } else {
20
+ if (cfg.checker) {
21
+ const [name, lang] = typeof cfg.checker === 'string' ? [cfg.checker, 'auto'] : [cfg.checker.file, cfg.checker.lang || 'auto'];
22
+ if (!name.includes('.')) {
23
+ config.checker = {
24
+ lang,
25
+ file: findFileSync(`@halooj/hydrojudge/vendor/testlib/checkers/${name}.cpp`, false),
26
+ };
27
+ }
28
+ config.checker ||= checkFileWithLang(name, 'Cannot find checker {0}.');
29
+ }
30
+ if (cfg.interactor) config.interactor = checkFileWithLang(cfg.interactor, 'Cannot find interactor {0}.');
31
+ if (cfg.manager) config.manager = checkFileWithLang(cfg.manager, 'Cannot find Manager {0}.');
32
+ if (cfg.validator) config.validator = checkFileWithLang(cfg.validator, 'Cannot find validator {0}.');
33
+ for (const n of ['judge', 'user']) {
34
+ const conf = cfg[`${n}_extra_files`];
35
+ if (!conf) continue;
36
+ if (conf instanceof Array) {
37
+ config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`));
38
+ } else throw new Error(`Invalid ${n}_extra_files config.`);
39
+ }
40
+ }
41
+ if (cfg.cases?.length) {
42
+ config.subtasks = [{
43
+ cases: cfg.cases,
44
+ type: 'sum',
45
+ }];
46
+ }
47
+ if (cfg.time) config.time = parseTimeMS(cfg.time);
48
+ if (cfg.memory) config.memory = parseMemoryMB(cfg.memory);
49
+ return Object.assign(cfg, config);
50
+ }
package/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './lang';
2
+ export * from './permission';
3
+ export * from './status';
4
+ export * from './subtask';
5
+ export * from './types';
package/lang.ts ADDED
@@ -0,0 +1,60 @@
1
+ import yaml from 'js-yaml';
2
+
3
+ export interface LangConfig {
4
+ disabled?: boolean;
5
+ compile?: string;
6
+ execute: string;
7
+ code_file: string;
8
+ highlight: string;
9
+ monaco: string;
10
+ time_limit_rate: number;
11
+ memory_limit_rate: number;
12
+ address_space_limit?: boolean;
13
+ process_limit?: number;
14
+ display: string;
15
+ target?: string;
16
+ key: string;
17
+ hidden: boolean;
18
+ isBinary?: boolean;
19
+ analysis?: string;
20
+ remote: boolean;
21
+ validAs?: Record<string, string>;
22
+ /** @deprecated */
23
+ pretest?: string | false;
24
+ comment?: string | [string, string];
25
+ compile_time_limit?: number;
26
+ compile_memory_limit?: number;
27
+ version?: string;
28
+ }
29
+
30
+ export function parseLang(config: string): Record<string, LangConfig> {
31
+ const file = yaml.load(config) as Record<string, LangConfig>;
32
+ if (typeof file === 'undefined' || typeof file === 'string' || typeof file === 'number') throw new Error();
33
+ for (const key of Object.keys(file)) if (key.startsWith('_')) delete file[key];
34
+ for (const key in file) {
35
+ const entry = file[key];
36
+ if (key.includes('.')) {
37
+ const base = key.split('.')[0];
38
+ const baseCfg = file[base];
39
+ for (const bkey in baseCfg || {}) {
40
+ if (!(bkey in entry)) entry[bkey] = baseCfg[bkey];
41
+ }
42
+ }
43
+ }
44
+ for (const key in file) {
45
+ const entry = file[key];
46
+ entry.highlight ||= key;
47
+ entry.monaco ||= entry.highlight;
48
+ entry.time_limit_rate ||= 1;
49
+ entry.memory_limit_rate ||= 1;
50
+ entry.code_file ||= `foo.${key}`;
51
+ entry.execute ||= '/w/foo';
52
+ entry.key = key;
53
+ entry.remote = !!entry.remote;
54
+ entry.hidden ||= false;
55
+ entry.disabled ||= false;
56
+ entry.isBinary ||= false;
57
+ entry.validAs ||= {};
58
+ }
59
+ return file;
60
+ }
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@halooj/common",
3
+ "main": "./index.ts",
4
+ "version": "0.0.10",
5
+ "repository": "https://github.com/hydro-dev/Hydro",
6
+ "sideEffects": false,
7
+ "dependencies": {
8
+ "@halooj/utils": "^1.5.6"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ }
13
+ }
package/permission.ts ADDED
@@ -0,0 +1,210 @@
1
+ export const PERM = {
2
+ PERM_NONE: 0n,
3
+
4
+ // Domain Settings
5
+ PERM_VIEW: 1n << 0n,
6
+ PERM_EDIT_DOMAIN: 1n << 1n,
7
+ /** @deprecated use PERM_VIEW_USER_PRIVATE_INFO instead */
8
+ PERM_VIEW_DISPLAYNAME: 1n << 67n,
9
+ PERM_VIEW_USER_PRIVATE_INFO: 1n << 67n,
10
+ PERM_MOD_BADGE: 1n << 2n,
11
+
12
+ // Problem
13
+ PERM_CREATE_PROBLEM: 1n << 4n,
14
+ PERM_EDIT_PROBLEM: 1n << 5n,
15
+ PERM_EDIT_PROBLEM_SELF: 1n << 6n,
16
+ PERM_VIEW_PROBLEM: 1n << 7n,
17
+ PERM_VIEW_PROBLEM_HIDDEN: 1n << 8n,
18
+ PERM_SUBMIT_PROBLEM: 1n << 9n,
19
+ PERM_READ_PROBLEM_DATA: 1n << 10n,
20
+
21
+ // Record
22
+ PERM_VIEW_RECORD: 1n << 70n,
23
+ PERM_READ_RECORD_CODE: 1n << 12n,
24
+ PERM_READ_RECORD_CODE_ACCEPT: 1n << 66n,
25
+ PERM_REJUDGE_PROBLEM: 1n << 13n,
26
+ PERM_REJUDGE: 1n << 14n,
27
+
28
+ // Problem Solution
29
+ PERM_VIEW_PROBLEM_SOLUTION: 1n << 15n,
30
+ PERM_VIEW_PROBLEM_SOLUTION_ACCEPT: 1n << 65n,
31
+ PERM_CREATE_PROBLEM_SOLUTION: 1n << 16n,
32
+ PERM_VOTE_PROBLEM_SOLUTION: 1n << 17n,
33
+ PERM_EDIT_PROBLEM_SOLUTION: 1n << 18n,
34
+ PERM_EDIT_PROBLEM_SOLUTION_SELF: 1n << 19n,
35
+ PERM_DELETE_PROBLEM_SOLUTION: 1n << 20n,
36
+ PERM_DELETE_PROBLEM_SOLUTION_SELF: 1n << 21n,
37
+ PERM_REPLY_PROBLEM_SOLUTION: 1n << 22n,
38
+ PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF: 1n << 24n,
39
+ PERM_DELETE_PROBLEM_SOLUTION_REPLY: 1n << 25n,
40
+ PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF: 1n << 26n,
41
+
42
+ // Discussion
43
+ PERM_VIEW_DISCUSSION: 1n << 27n,
44
+ PERM_CREATE_DISCUSSION: 1n << 28n,
45
+ PERM_HIGHLIGHT_DISCUSSION: 1n << 29n,
46
+ PERM_PIN_DISCUSSION: 1n << 61n,
47
+ PERM_EDIT_DISCUSSION: 1n << 30n,
48
+ PERM_EDIT_DISCUSSION_SELF: 1n << 31n,
49
+ PERM_DELETE_DISCUSSION: 1n << 32n,
50
+ PERM_DELETE_DISCUSSION_SELF: 1n << 33n,
51
+ PERM_REPLY_DISCUSSION: 1n << 34n,
52
+ PERM_ADD_REACTION: 1n << 62n,
53
+ PERM_EDIT_DISCUSSION_REPLY_SELF: 1n << 36n,
54
+ PERM_DELETE_DISCUSSION_REPLY: 1n << 38n,
55
+ PERM_DELETE_DISCUSSION_REPLY_SELF: 1n << 39n,
56
+ PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 40n,
57
+ PERM_LOCK_DISCUSSION: 1n << 64n,
58
+
59
+ // Contest
60
+ PERM_VIEW_CONTEST: 1n << 41n,
61
+ PERM_VIEW_CONTEST_SCOREBOARD: 1n << 42n,
62
+ PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD: 1n << 43n,
63
+ PERM_CREATE_CONTEST: 1n << 44n,
64
+ PERM_ATTEND_CONTEST: 1n << 45n,
65
+ PERM_EDIT_CONTEST: 1n << 50n,
66
+ PERM_EDIT_CONTEST_SELF: 1n << 51n,
67
+ PERM_VIEW_HIDDEN_CONTEST: 1n << 68n,
68
+
69
+ // Homework
70
+ PERM_VIEW_HOMEWORK: 1n << 52n,
71
+ PERM_VIEW_HOMEWORK_SCOREBOARD: 1n << 53n,
72
+ PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD: 1n << 54n,
73
+ PERM_CREATE_HOMEWORK: 1n << 55n,
74
+ PERM_ATTEND_HOMEWORK: 1n << 56n,
75
+ PERM_EDIT_HOMEWORK: 1n << 57n,
76
+ PERM_EDIT_HOMEWORK_SELF: 1n << 58n,
77
+ PERM_VIEW_HIDDEN_HOMEWORK: 1n << 69n,
78
+
79
+ // Training
80
+ PERM_VIEW_TRAINING: 1n << 46n,
81
+ PERM_CREATE_TRAINING: 1n << 47n,
82
+ PERM_EDIT_TRAINING: 1n << 48n,
83
+ PERM_PIN_TRAINING: 1n << 63n,
84
+ PERM_EDIT_TRAINING_SELF: 1n << 49n,
85
+
86
+ // Ranking
87
+ PERM_VIEW_RANKING: 1n << 59n,
88
+
89
+ // HaloOJ Advanced Domain
90
+ PERM_EDIT_DOMAIN_ADVANCED: 1n << 71n,
91
+ PERM_IMPORT_USER: 1n << 72n,
92
+ PERM_VIEW_USER_LIST: 1n << 73n,
93
+ PERM_VIEW_COURSE: 1n << 74n,
94
+ PERM_EDIT_COURSE: 1n << 75n,
95
+ PERM_VIEW_EXAM: 1n << 76n,
96
+ PERM_EDIT_EXAM: 1n << 77n,
97
+ PERM_USE_AI: 1n << 78n,
98
+ PERM_USE_BLOCKLY: 1n << 79n,
99
+ PERM_DOWNLOAD_FIRST_WA: 1n << 80n,
100
+ PERM_CONTEST_PDF: 1n << 81n,
101
+ PERM_CONTEST_BULK_SUBMIT: 1n << 82n,
102
+
103
+ // Placeholder
104
+ PERM_ALL: -1n,
105
+ PERM_BASIC: 0n,
106
+ PERM_DEFAULT: 0n,
107
+ PERM_ADMIN: -1n,
108
+
109
+ PERM_NEVER: 1n << 60n,
110
+ };
111
+
112
+ PERM.PERM_BASIC = PERM.PERM_VIEW
113
+ | PERM.PERM_VIEW_PROBLEM
114
+ | PERM.PERM_VIEW_PROBLEM_SOLUTION
115
+ | PERM.PERM_VIEW_PROBLEM_SOLUTION_ACCEPT
116
+ | PERM.PERM_VIEW_DISCUSSION
117
+ | PERM.PERM_VIEW_CONTEST
118
+ | PERM.PERM_VIEW_CONTEST_SCOREBOARD
119
+ | PERM.PERM_VIEW_HOMEWORK
120
+ | PERM.PERM_VIEW_HOMEWORK_SCOREBOARD
121
+ | PERM.PERM_VIEW_TRAINING
122
+ | PERM.PERM_VIEW_RANKING
123
+ | PERM.PERM_VIEW_COURSE
124
+ | PERM.PERM_USE_BLOCKLY;
125
+
126
+ PERM.PERM_DEFAULT = PERM.PERM_VIEW
127
+ | PERM.PERM_VIEW_USER_PRIVATE_INFO
128
+ | PERM.PERM_VIEW_PROBLEM
129
+ | PERM.PERM_EDIT_PROBLEM_SELF
130
+ | PERM.PERM_SUBMIT_PROBLEM
131
+ | PERM.PERM_VIEW_PROBLEM_SOLUTION
132
+ | PERM.PERM_VIEW_PROBLEM_SOLUTION_ACCEPT
133
+ | PERM.PERM_CREATE_PROBLEM_SOLUTION
134
+ | PERM.PERM_VOTE_PROBLEM_SOLUTION
135
+ | PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF
136
+ | PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF
137
+ | PERM.PERM_REPLY_PROBLEM_SOLUTION
138
+ | PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF
139
+ | PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF
140
+ | PERM.PERM_VIEW_DISCUSSION
141
+ | PERM.PERM_CREATE_DISCUSSION
142
+ | PERM.PERM_EDIT_DISCUSSION_SELF
143
+ | PERM.PERM_REPLY_DISCUSSION
144
+ | PERM.PERM_ADD_REACTION
145
+ | PERM.PERM_EDIT_DISCUSSION_REPLY_SELF
146
+ | PERM.PERM_DELETE_DISCUSSION_REPLY_SELF
147
+ | PERM.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION
148
+ | PERM.PERM_VIEW_CONTEST
149
+ | PERM.PERM_VIEW_CONTEST_SCOREBOARD
150
+ | PERM.PERM_ATTEND_CONTEST
151
+ | PERM.PERM_EDIT_CONTEST_SELF
152
+ | PERM.PERM_VIEW_HOMEWORK
153
+ | PERM.PERM_VIEW_HOMEWORK_SCOREBOARD
154
+ | PERM.PERM_ATTEND_HOMEWORK
155
+ | PERM.PERM_EDIT_HOMEWORK_SELF
156
+ | PERM.PERM_VIEW_TRAINING
157
+ | PERM.PERM_CREATE_TRAINING
158
+ | PERM.PERM_EDIT_TRAINING_SELF
159
+ | PERM.PERM_SUBMIT_PROBLEM
160
+ | PERM.PERM_CREATE_PROBLEM_SOLUTION
161
+ | PERM.PERM_VOTE_PROBLEM_SOLUTION
162
+ | PERM.PERM_REPLY_PROBLEM_SOLUTION
163
+ | PERM.PERM_CREATE_DISCUSSION
164
+ | PERM.PERM_REPLY_DISCUSSION
165
+ | PERM.PERM_ATTEND_CONTEST
166
+ | PERM.PERM_CREATE_TRAINING
167
+ | PERM.PERM_ATTEND_HOMEWORK
168
+ | PERM.PERM_VIEW_RANKING
169
+ | PERM.PERM_VIEW_RECORD
170
+ | PERM.PERM_VIEW_COURSE
171
+ | PERM.PERM_VIEW_EXAM
172
+ | PERM.PERM_USE_AI
173
+ | PERM.PERM_USE_BLOCKLY
174
+ | PERM.PERM_DOWNLOAD_FIRST_WA
175
+ | PERM.PERM_CONTEST_PDF;
176
+
177
+ PERM.PERM_ADMIN = PERM.PERM_ALL;
178
+
179
+ export const PRIV = {
180
+ PRIV_NONE: 0,
181
+ PRIV_MOD_BADGE: 1 << 25,
182
+ PRIV_EDIT_SYSTEM: 1 << 0, // renamed from PRIV_SET_PRIV
183
+ PRIV_SET_PERM: 1 << 1,
184
+ PRIV_USER_PROFILE: 1 << 2,
185
+ PRIV_REGISTER_USER: 1 << 3,
186
+ PRIV_READ_PROBLEM_DATA: 1 << 4,
187
+ PRIV_READ_RECORD_CODE: 1 << 7,
188
+ PRIV_VIEW_HIDDEN_RECORD: 1 << 8,
189
+ PRIV_JUDGE: 1 << 9, // (renamed)
190
+ PRIV_CREATE_DOMAIN: 1 << 10,
191
+ PRIV_VIEW_ALL_DOMAIN: 1 << 11,
192
+ PRIV_MANAGE_ALL_DOMAIN: 1 << 12,
193
+ PRIV_REJUDGE: 1 << 13,
194
+ PRIV_VIEW_USER_SECRET: 1 << 14,
195
+ PRIV_VIEW_JUDGE_STATISTICS: 1 << 15,
196
+ PRIV_UNLIMITED_ACCESS: 1 << 22,
197
+ PRIV_VIEW_SYSTEM_NOTIFICATION: 1 << 23,
198
+ PRIV_SEND_MESSAGE: 1 << 24,
199
+ PRIV_CREATE_FILE: 1 << 16,
200
+ PRIV_UNLIMITED_QUOTA: 1 << 17,
201
+ PRIV_DELETE_FILE: 1 << 18,
202
+
203
+ PRIV_ALL: -1,
204
+ PRIV_DEFAULT: 0,
205
+ PRIV_NEVER: 1 << 20,
206
+ };
207
+
208
+ PRIV.PRIV_DEFAULT = PRIV.PRIV_USER_PROFILE
209
+ + PRIV.PRIV_CREATE_FILE
210
+ + PRIV.PRIV_SEND_MESSAGE;
package/status.ts ADDED
@@ -0,0 +1,122 @@
1
+ export enum STATUS {
2
+ STATUS_WAITING = 0,
3
+ STATUS_ACCEPTED = 1,
4
+ STATUS_WRONG_ANSWER = 2,
5
+ STATUS_TIME_LIMIT_EXCEEDED = 3,
6
+ STATUS_MEMORY_LIMIT_EXCEEDED = 4,
7
+ STATUS_OUTPUT_LIMIT_EXCEEDED = 5,
8
+ STATUS_RUNTIME_ERROR = 6,
9
+ STATUS_COMPILE_ERROR = 7,
10
+ STATUS_SYSTEM_ERROR = 8,
11
+ STATUS_CANCELED = 9,
12
+ STATUS_ETC = 10,
13
+ STATUS_HACKED = 11,
14
+ STATUS_JUDGING = 20,
15
+ STATUS_COMPILING = 21,
16
+ STATUS_FETCHED = 22,
17
+ STATUS_IGNORED = 30,
18
+ STATUS_FORMAT_ERROR = 31,
19
+ STATUS_HACK_SUCCESSFUL = 32,
20
+ STATUS_HACK_UNSUCCESSFUL = 33,
21
+ }
22
+
23
+ export const STATUS_TEXTS: Record<STATUS, string> = {
24
+ [STATUS.STATUS_WAITING]: 'Waiting',
25
+ [STATUS.STATUS_ACCEPTED]: 'Accepted',
26
+ [STATUS.STATUS_WRONG_ANSWER]: 'Wrong Answer',
27
+ [STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'Time Exceeded',
28
+ [STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'Memory Exceeded',
29
+ [STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'Output Exceeded',
30
+ [STATUS.STATUS_RUNTIME_ERROR]: 'Runtime Error',
31
+ [STATUS.STATUS_COMPILE_ERROR]: 'Compile Error',
32
+ [STATUS.STATUS_SYSTEM_ERROR]: 'System Error',
33
+ [STATUS.STATUS_CANCELED]: 'Cancelled',
34
+ [STATUS.STATUS_ETC]: 'Unknown Error',
35
+ [STATUS.STATUS_HACKED]: 'Hacked',
36
+ [STATUS.STATUS_JUDGING]: 'Running',
37
+ [STATUS.STATUS_COMPILING]: 'Compiling',
38
+ [STATUS.STATUS_FETCHED]: 'Fetched',
39
+ [STATUS.STATUS_IGNORED]: 'Ignored',
40
+ [STATUS.STATUS_FORMAT_ERROR]: 'Format Error',
41
+ [STATUS.STATUS_HACK_SUCCESSFUL]: 'Hack Successful',
42
+ [STATUS.STATUS_HACK_UNSUCCESSFUL]: 'Hack Unsuccessful',
43
+ };
44
+
45
+ export const STATUS_SHORT_TEXTS: Partial<Record<STATUS, string>> = {
46
+ [STATUS.STATUS_ACCEPTED]: 'AC',
47
+ [STATUS.STATUS_WRONG_ANSWER]: 'WA',
48
+ [STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'TLE',
49
+ [STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'MLE',
50
+ [STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'OLE',
51
+ [STATUS.STATUS_RUNTIME_ERROR]: 'RE',
52
+ [STATUS.STATUS_COMPILE_ERROR]: 'CE',
53
+ [STATUS.STATUS_SYSTEM_ERROR]: 'SE',
54
+ [STATUS.STATUS_CANCELED]: 'IGN',
55
+ [STATUS.STATUS_HACKED]: 'HK',
56
+ [STATUS.STATUS_IGNORED]: 'IGN',
57
+ [STATUS.STATUS_FORMAT_ERROR]: 'FE',
58
+ };
59
+
60
+ export const STATUS_CODES: Record<STATUS, string> = {
61
+ [STATUS.STATUS_WAITING]: 'pending',
62
+ [STATUS.STATUS_ACCEPTED]: 'pass',
63
+ [STATUS.STATUS_WRONG_ANSWER]: 'fail',
64
+ [STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'fail',
65
+ [STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'fail',
66
+ [STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'fail',
67
+ [STATUS.STATUS_RUNTIME_ERROR]: 'fail',
68
+ [STATUS.STATUS_COMPILE_ERROR]: 'fail',
69
+ [STATUS.STATUS_SYSTEM_ERROR]: 'fail',
70
+ [STATUS.STATUS_CANCELED]: 'ignored',
71
+ [STATUS.STATUS_ETC]: 'fail',
72
+ [STATUS.STATUS_HACKED]: 'fail',
73
+ [STATUS.STATUS_JUDGING]: 'progress',
74
+ [STATUS.STATUS_COMPILING]: 'progress',
75
+ [STATUS.STATUS_FETCHED]: 'progress',
76
+ [STATUS.STATUS_IGNORED]: 'ignored',
77
+ [STATUS.STATUS_FORMAT_ERROR]: 'ignored',
78
+ [STATUS.STATUS_HACK_SUCCESSFUL]: 'pass',
79
+ [STATUS.STATUS_HACK_UNSUCCESSFUL]: 'fail',
80
+ };
81
+
82
+ export const NORMAL_STATUS: STATUS[] = [
83
+ STATUS.STATUS_ACCEPTED,
84
+ STATUS.STATUS_WRONG_ANSWER,
85
+ STATUS.STATUS_TIME_LIMIT_EXCEEDED,
86
+ STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
87
+ STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
88
+ STATUS.STATUS_RUNTIME_ERROR,
89
+ STATUS.STATUS_COMPILE_ERROR,
90
+ ];
91
+
92
+ export function getScoreColor(score: number | string): string {
93
+ if (score === null || score === undefined || !Number.isFinite(+score)) return '#000000';
94
+ return [
95
+ '#ff4f4f',
96
+ '#ff694f',
97
+ '#f8603a',
98
+ '#fc8354',
99
+ '#fa9231',
100
+ '#f7bb3b',
101
+ '#ecdb44',
102
+ '#e2ec52',
103
+ '#b0d628',
104
+ '#93b127',
105
+ '#25ad40',
106
+ ][Math.floor((Number(score) || 0) / 10)];
107
+ }
108
+
109
+ export const USER_GENDER_MALE = 0;
110
+ export const USER_GENDER_FEMALE = 1;
111
+ export const USER_GENDER_OTHER = 2;
112
+ export const USER_GENDERS = [USER_GENDER_MALE, USER_GENDER_FEMALE, USER_GENDER_OTHER];
113
+ export const USER_GENDER_RANGE = {
114
+ [USER_GENDER_MALE]: 'Boy ♂',
115
+ [USER_GENDER_FEMALE]: 'Girl ♀',
116
+ [USER_GENDER_OTHER]: 'Other',
117
+ };
118
+ export const USER_GENDER_ICONS = {
119
+ [USER_GENDER_MALE]: '♂',
120
+ [USER_GENDER_FEMALE]: '♀',
121
+ [USER_GENDER_OTHER]: '?',
122
+ };
package/subtask.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { parseMemoryMB, parseTimeMS, sortFiles } from '@halooj/utils/lib/common';
2
+
3
+ export function convertIniConfig(ini: string) {
4
+ const f = ini.split('\n');
5
+ const count = Number.parseInt(f[0], 10);
6
+ const res = { subtasks: [] };
7
+ for (let i = 1; i <= count; i++) {
8
+ if (!f[i]?.trim()) throw new Error('Testdata count incorrect.');
9
+ const [input, output, time, score, memory] = f[i].split('|');
10
+ const cur = {
11
+ cases: [{ input, output }],
12
+ score: Number.parseInt(score, 10),
13
+ time: `${time}s`,
14
+ memory: '256m',
15
+ };
16
+ if (!Number.isNaN(Number.parseInt(memory, 10))) cur.memory = `${Math.floor(Number.parseInt(memory, 10) / 1024)}m`;
17
+ res.subtasks.push(cur);
18
+ }
19
+ return res;
20
+ }
21
+
22
+ interface MatchRule {
23
+ regex: RegExp;
24
+ output: (a: RegExpExecArray) => string[];
25
+ id: (a: RegExpExecArray) => number;
26
+ subtask: (a: RegExpExecArray) => number;
27
+ preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum';
28
+ }
29
+
30
+ const SubtaskMatcher: MatchRule[] = [
31
+ {
32
+ // eslint-disable-next-line regexp/no-super-linear-backtracking
33
+ regex: /^(([\w.-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/,
34
+ output: (a) => ['out', 'ans']
35
+ .flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`])
36
+ .flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')])
37
+ .concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null),
38
+ id: (a) => +a[4],
39
+ subtask: (a) => +(a[3] || 1),
40
+ preferredScorerType: (a) => (a[3] ? 'min' : 'sum'),
41
+ },
42
+ {
43
+ regex: /^(\D*)\.(in|IN)(\d+)$/,
44
+ output: (a) => [
45
+ `${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`,
46
+ `${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`,
47
+ ].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]),
48
+ id: (a) => +a[2],
49
+ subtask: () => 1,
50
+ preferredScorerType: () => 'sum',
51
+ },
52
+ {
53
+ regex: /^(\D*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/,
54
+ output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`),
55
+ id: (a) => +a[4],
56
+ subtask: (a) => +a[2],
57
+ preferredScorerType: () => 'min',
58
+ },
59
+ {
60
+ regex: /^(([0-9]+)[-_].*)\.(in|IN)$/,
61
+ output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`),
62
+ id: (a) => +a[2],
63
+ subtask: () => 1,
64
+ preferredScorerType: () => 'sum',
65
+ },
66
+ ];
67
+
68
+ function* getScore(totalScore: number, count: number) {
69
+ const base = Math.floor(totalScore / count);
70
+ const extra = count - (totalScore % count);
71
+ for (let i = 0; i < count; i++) {
72
+ if (i >= extra) yield base + 1;
73
+ else yield base;
74
+ }
75
+ }
76
+
77
+ interface ParsedCase {
78
+ id?: number;
79
+ time?: number | string;
80
+ memory?: number | string;
81
+ score?: number;
82
+ input?: string;
83
+ output?: string;
84
+ }
85
+ interface ParsedSubtask {
86
+ cases: ParsedCase[];
87
+ type: 'min' | 'max' | 'sum';
88
+ time?: number | string;
89
+ memory?: number | string;
90
+ score?: number;
91
+ id?: number;
92
+ if?: number[];
93
+ }
94
+
95
+ export function readSubtasksFromFiles(files: string[], config) {
96
+ const subtask: Record<number, ParsedSubtask> = {};
97
+ for (const s of config.subtasks || []) if (s.id && Number.isSafeInteger(s.id)) subtask[s.id] = s;
98
+ for (const file of files) {
99
+ let match = false;
100
+ for (const rule of SubtaskMatcher) {
101
+ const data = rule.regex.exec(file);
102
+ if (!data) continue;
103
+ const sid = rule.subtask(data);
104
+ const c = { input: file, output: '', id: rule.id(data) };
105
+ const type = rule.preferredScorerType(data);
106
+ const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i);
107
+ for (const output of outputs) {
108
+ if (output === file) continue;
109
+ if (output === '/dev/null' || files.includes(output)) {
110
+ match = true;
111
+ c.output = output;
112
+ if (!subtask[sid]) {
113
+ subtask[sid] = {
114
+ time: config.time,
115
+ memory: config.memory,
116
+ type,
117
+ cases: [c],
118
+ id: sid,
119
+ };
120
+ } else if (!subtask[sid].cases) subtask[sid].cases = [c];
121
+ else if (!subtask[sid].cases.find((i) => i.input === c.input && i.output === c.output)) {
122
+ subtask[sid].cases.push(c);
123
+ }
124
+ break;
125
+ }
126
+ }
127
+ if (match) break;
128
+ }
129
+ }
130
+ for (const id in subtask) subtask[id].cases = sortFiles(subtask[id].cases, 'input');
131
+ return Object.values(subtask);
132
+ }
133
+
134
+ export interface NormalizedCase extends Required<ParsedCase> {
135
+ time: number;
136
+ memory: number;
137
+ }
138
+ export interface NormalizedSubtask extends Required<ParsedSubtask> {
139
+ cases: NormalizedCase[];
140
+ time: number;
141
+ memory: number;
142
+ }
143
+
144
+ export function normalizeSubtasks(
145
+ subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string,
146
+ time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false,
147
+ timeRate = 1, memoryRate = 1,
148
+ ): NormalizedSubtask[] {
149
+ subtasks.sort((a, b) => (a.id - b.id));
150
+ const subtaskScore = getScore(
151
+ Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0),
152
+ subtasks.filter((i) => !i.score).length,
153
+ );
154
+ return subtasks.map((s, id) => {
155
+ s.cases.sort((a, b) => (a.id - b.id));
156
+ const score = s.score || subtaskScore.next().value as number;
157
+ const caseScore = getScore(
158
+ Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0),
159
+ s.cases.filter((i) => !i.score).length,
160
+ );
161
+ return {
162
+ id: id + 1,
163
+ type: 'min',
164
+ if: [],
165
+ ...s,
166
+ score,
167
+ time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate,
168
+ memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate,
169
+ cases: s.cases.map((c, index) => ({
170
+ id: index + 1,
171
+ ...c,
172
+ score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score),
173
+ time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate,
174
+ memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate,
175
+ input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null',
176
+ output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null',
177
+ })) as NormalizedCase[],
178
+ };
179
+ });
180
+ }
@@ -0,0 +1,62 @@
1
+ import { expect } from 'chai';
2
+ import { describe, it } from 'node:test';
3
+ import { readSubtasksFromFiles } from '../subtask';
4
+
5
+ interface Info {
6
+ subtask: number;
7
+ id: number;
8
+ }
9
+
10
+ function shouldFindTestcase(files: string[], info?: Info) {
11
+ const subtasks = readSubtasksFromFiles(files, {});
12
+ if (files.includes('a2_1.in')) console.log(subtasks);
13
+ expect(subtasks).to.be.lengthOf(1);
14
+ if (info?.subtask) expect(subtasks[0].id).to.equal(info.subtask);
15
+ const cases = subtasks.map((i) => i.cases).flat();
16
+ expect(cases).to.be.lengthOf(1);
17
+ if (info?.id) expect(cases[0].id).to.deep.equal(info.id);
18
+ }
19
+
20
+ describe('single case', () => {
21
+ it('1.in/1.out', () => {
22
+ shouldFindTestcase(['1.in', '1.out']);
23
+ });
24
+ it('1.in/2.ans', () => {
25
+ shouldFindTestcase(['1.in', '1.ans']);
26
+ });
27
+ it('file1.in/file1.out', () => {
28
+ shouldFindTestcase(['file1.in', 'file1.out']);
29
+ });
30
+ it('file.in1/file.ou1', () => {
31
+ shouldFindTestcase(['file.in1', 'file.ou1']);
32
+ });
33
+ it('input1.txt/output1.txt', () => {
34
+ shouldFindTestcase(['input1.txt', 'output1.txt']);
35
+ });
36
+ it('data.1.in/data.1.out', () => {
37
+ shouldFindTestcase(['data.1.in', 'data.1.out']);
38
+ });
39
+ });
40
+ describe('subtask', () => {
41
+ it('1_1.in/1_1.out', () => {
42
+ shouldFindTestcase(['1_1.in', '1_1.out'], { subtask: 1, id: 1 });
43
+ });
44
+ it('a1_1.in/a1_1.out', () => {
45
+ shouldFindTestcase(['a1_1.in', 'a1_1.out'], { subtask: 1, id: 1 });
46
+ });
47
+ it('a01_01.in/a01_01.out', () => {
48
+ shouldFindTestcase(['a01_01.in', 'a01_01.out'], { subtask: 1, id: 1 });
49
+ });
50
+ it('1-1.in/1-1.out', () => {
51
+ shouldFindTestcase(['1-1.in', '1-1.out'], { subtask: 1, id: 1 });
52
+ });
53
+ it('a1-1.in/a1-1.out', () => {
54
+ shouldFindTestcase(['a1-1.in', 'a1-1.out'], { subtask: 1, id: 1 });
55
+ });
56
+ it('subtask_1_1.in/subtask_1_1.out', () => {
57
+ shouldFindTestcase(['subtask_1_1.in', 'subtask_1_1.out'], { subtask: 1, id: 1 });
58
+ });
59
+ it('01sample-01.in/01sample-01.out', () => {
60
+ shouldFindTestcase(['01sample-01.in', '01sample-01.out'], { subtask: 1, id: 1 });
61
+ });
62
+ });
package/tsconfig.json ADDED
@@ -0,0 +1 @@
1
+ {"compilerOptions":{"target":"es2022","lib":["esnext"],"module":"preserve","esModuleInterop":true,"moduleResolution":"bundler","jsx":"react-jsx","sourceMap":false,"composite":true,"strict":false,"strictBindCallApply":true,"resolveJsonModule":true,"experimentalDecorators":true,"incremental":true,"outDir":"/Users/mac/Desktop/HaloTeam/HaloOJ/未命名/.cache/ts-out/packages/common","rootDir":".","paths":{"vj/*":["../../packages/ui-default/*"]}},"include":["**/*.ts","**/*.tsx"],"exclude":["**/public","**/frontend","**/node_modules","**/bin","**/dist","**/__mocks__"]}
package/types.ts ADDED
@@ -0,0 +1,172 @@
1
+ export type CompilableSource = string | {
2
+ file: string;
3
+ lang: string;
4
+ };
5
+
6
+ export enum ProblemType {
7
+ Default = 'default',
8
+ SubmitAnswer = 'submit_answer',
9
+ Interactive = 'interactive',
10
+ Communication = 'communication',
11
+ Objective = 'objective',
12
+ Remote = 'remote_judge',
13
+ }
14
+
15
+ export interface TestCaseConfig {
16
+ input: string;
17
+ output: string;
18
+ time?: string;
19
+ memory?: string;
20
+ score?: number;
21
+ }
22
+
23
+ export enum SubtaskType {
24
+ min = 'min',
25
+ max = 'max',
26
+ sum = 'sum',
27
+ }
28
+
29
+ export interface SubtaskConfig {
30
+ time?: string;
31
+ memory?: string;
32
+ score?: number;
33
+ if?: number[];
34
+ id?: number;
35
+ type?: SubtaskType;
36
+ cases?: TestCaseConfig[];
37
+ }
38
+
39
+ export type DetailType = 'full' | 'case' | 'none';
40
+
41
+ export interface ProblemConfigFile {
42
+ type?: ProblemType;
43
+ subType?: string;
44
+ target?: string;
45
+ score?: number;
46
+ time?: string;
47
+ memory?: string;
48
+ filename?: string;
49
+ checker_type?: string;
50
+ num_processes?: number;
51
+ user_extra_files?: string[];
52
+ judge_extra_files?: string[];
53
+ detail?: DetailType | boolean;
54
+ answers?: Record<string, [string | string[], number]>;
55
+ redirect?: string;
56
+ cases?: TestCaseConfig[];
57
+ subtasks?: SubtaskConfig[];
58
+ langs?: string[];
59
+ multi_pass?: number;
60
+ checker?: CompilableSource;
61
+ interactor?: CompilableSource;
62
+ manager?: CompilableSource;
63
+ validator?: CompilableSource;
64
+ time_limit_rate?: Record<string, number>;
65
+ memory_limit_rate?: Record<string, number>;
66
+ }
67
+
68
+ export interface FileInfo {
69
+ /** storage path */
70
+ _id: string;
71
+ /** filename */
72
+ name: string;
73
+ /** file size (in bytes) */
74
+ size: number;
75
+ etag: string;
76
+ lastModified: Date;
77
+ }
78
+
79
+ export interface JudgeMeta {
80
+ problemOwner: number;
81
+ hackRejudge?: string;
82
+ rejudge?: boolean | 'controlled';
83
+ // FIXME stricter types
84
+ type?: string;
85
+ }
86
+
87
+ export interface RecordJudgeInfo {
88
+ score: number;
89
+ memory: number;
90
+ time: number;
91
+ judgeTexts: (string | JudgeMessage)[];
92
+ compilerTexts: string[];
93
+ testCases: Required<TestCase>[];
94
+ /** judge uid */
95
+ judger: number;
96
+ judgeAt: Date;
97
+ status: number;
98
+ subtasks?: Record<number, SubtaskResult>;
99
+ }
100
+
101
+ export interface RecordPayload extends RecordJudgeInfo {
102
+ domainId: string;
103
+ pid: number;
104
+ uid: number;
105
+ lang: string;
106
+ code: string;
107
+ rejudged: boolean;
108
+ source?: string;
109
+ progress?: number;
110
+ /** pretest */
111
+ input?: string | string[];
112
+ /** hack target rid */
113
+ hackTarget?: string;
114
+ /** 0 if pretest&script */
115
+ contest?: string;
116
+
117
+ files?: Record<string, string>;
118
+ }
119
+
120
+ export interface JudgeRequest extends Omit<RecordPayload, 'testCases'> {
121
+ priority: number;
122
+ type: 'judge' | 'generate';
123
+ rid: string;
124
+ config: ProblemConfigFile;
125
+ meta: JudgeMeta;
126
+ data: FileInfo[];
127
+ source: string;
128
+ trusted: boolean;
129
+ }
130
+
131
+ export interface TestCase {
132
+ id?: number;
133
+ subtaskId?: number;
134
+ score?: number;
135
+ time: number;
136
+ memory: number;
137
+ status: number;
138
+ message: string;
139
+ }
140
+
141
+ export interface JudgeMessage {
142
+ message: string;
143
+ params?: string[];
144
+ stack?: string;
145
+ }
146
+
147
+ export interface SubtaskResult {
148
+ type: SubtaskType;
149
+ score: number;
150
+ status: number;
151
+ }
152
+
153
+ export interface JudgeResultBody {
154
+ key: string;
155
+ domainId: string;
156
+ rid: string;
157
+ judger?: number;
158
+ progress?: number;
159
+ addProgress?: number;
160
+ case?: TestCase;
161
+ cases?: TestCase[];
162
+ status?: number;
163
+ score?: number;
164
+ /** in miliseconds */
165
+ time?: number;
166
+ /** in kilobytes */
167
+ memory?: number;
168
+ message?: string | JudgeMessage;
169
+ compilerText?: string;
170
+ nop?: boolean;
171
+ subtasks?: Record<number, SubtaskResult>;
172
+ }