@rhseung/ps-cli 1.8.0 → 1.9.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.
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getProblem,
4
+ scrapeProblem
5
+ } from "./chunk-YH6VDCNG.js";
6
+ import {
7
+ Command,
8
+ CommandBuilder,
9
+ CommandDef,
10
+ __decorateClass,
11
+ defineFlags,
12
+ getAutoOpenEditor,
13
+ getEditor,
14
+ getIncludeTag,
15
+ getLanguageConfig,
16
+ getSolvingDirPath,
17
+ getSupportedLanguages,
18
+ getSupportedLanguagesString,
19
+ getTierColor,
20
+ getTierImageUrl,
21
+ getTierName,
22
+ logger,
23
+ parseTimeLimitToMs,
24
+ resolveProblemContext
25
+ } from "./chunk-JZK2464U.js";
26
+
27
+ // src/commands/fetch.tsx
28
+ import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
29
+ import { Box as Box2 } from "ink";
30
+
31
+ // src/components/problem-dashboard.tsx
32
+ import { Box, Text } from "ink";
33
+ import { jsx, jsxs } from "react/jsx-runtime";
34
+ function ProblemDashboard({ problem }) {
35
+ const tierName = getTierName(problem.level);
36
+ const tierColor = getTierColor(problem.level);
37
+ const borderColorString = typeof tierColor === "string" ? tierColor : "#ff7ca8";
38
+ const textColorString = borderColorString;
39
+ return /* @__PURE__ */ jsx(
40
+ Box,
41
+ {
42
+ flexDirection: "column",
43
+ borderStyle: "round",
44
+ borderColor: borderColorString,
45
+ paddingX: 1,
46
+ alignSelf: "flex-start",
47
+ children: /* @__PURE__ */ jsxs(Text, { bold: true, color: textColorString, children: [
48
+ tierName,
49
+ " ",
50
+ /* @__PURE__ */ jsxs(Text, { color: "white", children: [
51
+ "#",
52
+ problem.id,
53
+ ": ",
54
+ problem.title
55
+ ] })
56
+ ] })
57
+ }
58
+ );
59
+ }
60
+
61
+ // src/hooks/use-fetch-problem.ts
62
+ import { execaCommand } from "execa";
63
+ import { useEffect, useState } from "react";
64
+
65
+ // src/services/file-generator.ts
66
+ import { existsSync } from "fs";
67
+ import { mkdir, writeFile, readFile } from "fs/promises";
68
+ import { join, dirname } from "path";
69
+ import { fileURLToPath } from "url";
70
+ function ensureTrailingNewline(content) {
71
+ if (!content || content.trim().length === 0) {
72
+ return content;
73
+ }
74
+ const trimmed = content.trimEnd();
75
+ if (trimmed.length === 0) {
76
+ return content;
77
+ }
78
+ const lines = trimmed.split("\n");
79
+ const lastLine = lines[lines.length - 1];
80
+ const isListItem = /^[\s]*[-*]\s/.test(lastLine) || /^[\s]*\d+[.)]\s/.test(lastLine);
81
+ const isTableRow = /^\s*\|.+\|\s*$/.test(lastLine);
82
+ const isCodeBlock = trimmed.endsWith("```");
83
+ const isImage = /!\[.*\]\(.*\)$/.test(trimmed);
84
+ if (isListItem || isTableRow || isCodeBlock || isImage) {
85
+ return trimmed + "\n\n";
86
+ }
87
+ if (!content.endsWith("\n")) {
88
+ return content + "\n\n";
89
+ }
90
+ if (!content.endsWith("\n\n")) {
91
+ return content + "\n";
92
+ }
93
+ return content;
94
+ }
95
+ function getProjectRoot() {
96
+ const __filename = fileURLToPath(import.meta.url);
97
+ const __dirname = dirname(__filename);
98
+ let current = __dirname;
99
+ while (current !== dirname(current)) {
100
+ if (existsSync(join(current, "templates"))) {
101
+ return current;
102
+ }
103
+ current = dirname(current);
104
+ }
105
+ return join(__dirname, "../..");
106
+ }
107
+ async function generateProblemFiles(problem, language = "python") {
108
+ const problemDir = getSolvingDirPath(problem.id, process.cwd(), problem);
109
+ await mkdir(problemDir, { recursive: true });
110
+ const langConfig = getLanguageConfig(language);
111
+ const projectRoot = getProjectRoot();
112
+ const templatePath = join(projectRoot, "templates", langConfig.templateFile);
113
+ const solutionPath = join(problemDir, `solution.${langConfig.extension}`);
114
+ try {
115
+ const templateContent = await readFile(templatePath, "utf-8");
116
+ await writeFile(solutionPath, templateContent, "utf-8");
117
+ } catch {
118
+ await writeFile(
119
+ solutionPath,
120
+ `// Problem ${problem.id}: ${problem.title}
121
+ `,
122
+ "utf-8"
123
+ );
124
+ }
125
+ const testcasesDir = join(problemDir, "testcases");
126
+ for (let i = 0; i < problem.testCases.length; i++) {
127
+ const testCase = problem.testCases[i];
128
+ const caseDir = join(testcasesDir, String(i + 1));
129
+ await mkdir(caseDir, { recursive: true });
130
+ const inputPath = join(caseDir, "input.txt");
131
+ const outputPath = join(caseDir, "output.txt");
132
+ await writeFile(inputPath, testCase.input, "utf-8");
133
+ await writeFile(outputPath, testCase.output, "utf-8");
134
+ }
135
+ const tierName = getTierName(problem.level);
136
+ const tierImageUrl = getTierImageUrl(problem.level);
137
+ const tags = problem.tags.length > 0 ? problem.tags.join(", ") : "\uC5C6\uC74C";
138
+ const includeTag = getIncludeTag();
139
+ const headers = [];
140
+ const values = [];
141
+ headers.push("\uB09C\uC774\uB3C4");
142
+ values.push(`<img src="${tierImageUrl}" alt="${tierName}" width="20" />`);
143
+ if (problem.timeLimit) {
144
+ headers.push("\uC2DC\uAC04 \uC81C\uD55C");
145
+ values.push(problem.timeLimit);
146
+ }
147
+ if (problem.memoryLimit) {
148
+ headers.push("\uBA54\uBAA8\uB9AC \uC81C\uD55C");
149
+ values.push(problem.memoryLimit);
150
+ }
151
+ if (problem.submissions) {
152
+ headers.push("\uC81C\uCD9C");
153
+ values.push(problem.submissions);
154
+ }
155
+ if (problem.accepted) {
156
+ headers.push("\uC815\uB2F5");
157
+ values.push(problem.accepted);
158
+ }
159
+ if (problem.acceptedUsers) {
160
+ headers.push("\uB9DE\uD78C \uC0AC\uB78C");
161
+ values.push(problem.acceptedUsers);
162
+ }
163
+ if (problem.acceptedRate) {
164
+ headers.push("\uC815\uB2F5 \uBE44\uC728");
165
+ values.push(problem.acceptedRate);
166
+ }
167
+ let infoTable = "";
168
+ if (headers.length > 0) {
169
+ const headerRow = `| ${headers.join(" | ")} |`;
170
+ const separatorRow = `|${headers.map(() => "---").join("|")}|`;
171
+ const valueRow = `| ${values.join(" | ")} |`;
172
+ infoTable = `
173
+ ${headerRow}
174
+ ${separatorRow}
175
+ ${valueRow}
176
+ `;
177
+ }
178
+ const description = ensureTrailingNewline(problem.description || "\uC124\uBA85 \uC5C6\uC74C");
179
+ const inputFormat = ensureTrailingNewline(
180
+ problem.inputFormat || "\uC785\uB825 \uD615\uC2DD \uC5C6\uC74C"
181
+ );
182
+ const outputFormat = ensureTrailingNewline(
183
+ problem.outputFormat || "\uCD9C\uB825 \uD615\uC2DD \uC5C6\uC74C"
184
+ );
185
+ const readmeContent = `
186
+ # [${problem.id}: ${problem.title}](https://www.acmicpc.net/problem/${problem.id})
187
+
188
+ ${infoTable.trim()}
189
+
190
+ ## \uBB38\uC81C \uC124\uBA85
191
+
192
+ ${description.trim()}
193
+
194
+ ## \uC785\uB825
195
+
196
+ ${inputFormat.trim()}
197
+
198
+ ## \uCD9C\uB825
199
+
200
+ ${outputFormat.trim()}
201
+
202
+ ## \uC608\uC81C
203
+
204
+ ${problem.testCases.map(
205
+ (tc, i) => `
206
+ ### \uC608\uC81C ${i + 1}
207
+
208
+ **\uC785\uB825:**
209
+
210
+ \`\`\`text
211
+ ${tc.input.trim()}
212
+ \`\`\`
213
+
214
+ **\uCD9C\uB825:**
215
+
216
+ \`\`\`text
217
+ ${tc.output.trim()}
218
+ \`\`\`
219
+ `.trim()
220
+ ).join("\n\n")}
221
+ ${includeTag ? `
222
+ ## \uD0DC\uADF8
223
+
224
+ ${tags.trim()}
225
+ ` : ""}`.trim() + "\n";
226
+ const readmePath = join(problemDir, "README.md");
227
+ await writeFile(readmePath, readmeContent, "utf-8");
228
+ const meta = {
229
+ id: problem.id,
230
+ title: problem.title,
231
+ level: problem.level,
232
+ tags: problem.tags,
233
+ timeLimit: problem.timeLimit,
234
+ timeLimitMs: parseTimeLimitToMs(problem.timeLimit),
235
+ memoryLimit: problem.memoryLimit
236
+ };
237
+ const metaPath = join(problemDir, "meta.json");
238
+ await writeFile(metaPath, JSON.stringify(meta, null, 2), "utf-8");
239
+ return problemDir;
240
+ }
241
+
242
+ // src/hooks/use-fetch-problem.ts
243
+ function useFetchProblem({
244
+ problemId,
245
+ language,
246
+ onComplete
247
+ }) {
248
+ const [status, setStatus] = useState(
249
+ "loading"
250
+ );
251
+ const [problem, setProblem] = useState(null);
252
+ const [error, setError] = useState(null);
253
+ const [message, setMessage] = useState("\uBB38\uC81C \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
254
+ useEffect(() => {
255
+ async function fetchProblem() {
256
+ try {
257
+ setMessage("Solved.ac\uC5D0\uC11C \uBB38\uC81C \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
258
+ const solvedAcData = await getProblem(problemId);
259
+ setMessage("BOJ\uC5D0\uC11C \uBB38\uC81C \uC0C1\uC138 \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
260
+ const scrapedData = await scrapeProblem(problemId);
261
+ if (!scrapedData.title && !solvedAcData.titleKo) {
262
+ throw new Error(
263
+ `\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.`
264
+ );
265
+ }
266
+ const combinedProblem = {
267
+ id: problemId,
268
+ title: solvedAcData.titleKo || scrapedData.title,
269
+ level: solvedAcData.level,
270
+ tier: getTierName(solvedAcData.level),
271
+ tags: solvedAcData.tags.map(
272
+ (tag) => tag.displayNames.find((d) => d.language === "ko")?.name || tag.displayNames[0]?.name || tag.key
273
+ ),
274
+ timeLimit: scrapedData.timeLimit,
275
+ memoryLimit: scrapedData.memoryLimit,
276
+ submissions: scrapedData.submissions,
277
+ accepted: scrapedData.accepted,
278
+ acceptedUsers: scrapedData.acceptedUsers,
279
+ acceptedRate: scrapedData.acceptedRate,
280
+ description: scrapedData.description,
281
+ inputFormat: scrapedData.inputFormat,
282
+ outputFormat: scrapedData.outputFormat,
283
+ testCases: scrapedData.testCases
284
+ };
285
+ setProblem(combinedProblem);
286
+ setMessage("\uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uB294 \uC911...");
287
+ const problemDir = await generateProblemFiles(
288
+ combinedProblem,
289
+ language
290
+ );
291
+ setStatus("success");
292
+ setMessage(`\uBB38\uC81C \uD30C\uC77C\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${problemDir}`);
293
+ if (getAutoOpenEditor()) {
294
+ try {
295
+ const editor = getEditor();
296
+ await execaCommand(`${editor} ${problemDir}`, {
297
+ shell: true,
298
+ detached: true,
299
+ stdio: "ignore"
300
+ });
301
+ setMessage(
302
+ `\uBB38\uC81C \uD30C\uC77C\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${problemDir}
303
+ ${editor}\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.`
304
+ );
305
+ } catch (err) {
306
+ console.warn(
307
+ `\uC5D0\uB514\uD130\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${err instanceof Error ? err.message : String(err)}`
308
+ );
309
+ }
310
+ }
311
+ setTimeout(() => {
312
+ onComplete?.();
313
+ }, 2e3);
314
+ } catch (err) {
315
+ setStatus("error");
316
+ setError(err instanceof Error ? err.message : String(err));
317
+ setTimeout(() => {
318
+ onComplete?.();
319
+ }, 2e3);
320
+ }
321
+ }
322
+ void fetchProblem();
323
+ }, [problemId, language, onComplete]);
324
+ return {
325
+ status,
326
+ problem,
327
+ error,
328
+ message
329
+ };
330
+ }
331
+
332
+ // src/commands/fetch.tsx
333
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
334
+ var fetchFlagsSchema = {
335
+ language: {
336
+ type: "string",
337
+ shortFlag: "l",
338
+ description: `\uC5B8\uC5B4 \uC120\uD0DD (${getSupportedLanguagesString()})
339
+ \uAE30\uBCF8\uAC12: python`
340
+ }
341
+ };
342
+ function FetchView({
343
+ problemId,
344
+ language = "python",
345
+ onComplete
346
+ }) {
347
+ const { status, problem, error, message } = useFetchProblem({
348
+ problemId,
349
+ language,
350
+ onComplete
351
+ });
352
+ if (status === "loading") {
353
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
354
+ /* @__PURE__ */ jsx2(Spinner, { label: message }),
355
+ problem && /* @__PURE__ */ jsx2(ProblemDashboard, { problem })
356
+ ] });
357
+ }
358
+ if (status === "error") {
359
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
360
+ "\uC624\uB958 \uBC1C\uC0DD: ",
361
+ error
362
+ ] }) });
363
+ }
364
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", children: [
365
+ problem && /* @__PURE__ */ jsx2(Box2, { alignSelf: "flex-start", children: /* @__PURE__ */ jsx2(ProblemDashboard, { problem }) }),
366
+ /* @__PURE__ */ jsx2(StatusMessage, { variant: "success", children: message })
367
+ ] });
368
+ }
369
+ var FetchCommand = class extends Command {
370
+ async execute(args, flags) {
371
+ const context = await resolveProblemContext(args, { requireId: true });
372
+ if (context.problemId === null) {
373
+ logger.error("\uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
374
+ console.log(`\uB3C4\uC6C0\uB9D0: ps fetch --help`);
375
+ process.exit(1);
376
+ return;
377
+ }
378
+ const validLanguages = getSupportedLanguages();
379
+ const language = flags.language;
380
+ if (language && !validLanguages.includes(language)) {
381
+ console.error(
382
+ `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4. (${getSupportedLanguagesString()})`
383
+ );
384
+ process.exit(1);
385
+ return;
386
+ }
387
+ await this.renderView(FetchView, {
388
+ problemId: context.problemId,
389
+ language: language || "python"
390
+ });
391
+ }
392
+ };
393
+ FetchCommand = __decorateClass([
394
+ CommandDef({
395
+ name: "fetch",
396
+ description: `\uBC31\uC900 \uBB38\uC81C\uB97C \uAC00\uC838\uC640\uC11C \uB85C\uCEEC\uC5D0 \uD30C\uC77C\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
397
+ - Solved.ac API\uC640 BOJ \uD06C\uB864\uB9C1\uC744 \uD1B5\uD574 \uBB38\uC81C \uC815\uBCF4 \uC218\uC9D1
398
+ - \uBB38\uC81C \uC124\uBA85, \uC785\uCD9C\uB825 \uD615\uC2DD, \uC608\uC81C \uC785\uCD9C\uB825 \uD30C\uC77C \uC790\uB3D9 \uC0DD\uC131
399
+ - \uC120\uD0DD\uD55C \uC5B8\uC5B4\uC758 \uC194\uB8E8\uC158 \uD15C\uD50C\uB9BF \uD30C\uC77C \uC0DD\uC131
400
+ - README.md\uC5D0 \uBB38\uC81C \uC815\uBCF4, \uD1B5\uACC4, \uD0DC\uADF8(\uC124\uC815 \uC2DC) \uB4F1 \uD3EC\uD568
401
+ - \uAE30\uBCF8 \uC5B8\uC5B4, \uC5D0\uB514\uD130 \uC124\uC815 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
402
+ flags: defineFlags(fetchFlagsSchema),
403
+ autoDetectProblemId: false,
404
+ requireProblemId: true,
405
+ examples: ["fetch 1000", "fetch 1000 --language python", "fetch 1000 -l cpp"]
406
+ })
407
+ ], FetchCommand);
408
+ var fetch_default = CommandBuilder.fromClass(FetchCommand);
409
+
410
+ export {
411
+ FetchView,
412
+ FetchCommand,
413
+ fetch_default
414
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getLanguageConfig
4
- } from "./chunk-7MQMPJ3X.js";
4
+ } from "./chunk-JZK2464U.js";
5
5
 
6
6
  // src/services/runner.ts
7
7
  import { readFile } from "fs/promises";