@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,606 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getProblem,
|
|
4
|
+
getTierColor,
|
|
5
|
+
getTierImageUrl,
|
|
6
|
+
getTierName,
|
|
7
|
+
source_default
|
|
8
|
+
} from "../chunk-2E4VSP6O.js";
|
|
9
|
+
import {
|
|
10
|
+
getAutoOpenEditor,
|
|
11
|
+
getEditor
|
|
12
|
+
} from "../chunk-KFQFQJYT.js";
|
|
13
|
+
import {
|
|
14
|
+
getProblemId
|
|
15
|
+
} from "../chunk-OOTPZD7O.js";
|
|
16
|
+
import {
|
|
17
|
+
getLanguageConfig,
|
|
18
|
+
getSupportedLanguages,
|
|
19
|
+
getSupportedLanguagesString
|
|
20
|
+
} from "../chunk-TQXMB7XV.js";
|
|
21
|
+
import {
|
|
22
|
+
LoadingSpinner
|
|
23
|
+
} from "../chunk-IJLJBKLK.js";
|
|
24
|
+
import "../chunk-FYS2JH42.js";
|
|
25
|
+
|
|
26
|
+
// src/commands/fetch.tsx
|
|
27
|
+
import { useState, useEffect } from "react";
|
|
28
|
+
import { render, Text as Text2, Box as Box2 } from "ink";
|
|
29
|
+
|
|
30
|
+
// src/services/scraper.ts
|
|
31
|
+
import * as cheerio from "cheerio";
|
|
32
|
+
var BOJ_BASE_URL = "https://www.acmicpc.net";
|
|
33
|
+
function htmlToMarkdown($, element) {
|
|
34
|
+
if (element.length === 0) return "";
|
|
35
|
+
let result = "";
|
|
36
|
+
const contents = element.contents();
|
|
37
|
+
if (contents.length === 0) {
|
|
38
|
+
return element.text().trim();
|
|
39
|
+
}
|
|
40
|
+
contents.each((_, node) => {
|
|
41
|
+
if (node.type === "text") {
|
|
42
|
+
const text = node.data || "";
|
|
43
|
+
if (text.trim()) {
|
|
44
|
+
result += text;
|
|
45
|
+
}
|
|
46
|
+
} else if (node.type === "tag") {
|
|
47
|
+
const tagName = node.name.toLowerCase();
|
|
48
|
+
const $node = $(node);
|
|
49
|
+
switch (tagName) {
|
|
50
|
+
case "sup":
|
|
51
|
+
result += `^${htmlToMarkdown($, $node)}`;
|
|
52
|
+
break;
|
|
53
|
+
case "sub":
|
|
54
|
+
result += `<sub>${htmlToMarkdown($, $node)}</sub>`;
|
|
55
|
+
break;
|
|
56
|
+
case "strong":
|
|
57
|
+
case "b":
|
|
58
|
+
result += `**${htmlToMarkdown($, $node)}**`;
|
|
59
|
+
break;
|
|
60
|
+
case "em":
|
|
61
|
+
case "i":
|
|
62
|
+
result += `*${htmlToMarkdown($, $node)}*`;
|
|
63
|
+
break;
|
|
64
|
+
case "br":
|
|
65
|
+
result += "\n";
|
|
66
|
+
break;
|
|
67
|
+
case "p":
|
|
68
|
+
const pContent = htmlToMarkdown($, $node);
|
|
69
|
+
if (pContent) {
|
|
70
|
+
result += pContent + "\n\n";
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case "div":
|
|
74
|
+
const divContent = htmlToMarkdown($, $node);
|
|
75
|
+
if (divContent) {
|
|
76
|
+
result += divContent + "\n";
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case "span":
|
|
80
|
+
result += htmlToMarkdown($, $node);
|
|
81
|
+
break;
|
|
82
|
+
case "code":
|
|
83
|
+
result += `\`${htmlToMarkdown($, $node)}\``;
|
|
84
|
+
break;
|
|
85
|
+
case "pre":
|
|
86
|
+
const preContent = htmlToMarkdown($, $node);
|
|
87
|
+
if (preContent) {
|
|
88
|
+
result += `
|
|
89
|
+
\`\`\`
|
|
90
|
+
${preContent}
|
|
91
|
+
\`\`\`
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case "ul":
|
|
96
|
+
case "ol":
|
|
97
|
+
$node.find("li").each((i, li) => {
|
|
98
|
+
const liContent = htmlToMarkdown($, $(li));
|
|
99
|
+
if (liContent) {
|
|
100
|
+
result += `- ${liContent}
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case "li":
|
|
106
|
+
result += htmlToMarkdown($, $node);
|
|
107
|
+
break;
|
|
108
|
+
case "img":
|
|
109
|
+
const imgSrc = $node.attr("src") || "";
|
|
110
|
+
const imgAlt = $node.attr("alt") || "";
|
|
111
|
+
if (imgSrc) {
|
|
112
|
+
let imageUrl = imgSrc;
|
|
113
|
+
if (imgSrc.startsWith("/")) {
|
|
114
|
+
imageUrl = `${BOJ_BASE_URL}${imgSrc}`;
|
|
115
|
+
} else if (!imgSrc.startsWith("http") && !imgSrc.startsWith("data:")) {
|
|
116
|
+
imageUrl = `${BOJ_BASE_URL}/${imgSrc}`;
|
|
117
|
+
}
|
|
118
|
+
result += ``;
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
result += htmlToMarkdown($, $node);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return result.trim();
|
|
127
|
+
}
|
|
128
|
+
async function scrapeProblem(problemId) {
|
|
129
|
+
const url = `${BOJ_BASE_URL}/problem/${problemId}`;
|
|
130
|
+
const response = await fetch(url, {
|
|
131
|
+
headers: {
|
|
132
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
throw new Error(`Failed to fetch problem page: HTTP ${response.status}`);
|
|
137
|
+
}
|
|
138
|
+
const html = await response.text();
|
|
139
|
+
const $ = cheerio.load(html);
|
|
140
|
+
const title = $("#problem_title").text().trim();
|
|
141
|
+
const descriptionEl = $("#problem_description");
|
|
142
|
+
let description = "";
|
|
143
|
+
if (descriptionEl.length > 0) {
|
|
144
|
+
description = htmlToMarkdown($, descriptionEl).trim();
|
|
145
|
+
if (!description) {
|
|
146
|
+
description = descriptionEl.text().trim();
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
const altDesc = $('[id*="description"]').first();
|
|
150
|
+
if (altDesc.length > 0) {
|
|
151
|
+
description = altDesc.text().trim();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const inputEl = $("#problem_input");
|
|
155
|
+
let inputFormat = "";
|
|
156
|
+
if (inputEl.length > 0) {
|
|
157
|
+
inputFormat = htmlToMarkdown($, inputEl).trim();
|
|
158
|
+
if (!inputFormat) {
|
|
159
|
+
inputFormat = inputEl.text().trim();
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
const altInput = $('[id*="input"]').first();
|
|
163
|
+
if (altInput.length > 0) {
|
|
164
|
+
inputFormat = altInput.text().trim();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const outputEl = $("#problem_output");
|
|
168
|
+
let outputFormat = "";
|
|
169
|
+
if (outputEl.length > 0) {
|
|
170
|
+
outputFormat = htmlToMarkdown($, outputEl).trim();
|
|
171
|
+
if (!outputFormat) {
|
|
172
|
+
outputFormat = outputEl.text().trim();
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
const altOutput = $('[id*="output"]').first();
|
|
176
|
+
if (altOutput.length > 0) {
|
|
177
|
+
outputFormat = altOutput.text().trim();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const problemInfo = {};
|
|
181
|
+
const problemInfoTable = $("#problem-info");
|
|
182
|
+
const tableInResponsive = $(".table-responsive table");
|
|
183
|
+
const targetTable = problemInfoTable.length > 0 ? problemInfoTable : tableInResponsive;
|
|
184
|
+
if (targetTable.length > 0) {
|
|
185
|
+
const headerRow = targetTable.find("thead tr");
|
|
186
|
+
const dataRow = targetTable.find("tbody tr");
|
|
187
|
+
if (headerRow.length > 0 && dataRow.length > 0) {
|
|
188
|
+
const headers = headerRow.find("th").map((_, th) => $(th).text().trim()).get();
|
|
189
|
+
const values = dataRow.find("td").map((_, td) => $(td).text().trim()).get();
|
|
190
|
+
headers.forEach((header, index) => {
|
|
191
|
+
if (values[index]) {
|
|
192
|
+
problemInfo[header] = values[index];
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
targetTable.find("tr").each((_, row) => {
|
|
197
|
+
const tds = $(row).find("td");
|
|
198
|
+
if (tds.length >= 2) {
|
|
199
|
+
const label = $(tds[0]).text().trim();
|
|
200
|
+
const value = $(tds[1]).text().trim();
|
|
201
|
+
problemInfo[label] = value;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const timeLimit = problemInfo["\uC2DC\uAC04 \uC81C\uD55C"] || problemInfo["Time Limit"] || void 0;
|
|
207
|
+
const memoryLimit = problemInfo["\uBA54\uBAA8\uB9AC \uC81C\uD55C"] || problemInfo["Memory Limit"] || void 0;
|
|
208
|
+
const submissions = problemInfo["\uC81C\uCD9C"] || problemInfo["Submit"] || void 0;
|
|
209
|
+
const accepted = problemInfo["\uC815\uB2F5"] || problemInfo["Accepted"] || void 0;
|
|
210
|
+
const acceptedUsers = problemInfo["\uB9DE\uD78C \uC0AC\uB78C"] || problemInfo["Accepted Users"] || void 0;
|
|
211
|
+
const acceptedRate = problemInfo["\uC815\uB2F5 \uBE44\uC728"] || problemInfo["Accepted Rate"] || void 0;
|
|
212
|
+
const testCases = [];
|
|
213
|
+
const sampleInputs = $(".sampledata").filter((_, el) => {
|
|
214
|
+
const id = $(el).attr("id");
|
|
215
|
+
return id?.startsWith("sample-input-") ?? false;
|
|
216
|
+
});
|
|
217
|
+
sampleInputs.each((_, el) => {
|
|
218
|
+
const inputId = $(el).attr("id");
|
|
219
|
+
if (!inputId) return;
|
|
220
|
+
const match = inputId.match(/sample-input-(\d+)/);
|
|
221
|
+
if (!match) return;
|
|
222
|
+
const sampleNumber = match[1];
|
|
223
|
+
const outputId = `sample-output-${sampleNumber}`;
|
|
224
|
+
const outputEl2 = $(`#${outputId}`);
|
|
225
|
+
if (outputEl2.length > 0) {
|
|
226
|
+
testCases.push({
|
|
227
|
+
input: $(el).text(),
|
|
228
|
+
output: outputEl2.text()
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
if (testCases.length === 0) {
|
|
233
|
+
$("pre").each((_, el) => {
|
|
234
|
+
const text = $(el).text().trim();
|
|
235
|
+
const prevText = $(el).prev().text().toLowerCase();
|
|
236
|
+
if (prevText.includes("\uC785\uB825") || prevText.includes("input")) {
|
|
237
|
+
const nextPre = $(el).next("pre");
|
|
238
|
+
if (nextPre.length > 0) {
|
|
239
|
+
testCases.push({
|
|
240
|
+
input: text,
|
|
241
|
+
output: nextPre.text().trim()
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (!title) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`\uBB38\uC81C ${problemId}\uC758 \uC81C\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. BOJ \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uAC70\uB098 \uBB38\uC81C\uAC00 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (!description && !inputFormat && !outputFormat) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`\uBB38\uC81C ${problemId}\uC758 \uB0B4\uC6A9\uC744 \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. BOJ \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uAC70\uB098 API \uC81C\uD55C\uC5D0 \uAC78\uB838\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
title,
|
|
259
|
+
description,
|
|
260
|
+
inputFormat,
|
|
261
|
+
outputFormat,
|
|
262
|
+
testCases,
|
|
263
|
+
timeLimit,
|
|
264
|
+
memoryLimit,
|
|
265
|
+
submissions,
|
|
266
|
+
accepted,
|
|
267
|
+
acceptedUsers,
|
|
268
|
+
acceptedRate
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/services/file-generator.ts
|
|
273
|
+
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
274
|
+
import { join, dirname } from "path";
|
|
275
|
+
import { fileURLToPath } from "url";
|
|
276
|
+
function parseTimeLimitToMs(timeLimit) {
|
|
277
|
+
if (!timeLimit) return void 0;
|
|
278
|
+
const match = timeLimit.match(/([\d.]+)/);
|
|
279
|
+
if (!match) return void 0;
|
|
280
|
+
const seconds = parseFloat(match[1]);
|
|
281
|
+
if (Number.isNaN(seconds)) return void 0;
|
|
282
|
+
return Math.round(seconds * 1e3);
|
|
283
|
+
}
|
|
284
|
+
function getProjectRoot() {
|
|
285
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
286
|
+
const __dirname = dirname(__filename);
|
|
287
|
+
if (__dirname.includes("dist")) {
|
|
288
|
+
return join(__dirname, "../..");
|
|
289
|
+
}
|
|
290
|
+
return join(__dirname, "../..");
|
|
291
|
+
}
|
|
292
|
+
async function generateProblemFiles(problem, language = "python") {
|
|
293
|
+
const problemDir = join(process.cwd(), "problems", problem.id.toString());
|
|
294
|
+
await mkdir(problemDir, { recursive: true });
|
|
295
|
+
const langConfig = getLanguageConfig(language);
|
|
296
|
+
const projectRoot = getProjectRoot();
|
|
297
|
+
const templatePath = join(projectRoot, "templates", langConfig.templateFile);
|
|
298
|
+
const solutionPath = join(problemDir, `solution.${langConfig.extension}`);
|
|
299
|
+
try {
|
|
300
|
+
const templateContent = await readFile(templatePath, "utf-8");
|
|
301
|
+
await writeFile(solutionPath, templateContent, "utf-8");
|
|
302
|
+
} catch (error) {
|
|
303
|
+
await writeFile(
|
|
304
|
+
solutionPath,
|
|
305
|
+
`// Problem ${problem.id}: ${problem.title}
|
|
306
|
+
`,
|
|
307
|
+
"utf-8"
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
for (let i = 0; i < problem.testCases.length; i++) {
|
|
311
|
+
const testCase = problem.testCases[i];
|
|
312
|
+
const inputPath = join(problemDir, `input${i + 1}.txt`);
|
|
313
|
+
const outputPath = join(problemDir, `output${i + 1}.txt`);
|
|
314
|
+
await writeFile(inputPath, testCase.input, "utf-8");
|
|
315
|
+
await writeFile(outputPath, testCase.output, "utf-8");
|
|
316
|
+
}
|
|
317
|
+
const tierName = getTierName(problem.level);
|
|
318
|
+
const tierImageUrl = getTierImageUrl(problem.level);
|
|
319
|
+
const tags = problem.tags.length > 0 ? problem.tags.join(", ") : "\uC5C6\uC74C";
|
|
320
|
+
const headers = [];
|
|
321
|
+
const values = [];
|
|
322
|
+
headers.push("\uB09C\uC774\uB3C4");
|
|
323
|
+
values.push(`<img src="${tierImageUrl}" alt="${tierName}" width="20" />`);
|
|
324
|
+
if (problem.timeLimit) {
|
|
325
|
+
headers.push("\uC2DC\uAC04 \uC81C\uD55C");
|
|
326
|
+
values.push(problem.timeLimit);
|
|
327
|
+
}
|
|
328
|
+
if (problem.memoryLimit) {
|
|
329
|
+
headers.push("\uBA54\uBAA8\uB9AC \uC81C\uD55C");
|
|
330
|
+
values.push(problem.memoryLimit);
|
|
331
|
+
}
|
|
332
|
+
if (problem.submissions) {
|
|
333
|
+
headers.push("\uC81C\uCD9C");
|
|
334
|
+
values.push(problem.submissions);
|
|
335
|
+
}
|
|
336
|
+
if (problem.accepted) {
|
|
337
|
+
headers.push("\uC815\uB2F5");
|
|
338
|
+
values.push(problem.accepted);
|
|
339
|
+
}
|
|
340
|
+
if (problem.acceptedUsers) {
|
|
341
|
+
headers.push("\uB9DE\uD78C \uC0AC\uB78C");
|
|
342
|
+
values.push(problem.acceptedUsers);
|
|
343
|
+
}
|
|
344
|
+
if (problem.acceptedRate) {
|
|
345
|
+
headers.push("\uC815\uB2F5 \uBE44\uC728");
|
|
346
|
+
values.push(problem.acceptedRate);
|
|
347
|
+
}
|
|
348
|
+
let infoTable = "";
|
|
349
|
+
if (headers.length > 0) {
|
|
350
|
+
const headerRow = `| ${headers.join(" | ")} |`;
|
|
351
|
+
const separatorRow = `|${headers.map(() => "---").join("|")}|`;
|
|
352
|
+
const valueRow = `| ${values.join(" | ")} |`;
|
|
353
|
+
infoTable = `
|
|
354
|
+
${headerRow}
|
|
355
|
+
${separatorRow}
|
|
356
|
+
${valueRow}
|
|
357
|
+
`;
|
|
358
|
+
}
|
|
359
|
+
const readmeContent = `# [${problem.id}: ${problem.title}](https://www.acmicpc.net/problem/${problem.id})
|
|
360
|
+
|
|
361
|
+
${infoTable}## \uBB38\uC81C \uC124\uBA85
|
|
362
|
+
${problem.description || "\uC124\uBA85 \uC5C6\uC74C"}
|
|
363
|
+
|
|
364
|
+
## \uC785\uB825
|
|
365
|
+
${problem.inputFormat || "\uC785\uB825 \uD615\uC2DD \uC5C6\uC74C"}
|
|
366
|
+
|
|
367
|
+
## \uCD9C\uB825
|
|
368
|
+
${problem.outputFormat || "\uCD9C\uB825 \uD615\uC2DD \uC5C6\uC74C"}
|
|
369
|
+
|
|
370
|
+
## \uC608\uC81C
|
|
371
|
+
${problem.testCases.map(
|
|
372
|
+
(tc, i) => `### \uC608\uC81C ${i + 1}
|
|
373
|
+
|
|
374
|
+
**\uC785\uB825:**
|
|
375
|
+
\`\`\`
|
|
376
|
+
${tc.input.trimEnd()}
|
|
377
|
+
\`\`\`
|
|
378
|
+
|
|
379
|
+
**\uCD9C\uB825:**
|
|
380
|
+
\`\`\`
|
|
381
|
+
${tc.output.trimEnd()}
|
|
382
|
+
\`\`\`
|
|
383
|
+
`
|
|
384
|
+
).join("\n")}
|
|
385
|
+
|
|
386
|
+
## \uD0DC\uADF8
|
|
387
|
+
${tags}
|
|
388
|
+
`;
|
|
389
|
+
const readmePath = join(problemDir, "README.md");
|
|
390
|
+
await writeFile(readmePath, readmeContent, "utf-8");
|
|
391
|
+
const meta = {
|
|
392
|
+
id: problem.id,
|
|
393
|
+
title: problem.title,
|
|
394
|
+
level: problem.level,
|
|
395
|
+
timeLimit: problem.timeLimit,
|
|
396
|
+
timeLimitMs: parseTimeLimitToMs(problem.timeLimit),
|
|
397
|
+
memoryLimit: problem.memoryLimit
|
|
398
|
+
};
|
|
399
|
+
const metaPath = join(problemDir, "meta.json");
|
|
400
|
+
await writeFile(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
401
|
+
return problemDir;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/components/problem-dashboard.tsx
|
|
405
|
+
import { Box, Text } from "ink";
|
|
406
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
407
|
+
function ProblemDashboard({ problem }) {
|
|
408
|
+
const tierName = getTierName(problem.level);
|
|
409
|
+
const tierColor = getTierColor(problem.level);
|
|
410
|
+
return /* @__PURE__ */ jsx(
|
|
411
|
+
Box,
|
|
412
|
+
{
|
|
413
|
+
flexDirection: "column",
|
|
414
|
+
borderStyle: "round",
|
|
415
|
+
borderColor: tierColor,
|
|
416
|
+
paddingX: 1,
|
|
417
|
+
alignSelf: "flex-start",
|
|
418
|
+
children: /* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
419
|
+
source_default.hex(tierColor)(tierName),
|
|
420
|
+
" ",
|
|
421
|
+
/* @__PURE__ */ jsxs(Text, { color: "white", children: [
|
|
422
|
+
"#",
|
|
423
|
+
problem.id,
|
|
424
|
+
": ",
|
|
425
|
+
problem.title
|
|
426
|
+
] })
|
|
427
|
+
] })
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/commands/fetch.tsx
|
|
433
|
+
import { execaCommand } from "execa";
|
|
434
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
435
|
+
function FetchCommand({
|
|
436
|
+
problemId,
|
|
437
|
+
language = "python",
|
|
438
|
+
onComplete
|
|
439
|
+
}) {
|
|
440
|
+
const [status, setStatus] = useState(
|
|
441
|
+
"loading"
|
|
442
|
+
);
|
|
443
|
+
const [problem, setProblem] = useState(null);
|
|
444
|
+
const [error, setError] = useState(null);
|
|
445
|
+
const [message, setMessage] = useState("\uBB38\uC81C \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
async function fetchProblem() {
|
|
448
|
+
try {
|
|
449
|
+
setMessage("Solved.ac\uC5D0\uC11C \uBB38\uC81C \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
|
|
450
|
+
const solvedAcData = await getProblem(problemId);
|
|
451
|
+
setMessage("BOJ\uC5D0\uC11C \uBB38\uC81C \uC0C1\uC138 \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
|
|
452
|
+
const scrapedData = await scrapeProblem(problemId);
|
|
453
|
+
if (!scrapedData.title && !solvedAcData.titleKo) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`\uBB38\uC81C ${problemId}\uC758 \uC81C\uBAA9\uC744 \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC81C\uAC00 \uC874\uC7AC\uD558\uC9C0 \uC54A\uAC70\uB098 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
const combinedProblem = {
|
|
459
|
+
id: problemId,
|
|
460
|
+
title: solvedAcData.titleKo || scrapedData.title,
|
|
461
|
+
level: solvedAcData.level,
|
|
462
|
+
tier: getTierName(solvedAcData.level),
|
|
463
|
+
tags: solvedAcData.tags.map(
|
|
464
|
+
(tag) => tag.displayNames.find((d) => d.language === "ko")?.name || tag.displayNames[0]?.name || tag.key
|
|
465
|
+
),
|
|
466
|
+
timeLimit: scrapedData.timeLimit,
|
|
467
|
+
memoryLimit: scrapedData.memoryLimit,
|
|
468
|
+
submissions: scrapedData.submissions,
|
|
469
|
+
accepted: scrapedData.accepted,
|
|
470
|
+
acceptedUsers: scrapedData.acceptedUsers,
|
|
471
|
+
acceptedRate: scrapedData.acceptedRate,
|
|
472
|
+
description: scrapedData.description,
|
|
473
|
+
inputFormat: scrapedData.inputFormat,
|
|
474
|
+
outputFormat: scrapedData.outputFormat,
|
|
475
|
+
testCases: scrapedData.testCases
|
|
476
|
+
};
|
|
477
|
+
setProblem(combinedProblem);
|
|
478
|
+
setMessage("\uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uB294 \uC911...");
|
|
479
|
+
const problemDir = await generateProblemFiles(
|
|
480
|
+
combinedProblem,
|
|
481
|
+
language
|
|
482
|
+
);
|
|
483
|
+
setStatus("success");
|
|
484
|
+
setMessage(`\u2713 \uBB38\uC81C \uD30C\uC77C\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${problemDir}`);
|
|
485
|
+
if (getAutoOpenEditor()) {
|
|
486
|
+
try {
|
|
487
|
+
const editor = getEditor();
|
|
488
|
+
await execaCommand(`${editor} ${problemDir}`, {
|
|
489
|
+
shell: true,
|
|
490
|
+
detached: true,
|
|
491
|
+
stdio: "ignore"
|
|
492
|
+
});
|
|
493
|
+
setMessage(
|
|
494
|
+
`\u2713 \uBB38\uC81C \uD30C\uC77C\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${problemDir}
|
|
495
|
+
\u2713 ${editor}\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.`
|
|
496
|
+
);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
console.warn(
|
|
499
|
+
`\uC5D0\uB514\uD130\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${err instanceof Error ? err.message : String(err)}`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
setTimeout(() => {
|
|
504
|
+
onComplete?.();
|
|
505
|
+
}, 2e3);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
setStatus("error");
|
|
508
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
509
|
+
setTimeout(() => {
|
|
510
|
+
onComplete?.();
|
|
511
|
+
}, 2e3);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
fetchProblem();
|
|
515
|
+
}, [problemId, language, onComplete]);
|
|
516
|
+
if (status === "loading") {
|
|
517
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
518
|
+
/* @__PURE__ */ jsx2(LoadingSpinner, { message }),
|
|
519
|
+
problem && /* @__PURE__ */ jsx2(ProblemDashboard, { problem })
|
|
520
|
+
] });
|
|
521
|
+
}
|
|
522
|
+
if (status === "error") {
|
|
523
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
524
|
+
"\u2717 \uC624\uB958 \uBC1C\uC0DD: ",
|
|
525
|
+
error
|
|
526
|
+
] }) });
|
|
527
|
+
}
|
|
528
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", children: [
|
|
529
|
+
problem && /* @__PURE__ */ jsx2(Box2, { alignSelf: "flex-start", children: /* @__PURE__ */ jsx2(ProblemDashboard, { problem }) }),
|
|
530
|
+
/* @__PURE__ */ jsx2(Text2, { color: "green", children: message })
|
|
531
|
+
] });
|
|
532
|
+
}
|
|
533
|
+
async function fetchCommand(problemId, language) {
|
|
534
|
+
return new Promise((resolve) => {
|
|
535
|
+
const { unmount } = render(
|
|
536
|
+
/* @__PURE__ */ jsx2(
|
|
537
|
+
FetchCommand,
|
|
538
|
+
{
|
|
539
|
+
problemId,
|
|
540
|
+
language,
|
|
541
|
+
onComplete: () => {
|
|
542
|
+
unmount();
|
|
543
|
+
resolve();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
var fetchHelp = `
|
|
551
|
+
\uC0AC\uC6A9\uBC95:
|
|
552
|
+
$ ps fetch <\uBB38\uC81C\uBC88\uD638> [\uC635\uC158]
|
|
553
|
+
|
|
554
|
+
\uC124\uBA85:
|
|
555
|
+
\uBC31\uC900 \uBB38\uC81C\uB97C \uAC00\uC838\uC640\uC11C \uB85C\uCEEC\uC5D0 \uD30C\uC77C\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
|
|
556
|
+
- Solved.ac API\uC640 BOJ \uD06C\uB864\uB9C1\uC744 \uD1B5\uD574 \uBB38\uC81C \uC815\uBCF4 \uC218\uC9D1
|
|
557
|
+
- \uBB38\uC81C \uC124\uBA85, \uC785\uCD9C\uB825 \uD615\uC2DD, \uC608\uC81C \uC785\uCD9C\uB825 \uD30C\uC77C \uC790\uB3D9 \uC0DD\uC131
|
|
558
|
+
- \uC120\uD0DD\uD55C \uC5B8\uC5B4\uC758 \uC194\uB8E8\uC158 \uD15C\uD50C\uB9BF \uD30C\uC77C \uC0DD\uC131
|
|
559
|
+
- README.md\uC5D0 \uBB38\uC81C \uC815\uBCF4, \uD1B5\uACC4, \uD0DC\uADF8 \uB4F1 \uD3EC\uD568
|
|
560
|
+
|
|
561
|
+
\uC635\uC158:
|
|
562
|
+
--language, -l \uC5B8\uC5B4 \uC120\uD0DD (${getSupportedLanguagesString()})
|
|
563
|
+
\uAE30\uBCF8\uAC12: python
|
|
564
|
+
|
|
565
|
+
\uC608\uC81C:
|
|
566
|
+
$ ps fetch 1000
|
|
567
|
+
$ ps fetch 1000 --language python
|
|
568
|
+
$ ps fetch 1000 -l cpp
|
|
569
|
+
`;
|
|
570
|
+
async function fetchExecute(args, flags) {
|
|
571
|
+
if (flags.help) {
|
|
572
|
+
console.log(fetchHelp.trim());
|
|
573
|
+
process.exit(0);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const problemId = getProblemId(args);
|
|
577
|
+
if (problemId === null) {
|
|
578
|
+
console.error("\uC624\uB958: \uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
|
|
579
|
+
console.error(`\uC0AC\uC6A9\uBC95: ps fetch <\uBB38\uC81C\uBC88\uD638> [\uC635\uC158]`);
|
|
580
|
+
console.error(`\uB3C4\uC6C0\uB9D0: ps fetch --help`);
|
|
581
|
+
console.error(
|
|
582
|
+
`\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.`
|
|
583
|
+
);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
const validLanguages = getSupportedLanguages();
|
|
587
|
+
const language = flags.language;
|
|
588
|
+
if (language && !validLanguages.includes(language)) {
|
|
589
|
+
console.error(
|
|
590
|
+
`\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4. (${getSupportedLanguagesString()})`
|
|
591
|
+
);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
await fetchCommand(problemId, language || "python");
|
|
595
|
+
}
|
|
596
|
+
var fetchCommandDef = {
|
|
597
|
+
name: "fetch",
|
|
598
|
+
help: fetchHelp,
|
|
599
|
+
execute: fetchExecute
|
|
600
|
+
};
|
|
601
|
+
var fetch_default = fetchCommandDef;
|
|
602
|
+
export {
|
|
603
|
+
fetch_default as default,
|
|
604
|
+
fetchExecute,
|
|
605
|
+
fetchHelp
|
|
606
|
+
};
|