atcoder-workspace 1.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +98 -0
- package/THIRD_PARTY_LICENSES +21 -0
- package/dist/atcoder/client.d.ts +2 -0
- package/dist/atcoder/client.js +23 -0
- package/dist/atcoder/new.d.ts +15 -0
- package/dist/atcoder/new.js +123 -0
- package/dist/atcoder/parser/contest-tasks.d.ts +6 -0
- package/dist/atcoder/parser/contest-tasks.js +86 -0
- package/dist/atcoder/parser/limits.d.ts +10 -0
- package/dist/atcoder/parser/limits.js +71 -0
- package/dist/atcoder/parser/problem-page.d.ts +12 -0
- package/dist/atcoder/parser/problem-page.js +136 -0
- package/dist/atcoder/parser/submission-status.d.ts +8 -0
- package/dist/atcoder/parser/submission-status.js +78 -0
- package/dist/atcoder/submit.d.ts +9 -0
- package/dist/atcoder/submit.js +182 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +508 -0
- package/dist/config/config-store.d.ts +17 -0
- package/dist/config/config-store.js +92 -0
- package/dist/session/auth.d.ts +8 -0
- package/dist/session/auth.js +117 -0
- package/dist/session/store.d.ts +15 -0
- package/dist/session/store.js +75 -0
- package/dist/test-runner/diff.d.ts +7 -0
- package/dist/test-runner/diff.js +32 -0
- package/dist/test-runner/runner.d.ts +46 -0
- package/dist/test-runner/runner.js +274 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.js +35 -0
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.js +89 -0
- package/dist/utils/i18n.d.ts +345 -0
- package/dist/utils/i18n.js +413 -0
- package/dist/utils/open.d.ts +8 -0
- package/dist/utils/open.js +50 -0
- package/dist/workspace/finder.d.ts +9 -0
- package/dist/workspace/finder.js +62 -0
- package/dist/workspace/initializer.d.ts +4 -0
- package/dist/workspace/initializer.js +109 -0
- package/package.json +38 -0
- package/src/atcoder/client.ts +21 -0
- package/src/atcoder/new.ts +107 -0
- package/src/atcoder/parser/contest-tasks.test.ts +37 -0
- package/src/atcoder/parser/contest-tasks.ts +61 -0
- package/src/atcoder/parser/limits.test.ts +52 -0
- package/src/atcoder/parser/limits.ts +75 -0
- package/src/atcoder/parser/problem-page.test.ts +68 -0
- package/src/atcoder/parser/problem-page.ts +126 -0
- package/src/atcoder/parser/submission-status.test.ts +36 -0
- package/src/atcoder/parser/submission-status.ts +54 -0
- package/src/atcoder/submit.ts +170 -0
- package/src/cli.ts +554 -0
- package/src/config/config-store.ts +72 -0
- package/src/session/auth.ts +87 -0
- package/src/session/store.ts +50 -0
- package/src/test-runner/diff.test.ts +26 -0
- package/src/test-runner/diff.ts +42 -0
- package/src/test-runner/runner.test.ts +70 -0
- package/src/test-runner/runner.ts +315 -0
- package/src/utils/errors.ts +31 -0
- package/src/utils/format.test.ts +69 -0
- package/src/utils/format.ts +95 -0
- package/src/utils/i18n.test.ts +74 -0
- package/src/utils/i18n.ts +418 -0
- package/src/utils/open.ts +47 -0
- package/src/workspace/finder.ts +29 -0
- package/src/workspace/initializer.ts +85 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseSubmissionStatus = parseSubmissionStatus;
|
|
37
|
+
const cheerio = __importStar(require("cheerio"));
|
|
38
|
+
function parseSubmissionStatus(html) {
|
|
39
|
+
const $ = cheerio.load(html);
|
|
40
|
+
let status = '';
|
|
41
|
+
let time;
|
|
42
|
+
let memory;
|
|
43
|
+
let score;
|
|
44
|
+
const statusElem = $('#judge-status');
|
|
45
|
+
if (statusElem.length > 0) {
|
|
46
|
+
status = statusElem.text().trim();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
$('table tr').each((_, tr) => {
|
|
50
|
+
const thText = $(tr).find('th').text().trim();
|
|
51
|
+
if (thText === 'Status' || thText === '状態' || thText === '結果') {
|
|
52
|
+
status = $(tr).find('td').text().trim();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
$('table tr').each((_, tr) => {
|
|
57
|
+
const thText = $(tr).find('th').text().trim();
|
|
58
|
+
const tdText = $(tr).find('td').text().trim();
|
|
59
|
+
if (thText === 'Execution Time' || thText === '実行時間') {
|
|
60
|
+
time = tdText;
|
|
61
|
+
}
|
|
62
|
+
else if (thText === 'Memory' || thText === 'メモリ') {
|
|
63
|
+
memory = tdText;
|
|
64
|
+
}
|
|
65
|
+
else if (thText === 'Score' || thText === '得点') {
|
|
66
|
+
score = tdText;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
status = status.replace(/\s+/g, ' ');
|
|
70
|
+
const isCompleted = status !== '' && !status.includes('WJ') && !status.includes('Judging');
|
|
71
|
+
return {
|
|
72
|
+
status: status || 'WJ',
|
|
73
|
+
time,
|
|
74
|
+
memory,
|
|
75
|
+
score,
|
|
76
|
+
isCompleted
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SubmissionDetails {
|
|
2
|
+
submissionId: string;
|
|
3
|
+
url: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Submits the code file for a task to AtCoder.
|
|
7
|
+
* Returns the submission ID and submission URL.
|
|
8
|
+
*/
|
|
9
|
+
export declare function submitTask(workspaceRoot: string, contestId: string, taskId: string, taskLabel: string, fileArg?: string): Promise<SubmissionDetails>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.submitTask = submitTask;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const cheerio = __importStar(require("cheerio"));
|
|
40
|
+
const client_1 = require("./client");
|
|
41
|
+
const config_store_1 = require("../config/config-store");
|
|
42
|
+
const runner_1 = require("../test-runner/runner");
|
|
43
|
+
const errors_1 = require("../utils/errors");
|
|
44
|
+
const i18n_1 = require("../utils/i18n");
|
|
45
|
+
/**
|
|
46
|
+
* Submits the code file for a task to AtCoder.
|
|
47
|
+
* Returns the submission ID and submission URL.
|
|
48
|
+
*/
|
|
49
|
+
async function submitTask(workspaceRoot, contestId, taskId, taskLabel, fileArg) {
|
|
50
|
+
const config = (0, config_store_1.loadConfig)(workspaceRoot);
|
|
51
|
+
const lang = (0, i18n_1.getLanguage)(workspaceRoot);
|
|
52
|
+
const contestParentDir = config.contestDir ? path.join(workspaceRoot, config.contestDir) : workspaceRoot;
|
|
53
|
+
const taskDir = path.join(contestParentDir, contestId, taskLabel);
|
|
54
|
+
if (!fs.existsSync(taskDir)) {
|
|
55
|
+
throw new errors_1.AtcError(`Task directory "${taskLabel}" not found in contest "${contestId}".`);
|
|
56
|
+
}
|
|
57
|
+
const { codeFile, langConfig } = (0, runner_1.detectCodeFile)(workspaceRoot, taskDir, config, fileArg);
|
|
58
|
+
const codePath = path.join(taskDir, codeFile);
|
|
59
|
+
const codeContent = fs.readFileSync(codePath, 'utf8');
|
|
60
|
+
const client = (0, client_1.createAtCoderClient)(workspaceRoot);
|
|
61
|
+
let submitPageHtml = '';
|
|
62
|
+
try {
|
|
63
|
+
const res = await client.get(`/contests/${contestId}/submit?taskScreenName=${taskId}`);
|
|
64
|
+
submitPageHtml = res.data;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new errors_1.AtcError(`Failed to access AtCoder submit page: ${err.message}`);
|
|
68
|
+
}
|
|
69
|
+
const $ = cheerio.load(submitPageHtml);
|
|
70
|
+
const csrfToken = $('input[name="csrf_token"]').val();
|
|
71
|
+
if (!csrfToken) {
|
|
72
|
+
throw new errors_1.AtcError('Could not find CSRF token. Make sure you are logged in (run "atc login").');
|
|
73
|
+
}
|
|
74
|
+
let selectElement = $(`#select-lang-${taskId} select`);
|
|
75
|
+
if (selectElement.length === 0) {
|
|
76
|
+
selectElement = $('div#select-lang select');
|
|
77
|
+
}
|
|
78
|
+
if (selectElement.length === 0) {
|
|
79
|
+
selectElement = $('select[name="data.LanguageId"]');
|
|
80
|
+
}
|
|
81
|
+
if (selectElement.length === 0) {
|
|
82
|
+
if ($('input[name="username"]').length > 0 || $('input[name="password"]').length > 0 || submitPageHtml.includes('/login')) {
|
|
83
|
+
throw new errors_1.AtcError((0, i18n_1.t)('submitSessionExpired', lang));
|
|
84
|
+
}
|
|
85
|
+
throw new errors_1.AtcError((0, i18n_1.t)('submitLangSelectNotFound', lang));
|
|
86
|
+
}
|
|
87
|
+
const options = [];
|
|
88
|
+
selectElement.find('option').each((_, opt) => {
|
|
89
|
+
const id = $(opt).val();
|
|
90
|
+
const name = $(opt).text().trim();
|
|
91
|
+
if (id && name) {
|
|
92
|
+
options.push({ id: id.toString(), name });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
let selectedLangId = '';
|
|
96
|
+
// Custom user regex in language config
|
|
97
|
+
const userRegexStr = langConfig.atcoderLanguageIdRegex;
|
|
98
|
+
if (userRegexStr) {
|
|
99
|
+
const userRegex = new RegExp(userRegexStr, 'i');
|
|
100
|
+
const matched = options.find(opt => userRegex.test(opt.name));
|
|
101
|
+
if (matched) {
|
|
102
|
+
selectedLangId = matched.id;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!selectedLangId) {
|
|
106
|
+
if (langConfig.extension === 'cpp') {
|
|
107
|
+
const matched = options.find(opt => /C\+\+/i.test(opt.name) && !/Clang/i.test(opt.name)) ||
|
|
108
|
+
options.find(opt => /C\+\+/i.test(opt.name));
|
|
109
|
+
if (matched)
|
|
110
|
+
selectedLangId = matched.id;
|
|
111
|
+
}
|
|
112
|
+
else if (langConfig.extension === 'py') {
|
|
113
|
+
const matched = options.find(opt => /PyPy3/i.test(opt.name)) ||
|
|
114
|
+
options.find(opt => /Python3/i.test(opt.name)) ||
|
|
115
|
+
options.find(opt => /Python/i.test(opt.name));
|
|
116
|
+
if (matched)
|
|
117
|
+
selectedLangId = matched.id;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!selectedLangId) {
|
|
121
|
+
const extRegex = new RegExp(langConfig.extension, 'i');
|
|
122
|
+
const matched = options.find(opt => extRegex.test(opt.name));
|
|
123
|
+
if (matched) {
|
|
124
|
+
selectedLangId = matched.id;
|
|
125
|
+
}
|
|
126
|
+
else if (options.length > 0) {
|
|
127
|
+
selectedLangId = options[0].id;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
throw new errors_1.AtcError('No language options available on AtCoder submit page.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const postData = new URLSearchParams();
|
|
134
|
+
postData.append('csrf_token', csrfToken.toString());
|
|
135
|
+
postData.append('data.TaskScreenName', taskId);
|
|
136
|
+
postData.append('data.LanguageId', selectedLangId);
|
|
137
|
+
postData.append('sourceCode', codeContent);
|
|
138
|
+
try {
|
|
139
|
+
const postRes = await client.post(`/contests/${contestId}/submit`, postData.toString(), {
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
142
|
+
'Referer': `https://atcoder.jp/contests/${contestId}/submit?taskScreenName=${taskId}`
|
|
143
|
+
},
|
|
144
|
+
maxRedirects: 0,
|
|
145
|
+
validateStatus: (status) => status >= 200 && status < 400
|
|
146
|
+
});
|
|
147
|
+
if (postRes.status !== 302) {
|
|
148
|
+
const $post = cheerio.load(postRes.data);
|
|
149
|
+
let alertText = $post('.alert-danger, .alert-warning').text().trim();
|
|
150
|
+
if (!alertText) {
|
|
151
|
+
if ($post('.cf-challenge').length > 0) {
|
|
152
|
+
throw new errors_1.AtcError((0, i18n_1.t)('submitTurnstileDetected', lang));
|
|
153
|
+
}
|
|
154
|
+
throw new errors_1.AtcError((0, i18n_1.t)('submitRejected', lang));
|
|
155
|
+
}
|
|
156
|
+
alertText = alertText.replace(/^×\s*/, '').trim();
|
|
157
|
+
throw new errors_1.AtcError(alertText);
|
|
158
|
+
}
|
|
159
|
+
const meRes = await client.get(`/contests/${contestId}/submissions/me`);
|
|
160
|
+
const $me = cheerio.load(meRes.data);
|
|
161
|
+
let submissionId = '';
|
|
162
|
+
$me('table tbody tr').first().find('a').each((_, a) => {
|
|
163
|
+
const href = $me(a).attr('href');
|
|
164
|
+
if (href) {
|
|
165
|
+
const match = href.match(/\/contests\/[^/]+\/submissions\/(\d+)/);
|
|
166
|
+
if (match && match[1]) {
|
|
167
|
+
submissionId = match[1];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
if (!submissionId) {
|
|
172
|
+
throw new errors_1.AtcError('Submission succeeded but could not retrieve the submission ID.');
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
submissionId,
|
|
176
|
+
url: `/contests/${contestId}/submissions/${submissionId}`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
throw new errors_1.AtcError(`Failed to submit code: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
package/dist/cli.d.ts
ADDED