@rhseung/ps-cli 1.7.5 → 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.
- package/README.md +55 -12
- package/dist/chunk-D5PTJ4WQ.js +210 -0
- package/dist/chunk-EGPBBIJ2.js +251 -0
- package/dist/{chunk-F4LZ6ENP.js → chunk-JZK2464U.js} +1037 -118
- package/dist/chunk-SBBC4CBS.js +414 -0
- package/dist/{chunk-VIHXBCOZ.js → chunk-TTTS4CDS.js} +1 -1
- package/dist/chunk-XV6UUPQC.js +288 -0
- package/dist/{chunk-4ISG24GW.js → chunk-YH6VDCNG.js} +173 -15
- package/dist/chunk-YXLVFZZH.js +370 -0
- package/dist/commands/archive.js +6 -195
- package/dist/commands/config.js +74 -188
- package/dist/commands/fetch.js +7 -394
- package/dist/commands/init.js +50 -10
- package/dist/commands/open.js +6 -93
- package/dist/commands/run.js +59 -47
- package/dist/commands/search.js +296 -125
- package/dist/commands/stats.js +333 -84
- package/dist/commands/submit.js +7 -245
- package/dist/commands/test.js +7 -371
- package/dist/index.js +90 -52
- package/package.json +2 -1
- package/dist/chunk-7MQMPJ3X.js +0 -88
- package/dist/chunk-A6STXEAE.js +0 -54
- package/dist/chunk-ASMT3CRD.js +0 -500
- package/dist/chunk-GCOFFYJ3.js +0 -47
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# ps-cli
|
|
2
2
|
|
|
3
|
-
백준 문제
|
|
3
|
+
**대화형** 인터페이스와 **사용자 친화적인 디자인**을 갖춘 백준 문제 해결 CLI 도구입니다.
|
|
4
|
+
검색부터 제출까지, 모든 과정을 직관적이고 아름다운 터미널 환경에서 해결하세요.
|
|
5
|
+
|
|
6
|
+
Nerd Font 사용을 권장합니다.
|
|
4
7
|
|
|
5
8
|
## 설치
|
|
6
9
|
|
|
@@ -13,6 +16,9 @@ bun install -g @rhseung/ps-cli
|
|
|
13
16
|
## 빠른 시작
|
|
14
17
|
|
|
15
18
|
```bash
|
|
19
|
+
# 0. 도움말 보기
|
|
20
|
+
ps --help
|
|
21
|
+
|
|
16
22
|
# 1. 프로젝트 초기화
|
|
17
23
|
ps init
|
|
18
24
|
|
|
@@ -60,6 +66,7 @@ ps fetch <문제번호> [옵션]
|
|
|
60
66
|
|
|
61
67
|
**옵션:**
|
|
62
68
|
|
|
69
|
+
- `--help`, `-h`: 도움말 표시
|
|
63
70
|
- `--language`, `-l`: 언어 선택 (python, javascript, typescript, cpp)
|
|
64
71
|
- 기본값: python 또는 설정 파일의 `default-language`
|
|
65
72
|
|
|
@@ -92,6 +99,7 @@ ps test [문제번호] [옵션]
|
|
|
92
99
|
|
|
93
100
|
**옵션:**
|
|
94
101
|
|
|
102
|
+
- `--help`, `-h`: 도움말 표시
|
|
95
103
|
- `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
|
|
96
104
|
- 지원 언어: python, javascript, typescript, cpp
|
|
97
105
|
- `--watch`, `-w`: watch 모드 (파일 변경 시 자동 재테스트)
|
|
@@ -128,6 +136,7 @@ ps run [문제번호] [옵션]
|
|
|
128
136
|
|
|
129
137
|
**옵션:**
|
|
130
138
|
|
|
139
|
+
- `--help`, `-h`: 도움말 표시
|
|
131
140
|
- `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
|
|
132
141
|
- 지원 언어: python, javascript, typescript, cpp
|
|
133
142
|
- `--input`, `-i`: 입력 파일 지정
|
|
@@ -166,6 +175,7 @@ ps submit [문제번호] [옵션]
|
|
|
166
175
|
|
|
167
176
|
**옵션:**
|
|
168
177
|
|
|
178
|
+
- `--help`, `-h`: 도움말 표시
|
|
169
179
|
- `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
|
|
170
180
|
- 지원 언어: python, javascript, typescript, cpp
|
|
171
181
|
|
|
@@ -188,7 +198,7 @@ ps submit --language python # Python으로 제출
|
|
|
188
198
|
|
|
189
199
|
### `archive` - 아카이빙
|
|
190
200
|
|
|
191
|
-
solving 디렉토리의 문제를 archive 디렉토리로 이동하고 Git 커밋을 생성합니다.
|
|
201
|
+
solving 디렉토리의 문제를 archive 디렉토리로 이동하고 (선택적으로) Git 커밋을 생성합니다.
|
|
192
202
|
|
|
193
203
|
**사용법:**
|
|
194
204
|
|
|
@@ -206,31 +216,47 @@ ps archive # 현재 디렉토리에서 문제 번호 자동
|
|
|
206
216
|
**설명:**
|
|
207
217
|
|
|
208
218
|
- solving 디렉토리에서 문제를 찾아 archive 디렉토리로 이동
|
|
209
|
-
- Git add 및 commit 실행
|
|
210
|
-
- 커밋 메시지: "solve: {
|
|
219
|
+
- 설정에 따라 Git add 및 commit 실행
|
|
220
|
+
- 기본 커밋 메시지: "solve: {id} - {title}"
|
|
221
|
+
- `archive-auto-commit` 이 `true` 인 경우, **archive 디렉토리로 이동 후** Git 커밋을 시도하며, 커밋 실패 시 다시 원래 위치로 되돌림 (롤백)
|
|
222
|
+
- `archive-auto-commit` 이 `false` 인 경우, Git 커밋 없이 디렉토리만 이동
|
|
211
223
|
|
|
212
224
|
---
|
|
213
225
|
|
|
214
|
-
### `open` - 문제 페이지 열기
|
|
226
|
+
### `open` - 문제 페이지 또는 에디터 열기
|
|
215
227
|
|
|
216
|
-
백준 문제
|
|
228
|
+
백준 문제 페이지 또는 에디터를 엽니다.
|
|
217
229
|
|
|
218
230
|
**사용법:**
|
|
219
231
|
|
|
220
232
|
```bash
|
|
221
|
-
ps open [문제번호]
|
|
233
|
+
ps open [문제번호] [옵션]
|
|
234
|
+
ps open --workbook <문제집ID>
|
|
235
|
+
ps open -w <문제집ID>
|
|
222
236
|
```
|
|
223
237
|
|
|
238
|
+
**옵션:**
|
|
239
|
+
|
|
240
|
+
- `--help`, `-h`: 도움말 표시
|
|
241
|
+
- `--workbook`, `-w`: 문제집 ID를 지정하여 해당 문제집 페이지를 엽니다
|
|
242
|
+
- `--editor`, `-e`: 문제 파일을 에디터로 엽니다
|
|
243
|
+
|
|
224
244
|
**예제:**
|
|
225
245
|
|
|
226
246
|
```bash
|
|
227
|
-
ps open 1000 # 1000번 문제 열기
|
|
228
|
-
ps open # 문제
|
|
247
|
+
ps open 1000 # 1000번 문제 브라우저로 열기
|
|
248
|
+
ps open # 현재 문제 브라우저로 열기
|
|
249
|
+
ps open -e # 현재 문제 에디터로 열기
|
|
250
|
+
ps open 1000 -e # 1000번 문제 에디터로 열기
|
|
251
|
+
ps open --workbook 25052 # 문제집 25052 열기
|
|
229
252
|
```
|
|
230
253
|
|
|
231
254
|
**설명:**
|
|
232
255
|
|
|
233
256
|
- 문제 번호를 인자로 전달하거나 문제 디렉토리에서 실행하면 자동으로 문제 번호를 추론
|
|
257
|
+
- `--workbook` 또는 `-w` 옵션으로 문제집 페이지를 열 수 있습니다
|
|
258
|
+
- `--editor` 또는 `-e` 옵션으로 문제의 솔루션 파일이나 디렉토리를 에디터로 열 수 있습니다
|
|
259
|
+
- 에디터는 `ps config set editor <명령어>`로 설정할 수 있습니다 (기본값: `code`)
|
|
234
260
|
|
|
235
261
|
---
|
|
236
262
|
|
|
@@ -247,6 +273,7 @@ ps search --workbook <문제집ID>
|
|
|
247
273
|
|
|
248
274
|
**옵션:**
|
|
249
275
|
|
|
276
|
+
- `--help`, `-h`: 도움말 표시
|
|
250
277
|
- `--workbook`: 문제집 ID를 지정하여 해당 문제집의 문제 목록을 표시
|
|
251
278
|
|
|
252
279
|
**예제:**
|
|
@@ -263,7 +290,15 @@ ps search --workbook 25052 # 문제집 25052의 문제 목록 표시
|
|
|
263
290
|
**설명:**
|
|
264
291
|
|
|
265
292
|
- solved.ac 검색어 문법을 지원합니다
|
|
266
|
-
- 문제 목록에서
|
|
293
|
+
- 문제 목록에서 해결 상태를 아이콘으로 확인할 수 있습니다
|
|
294
|
+
- **해결됨(``)**: 이미 해결되어 아카이브된 문제
|
|
295
|
+
- **해결 중(``)**: 현재 해결 중인 문제
|
|
296
|
+
- 문제 선택 시 인터랙티브 메뉴를 통해 다양한 작업을 수행할 수 있습니다
|
|
297
|
+
- ` 브라우저에서 열기 (open)`
|
|
298
|
+
- ` 문제 가져오기 (fetch)`
|
|
299
|
+
- ` 로컬 테스트 실행 (test)` (해결 중/완료인 경우)
|
|
300
|
+
- ` 코드 제출하기 (submit)` (해결 중/완료인 경우)
|
|
301
|
+
- ` 문제 아카이브 (archive)` (해결 중인 경우)
|
|
267
302
|
- 페이지네이션을 통해 여러 페이지의 결과를 탐색할 수 있습니다
|
|
268
303
|
- `--workbook` 옵션으로 백준 문제집의 문제 목록을 볼 수 있습니다
|
|
269
304
|
|
|
@@ -281,6 +316,7 @@ ps stats [핸들] [옵션]
|
|
|
281
316
|
|
|
282
317
|
**옵션:**
|
|
283
318
|
|
|
319
|
+
- `--help`, `-h`: 도움말 표시
|
|
284
320
|
- `--handle`, `-h`: Solved.ac 핸들
|
|
285
321
|
- 설정에 저장된 값 사용 가능
|
|
286
322
|
- 인자로 전달하거나 플래그로 지정 가능
|
|
@@ -308,7 +344,7 @@ ps stats # 설정에 저장된 핸들 사용
|
|
|
308
344
|
**사용법:**
|
|
309
345
|
|
|
310
346
|
```bash
|
|
311
|
-
ps config <명령어> [키] [값]
|
|
347
|
+
ps config <명령어> [키] [값] [옵션]
|
|
312
348
|
```
|
|
313
349
|
|
|
314
350
|
**명령어:**
|
|
@@ -318,6 +354,10 @@ ps config <명령어> [키] [값]
|
|
|
318
354
|
- `list`: 모든 설정 조회
|
|
319
355
|
- `clear`: .ps-cli.json 파일 삭제
|
|
320
356
|
|
|
357
|
+
**옵션:**
|
|
358
|
+
|
|
359
|
+
- `--help`, `-h`: 도움말 표시
|
|
360
|
+
|
|
321
361
|
**예제:**
|
|
322
362
|
|
|
323
363
|
```bash
|
|
@@ -343,10 +383,13 @@ ps config clear # .ps-cli.json 파일 삭제
|
|
|
343
383
|
- `default-language`: 기본 언어 (python, javascript, typescript, cpp)
|
|
344
384
|
- `editor`: 에디터 명령어 (code, cursor, vim 등)
|
|
345
385
|
- `auto-open-editor`: fetch 후 자동으로 에디터 열기 (true/false)
|
|
386
|
+
- `include-tag`: README에 알고리즘 분류(태그) 포함 여부 (true/false, 기본값: true)
|
|
346
387
|
- `solved-ac-handle`: Solved.ac 핸들
|
|
347
388
|
- `archive-dir`: 아카이브된 문제 디렉토리 (기본값: problems)
|
|
348
389
|
- `solving-dir`: 푸는 중인 문제 디렉토리 (기본값: solving)
|
|
349
390
|
- `archive-strategy`: 아카이빙 전략
|
|
391
|
+
- `archive-auto-commit`: archive 실행 시 Git 커밋 자동 실행 여부 (true/false, 기본값: true)
|
|
392
|
+
- `archive-commit-message`: archive 시 사용할 Git 커밋 메시지 템플릿 (`{id}`, `{title}` 사용 가능, 기본값: `solve: {id} - {title}`)
|
|
350
393
|
|
|
351
394
|
### 아카이빙 전략
|
|
352
395
|
|
|
@@ -416,7 +459,7 @@ node dist/index.js init
|
|
|
416
459
|
```bash
|
|
417
460
|
# 프로젝트 디렉토리에서
|
|
418
461
|
bun run build
|
|
419
|
-
|
|
462
|
+
bun link
|
|
420
463
|
|
|
421
464
|
# 외부 폴더에서 테스트
|
|
422
465
|
cd /path/to/test-project
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
Command,
|
|
4
|
+
CommandBuilder,
|
|
5
|
+
CommandDef,
|
|
6
|
+
__decorateClass,
|
|
7
|
+
findProjectRoot,
|
|
8
|
+
getArchiveAutoCommit,
|
|
9
|
+
getArchiveCommitMessage,
|
|
10
|
+
getArchiveDirPath,
|
|
11
|
+
getSolvingDir,
|
|
12
|
+
getSolvingDirPath,
|
|
13
|
+
logger,
|
|
14
|
+
resolveProblemContext
|
|
15
|
+
} from "./chunk-JZK2464U.js";
|
|
16
|
+
|
|
17
|
+
// src/commands/archive.tsx
|
|
18
|
+
import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
|
|
19
|
+
import { Box } from "ink";
|
|
20
|
+
|
|
21
|
+
// src/hooks/use-archive.ts
|
|
22
|
+
import { access, readFile, rename, mkdir, readdir, rmdir } from "fs/promises";
|
|
23
|
+
import { join, dirname } from "path";
|
|
24
|
+
import { execa } from "execa";
|
|
25
|
+
import { useEffect, useState } from "react";
|
|
26
|
+
function useArchive({
|
|
27
|
+
problemId,
|
|
28
|
+
onComplete
|
|
29
|
+
}) {
|
|
30
|
+
const [status, setStatus] = useState(
|
|
31
|
+
"loading"
|
|
32
|
+
);
|
|
33
|
+
const [message, setMessage] = useState("\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uB294 \uC911...");
|
|
34
|
+
const [error, setError] = useState(null);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
async function archive() {
|
|
37
|
+
try {
|
|
38
|
+
const projectRoot = findProjectRoot();
|
|
39
|
+
if (!projectRoot) {
|
|
40
|
+
throw new Error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
41
|
+
}
|
|
42
|
+
const solvingDir = getSolvingDirPath(problemId, projectRoot);
|
|
43
|
+
setMessage("solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uD655\uC778\uD558\uB294 \uC911...");
|
|
44
|
+
try {
|
|
45
|
+
await access(solvingDir);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`solving \uB514\uB809\uD1A0\uB9AC\uC5D0 \uBB38\uC81C ${problemId}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
setMessage("\uBB38\uC81C \uC815\uBCF4\uB97C \uC77D\uB294 \uC911...");
|
|
52
|
+
const metaPath = join(solvingDir, "meta.json");
|
|
53
|
+
let problemTitle = `\uBB38\uC81C ${problemId}`;
|
|
54
|
+
let problem;
|
|
55
|
+
try {
|
|
56
|
+
const metaContent = await readFile(metaPath, "utf-8");
|
|
57
|
+
const meta = JSON.parse(metaContent);
|
|
58
|
+
if (meta.title) {
|
|
59
|
+
problemTitle = meta.title;
|
|
60
|
+
}
|
|
61
|
+
if (meta.id && meta.level !== void 0) {
|
|
62
|
+
problem = {
|
|
63
|
+
id: meta.id,
|
|
64
|
+
title: meta.title || `\uBB38\uC81C ${meta.id}`,
|
|
65
|
+
level: meta.level,
|
|
66
|
+
tier: "",
|
|
67
|
+
// tier는 level에서 계산되므로 빈 문자열로 충분
|
|
68
|
+
tags: meta.tags || [],
|
|
69
|
+
testCases: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
const archiveDir = getArchiveDirPath(problemId, projectRoot, problem);
|
|
75
|
+
try {
|
|
76
|
+
await access(archiveDir);
|
|
77
|
+
throw new Error(
|
|
78
|
+
`archive \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC774\uBBF8 \uBB38\uC81C ${problemId}\uAC00 \uC874\uC7AC\uD569\uB2C8\uB2E4.`
|
|
79
|
+
);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (err instanceof Error && err.message.includes("\uC774\uBBF8")) {
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const autoCommit = getArchiveAutoCommit();
|
|
86
|
+
const template = getArchiveCommitMessage() ?? "solve: {id} - {title}";
|
|
87
|
+
const commitMessage = template.replace("{id}", String(problemId)).replace("{title}", problemTitle);
|
|
88
|
+
const archiveDirParent = dirname(archiveDir);
|
|
89
|
+
setMessage("\uC544\uCE74\uC774\uBE0C \uB514\uB809\uD1A0\uB9AC\uB97C \uC900\uBE44\uD558\uB294 \uC911...");
|
|
90
|
+
await mkdir(archiveDirParent, { recursive: true });
|
|
91
|
+
setMessage("\uBB38\uC81C\uB97C archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9\uD558\uB294 \uC911...");
|
|
92
|
+
await rename(solvingDir, archiveDir);
|
|
93
|
+
if (autoCommit) {
|
|
94
|
+
try {
|
|
95
|
+
setMessage("Git \uCEE4\uBC0B\uC744 \uC2E4\uD589\uD558\uB294 \uC911...");
|
|
96
|
+
await execa("git", ["add", archiveDir], { cwd: projectRoot });
|
|
97
|
+
await execa("git", ["commit", "-m", commitMessage], {
|
|
98
|
+
cwd: projectRoot
|
|
99
|
+
});
|
|
100
|
+
} catch (gitError) {
|
|
101
|
+
setMessage("\uCEE4\uBC0B \uC2E4\uD328\uB85C \uC778\uD574 \uB864\uBC31\uD558\uB294 \uC911...");
|
|
102
|
+
try {
|
|
103
|
+
await rename(archiveDir, solvingDir);
|
|
104
|
+
} catch (rollbackError) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Git \uCEE4\uBC0B \uC2E4\uD328 \uBC0F \uB864\uBC31 \uC2E4\uD328: ${gitError instanceof Error ? gitError.message : String(gitError)}. \uB864\uBC31 \uC5D0\uB7EC: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}. \uC218\uB3D9\uC73C\uB85C \uD30C\uC77C\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
throw gitError;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
setMessage("\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC \uC911...");
|
|
113
|
+
try {
|
|
114
|
+
const solvingDirConfig = getSolvingDir();
|
|
115
|
+
if (solvingDirConfig && solvingDirConfig !== "." && solvingDirConfig !== "") {
|
|
116
|
+
let currentParentDir = dirname(solvingDir);
|
|
117
|
+
const solvingBaseDir = join(projectRoot, solvingDirConfig);
|
|
118
|
+
while (currentParentDir !== solvingBaseDir && currentParentDir !== projectRoot) {
|
|
119
|
+
try {
|
|
120
|
+
const entries = await readdir(currentParentDir);
|
|
121
|
+
if (entries.length === 0) {
|
|
122
|
+
await rmdir(currentParentDir);
|
|
123
|
+
currentParentDir = dirname(currentParentDir);
|
|
124
|
+
} else {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
setStatus("success");
|
|
135
|
+
setMessage(`\uBB38\uC81C ${problemId}\uB97C \uC544\uCE74\uC774\uBE0C\uD588\uC2B5\uB2C8\uB2E4: ${archiveDir}`);
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
onComplete?.();
|
|
138
|
+
}, 2e3);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
setStatus("error");
|
|
141
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
onComplete?.();
|
|
144
|
+
}, 2e3);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
void archive();
|
|
148
|
+
}, [problemId, onComplete]);
|
|
149
|
+
return {
|
|
150
|
+
status,
|
|
151
|
+
message,
|
|
152
|
+
error
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/commands/archive.tsx
|
|
157
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
158
|
+
function ArchiveView({ problemId, onComplete }) {
|
|
159
|
+
const { status, message, error } = useArchive({
|
|
160
|
+
problemId,
|
|
161
|
+
onComplete
|
|
162
|
+
});
|
|
163
|
+
if (status === "loading") {
|
|
164
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
|
|
165
|
+
}
|
|
166
|
+
if (status === "error") {
|
|
167
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
|
|
168
|
+
"\uC624\uB958 \uBC1C\uC0DD: ",
|
|
169
|
+
error
|
|
170
|
+
] }) });
|
|
171
|
+
}
|
|
172
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: message }) });
|
|
173
|
+
}
|
|
174
|
+
var ArchiveCommand = class extends Command {
|
|
175
|
+
async execute(args, _) {
|
|
176
|
+
const context = await resolveProblemContext(args, { requireId: false });
|
|
177
|
+
if (context.problemId === null) {
|
|
178
|
+
logger.error("\uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
|
|
179
|
+
console.log(`\uB3C4\uC6C0\uB9D0: ps archive --help`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await this.renderView(ArchiveView, {
|
|
184
|
+
problemId: context.problemId
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
ArchiveCommand = __decorateClass([
|
|
189
|
+
CommandDef({
|
|
190
|
+
name: "archive",
|
|
191
|
+
description: `\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uACE0 Git \uCEE4\uBC0B\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
|
|
192
|
+
- solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uCC3E\uC544 archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9
|
|
193
|
+
- Git add \uBC0F commit \uC2E4\uD589 (\uCEE4\uBC0B \uBA54\uC2DC\uC9C0: "solve: {\uBB38\uC81C\uBC88\uD638} - {\uBB38\uC81C\uC774\uB984}")
|
|
194
|
+
- \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.`,
|
|
195
|
+
flags: [],
|
|
196
|
+
autoDetectProblemId: true,
|
|
197
|
+
requireProblemId: false,
|
|
198
|
+
examples: [
|
|
199
|
+
"archive 1000",
|
|
200
|
+
"archive # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C \uBC88\uD638 \uC790\uB3D9 \uAC10\uC9C0"
|
|
201
|
+
]
|
|
202
|
+
})
|
|
203
|
+
], ArchiveCommand);
|
|
204
|
+
var archive_default = CommandBuilder.fromClass(ArchiveCommand);
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
ArchiveView,
|
|
208
|
+
ArchiveCommand,
|
|
209
|
+
archive_default
|
|
210
|
+
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
openBrowser
|
|
4
|
+
} from "./chunk-QGMWUOJ3.js";
|
|
5
|
+
import {
|
|
6
|
+
Command,
|
|
7
|
+
CommandBuilder,
|
|
8
|
+
CommandDef,
|
|
9
|
+
__decorateClass,
|
|
10
|
+
defineFlags,
|
|
11
|
+
detectProblemIdFromPath,
|
|
12
|
+
findSolutionFile,
|
|
13
|
+
getSupportedLanguagesString,
|
|
14
|
+
resolveLanguage,
|
|
15
|
+
resolveProblemContext
|
|
16
|
+
} from "./chunk-JZK2464U.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/submit.tsx
|
|
19
|
+
import { Badge, StatusMessage, Alert, Spinner } from "@inkjs/ui";
|
|
20
|
+
import { Text, Box } from "ink";
|
|
21
|
+
|
|
22
|
+
// src/hooks/use-submit.ts
|
|
23
|
+
import { readFile } from "fs/promises";
|
|
24
|
+
import { useEffect, useState } from "react";
|
|
25
|
+
|
|
26
|
+
// src/utils/clipboard.ts
|
|
27
|
+
import { execa, execaCommand } from "execa";
|
|
28
|
+
async function copyToClipboard(text) {
|
|
29
|
+
try {
|
|
30
|
+
if (process.platform === "win32") {
|
|
31
|
+
await execaCommand("clip", {
|
|
32
|
+
shell: true,
|
|
33
|
+
input: text
|
|
34
|
+
});
|
|
35
|
+
} else if (process.platform === "darwin") {
|
|
36
|
+
await execaCommand("pbcopy", {
|
|
37
|
+
shell: false,
|
|
38
|
+
input: text
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
try {
|
|
42
|
+
await execa("xclip", ["-selection", "clipboard"], {
|
|
43
|
+
input: text
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
try {
|
|
47
|
+
await execa("xsel", ["--clipboard", "--input"], {
|
|
48
|
+
input: text
|
|
49
|
+
});
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/hooks/use-submit.ts
|
|
62
|
+
var BOJ_BASE_URL = "https://www.acmicpc.net";
|
|
63
|
+
function useSubmit({
|
|
64
|
+
problemId,
|
|
65
|
+
language: _language,
|
|
66
|
+
sourcePath,
|
|
67
|
+
onComplete
|
|
68
|
+
}) {
|
|
69
|
+
const [status, setStatus] = useState(
|
|
70
|
+
"loading"
|
|
71
|
+
);
|
|
72
|
+
const [message, setMessage] = useState("\uC81C\uCD9C \uC900\uBE44 \uC911...");
|
|
73
|
+
const [error, setError] = useState(null);
|
|
74
|
+
const [submitUrl, setSubmitUrl] = useState("");
|
|
75
|
+
const [clipboardSuccess, setClipboardSuccess] = useState(false);
|
|
76
|
+
const [clipboardError, setClipboardError] = useState(null);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
async function submit() {
|
|
79
|
+
try {
|
|
80
|
+
setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
|
|
81
|
+
const sourceCode = await readFile(sourcePath, "utf-8");
|
|
82
|
+
setMessage("\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD558\uB294 \uC911...");
|
|
83
|
+
const clipboardResult = await copyToClipboard(sourceCode);
|
|
84
|
+
setClipboardSuccess(clipboardResult);
|
|
85
|
+
if (!clipboardResult) {
|
|
86
|
+
setClipboardError("\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
87
|
+
}
|
|
88
|
+
const url = `${BOJ_BASE_URL}/submit/${problemId}`;
|
|
89
|
+
setSubmitUrl(url);
|
|
90
|
+
setMessage("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911...");
|
|
91
|
+
await openBrowser(url);
|
|
92
|
+
setStatus("success");
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
onComplete();
|
|
95
|
+
}, 2e3);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
setStatus("error");
|
|
98
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
onComplete();
|
|
101
|
+
}, 2e3);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
void submit();
|
|
105
|
+
}, [problemId, sourcePath, onComplete]);
|
|
106
|
+
return {
|
|
107
|
+
status,
|
|
108
|
+
message,
|
|
109
|
+
error,
|
|
110
|
+
submitUrl,
|
|
111
|
+
clipboardSuccess,
|
|
112
|
+
clipboardError
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/commands/submit.tsx
|
|
117
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
118
|
+
var submitFlagsSchema = {
|
|
119
|
+
language: {
|
|
120
|
+
type: "string",
|
|
121
|
+
shortFlag: "l",
|
|
122
|
+
description: `\uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
|
|
123
|
+
\uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}`
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function SubmitView({
|
|
127
|
+
problemId,
|
|
128
|
+
language,
|
|
129
|
+
sourcePath,
|
|
130
|
+
onComplete
|
|
131
|
+
}) {
|
|
132
|
+
const {
|
|
133
|
+
status,
|
|
134
|
+
message,
|
|
135
|
+
error,
|
|
136
|
+
submitUrl,
|
|
137
|
+
clipboardSuccess,
|
|
138
|
+
clipboardError
|
|
139
|
+
} = useSubmit({
|
|
140
|
+
problemId,
|
|
141
|
+
language,
|
|
142
|
+
sourcePath,
|
|
143
|
+
onComplete
|
|
144
|
+
});
|
|
145
|
+
if (status === "loading") {
|
|
146
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
147
|
+
/* @__PURE__ */ jsx(Spinner, { label: message }),
|
|
148
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
149
|
+
"\uBB38\uC81C #",
|
|
150
|
+
problemId
|
|
151
|
+
] }) })
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
154
|
+
if (status === "error") {
|
|
155
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
156
|
+
/* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
|
|
157
|
+
"\uC624\uB958 \uBC1C\uC0DD: ",
|
|
158
|
+
error
|
|
159
|
+
] }),
|
|
160
|
+
submitUrl && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
161
|
+
"URL: ",
|
|
162
|
+
submitUrl
|
|
163
|
+
] }) })
|
|
164
|
+
] });
|
|
165
|
+
}
|
|
166
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", children: [
|
|
167
|
+
/* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: "\uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
|
|
168
|
+
/* @__PURE__ */ jsxs(
|
|
169
|
+
Box,
|
|
170
|
+
{
|
|
171
|
+
flexDirection: "column",
|
|
172
|
+
borderStyle: "round",
|
|
173
|
+
borderColor: "gray",
|
|
174
|
+
marginTop: 1,
|
|
175
|
+
paddingX: 1,
|
|
176
|
+
paddingY: 0,
|
|
177
|
+
alignSelf: "flex-start",
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
180
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
|
|
181
|
+
" ",
|
|
182
|
+
problemId
|
|
183
|
+
] }),
|
|
184
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
185
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uC5B8\uC5B4:" }),
|
|
186
|
+
" ",
|
|
187
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: language })
|
|
188
|
+
] }),
|
|
189
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
190
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "URL:" }),
|
|
191
|
+
" ",
|
|
192
|
+
/* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: submitUrl })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: clipboardSuccess ? /* @__PURE__ */ jsx(Badge, { color: "green", children: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uB428" }) : /* @__PURE__ */ jsx(Badge, { color: "yellow", children: "\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC \uC2E4\uD328" }) }),
|
|
195
|
+
clipboardError && !clipboardSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Alert, { variant: "warning", children: clipboardError }) })
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
] });
|
|
200
|
+
}
|
|
201
|
+
var SubmitCommand = class extends Command {
|
|
202
|
+
async execute(args, flags) {
|
|
203
|
+
const context = await resolveProblemContext(args, { requireId: true });
|
|
204
|
+
const sourcePath = await findSolutionFile(context.archiveDir);
|
|
205
|
+
const detectedLanguage = await resolveLanguage(
|
|
206
|
+
context.archiveDir,
|
|
207
|
+
flags.language
|
|
208
|
+
);
|
|
209
|
+
let finalProblemId = context.problemId;
|
|
210
|
+
if (finalProblemId === null) {
|
|
211
|
+
finalProblemId = detectProblemIdFromPath(context.archiveDir);
|
|
212
|
+
}
|
|
213
|
+
if (finalProblemId === null) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
"\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."
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
await this.renderView(SubmitView, {
|
|
219
|
+
problemId: finalProblemId,
|
|
220
|
+
language: detectedLanguage,
|
|
221
|
+
sourcePath
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
SubmitCommand = __decorateClass([
|
|
226
|
+
CommandDef({
|
|
227
|
+
name: "submit",
|
|
228
|
+
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.
|
|
229
|
+
- \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
|
|
230
|
+
- solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
|
|
231
|
+
- \uC18C\uC2A4 \uCF54\uB4DC\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uC790\uB3D9 \uBCF5\uC0AC
|
|
232
|
+
- \uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC790\uB3D9 \uC5F4\uAE30
|
|
233
|
+
- \uAE30\uBCF8 \uC5B8\uC5B4 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
|
|
234
|
+
flags: defineFlags(submitFlagsSchema),
|
|
235
|
+
autoDetectProblemId: true,
|
|
236
|
+
autoDetectLanguage: true,
|
|
237
|
+
requireProblemId: true,
|
|
238
|
+
examples: [
|
|
239
|
+
"submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C",
|
|
240
|
+
"submit 1000 # 1000\uBC88 \uBB38\uC81C \uC81C\uCD9C",
|
|
241
|
+
"submit --language python # Python\uC73C\uB85C \uC81C\uCD9C"
|
|
242
|
+
]
|
|
243
|
+
})
|
|
244
|
+
], SubmitCommand);
|
|
245
|
+
var submit_default = CommandBuilder.fromClass(SubmitCommand);
|
|
246
|
+
|
|
247
|
+
export {
|
|
248
|
+
SubmitView,
|
|
249
|
+
SubmitCommand,
|
|
250
|
+
submit_default
|
|
251
|
+
};
|