@rhseung/ps-cli 1.11.1 → 1.12.2

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 CHANGED
@@ -82,7 +82,8 @@ ps fetch 1000 -l cpp
82
82
 
83
83
  - Solved.ac API와 BOJ 크롤링을 통해 문제 정보 수집
84
84
  - 문제 설명, 입출력 형식, 예제 입출력 파일 자동 생성
85
- - 선택한 언어의 솔루션 템플릿 파일 생성
85
+ - 선택한 언어의 솔루션 템플릿 파일 생성 (`solution-1.{확장자}` 형식)
86
+ - 같은 언어로 다시 fetch하면 자동으로 다음 인덱스 파일 생성 (`solution-2.{확장자}`, `solution-3.{확장자}` 등)
86
87
  - README.md에 문제 정보, 통계, 태그 등 포함
87
88
 
88
89
  ---
@@ -104,6 +105,8 @@ ps test [문제번호] [옵션]
104
105
  - 지원 언어: python, javascript, typescript, cpp
105
106
  - `--watch`, `-w`: watch 모드 (파일 변경 시 자동 재테스트)
106
107
  - solution._, testcases/\*\*/_.txt 파일 변경 감지
108
+ - `--file`, `-f`: 특정 solution 파일 지정 (예: `solution-2.py`)
109
+ - `--index`: 인덱스로 solution 파일 지정 (예: `2`)
107
110
 
108
111
  **예제:**
109
112
 
@@ -113,12 +116,16 @@ ps test 1000 # 1000번 문제 테스트
113
116
  ps test --watch # watch 모드로 테스트
114
117
  ps test 1000 --watch # 1000번 문제를 watch 모드로 테스트
115
118
  ps test --language python # Python으로 테스트
119
+ ps test --file solution-2.py # 특정 파일로 테스트
120
+ ps test --index 2 # 인덱스 2의 파일로 테스트
116
121
  ```
117
122
 
118
123
  **설명:**
119
124
 
120
125
  - 현재 디렉토리 또는 지정한 문제 번호의 테스트 실행
121
- - solution.\* 파일을 자동으로 찾아 언어 감지
126
+ - solution.\* 파일을 자동으로 찾아 언어 감지 (기본적으로 가장 최근 수정된 파일 사용)
127
+ - 여러 답안 파일이 있는 경우 `--file` 또는 `--index` 옵션으로 특정 파일 지정 가능
128
+ - 파일 네이밍: `solution-1.py`, `solution-2.py` 형식으로 여러 답안 관리 가능
122
129
  - testcases/{번호}/input.txt와 testcases/{번호}/output.txt 파일을 기반으로 테스트
123
130
  - 문제의 시간 제한을 자동으로 적용
124
131
 
@@ -142,6 +149,8 @@ ps run [문제번호] [옵션]
142
149
  - `--input`, `-i`: 입력 파일 지정
143
150
  - 숫자만 입력 시 testcases/{숫자}/input.txt로 자동 변환 (예: `--input 1`)
144
151
  - 전체 경로도 지원 (예: testcases/1/input.txt)
152
+ - `--file`, `-f`: 특정 solution 파일 지정 (예: `solution-2.py`)
153
+ - `--index`: 인덱스로 solution 파일 지정 (예: `2`)
145
154
 
146
155
  **예제:**
147
156
 
@@ -151,12 +160,16 @@ ps run 1000 # 1000번 문제 표준 입력으로 실행
151
160
  ps run --language python # Python으로 표준 입력으로 실행
152
161
  ps run --input 1 # 테스트 케이스 1번 사용
153
162
  ps run --input testcases/1/input.txt # 전체 경로로 입력 파일 지정
163
+ ps run --file solution-2.py # 특정 파일로 실행
164
+ ps run --index 2 # 인덱스 2의 파일로 실행
154
165
  ```
155
166
 
156
167
  **설명:**
157
168
 
158
169
  - 현재 디렉토리 또는 지정한 문제 번호의 코드 실행
159
- - solution.\* 파일을 자동으로 찾아 언어 감지
170
+ - solution.\* 파일을 자동으로 찾아 언어 감지 (기본적으로 가장 최근 수정된 파일 사용)
171
+ - 여러 답안 파일이 있는 경우 `--file` 또는 `--index` 옵션으로 특정 파일 지정 가능
172
+ - 파일 네이밍: `solution-1.py`, `solution-2.py` 형식으로 여러 답안 관리 가능
160
173
  - --input 옵션으로 입력 파일 지정 가능 (예: `--input 1` 또는 `--input testcases/1/input.txt`)
161
174
  - 옵션 없이 실행 시 표준 입력으로 입력 받기
162
175
  - 테스트 케이스 검증 없이 단순 실행
@@ -178,6 +191,8 @@ ps submit [문제번호] [옵션]
178
191
  - `--help`, `-h`: 도움말 표시
179
192
  - `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
180
193
  - 지원 언어: python, javascript, typescript, cpp
194
+ - `--file`, `-f`: 특정 solution 파일 지정 (예: `solution-2.py`)
195
+ - `--index`, `-i`: 인덱스로 solution 파일 지정 (예: `2`)
181
196
 
182
197
  **예제:**
183
198
 
@@ -185,12 +200,16 @@ ps submit [문제번호] [옵션]
185
200
  ps submit # 현재 디렉토리에서 제출
186
201
  ps submit 1000 # 1000번 문제 제출
187
202
  ps submit --language python # Python으로 제출
203
+ ps submit --file solution-2.py # 특정 파일로 제출
204
+ ps submit --index 2 # 인덱스 2의 파일로 제출
188
205
  ```
189
206
 
190
207
  **설명:**
191
208
 
192
209
  - 문제 번호를 인자로 전달하거나 문제 디렉토리에서 실행하면 자동으로 문제 번호를 추론
193
- - solution.\* 파일을 자동으로 찾아 언어 감지
210
+ - solution.\* 파일을 자동으로 찾아 언어 감지 (기본적으로 가장 최근 수정된 파일 사용)
211
+ - 여러 답안 파일이 있는 경우 `--file` 또는 `--index` 옵션으로 특정 파일 지정 가능
212
+ - 파일 네이밍: `solution-1.py`, `solution-2.py` 형식으로 여러 답안 관리 가능
194
213
  - 소스 코드를 클립보드에 자동 복사
195
214
  - 제출 페이지를 브라우저로 자동 열기
196
215
 
@@ -426,6 +445,42 @@ ps config clear # .ps-cli 폴더 및 모든 설정 삭제
426
445
 
427
446
  **참고:** solving 디렉토리는 항상 평면적으로 나열됩니다.
428
447
 
448
+ ## 여러 답안 파일 지원
449
+
450
+ 한 문제에 대해 여러 답안 파일을 관리할 수 있습니다.
451
+
452
+ ### 파일 네이밍
453
+
454
+ - 형식: `solution-{인덱스}.{확장자}` (예: `solution-1.py`, `solution-2.py`, `solution-1.cpp`)
455
+ - 첫 번째 파일은 `solution-1.{확장자}`로 시작
456
+ - 같은 언어로 `ps fetch`를 다시 실행하면 자동으로 다음 인덱스 파일 생성
457
+
458
+ ### 파일 선택
459
+
460
+ - **기본 동작**: 가장 최근 수정된 파일을 자동으로 선택
461
+ - **직접 지정**: `--file` 또는 `--index` 옵션으로 특정 파일 지정
462
+
463
+ **예제:**
464
+
465
+ ```bash
466
+ # 가장 최근 파일로 테스트 (기본값)
467
+ ps test
468
+
469
+ # 특정 파일로 테스트
470
+ ps test --file solution-2.py
471
+ ps test --index 2
472
+
473
+ # 다른 명령어에서도 동일하게 사용 가능
474
+ ps run --index 2
475
+ ps submit --file solution-3.py
476
+ ```
477
+
478
+ ### 사용 사례
479
+
480
+ - 같은 문제를 여러 알고리즘으로 해결한 경우
481
+ - 같은 언어로 여러 버전의 코드를 작성한 경우
482
+ - 다른 언어로 같은 문제를 해결한 경우
483
+
429
484
  ## 워크플로우
430
485
 
431
486
  1. **초기화**: `ps init`으로 프로젝트 설정
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useFetchProblem
4
- } from "./chunk-ERJEYECY.js";
4
+ } from "./chunk-KUUJ4DBH.js";
5
5
  import {
6
6
  Command,
7
7
  CommandBuilder,
@@ -14,7 +14,7 @@ import {
14
14
  getTierName,
15
15
  logger,
16
16
  resolveProblemContext
17
- } from "./chunk-AHE4QHJD.js";
17
+ } from "./chunk-UDZLWCHF.js";
18
18
 
19
19
  // src/commands/fetch.tsx
20
20
  import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
@@ -11,7 +11,7 @@ import {
11
11
  getEditor,
12
12
  logger,
13
13
  resolveProblemContext
14
- } from "./chunk-AHE4QHJD.js";
14
+ } from "./chunk-UDZLWCHF.js";
15
15
 
16
16
  // src/commands/open.tsx
17
17
  import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ findSolutionFile,
3
4
  getLanguageConfig,
4
5
  getProblemTimeLimitMs
5
- } from "./chunk-AHE4QHJD.js";
6
+ } from "./chunk-UDZLWCHF.js";
6
7
 
7
8
  // src/hooks/use-run-solution.ts
8
9
  import { useEffect, useState } from "react";
@@ -31,11 +32,20 @@ async function runSolution({
31
32
  problemDir,
32
33
  language,
33
34
  inputPath,
34
- timeoutMs = 5e3
35
+ timeoutMs = 5e3,
36
+ solutionPath: providedSolutionPath
35
37
  }) {
38
+ let solutionPath;
39
+ if (providedSolutionPath) {
40
+ if (providedSolutionPath.startsWith("/") || providedSolutionPath.match(/^[A-Z]:/)) {
41
+ solutionPath = providedSolutionPath;
42
+ } else {
43
+ solutionPath = join(problemDir, providedSolutionPath);
44
+ }
45
+ } else {
46
+ solutionPath = await findSolutionFile(problemDir, language);
47
+ }
36
48
  const langConfig = getLanguageConfig(language);
37
- const solutionFile = `solution.${langConfig.extension}`;
38
- const solutionPath = join(problemDir, solutionFile);
39
49
  let input;
40
50
  let capturedInput;
41
51
  if (inputPath) {
@@ -98,7 +108,8 @@ function useRunSolution({
98
108
  problemDir,
99
109
  language,
100
110
  inputFile,
101
- onComplete
111
+ onComplete,
112
+ solutionPath
102
113
  }) {
103
114
  const [status, setStatus] = useState(
104
115
  "loading"
@@ -114,7 +125,8 @@ function useRunSolution({
114
125
  problemDir,
115
126
  language,
116
127
  inputPath: inputFile,
117
- timeoutMs: effectiveTimeout
128
+ timeoutMs: effectiveTimeout,
129
+ solutionPath
118
130
  });
119
131
  setResult(runResult);
120
132
  setStatus("ready");
@@ -130,7 +142,7 @@ function useRunSolution({
130
142
  }
131
143
  }
132
144
  void run();
133
- }, [problemDir, language, inputFile, onComplete]);
145
+ }, [problemDir, language, inputFile, onComplete, solutionPath]);
134
146
  return {
135
147
  status,
136
148
  result,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useArchive
4
- } from "./chunk-LCHSAHUP.js";
4
+ } from "./chunk-V27D7TPW.js";
5
5
  import {
6
6
  Command,
7
7
  CommandBuilder,
@@ -9,7 +9,7 @@ import {
9
9
  __decorateClass,
10
10
  logger,
11
11
  resolveProblemContext
12
- } from "./chunk-AHE4QHJD.js";
12
+ } from "./chunk-UDZLWCHF.js";
13
13
 
14
14
  // src/commands/archive.tsx
15
15
  import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  logger
4
- } from "./chunk-AHE4QHJD.js";
4
+ } from "./chunk-UDZLWCHF.js";
5
5
 
6
6
  // src/services/scraper.ts
7
7
  import * as cheerio from "cheerio";
@@ -7,21 +7,24 @@ import {
7
7
  } from "./chunk-QGMWUOJ3.js";
8
8
  import {
9
9
  runSolution
10
- } from "./chunk-4VPUJY7G.js";
10
+ } from "./chunk-CKSAX7KT.js";
11
11
  import {
12
12
  Command,
13
13
  CommandBuilder,
14
14
  CommandDef,
15
15
  __decorateClass,
16
16
  defineFlags,
17
+ detectLanguageFromFile,
17
18
  detectProblemIdFromPath,
18
19
  findSolutionFile,
20
+ findSolutionFileByIndex,
21
+ findSolutionFiles,
19
22
  getProblemTimeLimitMs,
20
23
  getSupportedLanguagesString,
21
24
  icons,
22
25
  resolveLanguage,
23
26
  resolveProblemContext
24
- } from "./chunk-AHE4QHJD.js";
27
+ } from "./chunk-UDZLWCHF.js";
25
28
 
26
29
  // src/commands/test.tsx
27
30
  import { Alert, Badge as Badge2, Spinner, StatusMessage as StatusMessage2 } from "@inkjs/ui";
@@ -134,7 +137,8 @@ function buildSummary(results) {
134
137
  async function runAllTests({
135
138
  problemDir,
136
139
  language,
137
- timeoutMs
140
+ timeoutMs,
141
+ solutionPath
138
142
  }) {
139
143
  const testcasesDir = join(problemDir, "testcases");
140
144
  let caseDirs = [];
@@ -172,7 +176,8 @@ async function runAllTests({
172
176
  problemDir,
173
177
  language,
174
178
  inputPath,
175
- timeoutMs: effectiveTimeout
179
+ timeoutMs: effectiveTimeout,
180
+ solutionPath
176
181
  });
177
182
  if (runResult.exitCode !== 0 || runResult.timedOut) {
178
183
  results.push({
@@ -204,7 +209,8 @@ function useTestRunner({
204
209
  problemDir,
205
210
  language,
206
211
  watch,
207
- timeoutMs
212
+ timeoutMs,
213
+ solutionPath
208
214
  }) {
209
215
  const [status, setStatus] = useState("loading");
210
216
  const [results, setResults] = useState([]);
@@ -224,7 +230,8 @@ function useTestRunner({
224
230
  void runAllTests({
225
231
  problemDir,
226
232
  language,
227
- timeoutMs
233
+ timeoutMs,
234
+ solutionPath
228
235
  }).then(({ results: results2, summary: summary2 }) => {
229
236
  setResults(results2);
230
237
  setSummary(summary2);
@@ -234,7 +241,7 @@ function useTestRunner({
234
241
  setStatus("error");
235
242
  });
236
243
  },
237
- [problemDir, language, timeoutMs, watch]
244
+ [problemDir, language, timeoutMs, watch, solutionPath]
238
245
  );
239
246
  useEffect(() => {
240
247
  const timer = setTimeout(() => {
@@ -344,6 +351,16 @@ var testFlagsSchema = {
344
351
  testcaseAc: {
345
352
  type: "boolean",
346
353
  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"
354
+ },
355
+ file: {
356
+ type: "string",
357
+ shortFlag: "f",
358
+ description: "\uD2B9\uC815 solution \uD30C\uC77C \uC9C0\uC815 (\uC608: solution-2.py)"
359
+ },
360
+ index: {
361
+ type: "string",
362
+ shortFlag: "i",
363
+ description: "\uC778\uB371\uC2A4\uB85C solution \uD30C\uC77C \uC9C0\uC815 (\uC608: 2)"
347
364
  }
348
365
  };
349
366
  function TestcaseAcPanel({
@@ -408,13 +425,15 @@ function TestView({
408
425
  timeoutMs,
409
426
  onComplete,
410
427
  problemId,
411
- sourcePath
428
+ sourcePath,
429
+ solutionPath
412
430
  }) {
413
431
  const { status, results, summary, error } = useTestRunner({
414
432
  problemDir,
415
433
  language,
416
434
  watch,
417
435
  timeoutMs,
436
+ solutionPath,
418
437
  onComplete: () => {
419
438
  }
420
439
  });
@@ -526,7 +545,58 @@ function TestView({
526
545
  var TestCommand = class extends Command {
527
546
  async execute(args, flags) {
528
547
  const context = await resolveProblemContext(args);
529
- const sourcePath = await findSolutionFile(context.archiveDir);
548
+ let sourcePath;
549
+ let language;
550
+ if (flags.file) {
551
+ const filePath = flags.file;
552
+ if (filePath.startsWith("/") || filePath.match(/^[A-Z]:/)) {
553
+ sourcePath = filePath;
554
+ } else {
555
+ const { join: join3 } = await import("path");
556
+ sourcePath = join3(context.archiveDir, filePath);
557
+ }
558
+ const fileName = sourcePath.split(/[/\\]/).pop() || "";
559
+ const fileLang = detectLanguageFromFile(fileName);
560
+ if (flags.language) {
561
+ language = flags.language;
562
+ } else if (fileLang) {
563
+ language = fileLang;
564
+ } else {
565
+ language = await resolveLanguage(
566
+ context.archiveDir,
567
+ flags.language
568
+ );
569
+ }
570
+ } else if (flags.index) {
571
+ const index = parseInt(flags.index, 10);
572
+ if (isNaN(index) || index < 1) {
573
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC778\uB371\uC2A4\uC785\uB2C8\uB2E4: ${flags.index}`);
574
+ }
575
+ if (flags.language) {
576
+ language = flags.language;
577
+ sourcePath = await findSolutionFileByIndex(
578
+ context.archiveDir,
579
+ index,
580
+ language
581
+ );
582
+ } else {
583
+ const files = await findSolutionFiles(context.archiveDir);
584
+ const targetFile = files.find((f) => f.index === index);
585
+ if (!targetFile) {
586
+ throw new Error(
587
+ `\uC778\uB371\uC2A4 ${index}\uC758 solution \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
588
+ );
589
+ }
590
+ sourcePath = targetFile.path;
591
+ language = targetFile.language;
592
+ }
593
+ } else {
594
+ language = await resolveLanguage(
595
+ context.archiveDir,
596
+ flags.language
597
+ );
598
+ sourcePath = await findSolutionFile(context.archiveDir, language);
599
+ }
530
600
  let finalProblemId = context.problemId;
531
601
  if (finalProblemId === null) {
532
602
  finalProblemId = detectProblemIdFromPath(context.archiveDir);
@@ -543,17 +613,14 @@ var TestCommand = class extends Command {
543
613
  });
544
614
  return;
545
615
  }
546
- const language = await resolveLanguage(
547
- context.archiveDir,
548
- flags.language
549
- );
550
616
  await this.renderView(TestView, {
551
617
  problemDir: context.archiveDir,
552
618
  language,
553
619
  watch: Boolean(flags.watch),
554
620
  timeoutMs: flags.timeoutMs,
555
621
  problemId: finalProblemId,
556
- sourcePath
622
+ sourcePath,
623
+ solutionPath: sourcePath
557
624
  });
558
625
  }
559
626
  };
@@ -562,7 +629,9 @@ TestCommand = __decorateClass([
562
629
  name: "test",
563
630
  description: `\uC608\uC81C \uC785\uCD9C\uB825 \uAE30\uBC18\uC73C\uB85C \uB85C\uCEEC \uD14C\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
564
631
  - \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC \uB610\uB294 \uC9C0\uC815\uD55C \uBB38\uC81C \uBC88\uD638\uC758 \uD14C\uC2A4\uD2B8 \uC2E4\uD589
565
- - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
632
+ - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0 (\uAE30\uBCF8\uC801\uC73C\uB85C \uAC00\uC7A5 \uCD5C\uADFC \uC218\uC815\uB41C \uD30C\uC77C \uC0AC\uC6A9)
633
+ - \uC5EC\uB7EC \uB2F5\uC548 \uD30C\uC77C \uC9C0\uC6D0: solution-1.py, solution-2.py \uD615\uC2DD\uC73C\uB85C \uC5EC\uB7EC \uB2F5\uC548 \uAD00\uB9AC \uAC00\uB2A5
634
+ - --file \uB610\uB294 --index \uC635\uC158\uC73C\uB85C \uD2B9\uC815 \uD30C\uC77C \uC9C0\uC815 \uAC00\uB2A5
566
635
  - testcases/{\uBC88\uD638}/input.txt\uC640 testcases/{\uBC88\uD638}/output.txt \uD30C\uC77C\uC744 \uAE30\uBC18\uC73C\uB85C \uD14C\uC2A4\uD2B8
567
636
  - \uBB38\uC81C\uC758 \uC2DC\uAC04 \uC81C\uD55C\uC744 \uC790\uB3D9\uC73C\uB85C \uC801\uC6A9
568
637
  - --watch \uC635\uC158\uC73C\uB85C \uD30C\uC77C \uBCC0\uACBD \uC2DC \uC790\uB3D9 \uC7AC\uD14C\uC2A4\uD2B8
@@ -575,7 +644,9 @@ TestCommand = __decorateClass([
575
644
  "test 1000 # 1000\uBC88 \uBB38\uC81C \uD14C\uC2A4\uD2B8",
576
645
  "test --watch # watch \uBAA8\uB4DC\uB85C \uD14C\uC2A4\uD2B8",
577
646
  "test 1000 --watch # 1000\uBC88 \uBB38\uC81C\uB97C watch \uBAA8\uB4DC\uB85C \uD14C\uC2A4\uD2B8",
578
- "test --language python # Python\uC73C\uB85C \uD14C\uC2A4\uD2B8"
647
+ "test --language python # Python\uC73C\uB85C \uD14C\uC2A4\uD2B8",
648
+ "test --file solution-2.py # \uD2B9\uC815 \uD30C\uC77C\uB85C \uD14C\uC2A4\uD2B8",
649
+ "test --index 2 # \uC778\uB371\uC2A4 2\uC758 \uD30C\uC77C\uB85C \uD14C\uC2A4\uD2B8"
579
650
  ]
580
651
  })
581
652
  ], TestCommand);
@@ -2,8 +2,9 @@
2
2
  import {
3
3
  getProblem,
4
4
  scrapeProblem
5
- } from "./chunk-U6KQM2P4.js";
5
+ } from "./chunk-G2SUJE4H.js";
6
6
  import {
7
+ detectLanguageFromFile,
7
8
  findProjectRoot,
8
9
  getAutoOpenEditor,
9
10
  getEditor,
@@ -13,7 +14,7 @@ import {
13
14
  getTierImageUrl,
14
15
  getTierName,
15
16
  parseTimeLimitToMs
16
- } from "./chunk-AHE4QHJD.js";
17
+ } from "./chunk-UDZLWCHF.js";
17
18
 
18
19
  // src/hooks/use-fetch-problem.ts
19
20
  import { execaCommand } from "execa";
@@ -21,7 +22,7 @@ import { useEffect, useState } from "react";
21
22
 
22
23
  // src/services/file-generator.ts
23
24
  import { existsSync } from "fs";
24
- import { mkdir, writeFile, readFile } from "fs/promises";
25
+ import { mkdir, writeFile, readFile, readdir } from "fs/promises";
25
26
  import { join, dirname } from "path";
26
27
  import { fileURLToPath } from "url";
27
28
  function ensureTrailingNewline(content) {
@@ -61,13 +62,60 @@ function getProjectRoot() {
61
62
  }
62
63
  return join(__dirname, "../..");
63
64
  }
65
+ function parseSolutionIndex(filename) {
66
+ const match = filename.match(/^solution-(\d+)\./);
67
+ if (match) {
68
+ const index = parseInt(match[1], 10);
69
+ if (!isNaN(index) && index > 0) {
70
+ return index;
71
+ }
72
+ }
73
+ if (filename.match(/^solution\./)) {
74
+ return 1;
75
+ }
76
+ return null;
77
+ }
78
+ async function getNextSolutionIndex(problemDir, language) {
79
+ try {
80
+ if (!existsSync(problemDir)) {
81
+ return 1;
82
+ }
83
+ const files = await readdir(problemDir);
84
+ const langConfig = getLanguageConfig(language);
85
+ const extension = langConfig.extension;
86
+ let maxIndex = 0;
87
+ for (const file of files) {
88
+ if (!file.endsWith(`.${extension}`)) {
89
+ continue;
90
+ }
91
+ if (!file.startsWith("solution")) {
92
+ continue;
93
+ }
94
+ const detectedLang = detectLanguageFromFile(file);
95
+ if (detectedLang !== language) {
96
+ continue;
97
+ }
98
+ const index = parseSolutionIndex(file);
99
+ if (index !== null && index > maxIndex) {
100
+ maxIndex = index;
101
+ }
102
+ }
103
+ return maxIndex + 1;
104
+ } catch {
105
+ return 1;
106
+ }
107
+ }
64
108
  async function generateProblemFiles(problem, language = "python") {
65
109
  const problemDir = getSolvingDirPath(problem.id, process.cwd(), problem);
66
110
  await mkdir(problemDir, { recursive: true });
67
111
  const langConfig = getLanguageConfig(language);
68
112
  const projectRoot = getProjectRoot();
69
113
  const userProjectRoot = findProjectRoot(process.cwd());
70
- const solutionPath = join(problemDir, `solution.${langConfig.extension}`);
114
+ const nextIndex = await getNextSolutionIndex(problemDir, language);
115
+ const solutionPath = join(
116
+ problemDir,
117
+ `solution-${nextIndex}.${langConfig.extension}`
118
+ );
71
119
  let templateContent = "";
72
120
  let templateFound = false;
73
121
  if (userProjectRoot) {
@@ -8,12 +8,15 @@ import {
8
8
  CommandDef,
9
9
  __decorateClass,
10
10
  defineFlags,
11
+ detectLanguageFromFile,
11
12
  detectProblemIdFromPath,
12
13
  findSolutionFile,
14
+ findSolutionFileByIndex,
15
+ findSolutionFiles,
13
16
  getSupportedLanguagesString,
14
17
  resolveLanguage,
15
18
  resolveProblemContext
16
- } from "./chunk-AHE4QHJD.js";
19
+ } from "./chunk-UDZLWCHF.js";
17
20
 
18
21
  // src/commands/submit.tsx
19
22
  import { Badge, StatusMessage, Alert, Spinner } from "@inkjs/ui";
@@ -25,6 +28,16 @@ var submitFlagsSchema = {
25
28
  shortFlag: "l",
26
29
  description: `\uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
27
30
  \uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}`
31
+ },
32
+ file: {
33
+ type: "string",
34
+ shortFlag: "f",
35
+ description: "\uD2B9\uC815 solution \uD30C\uC77C \uC9C0\uC815 (\uC608: solution-2.py)"
36
+ },
37
+ index: {
38
+ type: "string",
39
+ shortFlag: "i",
40
+ description: "\uC778\uB371\uC2A4\uB85C solution \uD30C\uC77C \uC9C0\uC815 (\uC608: 2)"
28
41
  }
29
42
  };
30
43
  function SubmitView({
@@ -105,11 +118,58 @@ function SubmitView({
105
118
  var SubmitCommand = class extends Command {
106
119
  async execute(args, flags) {
107
120
  const context = await resolveProblemContext(args, { requireId: true });
108
- const sourcePath = await findSolutionFile(context.archiveDir);
109
- const detectedLanguage = await resolveLanguage(
110
- context.archiveDir,
111
- flags.language
112
- );
121
+ let sourcePath;
122
+ let detectedLanguage;
123
+ if (flags.file) {
124
+ const filePath = flags.file;
125
+ if (filePath.startsWith("/") || filePath.match(/^[A-Z]:/)) {
126
+ sourcePath = filePath;
127
+ } else {
128
+ const { join } = await import("path");
129
+ sourcePath = join(context.archiveDir, filePath);
130
+ }
131
+ const fileName = sourcePath.split(/[/\\]/).pop() || "";
132
+ const fileLang = detectLanguageFromFile(fileName);
133
+ if (flags.language) {
134
+ detectedLanguage = flags.language;
135
+ } else if (fileLang) {
136
+ detectedLanguage = fileLang;
137
+ } else {
138
+ detectedLanguage = await resolveLanguage(
139
+ context.archiveDir,
140
+ flags.language
141
+ );
142
+ }
143
+ } else if (flags.index) {
144
+ const index = parseInt(flags.index, 10);
145
+ if (isNaN(index) || index < 1) {
146
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC778\uB371\uC2A4\uC785\uB2C8\uB2E4: ${flags.index}`);
147
+ }
148
+ if (flags.language) {
149
+ detectedLanguage = flags.language;
150
+ sourcePath = await findSolutionFileByIndex(
151
+ context.archiveDir,
152
+ index,
153
+ detectedLanguage
154
+ );
155
+ } else {
156
+ const files = await findSolutionFiles(context.archiveDir);
157
+ const targetFile = files.find((f) => f.index === index);
158
+ if (!targetFile) {
159
+ throw new Error(
160
+ `\uC778\uB371\uC2A4 ${index}\uC758 solution \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
161
+ );
162
+ }
163
+ sourcePath = targetFile.path;
164
+ detectedLanguage = targetFile.language;
165
+ }
166
+ } else {
167
+ detectedLanguage = await resolveLanguage(
168
+ context.archiveDir,
169
+ flags.language
170
+ );
171
+ sourcePath = await findSolutionFile(context.archiveDir, detectedLanguage);
172
+ }
113
173
  let finalProblemId = context.problemId;
114
174
  if (finalProblemId === null) {
115
175
  finalProblemId = detectProblemIdFromPath(context.archiveDir);
@@ -131,7 +191,9 @@ SubmitCommand = __decorateClass([
131
191
  name: "submit",
132
192
  description: `\uBC31\uC900 \uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uACE0 \uC18C\uC2A4 \uCF54\uB4DC\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD569\uB2C8\uB2E4.
133
193
  - \uBB38\uC81C \uBC88\uD638\uB97C \uC778\uC790\uB85C \uC804\uB2EC\uD558\uAC70\uB098 \uBB38\uC81C \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uBB38\uC81C \uBC88\uD638\uB97C \uCD94\uB860
134
- - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
194
+ - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0 (\uAE30\uBCF8\uC801\uC73C\uB85C \uAC00\uC7A5 \uCD5C\uADFC \uC218\uC815\uB41C \uD30C\uC77C \uC0AC\uC6A9)
195
+ - \uC5EC\uB7EC \uB2F5\uC548 \uD30C\uC77C \uC9C0\uC6D0: solution-1.py, solution-2.py \uD615\uC2DD\uC73C\uB85C \uC5EC\uB7EC \uB2F5\uC548 \uAD00\uB9AC \uAC00\uB2A5
196
+ - --file \uB610\uB294 --index \uC635\uC158\uC73C\uB85C \uD2B9\uC815 \uD30C\uC77C \uC9C0\uC815 \uAC00\uB2A5
135
197
  - \uC18C\uC2A4 \uCF54\uB4DC\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uC790\uB3D9 \uBCF5\uC0AC
136
198
  - \uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC790\uB3D9 \uC5F4\uAE30
137
199
  - \uAE30\uBCF8 \uC5B8\uC5B4 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
@@ -142,7 +204,9 @@ SubmitCommand = __decorateClass([
142
204
  examples: [
143
205
  "submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C",
144
206
  "submit 1000 # 1000\uBC88 \uBB38\uC81C \uC81C\uCD9C",
145
- "submit --language python # Python\uC73C\uB85C \uC81C\uCD9C"
207
+ "submit --language python # Python\uC73C\uB85C \uC81C\uCD9C",
208
+ "submit --file solution-2.py # \uD2B9\uC815 \uD30C\uC77C\uB85C \uC81C\uCD9C",
209
+ "submit --index 2 # \uC778\uB371\uC2A4 2\uC758 \uD30C\uC77C\uB85C \uC81C\uCD9C"
146
210
  ]
147
211
  })
148
212
  ], SubmitCommand);
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  findProjectRoot,
4
4
  getConfigMetadata
5
- } from "./chunk-AHE4QHJD.js";
5
+ } from "./chunk-UDZLWCHF.js";
6
6
 
7
7
  // src/hooks/use-config.ts
8
8
  import { existsSync, mkdirSync } from "fs";
@@ -5,12 +5,12 @@ import {
5
5
  getUserTagRatings,
6
6
  getUserTop100,
7
7
  scrapeUserStats
8
- } from "./chunk-U6KQM2P4.js";
8
+ } from "./chunk-G2SUJE4H.js";
9
9
  import {
10
10
  findProjectRoot,
11
11
  getArchiveDir,
12
12
  getSolvingDir
13
- } from "./chunk-AHE4QHJD.js";
13
+ } from "./chunk-UDZLWCHF.js";
14
14
 
15
15
  // src/hooks/use-user-stats.ts
16
16
  import { existsSync } from "fs";
@@ -10642,7 +10642,7 @@ function getSolvingDirPath(problemId, cwd = process.cwd(), _) {
10642
10642
  }
10643
10643
 
10644
10644
  // src/core/execution-context.ts
10645
- import { access } from "fs/promises";
10645
+ import { access, stat } from "fs/promises";
10646
10646
  import { readdir } from "fs/promises";
10647
10647
  import { join as join3 } from "path";
10648
10648
  async function directoryExists(dirPath) {
@@ -10708,13 +10708,79 @@ async function resolveLanguage(problemDir, override) {
10708
10708
  }
10709
10709
  return detectedLanguage;
10710
10710
  }
10711
- async function findSolutionFile(problemDir) {
10712
- const files = await readdir(problemDir);
10713
- const solutionFile = files.find((f) => f.startsWith("solution."));
10714
- if (!solutionFile) {
10711
+ function parseSolutionIndex(filename) {
10712
+ const match = filename.match(/^solution-(\d+)\./);
10713
+ if (match) {
10714
+ const index = parseInt(match[1], 10);
10715
+ if (!isNaN(index) && index > 0) {
10716
+ return index;
10717
+ }
10718
+ }
10719
+ if (filename.match(/^solution\./)) {
10720
+ return 1;
10721
+ }
10722
+ return null;
10723
+ }
10724
+ async function findSolutionFiles(problemDir, language) {
10725
+ try {
10726
+ const files = await readdir(problemDir);
10727
+ const solutionFiles = [];
10728
+ for (const file of files) {
10729
+ if (!file.startsWith("solution")) {
10730
+ continue;
10731
+ }
10732
+ const detectedLang = detectLanguageFromFile(file);
10733
+ if (!detectedLang) {
10734
+ continue;
10735
+ }
10736
+ if (language && detectedLang !== language) {
10737
+ continue;
10738
+ }
10739
+ const index = parseSolutionIndex(file);
10740
+ if (index === null) {
10741
+ continue;
10742
+ }
10743
+ const filePath = join3(problemDir, file);
10744
+ try {
10745
+ const stats = await stat(filePath);
10746
+ solutionFiles.push({
10747
+ path: filePath,
10748
+ filename: file,
10749
+ index,
10750
+ language: detectedLang,
10751
+ mtime: stats.mtime
10752
+ });
10753
+ } catch {
10754
+ }
10755
+ }
10756
+ return solutionFiles;
10757
+ } catch {
10758
+ return [];
10759
+ }
10760
+ }
10761
+ async function findLatestSolutionFile(problemDir, language) {
10762
+ const files = await findSolutionFiles(problemDir, language);
10763
+ if (files.length === 0) {
10715
10764
  throw new Error("solution.* \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
10716
10765
  }
10717
- return join3(problemDir, solutionFile);
10766
+ const latest = files.reduce((prev, current) => {
10767
+ return current.mtime > prev.mtime ? current : prev;
10768
+ });
10769
+ return latest.path;
10770
+ }
10771
+ async function findSolutionFileByIndex(problemDir, index, language) {
10772
+ const files = await findSolutionFiles(problemDir, language);
10773
+ const targetFile = files.find((f) => f.index === index);
10774
+ if (!targetFile) {
10775
+ const langText = language ? ` (\uC5B8\uC5B4: ${language})` : "";
10776
+ throw new Error(
10777
+ `\uC778\uB371\uC2A4 ${index}\uC758 solution \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.${langText}`
10778
+ );
10779
+ }
10780
+ return targetFile.path;
10781
+ }
10782
+ async function findSolutionFile(problemDir, language) {
10783
+ return findLatestSolutionFile(problemDir, language);
10718
10784
  }
10719
10785
 
10720
10786
  // src/core/base-command.tsx
@@ -11556,6 +11622,7 @@ export {
11556
11622
  getSupportedLanguages,
11557
11623
  getSupportedLanguagesString,
11558
11624
  getLanguageConfig,
11625
+ detectLanguageFromFile,
11559
11626
  TIER_COLORS,
11560
11627
  getNextTierMinRating,
11561
11628
  calculateTierProgress,
@@ -11570,6 +11637,8 @@ export {
11570
11637
  getSolvingDirPath,
11571
11638
  resolveProblemContext,
11572
11639
  resolveLanguage,
11640
+ findSolutionFiles,
11641
+ findSolutionFileByIndex,
11573
11642
  findSolutionFile,
11574
11643
  Command,
11575
11644
  source_default,
@@ -6,7 +6,7 @@ import {
6
6
  getArchiveDirPath,
7
7
  getSolvingDir,
8
8
  getSolvingDirPath
9
- } from "./chunk-AHE4QHJD.js";
9
+ } from "./chunk-UDZLWCHF.js";
10
10
 
11
11
  // src/hooks/use-archive.ts
12
12
  import { access, readFile, rename, mkdir, readdir, rmdir } from "fs/promises";
@@ -9,7 +9,7 @@ import {
9
9
  getIncludeTag,
10
10
  getSolvedAcHandle,
11
11
  getSolvingDir
12
- } from "./chunk-AHE4QHJD.js";
12
+ } from "./chunk-UDZLWCHF.js";
13
13
 
14
14
  // src/hooks/use-init.ts
15
15
  import { existsSync } from "fs";
@@ -3,9 +3,9 @@ import {
3
3
  ArchiveCommand,
4
4
  ArchiveView,
5
5
  archive_default
6
- } from "../chunk-EZLFR55N.js";
7
- import "../chunk-LCHSAHUP.js";
8
- import "../chunk-AHE4QHJD.js";
6
+ } from "../chunk-EQDQI3RC.js";
7
+ import "../chunk-V27D7TPW.js";
8
+ import "../chunk-UDZLWCHF.js";
9
9
  export {
10
10
  ArchiveCommand,
11
11
  ArchiveView,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useConfig
4
- } from "../chunk-XNZBOLC2.js";
4
+ } from "../chunk-RHWSWBUG.js";
5
5
  import {
6
6
  Command,
7
7
  CommandBuilder,
@@ -10,7 +10,7 @@ import {
10
10
  getConfigMetadata,
11
11
  icons,
12
12
  logger
13
- } from "../chunk-AHE4QHJD.js";
13
+ } from "../chunk-UDZLWCHF.js";
14
14
 
15
15
  // src/commands/config.tsx
16
16
  import { StatusMessage, Select, TextInput, Alert, Badge } from "@inkjs/ui";
@@ -3,10 +3,10 @@ import {
3
3
  FetchCommand,
4
4
  FetchView,
5
5
  fetch_default
6
- } from "../chunk-CGL3LQEB.js";
7
- import "../chunk-ERJEYECY.js";
8
- import "../chunk-U6KQM2P4.js";
9
- import "../chunk-AHE4QHJD.js";
6
+ } from "../chunk-4J6PFK66.js";
7
+ import "../chunk-KUUJ4DBH.js";
8
+ import "../chunk-G2SUJE4H.js";
9
+ import "../chunk-UDZLWCHF.js";
10
10
  export {
11
11
  FetchCommand,
12
12
  FetchView,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useInit
4
- } from "../chunk-QT2FQELR.js";
4
+ } from "../chunk-ZZ76HNFP.js";
5
5
  import {
6
6
  Command,
7
7
  CommandBuilder,
@@ -10,7 +10,7 @@ import {
10
10
  getSupportedLanguages,
11
11
  getVersion,
12
12
  icons
13
- } from "../chunk-AHE4QHJD.js";
13
+ } from "../chunk-UDZLWCHF.js";
14
14
 
15
15
  // src/commands/init.tsx
16
16
  import {
@@ -3,10 +3,10 @@ import {
3
3
  OpenCommand,
4
4
  OpenView,
5
5
  open_default
6
- } from "../chunk-3DTUNRPH.js";
6
+ } from "../chunk-C3IOK7V4.js";
7
7
  import "../chunk-3LR2NGRC.js";
8
8
  import "../chunk-QGMWUOJ3.js";
9
- import "../chunk-AHE4QHJD.js";
9
+ import "../chunk-UDZLWCHF.js";
10
10
  export {
11
11
  OpenCommand,
12
12
  OpenView,
@@ -1,18 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useRunSolution
4
- } from "../chunk-4VPUJY7G.js";
4
+ } from "../chunk-CKSAX7KT.js";
5
5
  import {
6
6
  Command,
7
7
  CommandBuilder,
8
8
  CommandDef,
9
9
  __decorateClass,
10
10
  defineFlags,
11
+ detectLanguageFromFile,
12
+ findSolutionFileByIndex,
13
+ findSolutionFiles,
11
14
  getSupportedLanguagesString,
12
15
  icons,
13
16
  resolveLanguage,
14
17
  resolveProblemContext
15
- } from "../chunk-AHE4QHJD.js";
18
+ } from "../chunk-UDZLWCHF.js";
16
19
 
17
20
  // src/commands/run.tsx
18
21
  import { join } from "path";
@@ -31,19 +34,30 @@ var runFlagsSchema = {
31
34
  type: "string",
32
35
  shortFlag: "i",
33
36
  description: "\uC785\uB825 \uD30C\uC77C \uC9C0\uC815 (\uC608: 1 \uB610\uB294 testcases/1/input.txt)"
37
+ },
38
+ file: {
39
+ type: "string",
40
+ shortFlag: "f",
41
+ description: "\uD2B9\uC815 solution \uD30C\uC77C \uC9C0\uC815 (\uC608: solution-2.py)"
42
+ },
43
+ index: {
44
+ type: "string",
45
+ description: "\uC778\uB371\uC2A4\uB85C solution \uD30C\uC77C \uC9C0\uC815 (\uC608: 2)"
34
46
  }
35
47
  };
36
48
  function RunView({
37
49
  problemDir,
38
50
  language,
39
51
  inputFile,
40
- onComplete
52
+ onComplete,
53
+ solutionPath
41
54
  }) {
42
55
  const { status, result, error } = useRunSolution({
43
56
  problemDir,
44
57
  language,
45
58
  inputFile,
46
- onComplete
59
+ onComplete,
60
+ solutionPath
47
61
  });
48
62
  if (status === "loading") {
49
63
  if (!inputFile) {
@@ -115,6 +129,54 @@ function RunView({
115
129
  var RunCommand = class extends Command {
116
130
  async execute(args, flags) {
117
131
  const context = await resolveProblemContext(args);
132
+ let solutionPath;
133
+ let detectedLanguage;
134
+ if (flags.file) {
135
+ const filePath = flags.file;
136
+ if (filePath.startsWith("/") || filePath.match(/^[A-Z]:/)) {
137
+ solutionPath = filePath;
138
+ } else {
139
+ solutionPath = join(context.archiveDir, filePath);
140
+ }
141
+ const fileName = solutionPath.split(/[/\\]/).pop() || "";
142
+ const fileLang = detectLanguageFromFile(fileName);
143
+ if (flags.language) {
144
+ detectedLanguage = flags.language;
145
+ } else if (fileLang) {
146
+ detectedLanguage = fileLang;
147
+ } else {
148
+ detectedLanguage = await resolveLanguage(
149
+ context.archiveDir,
150
+ flags.language
151
+ );
152
+ }
153
+ } else if (flags.index) {
154
+ const index = parseInt(flags.index, 10);
155
+ if (isNaN(index) || index < 1) {
156
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC778\uB371\uC2A4\uC785\uB2C8\uB2E4: ${flags.index}`);
157
+ }
158
+ if (flags.language) {
159
+ detectedLanguage = flags.language;
160
+ solutionPath = await findSolutionFileByIndex(
161
+ context.archiveDir,
162
+ index,
163
+ detectedLanguage
164
+ );
165
+ } else {
166
+ const files = await findSolutionFiles(context.archiveDir);
167
+ const targetFile = files.find((f) => f.index === index);
168
+ if (!targetFile) {
169
+ throw new Error(`\uC778\uB371\uC2A4 ${index}\uC758 solution \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);
170
+ }
171
+ solutionPath = targetFile.path;
172
+ detectedLanguage = targetFile.language;
173
+ }
174
+ } else {
175
+ detectedLanguage = await resolveLanguage(
176
+ context.archiveDir,
177
+ flags.language
178
+ );
179
+ }
118
180
  let inputPath;
119
181
  if (flags.input) {
120
182
  const inputValue = flags.input;
@@ -129,14 +191,11 @@ var RunCommand = class extends Command {
129
191
  inputPath = join(context.archiveDir, inputValue);
130
192
  }
131
193
  }
132
- const detectedLanguage = await resolveLanguage(
133
- context.archiveDir,
134
- flags.language
135
- );
136
194
  await this.renderView(RunView, {
137
195
  problemDir: context.archiveDir,
138
196
  language: detectedLanguage,
139
- inputFile: inputPath
197
+ inputFile: inputPath,
198
+ solutionPath
140
199
  });
141
200
  }
142
201
  };
@@ -145,7 +204,9 @@ RunCommand = __decorateClass([
145
204
  name: "run",
146
205
  description: `\uCF54\uB4DC\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4 (\uD14C\uC2A4\uD2B8 \uC5C6\uC774).
147
206
  - \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC \uB610\uB294 \uC9C0\uC815\uD55C \uBB38\uC81C \uBC88\uD638\uC758 \uCF54\uB4DC \uC2E4\uD589
148
- - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
207
+ - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0 (\uAE30\uBCF8\uC801\uC73C\uB85C \uAC00\uC7A5 \uCD5C\uADFC \uC218\uC815\uB41C \uD30C\uC77C \uC0AC\uC6A9)
208
+ - \uC5EC\uB7EC \uB2F5\uC548 \uD30C\uC77C \uC9C0\uC6D0: solution-1.py, solution-2.py \uD615\uC2DD\uC73C\uB85C \uC5EC\uB7EC \uB2F5\uC548 \uAD00\uB9AC \uAC00\uB2A5
209
+ - --file \uB610\uB294 --index \uC635\uC158\uC73C\uB85C \uD2B9\uC815 \uD30C\uC77C \uC9C0\uC815 \uAC00\uB2A5
149
210
  - --input \uC635\uC158\uC73C\uB85C \uC785\uB825 \uD30C\uC77C \uC9C0\uC815 \uAC00\uB2A5 (\uC608: testcases/1/input.txt)
150
211
  - \uC635\uC158 \uC5C6\uC774 \uC2E4\uD589 \uC2DC \uD45C\uC900 \uC785\uB825\uC73C\uB85C \uC785\uB825 \uBC1B\uAE30
151
212
  - \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 \uAC80\uC99D \uC5C6\uC774 \uB2E8\uC21C \uC2E4\uD589
@@ -158,7 +219,9 @@ RunCommand = __decorateClass([
158
219
  "run 1000 # 1000\uBC88 \uBB38\uC81C \uD45C\uC900 \uC785\uB825\uC73C\uB85C \uC2E4\uD589",
159
220
  "run --language python # Python\uC73C\uB85C \uD45C\uC900 \uC785\uB825\uC73C\uB85C \uC2E4\uD589",
160
221
  "run --input 1 # \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 1\uBC88 \uC0AC\uC6A9",
161
- "run --input testcases/1/input.txt # \uC804\uCCB4 \uACBD\uB85C\uB85C \uC785\uB825 \uD30C\uC77C \uC9C0\uC815"
222
+ "run --input testcases/1/input.txt # \uC804\uCCB4 \uACBD\uB85C\uB85C \uC785\uB825 \uD30C\uC77C \uC9C0\uC815",
223
+ "run --file solution-2.py # \uD2B9\uC815 \uD30C\uC77C\uB85C \uC2E4\uD589",
224
+ "run --index 2 # \uC778\uB371\uC2A4 2\uC758 \uD30C\uC77C\uB85C \uC2E4\uD589"
162
225
  ]
163
226
  })
164
227
  ], RunCommand);
@@ -1,33 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SubmitView
4
- } from "../chunk-C25Y3KFE.js";
4
+ } from "../chunk-LGSE3IEE.js";
5
5
  import {
6
6
  TestView
7
- } from "../chunk-COPOWZM5.js";
8
- import "../chunk-G272SKVM.js";
7
+ } from "../chunk-K5KMUKVD.js";
8
+ import "../chunk-TPIID7I2.js";
9
9
  import "../chunk-MZUR7SER.js";
10
10
  import {
11
11
  ArchiveView
12
- } from "../chunk-EZLFR55N.js";
13
- import "../chunk-LCHSAHUP.js";
14
- import "../chunk-XNZBOLC2.js";
12
+ } from "../chunk-EQDQI3RC.js";
13
+ import "../chunk-V27D7TPW.js";
14
+ import "../chunk-RHWSWBUG.js";
15
15
  import {
16
16
  FetchView
17
- } from "../chunk-CGL3LQEB.js";
18
- import "../chunk-ERJEYECY.js";
17
+ } from "../chunk-4J6PFK66.js";
18
+ import "../chunk-KUUJ4DBH.js";
19
19
  import {
20
20
  fetchWithRetry,
21
21
  getProblem,
22
22
  searchProblems
23
- } from "../chunk-U6KQM2P4.js";
24
- import "../chunk-QT2FQELR.js";
23
+ } from "../chunk-G2SUJE4H.js";
24
+ import "../chunk-ZZ76HNFP.js";
25
25
  import {
26
26
  OpenView
27
- } from "../chunk-3DTUNRPH.js";
27
+ } from "../chunk-C3IOK7V4.js";
28
28
  import "../chunk-3LR2NGRC.js";
29
29
  import "../chunk-QGMWUOJ3.js";
30
- import "../chunk-4VPUJY7G.js";
30
+ import "../chunk-CKSAX7KT.js";
31
31
  import {
32
32
  Command,
33
33
  CommandBuilder,
@@ -44,7 +44,7 @@ import {
44
44
  logger,
45
45
  resolveLanguage,
46
46
  source_default
47
- } from "../chunk-AHE4QHJD.js";
47
+ } from "../chunk-UDZLWCHF.js";
48
48
 
49
49
  // src/commands/search.tsx
50
50
  import { existsSync } from "fs";
@@ -282,7 +282,7 @@ function ProblemActionView({
282
282
  OpenView,
283
283
  {
284
284
  problemId,
285
- problemDir: sourcePath || problemDir || void 0,
285
+ problemDir: problemDir || void 0,
286
286
  mode: "editor",
287
287
  onComplete
288
288
  }
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useUserStats
4
- } from "../chunk-G272SKVM.js";
5
- import "../chunk-U6KQM2P4.js";
4
+ } from "../chunk-TPIID7I2.js";
5
+ import "../chunk-G2SUJE4H.js";
6
6
  import {
7
7
  Command,
8
8
  CommandBuilder,
@@ -19,7 +19,7 @@ import {
19
19
  icons,
20
20
  logger,
21
21
  source_default
22
- } from "../chunk-AHE4QHJD.js";
22
+ } from "../chunk-UDZLWCHF.js";
23
23
 
24
24
  // src/commands/stats.tsx
25
25
  import { Alert, Spinner } from "@inkjs/ui";
@@ -3,10 +3,10 @@ import {
3
3
  SubmitCommand,
4
4
  SubmitView,
5
5
  submit_default
6
- } from "../chunk-C25Y3KFE.js";
6
+ } from "../chunk-LGSE3IEE.js";
7
7
  import "../chunk-MZUR7SER.js";
8
8
  import "../chunk-QGMWUOJ3.js";
9
- import "../chunk-AHE4QHJD.js";
9
+ import "../chunk-UDZLWCHF.js";
10
10
  export {
11
11
  SubmitCommand,
12
12
  SubmitView,
@@ -3,18 +3,18 @@ import {
3
3
  TestCommand,
4
4
  TestView,
5
5
  test_default
6
- } from "../chunk-COPOWZM5.js";
7
- import "../chunk-G272SKVM.js";
6
+ } from "../chunk-K5KMUKVD.js";
7
+ import "../chunk-TPIID7I2.js";
8
8
  import "../chunk-MZUR7SER.js";
9
- import "../chunk-LCHSAHUP.js";
10
- import "../chunk-XNZBOLC2.js";
11
- import "../chunk-ERJEYECY.js";
12
- import "../chunk-U6KQM2P4.js";
13
- import "../chunk-QT2FQELR.js";
9
+ import "../chunk-V27D7TPW.js";
10
+ import "../chunk-RHWSWBUG.js";
11
+ import "../chunk-KUUJ4DBH.js";
12
+ import "../chunk-G2SUJE4H.js";
13
+ import "../chunk-ZZ76HNFP.js";
14
14
  import "../chunk-3LR2NGRC.js";
15
15
  import "../chunk-QGMWUOJ3.js";
16
- import "../chunk-4VPUJY7G.js";
17
- import "../chunk-AHE4QHJD.js";
16
+ import "../chunk-CKSAX7KT.js";
17
+ import "../chunk-UDZLWCHF.js";
18
18
  export {
19
19
  TestCommand,
20
20
  TestView,
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findProjectRoot,
4
4
  generateGlobalHelp,
5
5
  logger
6
- } from "./chunk-AHE4QHJD.js";
6
+ } from "./chunk-UDZLWCHF.js";
7
7
 
8
8
  // src/index.ts
9
9
  import { readdir } from "fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhseung/ps-cli",
3
- "version": "1.11.1",
3
+ "version": "1.12.2",
4
4
  "description": "백준(BOJ) 문제 해결을 위한 통합 CLI 도구",
5
5
  "type": "module",
6
6
  "bin": {