@rhseung/ps-cli 1.0.0
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/README.md +290 -0
- package/dist/chunk-2E4VSP6O.js +641 -0
- package/dist/chunk-EIFFWFLS.js +68 -0
- package/dist/chunk-FYS2JH42.js +31 -0
- package/dist/chunk-IJLJBKLK.js +1689 -0
- package/dist/chunk-KFQFQJYT.js +9948 -0
- package/dist/chunk-OOTPZD7O.js +42 -0
- package/dist/chunk-TQXMB7XV.js +52 -0
- package/dist/commands/config.js +234 -0
- package/dist/commands/fetch.js +606 -0
- package/dist/commands/run.js +191 -0
- package/dist/commands/stats.js +165 -0
- package/dist/commands/submit.js +493 -0
- package/dist/commands/test.js +413 -0
- package/dist/index.js +9503 -0
- package/package.json +58 -0
- package/templates/solution.cpp +13 -0
- package/templates/solution.py +5 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getBojSessionCookie,
|
|
4
|
+
getCodeOpen
|
|
5
|
+
} from "../chunk-KFQFQJYT.js";
|
|
6
|
+
import {
|
|
7
|
+
detectProblemIdFromPath,
|
|
8
|
+
getProblemId
|
|
9
|
+
} from "../chunk-OOTPZD7O.js";
|
|
10
|
+
import {
|
|
11
|
+
detectLanguageFromFile,
|
|
12
|
+
getLanguageConfig,
|
|
13
|
+
getSupportedLanguages,
|
|
14
|
+
getSupportedLanguagesString
|
|
15
|
+
} from "../chunk-TQXMB7XV.js";
|
|
16
|
+
import {
|
|
17
|
+
LoadingSpinner
|
|
18
|
+
} from "../chunk-IJLJBKLK.js";
|
|
19
|
+
import "../chunk-FYS2JH42.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/submit.tsx
|
|
22
|
+
import { useState, useEffect } from "react";
|
|
23
|
+
import { render, Text, Box } from "ink";
|
|
24
|
+
import { readdir } from "fs/promises";
|
|
25
|
+
import { join } from "path";
|
|
26
|
+
import { readFile } from "fs/promises";
|
|
27
|
+
|
|
28
|
+
// src/services/submitter.ts
|
|
29
|
+
import * as cheerio from "cheerio";
|
|
30
|
+
var BOJ_BASE_URL = "https://www.acmicpc.net";
|
|
31
|
+
async function pollSubmitResult(problemId, submitId, sessionCookie) {
|
|
32
|
+
const maxAttempts = 60;
|
|
33
|
+
const pollInterval = 2e3;
|
|
34
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(
|
|
38
|
+
`${BOJ_BASE_URL}/status?from_mine=1&problem_id=${problemId}`,
|
|
39
|
+
{
|
|
40
|
+
headers: {
|
|
41
|
+
Cookie: sessionCookie,
|
|
42
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const html = await response.text();
|
|
50
|
+
const $ = cheerio.load(html);
|
|
51
|
+
let targetRow = null;
|
|
52
|
+
$("table#status-table tbody tr").each((_, element) => {
|
|
53
|
+
const row = $(element);
|
|
54
|
+
const rowSubmitId = parseInt(
|
|
55
|
+
row.find("td:first-child").text().trim(),
|
|
56
|
+
10
|
|
57
|
+
);
|
|
58
|
+
if (rowSubmitId === submitId) {
|
|
59
|
+
targetRow = row;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (!targetRow) {
|
|
64
|
+
targetRow = $("table#status-table tbody tr").first();
|
|
65
|
+
}
|
|
66
|
+
if (!targetRow || targetRow.length === 0) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const statusText = targetRow.find("td.result").text().trim();
|
|
70
|
+
const status = parseSubmitStatus(statusText);
|
|
71
|
+
if (status !== "WAITING" && status !== "JUDGING") {
|
|
72
|
+
const timeText = targetRow.find("td.time").text().trim();
|
|
73
|
+
const memoryText = targetRow.find("td.memory").text().trim();
|
|
74
|
+
const languageText = targetRow.find("td.language").text().trim();
|
|
75
|
+
const time = parseTime(timeText);
|
|
76
|
+
const memory = parseMemory(memoryText);
|
|
77
|
+
return {
|
|
78
|
+
problemId,
|
|
79
|
+
submitId: parseInt(
|
|
80
|
+
targetRow.find("td:first-child").text().trim(),
|
|
81
|
+
10
|
|
82
|
+
),
|
|
83
|
+
status,
|
|
84
|
+
time,
|
|
85
|
+
memory,
|
|
86
|
+
submittedAt: /* @__PURE__ */ new Date(),
|
|
87
|
+
language: languageText
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw new Error(
|
|
95
|
+
"\uC81C\uCD9C \uACB0\uACFC \uD655\uC778 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. BOJ \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC9C1\uC811 \uD655\uC778\uD574\uC8FC\uC138\uC694."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function parseSubmitStatus(statusText) {
|
|
99
|
+
const upper = statusText.toUpperCase();
|
|
100
|
+
if (upper.includes("ACCEPTED") || upper.includes("\uB9DE\uC558\uC2B5\uB2C8\uB2E4")) {
|
|
101
|
+
return "AC";
|
|
102
|
+
}
|
|
103
|
+
if (upper.includes("WRONG ANSWER") || upper.includes("\uD2C0\uB838\uC2B5\uB2C8\uB2E4")) {
|
|
104
|
+
return "WA";
|
|
105
|
+
}
|
|
106
|
+
if (upper.includes("TIME LIMIT") || upper.includes("\uC2DC\uAC04 \uCD08\uACFC")) {
|
|
107
|
+
return "TLE";
|
|
108
|
+
}
|
|
109
|
+
if (upper.includes("MEMORY LIMIT") || upper.includes("\uBA54\uBAA8\uB9AC \uCD08\uACFC")) {
|
|
110
|
+
return "MLE";
|
|
111
|
+
}
|
|
112
|
+
if (upper.includes("RUNTIME ERROR") || upper.includes("\uB7F0\uD0C0\uC784 \uC5D0\uB7EC")) {
|
|
113
|
+
return "RE";
|
|
114
|
+
}
|
|
115
|
+
if (upper.includes("COMPILE ERROR") || upper.includes("\uCEF4\uD30C\uC77C \uC5D0\uB7EC")) {
|
|
116
|
+
return "CE";
|
|
117
|
+
}
|
|
118
|
+
if (upper.includes("OUTPUT LIMIT") || upper.includes("\uCD9C\uB825 \uCD08\uACFC")) {
|
|
119
|
+
return "OLE";
|
|
120
|
+
}
|
|
121
|
+
if (upper.includes("PRESENTATION ERROR") || upper.includes("\uCD9C\uB825 \uD615\uC2DD")) {
|
|
122
|
+
return "PE";
|
|
123
|
+
}
|
|
124
|
+
if (upper.includes("WAITING") || upper.includes("\uAE30\uB2E4\uB9AC\uB294 \uC911")) {
|
|
125
|
+
return "WAITING";
|
|
126
|
+
}
|
|
127
|
+
if (upper.includes("JUDGING") || upper.includes("\uCC44\uC810 \uC911")) {
|
|
128
|
+
return "JUDGING";
|
|
129
|
+
}
|
|
130
|
+
return "WAITING";
|
|
131
|
+
}
|
|
132
|
+
function parseTime(timeText) {
|
|
133
|
+
const match = timeText.match(/(\d+)\s*ms/);
|
|
134
|
+
if (match) {
|
|
135
|
+
return parseInt(match[1], 10);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
function parseMemory(memoryText) {
|
|
140
|
+
const match = memoryText.match(/(\d+)\s*KB/);
|
|
141
|
+
if (match) {
|
|
142
|
+
return parseInt(match[1], 10);
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
async function getCsrfToken(problemId, sessionCookie) {
|
|
147
|
+
const response = await fetch(`${BOJ_BASE_URL}/submit/${problemId}`, {
|
|
148
|
+
headers: {
|
|
149
|
+
Cookie: sessionCookie,
|
|
150
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`\uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (HTTP ${response.status})`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const html = await response.text();
|
|
159
|
+
const $ = cheerio.load(html);
|
|
160
|
+
let csrfToken;
|
|
161
|
+
csrfToken = $('input[name="csrf_key"]').val()?.toString() || $('input[name="_csrf"]').val()?.toString() || $('input[name="csrf"]').val()?.toString() || $('input[type="hidden"][name*="csrf"]').val()?.toString();
|
|
162
|
+
if (!csrfToken) {
|
|
163
|
+
csrfToken = $('meta[name="csrf-token"]').attr("content") || $('meta[name="_csrf"]').attr("content") || $('meta[name="csrf"]').attr("content");
|
|
164
|
+
}
|
|
165
|
+
if (!csrfToken) {
|
|
166
|
+
const scriptTags = $("script").toArray();
|
|
167
|
+
for (const script of scriptTags) {
|
|
168
|
+
const scriptContent = $(script).html() || "";
|
|
169
|
+
const match = scriptContent.match(/csrf[_-]?key["\s:=]+["']([^"']+)["']/) || scriptContent.match(/csrf[_-]?token["\s:=]+["']([^"']+)["']/) || scriptContent.match(/window\.csrf[_-]?key\s*=\s*["']([^"']+)["']/);
|
|
170
|
+
if (match && match[1]) {
|
|
171
|
+
csrfToken = match[1];
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!csrfToken) {
|
|
177
|
+
const formAction = $('form[action*="submit"]').attr("action");
|
|
178
|
+
if (formAction) {
|
|
179
|
+
const match = formAction.match(/csrf[_-]?key=([^&]+)/);
|
|
180
|
+
if (match && match[1]) {
|
|
181
|
+
csrfToken = decodeURIComponent(match[1]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!csrfToken) {
|
|
186
|
+
const formHtml = $("form").html()?.substring(0, 500) || "";
|
|
187
|
+
throw new Error(
|
|
188
|
+
"CSRF \uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. BOJ \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC74C \uC704\uCE58\uB4E4\uC744 \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4:\n- input[name='csrf_key'], input[name='_csrf'], input[name='csrf']\n- meta[name='csrf-token'], meta[name='_csrf']\n- JavaScript \uBCC0\uC218 (csrf_key, csrf_token)\n- \uD3FC action URL\n\n\uC81C\uCD9C \uD398\uC774\uC9C0 HTML \uAD6C\uC870\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694."
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return String(csrfToken);
|
|
192
|
+
}
|
|
193
|
+
async function submitSolution({
|
|
194
|
+
problemId,
|
|
195
|
+
language,
|
|
196
|
+
sourceCode,
|
|
197
|
+
dryRun = false
|
|
198
|
+
}) {
|
|
199
|
+
if (dryRun) {
|
|
200
|
+
const langConfig2 = getLanguageConfig(language);
|
|
201
|
+
if (!langConfig2.bojLangId) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`\uC5B8\uC5B4 ${language}\uC5D0 \uB300\uD55C BOJ \uC5B8\uC5B4 ID\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
problemId,
|
|
208
|
+
status: "WAITING",
|
|
209
|
+
language,
|
|
210
|
+
message: `[DRY RUN] \uBB38\uC81C ${problemId}, \uC5B8\uC5B4: ${language} (BOJ ID: ${langConfig2.bojLangId}), \uCF54\uB4DC \uAE38\uC774: ${sourceCode.length}\uC790`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const sessionCookie = getBojSessionCookie();
|
|
214
|
+
if (!sessionCookie) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
"BOJ \uC138\uC158 \uCFE0\uD0A4\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n\n\u{1F4CB} \uCFE0\uD0A4 \uBCF5\uC0AC \uBC29\uBC95:\n1. \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C https://www.acmicpc.net \uC5D0 \uB85C\uADF8\uC778\n2. \uAC1C\uBC1C\uC790 \uB3C4\uAD6C(F12) \uC5F4\uAE30\n3. \uBC29\uBC95 A - Network \uD0ED \uC0AC\uC6A9 (\uCD94\uCC9C):\n - Network \uD0ED \uC5F4\uAE30 \u2192 \uD398\uC774\uC9C0 \uC0C8\uB85C\uACE0\uCE68(F5)\n - \uC544\uBB34 \uC694\uCCAD \uD074\uB9AD \u2192 Headers \uD0ED\n - Request Headers \uC139\uC158\uC5D0\uC11C 'Cookie:' \uAC12\uC744 \uBCF5\uC0AC\n (\uC804\uCCB4 Cookie \uD5E4\uB354 \uAC12 \uBCF5\uC0AC, \uC608: 'OnlineJudge=xxx; __ga=yyy; ...')\n\n4. \uBC29\uBC95 B - Application \uD0ED \uC0AC\uC6A9:\n - Application/\uC800\uC7A5\uC18C \uD0ED \u2192 Cookies \u2192 https://www.acmicpc.net\n - 'OnlineJudge' \uCFE0\uD0A4\uC758 Name\uACFC Value\uB97C \uBCF5\uC0AC\n - \uD615\uC2DD: 'OnlineJudge=\uAC12'\n\n\u{1F4A1} \uD301: Network \uD0ED\uC5D0\uC11C \uBCF5\uC0AC\uD558\uB294 \uAC83\uC774 \uAC00\uC7A5 \uC815\uD655\uD569\uB2C8\uB2E4!\n\n\u2699\uFE0F \uC124\uC815 \uBC29\uBC95:\n export PS_CLI_BOJ_COOKIE='\uBCF5\uC0AC\uD55C_\uCFE0\uD0A4_\uAC12'\n\n\uC608\uC2DC:\n export PS_CLI_BOJ_COOKIE='OnlineJudge=abc123; __ga=xyz789; ...'"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const langConfig = getLanguageConfig(language);
|
|
220
|
+
if (!langConfig.bojLangId) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`\uC5B8\uC5B4 ${language}\uC5D0 \uB300\uD55C BOJ \uC5B8\uC5B4 ID\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
let csrfToken;
|
|
226
|
+
try {
|
|
227
|
+
csrfToken = await getCsrfToken(problemId, sessionCookie);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.warn("CSRF \uD1A0\uD070\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C\uC744 \uACC4\uC18D \uC2DC\uB3C4\uD569\uB2C8\uB2E4...");
|
|
230
|
+
csrfToken = void 0;
|
|
231
|
+
}
|
|
232
|
+
const codeOpen = getCodeOpen() ? "open" : "close";
|
|
233
|
+
const formData = new URLSearchParams();
|
|
234
|
+
formData.append("problem_id", String(problemId));
|
|
235
|
+
formData.append("language", String(langConfig.bojLangId));
|
|
236
|
+
formData.append("code_open", codeOpen);
|
|
237
|
+
formData.append("source", sourceCode);
|
|
238
|
+
if (csrfToken) {
|
|
239
|
+
formData.append("csrf_key", csrfToken);
|
|
240
|
+
}
|
|
241
|
+
const submitResponse = await fetch(`${BOJ_BASE_URL}/submit/${problemId}`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
Cookie: sessionCookie,
|
|
245
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
246
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
247
|
+
Referer: `${BOJ_BASE_URL}/submit/${problemId}`
|
|
248
|
+
},
|
|
249
|
+
body: formData.toString(),
|
|
250
|
+
redirect: "manual"
|
|
251
|
+
// 리다이렉트를 수동으로 처리
|
|
252
|
+
});
|
|
253
|
+
const responseText = await submitResponse.text();
|
|
254
|
+
const $response = cheerio.load(responseText);
|
|
255
|
+
const errorMessage = $response(".alert-danger, .error, .warning").text().trim();
|
|
256
|
+
if (!submitResponse.ok) {
|
|
257
|
+
if (submitResponse.status === 401 || submitResponse.status === 403) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
"\uB85C\uADF8\uC778\uC774 \uD544\uC694\uD558\uAC70\uB098 \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCFE0\uD0A4\uB97C \uB2E4\uC2DC \uC124\uC815\uD574\uC8FC\uC138\uC694."
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const error = errorMessage || `HTTP ${submitResponse.status} ${submitResponse.statusText}`;
|
|
263
|
+
throw new Error(`\uC81C\uCD9C \uC2E4\uD328: ${error}`);
|
|
264
|
+
}
|
|
265
|
+
const location = submitResponse.headers.get("location");
|
|
266
|
+
let submitId;
|
|
267
|
+
if (location) {
|
|
268
|
+
const match = location.match(/\/status\/(\d+)/);
|
|
269
|
+
if (match) {
|
|
270
|
+
submitId = parseInt(match[1], 10);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!submitId) {
|
|
274
|
+
if (errorMessage) {
|
|
275
|
+
throw new Error(`\uC81C\uCD9C \uC2E4\uD328: ${errorMessage}`);
|
|
276
|
+
}
|
|
277
|
+
const statusResponse = await fetch(
|
|
278
|
+
`${BOJ_BASE_URL}/status?from_mine=1&problem_id=${problemId}`,
|
|
279
|
+
{
|
|
280
|
+
headers: {
|
|
281
|
+
Cookie: sessionCookie,
|
|
282
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
if (statusResponse.ok) {
|
|
287
|
+
const html = await statusResponse.text();
|
|
288
|
+
const $ = cheerio.load(html);
|
|
289
|
+
const firstRow = $("table#status-table tbody tr").first();
|
|
290
|
+
if (firstRow.length > 0) {
|
|
291
|
+
const idText = firstRow.find("td:first-child").text().trim();
|
|
292
|
+
submitId = parseInt(idText, 10);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!submitId) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"\uC81C\uCD9C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C ID\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uAC00\uB2A5\uD55C \uC6D0\uC778:\n- CSRF \uD1A0\uD070\uC774 \uD544\uC694\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4\n- \uB85C\uADF8\uC778 \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4\n- BOJ \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4\n\n\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC9C1\uC811 \uC81C\uCD9C\uD574\uBCF4\uC2DC\uACE0, \uBB38\uC81C\uAC00 \uACC4\uC18D\uB418\uBA74 \uC774\uC288\uB97C \uB4F1\uB85D\uD574\uC8FC\uC138\uC694."
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const result = await pollSubmitResult(problemId, submitId, sessionCookie);
|
|
302
|
+
return {
|
|
303
|
+
...result,
|
|
304
|
+
problemId,
|
|
305
|
+
submitId
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/commands/submit.tsx
|
|
310
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
311
|
+
function SubmitCommand({
|
|
312
|
+
problemId,
|
|
313
|
+
language,
|
|
314
|
+
sourcePath,
|
|
315
|
+
dryRun,
|
|
316
|
+
onComplete
|
|
317
|
+
}) {
|
|
318
|
+
const [status, setStatus] = useState(
|
|
319
|
+
"loading"
|
|
320
|
+
);
|
|
321
|
+
const [message, setMessage] = useState("\uC81C\uCD9C \uC900\uBE44 \uC911...");
|
|
322
|
+
const [result, setResult] = useState(null);
|
|
323
|
+
const [error, setError] = useState(null);
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
async function submit() {
|
|
326
|
+
try {
|
|
327
|
+
setMessage("\uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
|
|
328
|
+
const sourceCode = await readFile(sourcePath, "utf-8");
|
|
329
|
+
if (dryRun) {
|
|
330
|
+
setMessage("[DRY RUN] \uC81C\uCD9C \uAC80\uC99D \uC911...");
|
|
331
|
+
} else {
|
|
332
|
+
setMessage("BOJ\uC5D0 \uC81C\uCD9C\uD558\uB294 \uC911...");
|
|
333
|
+
}
|
|
334
|
+
const submitResult = await submitSolution({
|
|
335
|
+
problemId,
|
|
336
|
+
language,
|
|
337
|
+
sourceCode,
|
|
338
|
+
dryRun
|
|
339
|
+
});
|
|
340
|
+
setResult(submitResult);
|
|
341
|
+
setStatus("success");
|
|
342
|
+
setMessage("\uC81C\uCD9C \uC644\uB8CC");
|
|
343
|
+
setTimeout(() => {
|
|
344
|
+
onComplete();
|
|
345
|
+
}, 3e3);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
setStatus("error");
|
|
348
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
onComplete();
|
|
351
|
+
}, 3e3);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
submit();
|
|
355
|
+
}, [problemId, language, sourcePath, dryRun, onComplete]);
|
|
356
|
+
if (status === "loading") {
|
|
357
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(LoadingSpinner, { message }) });
|
|
358
|
+
}
|
|
359
|
+
if (status === "error") {
|
|
360
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
361
|
+
"\u2717 \uC81C\uCD9C \uC2E4\uD328: ",
|
|
362
|
+
error
|
|
363
|
+
] }) });
|
|
364
|
+
}
|
|
365
|
+
if (result) {
|
|
366
|
+
const statusColor = result.status === "AC" ? "green" : result.status === "WA" || result.status === "CE" || result.status === "RE" ? "red" : result.status === "TLE" || result.status === "MLE" ? "yellow" : "cyan";
|
|
367
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
368
|
+
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uC81C\uCD9C \uACB0\uACFC" }) }),
|
|
369
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
370
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
371
|
+
"\uBB38\uC81C: ",
|
|
372
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: result.problemId })
|
|
373
|
+
] }),
|
|
374
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
375
|
+
"\uC5B8\uC5B4: ",
|
|
376
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: result.language })
|
|
377
|
+
] }),
|
|
378
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
379
|
+
"\uC0C1\uD0DC:",
|
|
380
|
+
" ",
|
|
381
|
+
/* @__PURE__ */ jsx(Text, { color: statusColor, bold: true, children: result.status })
|
|
382
|
+
] }),
|
|
383
|
+
result.time !== null && result.time !== void 0 && /* @__PURE__ */ jsxs(Text, { children: [
|
|
384
|
+
"\uC2DC\uAC04: ",
|
|
385
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
386
|
+
result.time,
|
|
387
|
+
"ms"
|
|
388
|
+
] })
|
|
389
|
+
] }),
|
|
390
|
+
result.memory !== null && result.memory !== void 0 && /* @__PURE__ */ jsxs(Text, { children: [
|
|
391
|
+
"\uBA54\uBAA8\uB9AC: ",
|
|
392
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
393
|
+
result.memory,
|
|
394
|
+
"KB"
|
|
395
|
+
] })
|
|
396
|
+
] }),
|
|
397
|
+
result.message && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: result.message }) })
|
|
398
|
+
] })
|
|
399
|
+
] });
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
async function detectSolutionFile(problemDir) {
|
|
404
|
+
const files = await readdir(problemDir);
|
|
405
|
+
const solutionFile = files.find((f) => f.startsWith("solution."));
|
|
406
|
+
if (!solutionFile) {
|
|
407
|
+
throw new Error("solution.* \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
408
|
+
}
|
|
409
|
+
return join(problemDir, solutionFile);
|
|
410
|
+
}
|
|
411
|
+
async function submitCommand(problemId, language, dryRun = false) {
|
|
412
|
+
const currentPathProblemId = detectProblemIdFromPath(process.cwd());
|
|
413
|
+
const problemDir = currentPathProblemId === problemId ? process.cwd() : join(process.cwd(), "problems", String(problemId));
|
|
414
|
+
const sourcePath = await detectSolutionFile(problemDir);
|
|
415
|
+
const detectedLanguage = language ?? detectLanguageFromFile(sourcePath);
|
|
416
|
+
if (!detectedLanguage) {
|
|
417
|
+
throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4: ${sourcePath}`);
|
|
418
|
+
}
|
|
419
|
+
return new Promise((resolve) => {
|
|
420
|
+
const { unmount } = render(
|
|
421
|
+
/* @__PURE__ */ jsx(
|
|
422
|
+
SubmitCommand,
|
|
423
|
+
{
|
|
424
|
+
problemId,
|
|
425
|
+
language: detectedLanguage,
|
|
426
|
+
sourcePath,
|
|
427
|
+
dryRun,
|
|
428
|
+
onComplete: () => {
|
|
429
|
+
unmount();
|
|
430
|
+
resolve();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
var submitHelp = `
|
|
438
|
+
\uC0AC\uC6A9\uBC95:
|
|
439
|
+
$ ps submit [\uBB38\uC81C\uBC88\uD638] [\uC635\uC158]
|
|
440
|
+
|
|
441
|
+
\uC124\uBA85:
|
|
442
|
+
\uD604\uC7AC \uBB38\uC81C\uC758 \uC194\uB8E8\uC158 \uD30C\uC77C\uC744 BOJ\uC5D0 \uC81C\uCD9C\uD569\uB2C8\uB2E4.
|
|
443
|
+
- \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC \uB610\uB294 \uC9C0\uC815\uD55C \uBB38\uC81C \uBC88\uD638\uC758 \uC194\uB8E8\uC158 \uD30C\uC77C \uC81C\uCD9C
|
|
444
|
+
- solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
|
|
445
|
+
- \uC81C\uCD9C \uD6C4 \uCC44\uC810 \uACB0\uACFC\uB97C \uC790\uB3D9\uC73C\uB85C \uD655\uC778
|
|
446
|
+
|
|
447
|
+
\uC635\uC158:
|
|
448
|
+
--language, -l \uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
|
|
449
|
+
\uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}
|
|
450
|
+
--dry-run \uC2E4\uC81C \uC81C\uCD9C \uC5C6\uC774 \uAC80\uC99D\uB9CC \uC218\uD589
|
|
451
|
+
|
|
452
|
+
\uC608\uC81C:
|
|
453
|
+
$ ps submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C
|
|
454
|
+
$ ps submit 1000 # 1000\uBC88 \uBB38\uC81C \uC81C\uCD9C
|
|
455
|
+
$ ps submit --language python # Python\uC73C\uB85C \uC81C\uCD9C
|
|
456
|
+
`;
|
|
457
|
+
async function submitExecute(args, flags) {
|
|
458
|
+
if (flags.help) {
|
|
459
|
+
console.log(submitHelp.trim());
|
|
460
|
+
process.exit(0);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const problemId = getProblemId(args);
|
|
464
|
+
if (problemId === null) {
|
|
465
|
+
console.error("\uC624\uB958: \uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
|
|
466
|
+
console.error(`\uC0AC\uC6A9\uBC95: ps submit [\uBB38\uC81C\uBC88\uD638] [\uC635\uC158]`);
|
|
467
|
+
console.error(`\uB3C4\uC6C0\uB9D0: ps submit --help`);
|
|
468
|
+
console.error(
|
|
469
|
+
`\uD78C\uD2B8: problems/{\uBB38\uC81C\uBC88\uD638} \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uBB38\uC81C \uBC88\uD638\uB97C \uCD94\uB860\uD569\uB2C8\uB2E4.`
|
|
470
|
+
);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
const validLanguages = getSupportedLanguages();
|
|
474
|
+
const language = flags.language;
|
|
475
|
+
if (flags.language && language && !validLanguages.includes(language)) {
|
|
476
|
+
console.error(
|
|
477
|
+
`\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4. (${getSupportedLanguagesString()})`
|
|
478
|
+
);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
await submitCommand(problemId, language, Boolean(flags["dry-run"]));
|
|
482
|
+
}
|
|
483
|
+
var submitCommandDef = {
|
|
484
|
+
name: "submit",
|
|
485
|
+
help: submitHelp,
|
|
486
|
+
execute: submitExecute
|
|
487
|
+
};
|
|
488
|
+
var submit_default = submitCommandDef;
|
|
489
|
+
export {
|
|
490
|
+
submit_default as default,
|
|
491
|
+
submitExecute,
|
|
492
|
+
submitHelp
|
|
493
|
+
};
|