@rhseung/ps-cli 1.10.2 → 1.11.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.
@@ -1,13 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ copyToClipboard
4
+ } from "./chunk-MZUR7SER.js";
5
+ import {
6
+ openBrowser
7
+ } from "./chunk-QGMWUOJ3.js";
2
8
  import {
3
9
  runSolution
4
- } from "./chunk-GV265WOR.js";
10
+ } from "./chunk-4VPUJY7G.js";
5
11
  import {
6
12
  Command,
7
13
  CommandBuilder,
8
14
  CommandDef,
9
15
  __decorateClass,
10
16
  defineFlags,
17
+ detectProblemIdFromPath,
18
+ findSolutionFile,
11
19
  getProblemTimeLimitMs,
12
20
  getSupportedLanguagesString,
13
21
  icons,
@@ -16,8 +24,9 @@ import {
16
24
  } from "./chunk-AHE4QHJD.js";
17
25
 
18
26
  // src/commands/test.tsx
19
- import { Alert, Spinner } from "@inkjs/ui";
20
- import { Box as Box2, Text as Text2 } from "ink";
27
+ import { Alert, Badge as Badge2, Spinner, StatusMessage as StatusMessage2 } from "@inkjs/ui";
28
+ import { Box as Box2, Text as Text2, useInput } from "ink";
29
+ import { useEffect as useEffect3, useState as useState3 } from "react";
21
30
 
22
31
  // src/components/test-result.tsx
23
32
  import { Badge, StatusMessage } from "@inkjs/ui";
@@ -195,8 +204,7 @@ function useTestRunner({
195
204
  problemDir,
196
205
  language,
197
206
  watch,
198
- timeoutMs,
199
- onComplete
207
+ timeoutMs
200
208
  }) {
201
209
  const [status, setStatus] = useState("loading");
202
210
  const [results, setResults] = useState([]);
@@ -254,13 +262,6 @@ function useTestRunner({
254
262
  void watcher.close();
255
263
  };
256
264
  }, [problemDir, watch, runTests]);
257
- useEffect(() => {
258
- if (!watch && status === "ready") {
259
- const timer = setTimeout(() => onComplete(), 200);
260
- return () => clearTimeout(timer);
261
- }
262
- return void 0;
263
- }, [status, watch, onComplete]);
264
265
  return {
265
266
  status,
266
267
  results,
@@ -269,6 +270,62 @@ function useTestRunner({
269
270
  };
270
271
  }
271
272
 
273
+ // src/hooks/use-testcase-ac.ts
274
+ import { readFile as readFile2 } from "fs/promises";
275
+ import { useEffect as useEffect2, useState as useState2 } from "react";
276
+ var TESTCASE_AC_BASE_URL = "https://testcase.ac";
277
+ function useTestcaseAc({
278
+ problemId,
279
+ sourcePath,
280
+ onComplete
281
+ }) {
282
+ const [status, setStatus] = useState2(
283
+ "loading"
284
+ );
285
+ const [message, setMessage] = useState2("testcase.ac \uC900\uBE44 \uC911...");
286
+ const [error, setError] = useState2(null);
287
+ const [url, setUrl] = useState2("");
288
+ const [clipboardSuccess, setClipboardSuccess] = useState2(false);
289
+ const [clipboardError, setClipboardError] = useState2(null);
290
+ useEffect2(() => {
291
+ async function open() {
292
+ try {
293
+ setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
294
+ const sourceCode = await readFile2(sourcePath, "utf-8");
295
+ setMessage("\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD558\uB294 \uC911...");
296
+ const clipboardResult = await copyToClipboard(sourceCode);
297
+ setClipboardSuccess(clipboardResult);
298
+ if (!clipboardResult) {
299
+ setClipboardError("\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
300
+ }
301
+ const url2 = `${TESTCASE_AC_BASE_URL}/problems/${problemId}`;
302
+ setUrl(url2);
303
+ setMessage("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911...");
304
+ await openBrowser(url2);
305
+ setStatus("success");
306
+ setTimeout(() => {
307
+ onComplete();
308
+ }, 2e3);
309
+ } catch (err) {
310
+ setStatus("error");
311
+ setError(err instanceof Error ? err.message : String(err));
312
+ setTimeout(() => {
313
+ onComplete();
314
+ }, 2e3);
315
+ }
316
+ }
317
+ void open();
318
+ }, [problemId, sourcePath, onComplete]);
319
+ return {
320
+ status,
321
+ message,
322
+ error,
323
+ url,
324
+ clipboardSuccess,
325
+ clipboardError
326
+ };
327
+ }
328
+
272
329
  // src/commands/test.tsx
273
330
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
274
331
  var testFlagsSchema = {
@@ -283,21 +340,146 @@ var testFlagsSchema = {
283
340
  shortFlag: "w",
284
341
  description: `watch \uBAA8\uB4DC (\uD30C\uC77C \uBCC0\uACBD \uC2DC \uC790\uB3D9 \uC7AC\uD14C\uC2A4\uD2B8)
285
342
  solution.*, testcases/**/*.txt \uD30C\uC77C \uBCC0\uACBD \uAC10\uC9C0`
343
+ },
344
+ testcaseAc: {
345
+ type: "boolean",
346
+ description: "\uB85C\uCEEC \uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uC5C6\uC774 testcase.ac \uBB38\uC81C \uD398\uC774\uC9C0\uB97C \uC5F4\uACE0 \uC18C\uC2A4\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD569\uB2C8\uB2E4"
286
347
  }
287
348
  };
349
+ function TestcaseAcPanel({
350
+ problemId,
351
+ sourcePath,
352
+ onComplete
353
+ }) {
354
+ const { status, message, error, url, clipboardSuccess, clipboardError } = useTestcaseAc({
355
+ problemId,
356
+ sourcePath,
357
+ onComplete
358
+ });
359
+ if (status === "loading") {
360
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx2(Spinner, { label: message }) });
361
+ }
362
+ if (status === "error") {
363
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
364
+ /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
365
+ "testcase.ac \uC5F4\uAE30 \uC2E4\uD328: ",
366
+ error
367
+ ] }),
368
+ url && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
369
+ "URL: ",
370
+ url
371
+ ] }) })
372
+ ] });
373
+ }
374
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", marginTop: 1, children: [
375
+ /* @__PURE__ */ jsx2(StatusMessage2, { variant: "success", children: "testcase.ac \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
376
+ /* @__PURE__ */ jsxs2(
377
+ Box2,
378
+ {
379
+ flexDirection: "column",
380
+ borderStyle: "round",
381
+ borderColor: "gray",
382
+ marginTop: 1,
383
+ paddingX: 1,
384
+ paddingY: 0,
385
+ alignSelf: "flex-start",
386
+ children: [
387
+ /* @__PURE__ */ jsxs2(Text2, { children: [
388
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
389
+ " ",
390
+ problemId
391
+ ] }),
392
+ url && /* @__PURE__ */ jsxs2(Text2, { children: [
393
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "URL:" }),
394
+ " ",
395
+ /* @__PURE__ */ jsx2(Text2, { color: "blue", underline: true, children: url })
396
+ ] }),
397
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: clipboardSuccess ? /* @__PURE__ */ jsx2(Badge2, { color: "green", children: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uB428" }) : /* @__PURE__ */ jsx2(Badge2, { color: "yellow", children: "\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC \uC2E4\uD328" }) }),
398
+ clipboardError && !clipboardSuccess && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Alert, { variant: "warning", children: clipboardError }) })
399
+ ]
400
+ }
401
+ )
402
+ ] });
403
+ }
288
404
  function TestView({
289
405
  problemDir,
290
406
  language,
291
407
  watch,
292
408
  timeoutMs,
293
- onComplete
409
+ onComplete,
410
+ problemId,
411
+ sourcePath
294
412
  }) {
295
413
  const { status, results, summary, error } = useTestRunner({
296
414
  problemDir,
297
415
  language,
298
416
  watch,
299
417
  timeoutMs,
300
- onComplete
418
+ onComplete: () => {
419
+ }
420
+ });
421
+ const [completed, setCompleted] = useState3(false);
422
+ const [showSuggestion, setShowSuggestion] = useState3(false);
423
+ const [acceptedSuggestion, setAcceptedSuggestion] = useState3(false);
424
+ const allPassed = summary.total > 0 && summary.total === summary.passed && summary.failed === 0 && summary.errored === 0;
425
+ useEffect3(() => {
426
+ if (watch || completed) {
427
+ return;
428
+ }
429
+ if (status === "error") {
430
+ const timer = setTimeout(() => {
431
+ setCompleted(true);
432
+ onComplete();
433
+ }, 200);
434
+ return () => clearTimeout(timer);
435
+ }
436
+ if (status === "ready") {
437
+ const canSuggest = allPassed && !!problemId && !!sourcePath && !acceptedSuggestion;
438
+ if (!canSuggest) {
439
+ const timer = setTimeout(() => {
440
+ setCompleted(true);
441
+ onComplete();
442
+ }, 200);
443
+ return () => clearTimeout(timer);
444
+ }
445
+ }
446
+ return void 0;
447
+ }, [
448
+ status,
449
+ watch,
450
+ completed,
451
+ onComplete,
452
+ allPassed,
453
+ acceptedSuggestion,
454
+ problemId,
455
+ sourcePath
456
+ ]);
457
+ useEffect3(() => {
458
+ if (watch) {
459
+ setShowSuggestion(false);
460
+ return;
461
+ }
462
+ if (status === "ready" && allPassed && problemId && sourcePath) {
463
+ setShowSuggestion(true);
464
+ } else {
465
+ setShowSuggestion(false);
466
+ }
467
+ }, [status, allPassed, watch]);
468
+ useInput((input, key) => {
469
+ if (!showSuggestion || acceptedSuggestion || completed) {
470
+ return;
471
+ }
472
+ const lower = input.toLowerCase();
473
+ if (lower === "y" || key.return) {
474
+ setAcceptedSuggestion(true);
475
+ setShowSuggestion(false);
476
+ } else if (lower === "n" || key.escape) {
477
+ setShowSuggestion(false);
478
+ if (!completed) {
479
+ setCompleted(true);
480
+ onComplete();
481
+ }
482
+ }
301
483
  });
302
484
  if (status === "loading") {
303
485
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(Spinner, { label: "\uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uC911..." }) });
@@ -321,12 +503,46 @@ function TestView({
321
503
  watch && ` ${icons.solving} watch`
322
504
  ] })
323
505
  ] }),
324
- /* @__PURE__ */ jsx2(TestResultView, { results, summary })
506
+ /* @__PURE__ */ jsx2(TestResultView, { results, summary }),
507
+ showSuggestion && !acceptedSuggestion && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
508
+ /* @__PURE__ */ jsx2(Alert, { variant: "info", children: "\uBAA8\uB4E0 \uB85C\uCEEC \uD14C\uC2A4\uD2B8\uB97C \uD1B5\uACFC\uD588\uC2B5\uB2C8\uB2E4. testcase.ac\uC5D0\uC11C \uCD94\uAC00 \uD14C\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD574 \uBCFC\uAE4C\uC694? (Y/n)" }),
509
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: "Y \uB610\uB294 Enter: testcase.ac \uC5F4\uAE30 / N \uB610\uB294 Esc: \uAC74\uB108\uB6F0\uAE30" }) })
510
+ ] }),
511
+ acceptedSuggestion && problemId && sourcePath && !completed && /* @__PURE__ */ jsx2(
512
+ TestcaseAcPanel,
513
+ {
514
+ problemId,
515
+ sourcePath,
516
+ onComplete: () => {
517
+ if (!completed) {
518
+ setCompleted(true);
519
+ onComplete();
520
+ }
521
+ }
522
+ }
523
+ )
325
524
  ] });
326
525
  }
327
526
  var TestCommand = class extends Command {
328
527
  async execute(args, flags) {
329
528
  const context = await resolveProblemContext(args);
529
+ const sourcePath = await findSolutionFile(context.archiveDir);
530
+ let finalProblemId = context.problemId;
531
+ if (finalProblemId === null) {
532
+ finalProblemId = detectProblemIdFromPath(context.archiveDir);
533
+ }
534
+ if (flags.testcaseAc) {
535
+ if (finalProblemId === null) {
536
+ throw new Error(
537
+ "\uBB38\uC81C \uBC88\uD638\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC81C \uBC88\uD638\uB97C \uC778\uC790\uB85C \uC804\uB2EC\uD558\uAC70\uB098 \uBB38\uC81C \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD574\uC8FC\uC138\uC694."
538
+ );
539
+ }
540
+ await this.renderView(TestcaseAcPanel, {
541
+ problemId: finalProblemId,
542
+ sourcePath
543
+ });
544
+ return;
545
+ }
330
546
  const language = await resolveLanguage(
331
547
  context.archiveDir,
332
548
  flags.language
@@ -335,7 +551,9 @@ var TestCommand = class extends Command {
335
551
  problemDir: context.archiveDir,
336
552
  language,
337
553
  watch: Boolean(flags.watch),
338
- timeoutMs: flags.timeoutMs
554
+ timeoutMs: flags.timeoutMs,
555
+ problemId: finalProblemId,
556
+ sourcePath
339
557
  });
340
558
  }
341
559
  };
@@ -2,63 +2,19 @@
2
2
  import {
3
3
  getProblem,
4
4
  scrapeProblem
5
- } from "./chunk-S7IL7OXF.js";
5
+ } from "./chunk-U6KQM2P4.js";
6
6
  import {
7
- Command,
8
- CommandBuilder,
9
- CommandDef,
10
- __decorateClass,
11
- defineFlags,
12
7
  findProjectRoot,
13
8
  getAutoOpenEditor,
14
9
  getEditor,
15
10
  getIncludeTag,
16
11
  getLanguageConfig,
17
12
  getSolvingDirPath,
18
- getSupportedLanguages,
19
- getSupportedLanguagesString,
20
- getTierColor,
21
13
  getTierImageUrl,
22
14
  getTierName,
23
- logger,
24
- parseTimeLimitToMs,
25
- resolveProblemContext
15
+ parseTimeLimitToMs
26
16
  } from "./chunk-AHE4QHJD.js";
27
17
 
28
- // src/commands/fetch.tsx
29
- import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
30
- import { Box as Box2 } from "ink";
31
-
32
- // src/components/problem-dashboard.tsx
33
- import { Box, Text } from "ink";
34
- import { jsx, jsxs } from "react/jsx-runtime";
35
- function ProblemDashboard({ problem }) {
36
- const tierName = getTierName(problem.level);
37
- const tierColor = getTierColor(problem.level);
38
- const borderColorString = typeof tierColor === "string" ? tierColor : "#ff7ca8";
39
- const textColorString = borderColorString;
40
- return /* @__PURE__ */ jsx(
41
- Box,
42
- {
43
- flexDirection: "column",
44
- borderStyle: "round",
45
- borderColor: borderColorString,
46
- paddingX: 1,
47
- alignSelf: "flex-start",
48
- children: /* @__PURE__ */ jsxs(Text, { bold: true, color: textColorString, children: [
49
- tierName,
50
- " ",
51
- /* @__PURE__ */ jsxs(Text, { color: "white", children: [
52
- "#",
53
- problem.id,
54
- ": ",
55
- problem.title
56
- ] })
57
- ] })
58
- }
59
- );
60
- }
61
-
62
18
  // src/hooks/use-fetch-problem.ts
63
19
  import { execaCommand } from "execa";
64
20
  import { useEffect, useState } from "react";
@@ -350,86 +306,6 @@ ${editor}\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.`
350
306
  };
351
307
  }
352
308
 
353
- // src/commands/fetch.tsx
354
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
355
- var fetchFlagsSchema = {
356
- language: {
357
- type: "string",
358
- shortFlag: "l",
359
- description: `\uC5B8\uC5B4 \uC120\uD0DD (${getSupportedLanguagesString()})
360
- \uAE30\uBCF8\uAC12: python`
361
- }
362
- };
363
- function FetchView({
364
- problemId,
365
- language = "python",
366
- onComplete
367
- }) {
368
- const { status, problem, error, message } = useFetchProblem({
369
- problemId,
370
- language,
371
- onComplete
372
- });
373
- if (status === "loading") {
374
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
375
- /* @__PURE__ */ jsx2(Spinner, { label: message }),
376
- problem && /* @__PURE__ */ jsx2(ProblemDashboard, { problem })
377
- ] });
378
- }
379
- if (status === "error") {
380
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
381
- "\uC624\uB958 \uBC1C\uC0DD: ",
382
- error
383
- ] }) });
384
- }
385
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", children: [
386
- problem && /* @__PURE__ */ jsx2(Box2, { alignSelf: "flex-start", children: /* @__PURE__ */ jsx2(ProblemDashboard, { problem }) }),
387
- /* @__PURE__ */ jsx2(StatusMessage, { variant: "success", children: message })
388
- ] });
389
- }
390
- var FetchCommand = class extends Command {
391
- async execute(args, flags) {
392
- const context = await resolveProblemContext(args, { requireId: true });
393
- if (context.problemId === null) {
394
- logger.error("\uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
395
- console.log(`\uB3C4\uC6C0\uB9D0: ps fetch --help`);
396
- process.exit(1);
397
- return;
398
- }
399
- const validLanguages = getSupportedLanguages();
400
- const language = flags.language;
401
- if (language && !validLanguages.includes(language)) {
402
- console.error(
403
- `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4. (${getSupportedLanguagesString()})`
404
- );
405
- process.exit(1);
406
- return;
407
- }
408
- await this.renderView(FetchView, {
409
- problemId: context.problemId,
410
- language: language || "python"
411
- });
412
- }
413
- };
414
- FetchCommand = __decorateClass([
415
- CommandDef({
416
- name: "fetch",
417
- description: `\uBC31\uC900 \uBB38\uC81C\uB97C \uAC00\uC838\uC640\uC11C \uB85C\uCEEC\uC5D0 \uD30C\uC77C\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
418
- - Solved.ac API\uC640 BOJ \uD06C\uB864\uB9C1\uC744 \uD1B5\uD574 \uBB38\uC81C \uC815\uBCF4 \uC218\uC9D1
419
- - \uBB38\uC81C \uC124\uBA85, \uC785\uCD9C\uB825 \uD615\uC2DD, \uC608\uC81C \uC785\uCD9C\uB825 \uD30C\uC77C \uC790\uB3D9 \uC0DD\uC131
420
- - \uC120\uD0DD\uD55C \uC5B8\uC5B4\uC758 \uC194\uB8E8\uC158 \uD15C\uD50C\uB9BF \uD30C\uC77C \uC0DD\uC131
421
- - README.md\uC5D0 \uBB38\uC81C \uC815\uBCF4, \uD1B5\uACC4, \uD0DC\uADF8(\uC124\uC815 \uC2DC) \uB4F1 \uD3EC\uD568
422
- - \uAE30\uBCF8 \uC5B8\uC5B4, \uC5D0\uB514\uD130 \uC124\uC815 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
423
- flags: defineFlags(fetchFlagsSchema),
424
- autoDetectProblemId: false,
425
- requireProblemId: true,
426
- examples: ["fetch 1000", "fetch 1000 --language python", "fetch 1000 -l cpp"]
427
- })
428
- ], FetchCommand);
429
- var fetch_default = CommandBuilder.fromClass(FetchCommand);
430
-
431
309
  export {
432
- FetchView,
433
- FetchCommand,
434
- fetch_default
310
+ useFetchProblem
435
311
  };
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ useArchive
4
+ } from "./chunk-LCHSAHUP.js";
5
+ import {
6
+ Command,
7
+ CommandBuilder,
8
+ CommandDef,
9
+ __decorateClass,
10
+ logger,
11
+ resolveProblemContext
12
+ } from "./chunk-AHE4QHJD.js";
13
+
14
+ // src/commands/archive.tsx
15
+ import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
16
+ import { Box } from "ink";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ function ArchiveView({ problemId, onComplete }) {
19
+ const { status, message, error } = useArchive({
20
+ problemId,
21
+ onComplete
22
+ });
23
+ if (status === "loading") {
24
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
25
+ }
26
+ if (status === "error") {
27
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
28
+ "\uC624\uB958 \uBC1C\uC0DD: ",
29
+ error
30
+ ] }) });
31
+ }
32
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: message }) });
33
+ }
34
+ var ArchiveCommand = class extends Command {
35
+ async execute(args, _) {
36
+ const context = await resolveProblemContext(args, { requireId: false });
37
+ if (context.problemId === null) {
38
+ logger.error("\uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
39
+ console.log(`\uB3C4\uC6C0\uB9D0: ps archive --help`);
40
+ process.exit(1);
41
+ return;
42
+ }
43
+ await this.renderView(ArchiveView, {
44
+ problemId: context.problemId
45
+ });
46
+ }
47
+ };
48
+ ArchiveCommand = __decorateClass([
49
+ CommandDef({
50
+ name: "archive",
51
+ description: `\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uACE0 Git \uCEE4\uBC0B\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
52
+ - solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uCC3E\uC544 archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9
53
+ - Git add \uBC0F commit \uC2E4\uD589 (\uCEE4\uBC0B \uBA54\uC2DC\uC9C0: "solve: {\uBB38\uC81C\uBC88\uD638} - {\uBB38\uC81C\uC774\uB984}")
54
+ - \uC544\uCE74\uC774\uBE0C \uACBD\uB85C, \uC804\uB7B5, \uC790\uB3D9 \uCEE4\uBC0B \uC5EC\uBD80 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
55
+ flags: [],
56
+ autoDetectProblemId: true,
57
+ requireProblemId: false,
58
+ examples: [
59
+ "archive 1000",
60
+ "archive # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C \uBC88\uD638 \uC790\uB3D9 \uAC10\uC9C0"
61
+ ]
62
+ })
63
+ ], ArchiveCommand);
64
+ var archive_default = CommandBuilder.fromClass(ArchiveCommand);
65
+
66
+ export {
67
+ ArchiveView,
68
+ ArchiveCommand,
69
+ archive_default
70
+ };
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getUserProblemStats,
4
+ getUserStats,
5
+ getUserTagRatings,
6
+ getUserTop100,
7
+ scrapeUserStats
8
+ } from "./chunk-U6KQM2P4.js";
9
+ import {
10
+ findProjectRoot,
11
+ getArchiveDir,
12
+ getSolvingDir
13
+ } from "./chunk-AHE4QHJD.js";
14
+
15
+ // src/hooks/use-user-stats.ts
16
+ import { existsSync } from "fs";
17
+ import { readdir, stat } from "fs/promises";
18
+ import { join } from "path";
19
+ import { useEffect, useState } from "react";
20
+ async function countProblems(dir) {
21
+ let count = 0;
22
+ try {
23
+ if (!existsSync(dir)) return 0;
24
+ const entries = await readdir(dir);
25
+ for (const entry of entries) {
26
+ if (entry.startsWith(".")) continue;
27
+ const fullPath = join(dir, entry);
28
+ const s = await stat(fullPath);
29
+ if (s.isDirectory()) {
30
+ if (existsSync(join(fullPath, "meta.json"))) {
31
+ count++;
32
+ } else {
33
+ count += await countProblems(fullPath);
34
+ }
35
+ }
36
+ }
37
+ } catch {
38
+ }
39
+ return count;
40
+ }
41
+ function useUserStats({
42
+ handle,
43
+ onComplete,
44
+ fetchLocalCount = false
45
+ }) {
46
+ const [status, setStatus] = useState(
47
+ "loading"
48
+ );
49
+ const [user, setUser] = useState(null);
50
+ const [top100, setTop100] = useState(null);
51
+ const [problemStats, setProblemStats] = useState(null);
52
+ const [tagRatings, setTagRatings] = useState(
53
+ null
54
+ );
55
+ const [bojStats, setBojStats] = useState(null);
56
+ const [localSolvedCount, setLocalSolvedCount] = useState(null);
57
+ const [error, setError] = useState(null);
58
+ useEffect(() => {
59
+ async function fetchData() {
60
+ try {
61
+ const userData = await getUserStats(handle).catch((err) => {
62
+ if (err instanceof Error && err.message.includes("404")) {
63
+ throw new Error(`\uC0AC\uC6A9\uC790 '${handle}'\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);
64
+ }
65
+ throw err;
66
+ });
67
+ if (!userData) {
68
+ throw new Error(`\uC0AC\uC6A9\uC790 '${handle}'\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);
69
+ }
70
+ setUser(userData);
71
+ const [top100Data, problemStatsData, tagRatingsData, bojStatsData] = await Promise.all([
72
+ getUserTop100(handle).catch((err) => {
73
+ console.error("Error fetching top 100:", err);
74
+ return null;
75
+ }),
76
+ getUserProblemStats(handle).catch((err) => {
77
+ console.error("Error fetching problem stats:", err);
78
+ return null;
79
+ }),
80
+ getUserTagRatings(handle).catch((err) => {
81
+ console.error("Error fetching tag ratings:", err);
82
+ return null;
83
+ }),
84
+ scrapeUserStats(handle).catch((err) => {
85
+ console.error("Error scraping BOJ stats:", err);
86
+ return null;
87
+ })
88
+ ]);
89
+ setTop100(top100Data);
90
+ setProblemStats(problemStatsData);
91
+ setTagRatings(tagRatingsData);
92
+ setBojStats(bojStatsData);
93
+ if (fetchLocalCount) {
94
+ const projectRoot = findProjectRoot();
95
+ if (projectRoot) {
96
+ const archiveDir = getArchiveDir();
97
+ const solvingDir = getSolvingDir();
98
+ const archivePath = join(projectRoot, archiveDir);
99
+ const solvingPath = join(projectRoot, solvingDir);
100
+ const [archiveCount, solvingCount] = await Promise.all([
101
+ countProblems(archivePath),
102
+ countProblems(solvingPath)
103
+ ]);
104
+ setLocalSolvedCount(archiveCount + solvingCount);
105
+ }
106
+ }
107
+ setStatus("success");
108
+ setTimeout(() => {
109
+ onComplete();
110
+ }, 5e3);
111
+ } catch (err) {
112
+ setError(err instanceof Error ? err.message : String(err));
113
+ setStatus("error");
114
+ setTimeout(() => {
115
+ onComplete();
116
+ }, 3e3);
117
+ }
118
+ }
119
+ void fetchData();
120
+ }, [fetchLocalCount, handle, onComplete]);
121
+ return {
122
+ status,
123
+ user,
124
+ top100,
125
+ problemStats,
126
+ tagRatings,
127
+ bojStats,
128
+ localSolvedCount,
129
+ error
130
+ };
131
+ }
132
+
133
+ export {
134
+ useUserStats
135
+ };