@rhseung/ps-cli 1.3.3 → 1.5.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.
@@ -1,273 +1,71 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getProblem,
3
+ scrapeProblem
4
+ } from "../chunk-AG6KWWHS.js";
5
+ import {
6
+ getProblem
7
+ } from "../chunk-A6STXEAE.js";
8
+ import {
4
9
  getTierColor,
5
10
  getTierImageUrl,
6
11
  getTierName,
7
12
  source_default
8
- } from "../chunk-2E4VSP6O.js";
9
- import {
10
- getLanguageConfig,
11
- getSupportedLanguages,
12
- getSupportedLanguagesString
13
- } from "../chunk-TQXMB7XV.js";
13
+ } from "../chunk-HDNNR5OY.js";
14
14
  import {
15
+ Command,
16
+ CommandBuilder,
17
+ CommandDef,
18
+ getAutoOpenEditor,
19
+ getEditor,
15
20
  getProblemDirPath,
16
21
  getProblemId
17
- } from "../chunk-6ENX5K3C.js";
22
+ } from "../chunk-7SVCS23X.js";
18
23
  import {
19
- getAutoOpenEditor,
20
- getEditor
21
- } from "../chunk-PNIGP6LX.js";
22
- import "../chunk-FYS2JH42.js";
24
+ __decorateClass,
25
+ getLanguageConfig,
26
+ getSupportedLanguages,
27
+ getSupportedLanguagesString
28
+ } from "../chunk-7MQMPJ3X.js";
23
29
 
24
30
  // src/commands/fetch.tsx
25
- import { useState, useEffect } from "react";
26
- import { render, Box as Box2 } from "ink";
27
31
  import { StatusMessage, Alert } from "@inkjs/ui";
32
+ import { Spinner } from "@inkjs/ui";
33
+ import { Box as Box2 } from "ink";
28
34
 
29
- // src/services/scraper.ts
30
- import * as cheerio from "cheerio";
31
- var BOJ_BASE_URL = "https://www.acmicpc.net";
32
- function htmlToMarkdown($, element) {
33
- if (element.length === 0) return "";
34
- let result = "";
35
- const contents = element.contents();
36
- if (contents.length === 0) {
37
- return element.text().trim();
38
- }
39
- contents.each((_, node) => {
40
- if (node.type === "text") {
41
- const text = node.data || "";
42
- if (text.trim()) {
43
- result += text;
44
- }
45
- } else if (node.type === "tag") {
46
- const tagName = node.name.toLowerCase();
47
- const $node = $(node);
48
- switch (tagName) {
49
- case "sup":
50
- result += `^${htmlToMarkdown($, $node)}`;
51
- break;
52
- case "sub":
53
- result += `<sub>${htmlToMarkdown($, $node)}</sub>`;
54
- break;
55
- case "strong":
56
- case "b":
57
- result += `**${htmlToMarkdown($, $node)}**`;
58
- break;
59
- case "em":
60
- case "i":
61
- result += `*${htmlToMarkdown($, $node)}*`;
62
- break;
63
- case "br":
64
- result += "\n";
65
- break;
66
- case "p":
67
- const pContent = htmlToMarkdown($, $node);
68
- if (pContent) {
69
- result += pContent + "\n\n";
70
- }
71
- break;
72
- case "div":
73
- const divContent = htmlToMarkdown($, $node);
74
- if (divContent) {
75
- result += divContent + "\n";
76
- }
77
- break;
78
- case "span":
79
- result += htmlToMarkdown($, $node);
80
- break;
81
- case "code":
82
- result += `\`${htmlToMarkdown($, $node)}\``;
83
- break;
84
- case "pre":
85
- const preContent = htmlToMarkdown($, $node);
86
- if (preContent) {
87
- result += `
88
- \`\`\`
89
- ${preContent}
90
- \`\`\`
91
- `;
92
- }
93
- break;
94
- case "ul":
95
- case "ol":
96
- $node.find("li").each((i, li) => {
97
- const liContent = htmlToMarkdown($, $(li));
98
- if (liContent) {
99
- result += `- ${liContent}
100
- `;
101
- }
102
- });
103
- break;
104
- case "li":
105
- result += htmlToMarkdown($, $node);
106
- break;
107
- case "img":
108
- const imgSrc = $node.attr("src") || "";
109
- const imgAlt = $node.attr("alt") || "";
110
- if (imgSrc) {
111
- let imageUrl = imgSrc;
112
- if (imgSrc.startsWith("/")) {
113
- imageUrl = `${BOJ_BASE_URL}${imgSrc}`;
114
- } else if (!imgSrc.startsWith("http") && !imgSrc.startsWith("data:")) {
115
- imageUrl = `${BOJ_BASE_URL}/${imgSrc}`;
116
- }
117
- result += `![${imgAlt}](${imageUrl})`;
118
- }
119
- break;
120
- default:
121
- result += htmlToMarkdown($, $node);
122
- }
35
+ // src/components/problem-dashboard.tsx
36
+ import { Box, Text, Transform } from "ink";
37
+ import { jsx, jsxs } from "react/jsx-runtime";
38
+ function ProblemDashboard({ problem }) {
39
+ const tierName = getTierName(problem.level);
40
+ const tierColor = getTierColor(problem.level);
41
+ const tierColorFn = typeof tierColor === "string" ? source_default.hex(tierColor) : tierColor.multiline;
42
+ const borderColorString = typeof tierColor === "string" ? tierColor : "#ff7ca8";
43
+ return /* @__PURE__ */ jsx(Transform, { transform: (output) => tierColorFn(output), children: /* @__PURE__ */ jsx(
44
+ Box,
45
+ {
46
+ flexDirection: "column",
47
+ borderStyle: "round",
48
+ borderColor: borderColorString,
49
+ paddingX: 1,
50
+ alignSelf: "flex-start",
51
+ children: /* @__PURE__ */ jsxs(Text, { bold: true, children: [
52
+ tierName,
53
+ " ",
54
+ /* @__PURE__ */ jsxs(Text, { color: "white", children: [
55
+ "#",
56
+ problem.id,
57
+ ": ",
58
+ problem.title
59
+ ] })
60
+ ] })
123
61
  }
124
- });
125
- return result.trim();
126
- }
127
- async function scrapeProblem(problemId) {
128
- const url = `${BOJ_BASE_URL}/problem/${problemId}`;
129
- const response = await fetch(url, {
130
- headers: {
131
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
132
- }
133
- });
134
- if (!response.ok) {
135
- throw new Error(`Failed to fetch problem page: HTTP ${response.status}`);
136
- }
137
- const html = await response.text();
138
- const $ = cheerio.load(html);
139
- const title = $("#problem_title").text().trim();
140
- const descriptionEl = $("#problem_description");
141
- let description = "";
142
- if (descriptionEl.length > 0) {
143
- description = htmlToMarkdown($, descriptionEl).trim();
144
- if (!description) {
145
- description = descriptionEl.text().trim();
146
- }
147
- } else {
148
- const altDesc = $('[id*="description"]').first();
149
- if (altDesc.length > 0) {
150
- description = altDesc.text().trim();
151
- }
152
- }
153
- const inputEl = $("#problem_input");
154
- let inputFormat = "";
155
- if (inputEl.length > 0) {
156
- inputFormat = htmlToMarkdown($, inputEl).trim();
157
- if (!inputFormat) {
158
- inputFormat = inputEl.text().trim();
159
- }
160
- } else {
161
- const altInput = $('[id*="input"]').first();
162
- if (altInput.length > 0) {
163
- inputFormat = altInput.text().trim();
164
- }
165
- }
166
- const outputEl = $("#problem_output");
167
- let outputFormat = "";
168
- if (outputEl.length > 0) {
169
- outputFormat = htmlToMarkdown($, outputEl).trim();
170
- if (!outputFormat) {
171
- outputFormat = outputEl.text().trim();
172
- }
173
- } else {
174
- const altOutput = $('[id*="output"]').first();
175
- if (altOutput.length > 0) {
176
- outputFormat = altOutput.text().trim();
177
- }
178
- }
179
- const problemInfo = {};
180
- const problemInfoTable = $("#problem-info");
181
- const tableInResponsive = $(".table-responsive table");
182
- const targetTable = problemInfoTable.length > 0 ? problemInfoTable : tableInResponsive;
183
- if (targetTable.length > 0) {
184
- const headerRow = targetTable.find("thead tr");
185
- const dataRow = targetTable.find("tbody tr");
186
- if (headerRow.length > 0 && dataRow.length > 0) {
187
- const headers = headerRow.find("th").map((_, th) => $(th).text().trim()).get();
188
- const values = dataRow.find("td").map((_, td) => $(td).text().trim()).get();
189
- headers.forEach((header, index) => {
190
- if (values[index]) {
191
- problemInfo[header] = values[index];
192
- }
193
- });
194
- } else {
195
- targetTable.find("tr").each((_, row) => {
196
- const tds = $(row).find("td");
197
- if (tds.length >= 2) {
198
- const label = $(tds[0]).text().trim();
199
- const value = $(tds[1]).text().trim();
200
- problemInfo[label] = value;
201
- }
202
- });
203
- }
204
- }
205
- const timeLimit = problemInfo["\uC2DC\uAC04 \uC81C\uD55C"] || problemInfo["Time Limit"] || void 0;
206
- const memoryLimit = problemInfo["\uBA54\uBAA8\uB9AC \uC81C\uD55C"] || problemInfo["Memory Limit"] || void 0;
207
- const submissions = problemInfo["\uC81C\uCD9C"] || problemInfo["Submit"] || void 0;
208
- const accepted = problemInfo["\uC815\uB2F5"] || problemInfo["Accepted"] || void 0;
209
- const acceptedUsers = problemInfo["\uB9DE\uD78C \uC0AC\uB78C"] || problemInfo["Accepted Users"] || void 0;
210
- const acceptedRate = problemInfo["\uC815\uB2F5 \uBE44\uC728"] || problemInfo["Accepted Rate"] || void 0;
211
- const testCases = [];
212
- const sampleInputs = $(".sampledata").filter((_, el) => {
213
- const id = $(el).attr("id");
214
- return id?.startsWith("sample-input-") ?? false;
215
- });
216
- sampleInputs.each((_, el) => {
217
- const inputId = $(el).attr("id");
218
- if (!inputId) return;
219
- const match = inputId.match(/sample-input-(\d+)/);
220
- if (!match) return;
221
- const sampleNumber = match[1];
222
- const outputId = `sample-output-${sampleNumber}`;
223
- const outputEl2 = $(`#${outputId}`);
224
- if (outputEl2.length > 0) {
225
- testCases.push({
226
- input: $(el).text(),
227
- output: outputEl2.text()
228
- });
229
- }
230
- });
231
- if (testCases.length === 0) {
232
- $("pre").each((_, el) => {
233
- const text = $(el).text().trim();
234
- const prevText = $(el).prev().text().toLowerCase();
235
- if (prevText.includes("\uC785\uB825") || prevText.includes("input")) {
236
- const nextPre = $(el).next("pre");
237
- if (nextPre.length > 0) {
238
- testCases.push({
239
- input: text,
240
- output: nextPre.text().trim()
241
- });
242
- }
243
- }
244
- });
245
- }
246
- if (!title) {
247
- throw new Error(
248
- `\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.`
249
- );
250
- }
251
- if (!description && !inputFormat && !outputFormat) {
252
- throw new Error(
253
- `\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.`
254
- );
255
- }
256
- return {
257
- title,
258
- description,
259
- inputFormat,
260
- outputFormat,
261
- testCases,
262
- timeLimit,
263
- memoryLimit,
264
- submissions,
265
- accepted,
266
- acceptedUsers,
267
- acceptedRate
268
- };
62
+ ) });
269
63
  }
270
64
 
65
+ // src/hooks/use-fetch-problem.ts
66
+ import { execaCommand } from "execa";
67
+ import { useEffect, useState } from "react";
68
+
271
69
  // src/services/file-generator.ts
272
70
  import { mkdir, writeFile, readFile } from "fs/promises";
273
71
  import { join, dirname } from "path";
@@ -298,7 +96,7 @@ async function generateProblemFiles(problem, language = "python") {
298
96
  try {
299
97
  const templateContent = await readFile(templatePath, "utf-8");
300
98
  await writeFile(solutionPath, templateContent, "utf-8");
301
- } catch (error) {
99
+ } catch {
302
100
  await writeFile(
303
101
  solutionPath,
304
102
  `// Problem ${problem.id}: ${problem.title}
@@ -400,41 +198,10 @@ ${tags}
400
198
  return problemDir;
401
199
  }
402
200
 
403
- // src/components/problem-dashboard.tsx
404
- import { Box, Text } from "ink";
405
- import { jsx, jsxs } from "react/jsx-runtime";
406
- function ProblemDashboard({ problem }) {
407
- const tierName = getTierName(problem.level);
408
- const tierColor = getTierColor(problem.level);
409
- return /* @__PURE__ */ jsx(
410
- Box,
411
- {
412
- flexDirection: "column",
413
- borderStyle: "round",
414
- borderColor: tierColor,
415
- paddingX: 1,
416
- alignSelf: "flex-start",
417
- children: /* @__PURE__ */ jsxs(Text, { bold: true, children: [
418
- source_default.hex(tierColor)(tierName),
419
- " ",
420
- /* @__PURE__ */ jsxs(Text, { color: "white", children: [
421
- "#",
422
- problem.id,
423
- ": ",
424
- problem.title
425
- ] })
426
- ] })
427
- }
428
- );
429
- }
430
-
431
- // src/commands/fetch.tsx
432
- import { Spinner } from "@inkjs/ui";
433
- import { execaCommand } from "execa";
434
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
435
- function FetchCommand({
201
+ // src/hooks/use-fetch-problem.ts
202
+ function useFetchProblem({
436
203
  problemId,
437
- language = "python",
204
+ language,
438
205
  onComplete
439
206
  }) {
440
207
  const [status, setStatus] = useState(
@@ -511,8 +278,28 @@ ${editor}\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.`
511
278
  }, 2e3);
512
279
  }
513
280
  }
514
- fetchProblem();
281
+ void fetchProblem();
515
282
  }, [problemId, language, onComplete]);
283
+ return {
284
+ status,
285
+ problem,
286
+ error,
287
+ message
288
+ };
289
+ }
290
+
291
+ // src/commands/fetch.tsx
292
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
293
+ function FetchView({
294
+ problemId,
295
+ language = "python",
296
+ onComplete
297
+ }) {
298
+ const { status, problem, error, message } = useFetchProblem({
299
+ problemId,
300
+ language,
301
+ onComplete
302
+ });
516
303
  if (status === "loading") {
517
304
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
518
305
  /* @__PURE__ */ jsx2(Spinner, { label: message }),
@@ -530,77 +317,59 @@ ${editor}\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.`
530
317
  /* @__PURE__ */ jsx2(StatusMessage, { variant: "success", children: message })
531
318
  ] });
532
319
  }
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);
320
+ var FetchCommand = class extends Command {
321
+ async execute(args, flags) {
322
+ const problemId = getProblemId(args);
323
+ if (problemId === null) {
324
+ console.error("\uC624\uB958: \uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
325
+ console.error(`\uC0AC\uC6A9\uBC95: ps fetch <\uBB38\uC81C\uBC88\uD638> [\uC635\uC158]`);
326
+ console.error(`\uB3C4\uC6C0\uB9D0: ps fetch --help`);
327
+ console.error(
328
+ `\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.`
329
+ );
330
+ process.exit(1);
331
+ return;
332
+ }
333
+ const validLanguages = getSupportedLanguages();
334
+ const language = flags.language;
335
+ if (language && !validLanguages.includes(language)) {
336
+ console.error(
337
+ `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4. (${getSupportedLanguagesString()})`
338
+ );
339
+ process.exit(1);
340
+ return;
341
+ }
342
+ await this.renderView(FetchView, {
343
+ problemId,
344
+ language: language || "python"
345
+ });
593
346
  }
594
- await fetchCommand(problemId, language || "python");
595
- }
596
- var fetchCommandDef = {
597
- name: "fetch",
598
- help: fetchHelp,
599
- execute: fetchExecute
600
347
  };
601
- var fetch_default = fetchCommandDef;
348
+ FetchCommand = __decorateClass([
349
+ CommandDef({
350
+ name: "fetch",
351
+ description: `\uBC31\uC900 \uBB38\uC81C\uB97C \uAC00\uC838\uC640\uC11C \uB85C\uCEEC\uC5D0 \uD30C\uC77C\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
352
+ - Solved.ac API\uC640 BOJ \uD06C\uB864\uB9C1\uC744 \uD1B5\uD574 \uBB38\uC81C \uC815\uBCF4 \uC218\uC9D1
353
+ - \uBB38\uC81C \uC124\uBA85, \uC785\uCD9C\uB825 \uD615\uC2DD, \uC608\uC81C \uC785\uCD9C\uB825 \uD30C\uC77C \uC790\uB3D9 \uC0DD\uC131
354
+ - \uC120\uD0DD\uD55C \uC5B8\uC5B4\uC758 \uC194\uB8E8\uC158 \uD15C\uD50C\uB9BF \uD30C\uC77C \uC0DD\uC131
355
+ - README.md\uC5D0 \uBB38\uC81C \uC815\uBCF4, \uD1B5\uACC4, \uD0DC\uADF8 \uB4F1 \uD3EC\uD568`,
356
+ flags: [
357
+ {
358
+ name: "language",
359
+ options: {
360
+ shortFlag: "l",
361
+ description: `\uC5B8\uC5B4 \uC120\uD0DD (${getSupportedLanguagesString()})
362
+ \uAE30\uBCF8\uAC12: python`
363
+ }
364
+ }
365
+ ],
366
+ autoDetectProblemId: false,
367
+ requireProblemId: true,
368
+ examples: ["fetch 1000", "fetch 1000 --language python", "fetch 1000 -l cpp"]
369
+ })
370
+ ], FetchCommand);
371
+ var fetch_default = CommandBuilder.fromClass(FetchCommand);
602
372
  export {
603
- fetch_default as default,
604
- fetchExecute,
605
- fetchHelp
373
+ FetchCommand,
374
+ fetch_default as default
606
375
  };