@rhseung/ps-cli 1.0.0 → 1.2.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.
package/README.md CHANGED
@@ -36,6 +36,31 @@ bunx @rhseung/ps-cli fetch 1000
36
36
 
37
37
  ## 명령어
38
38
 
39
+ ### `init` - 프로젝트 초기화
40
+
41
+ 현재 디렉토리를 ps-cli 프로젝트로 초기화합니다.
42
+
43
+ ```bash
44
+ ps init
45
+ ```
46
+
47
+ **기능:**
48
+
49
+ - `problems/` 디렉토리 생성
50
+ - `.gitignore`에 `problems/` 추가 (이미 있으면 스킵)
51
+
52
+ **예제:**
53
+
54
+ ```bash
55
+ # 프로젝트 폴더에서 초기화
56
+ mkdir my-algorithm-problems
57
+ cd my-algorithm-problems
58
+ ps init
59
+
60
+ # 이제 문제를 가져올 수 있습니다
61
+ ps fetch 1000
62
+ ```
63
+
39
64
  ### `fetch` - 문제 가져오기
40
65
 
41
66
  백준 문제를 가져와서 로컬에 파일을 생성합니다.
@@ -206,6 +231,7 @@ ps config --list
206
231
  - `editor`: 에디터 명령어 (예: code, vim, nano)
207
232
  - `auto-open-editor`: fetch 후 자동으로 에디터 열기 (true/false)
208
233
  - `solved-ac-handle`: Solved.ac 핸들 (stats 명령어용)
234
+ - `problem-dir`: 문제 디렉토리 경로 (기본값: `problems`, `"."` 또는 `""`는 프로젝트 루트에 직접 저장)
209
235
 
210
236
  **옵션:**
211
237
 
@@ -247,24 +273,27 @@ ps <명령어> --help
247
273
  ### 전체 워크플로우
248
274
 
249
275
  ```bash
250
- # 1. 문제 가져오기
276
+ # 1. 프로젝트 초기화 (최초 1회)
277
+ ps init
278
+
279
+ # 2. 문제 가져오기
251
280
  ps fetch 1000 --language python
252
281
 
253
- # 2. 문제 디렉토리로 이동
282
+ # 3. 문제 디렉토리로 이동
254
283
  cd problems/1000
255
284
 
256
- # 3. 코드 작성 (solution.py 편집)
285
+ # 4. 코드 작성 (solution.py 편집)
257
286
 
258
- # 4. 로컬 테스트
287
+ # 5. 로컬 테스트
259
288
  ps test
260
289
 
261
- # 5. Watch 모드로 개발 (파일 저장 시 자동 테스트)
290
+ # 6. Watch 모드로 개발 (파일 저장 시 자동 테스트)
262
291
  ps test --watch
263
292
 
264
- # 6. 단일 입력으로 실행 테스트
293
+ # 7. 단일 입력으로 실행 테스트
265
294
  ps run
266
295
 
267
- # 7. BOJ에 제출
296
+ # 8. BOJ에 제출
268
297
  ps submit
269
298
  ```
270
299
 
@@ -283,6 +312,30 @@ ps config solved-ac-handle myhandle
283
312
  # fetch 후 자동으로 VS Code 열기
284
313
  ps config editor code
285
314
  ps config auto-open-editor true
315
+
316
+ # 문제 디렉토리 설정
317
+ ps config problem-dir "problems" # problems 디렉토리 사용 (기본값)
318
+ ps config problem-dir "." # 프로젝트 루트에 직접 저장
319
+ ps config problem-dir "solutions" # solutions 디렉토리 사용
320
+ ```
321
+
322
+ ### 문제 디렉토리 설정
323
+
324
+ `problem-dir` 설정을 통해 문제 파일이 저장되는 위치를 변경할 수 있습니다:
325
+
326
+ - **기본값 (`problems`)**: `problems/{문제번호}/` 형식으로 저장
327
+ - **프로젝트 루트 (`"."` 또는 `""`)**: 프로젝트 루트에 `{문제번호}/` 형식으로 직접 저장
328
+
329
+ **예제:**
330
+
331
+ ```bash
332
+ # 프로젝트 루트에 직접 저장하도록 설정
333
+ ps config problem-dir "."
334
+
335
+ # 문제 가져오기 (프로젝트 루트에 1000/ 디렉토리 생성)
336
+ ps fetch 1000
337
+
338
+ # 결과: ./1000/solution.py, ./1000/input1.txt 등
286
339
  ```
287
340
 
288
341
  ## 라이선스
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getProblemDir
4
+ } from "./chunk-63CK6URL.js";
5
+
6
+ // src/utils/problem-id.ts
7
+ import { join } from "path";
8
+ function detectProblemIdFromPath(cwd = process.cwd()) {
9
+ const problemDir = getProblemDir();
10
+ const normalizedPath = cwd.replace(/\\/g, "/");
11
+ if (problemDir === "." || problemDir === "") {
12
+ const segments = normalizedPath.split("/").filter(Boolean);
13
+ const lastSegment = segments[segments.length - 1];
14
+ if (lastSegment) {
15
+ const problemId2 = parseInt(lastSegment, 10);
16
+ if (!isNaN(problemId2) && problemId2 > 0 && lastSegment === problemId2.toString()) {
17
+ return problemId2;
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ const dirPattern = `/${problemDir}/`;
23
+ const dirIndex = normalizedPath.indexOf(dirPattern);
24
+ if (dirIndex === -1) {
25
+ return null;
26
+ }
27
+ const afterDir = normalizedPath.substring(dirIndex + dirPattern.length);
28
+ if (!afterDir) {
29
+ return null;
30
+ }
31
+ const firstSegment = afterDir.split("/")[0];
32
+ if (!firstSegment) {
33
+ return null;
34
+ }
35
+ const problemId = parseInt(firstSegment, 10);
36
+ if (isNaN(problemId) || problemId <= 0) {
37
+ return null;
38
+ }
39
+ if (firstSegment !== problemId.toString()) {
40
+ return null;
41
+ }
42
+ return problemId;
43
+ }
44
+ function getProblemId(args, cwd = process.cwd()) {
45
+ if (args.length > 0 && args[0]) {
46
+ const problemId = parseInt(args[0], 10);
47
+ if (!isNaN(problemId) && problemId > 0) {
48
+ return problemId;
49
+ }
50
+ }
51
+ return detectProblemIdFromPath(cwd);
52
+ }
53
+ function getProblemDirPath(problemId, cwd = process.cwd()) {
54
+ const problemDir = getProblemDir();
55
+ if (problemDir === "." || problemDir === "") {
56
+ return join(cwd, problemId.toString());
57
+ }
58
+ return join(cwd, problemDir, problemId.toString());
59
+ }
60
+
61
+ export {
62
+ detectProblemIdFromPath,
63
+ getProblemId,
64
+ getProblemDirPath
65
+ };
@@ -9878,6 +9878,8 @@ var Conf = class {
9878
9878
  };
9879
9879
 
9880
9880
  // src/utils/config.ts
9881
+ import { readFileSync, existsSync } from "fs";
9882
+ import { join } from "path";
9881
9883
  var config = new Conf({
9882
9884
  projectName: "ps-cli",
9883
9885
  defaults: {
@@ -9888,9 +9890,40 @@ var config = new Conf({
9888
9890
  // 기본값: VS Code
9889
9891
  autoOpenEditor: false,
9890
9892
  // 기본값: 자동 열기 비활성화
9891
- solvedAcHandle: void 0
9893
+ solvedAcHandle: void 0,
9894
+ problemDir: "problems"
9895
+ // 기본값: problems 디렉토리
9892
9896
  }
9893
9897
  });
9898
+ var projectConfigCache = null;
9899
+ var projectConfigCachePath = null;
9900
+ function getProjectConfigSync() {
9901
+ try {
9902
+ const cwd = process.cwd();
9903
+ const projectConfigPath = join(cwd, ".ps-cli.json");
9904
+ if (projectConfigCache && projectConfigCachePath === projectConfigPath) {
9905
+ return projectConfigCache;
9906
+ }
9907
+ if (!existsSync(projectConfigPath)) {
9908
+ projectConfigCache = null;
9909
+ projectConfigCachePath = null;
9910
+ return null;
9911
+ }
9912
+ try {
9913
+ const content = readFileSync(projectConfigPath, "utf-8");
9914
+ const projectConfig = JSON.parse(content);
9915
+ projectConfigCache = projectConfig;
9916
+ projectConfigCachePath = projectConfigPath;
9917
+ return projectConfig;
9918
+ } catch {
9919
+ projectConfigCache = null;
9920
+ projectConfigCachePath = null;
9921
+ return null;
9922
+ }
9923
+ } catch {
9924
+ return null;
9925
+ }
9926
+ }
9894
9927
  function getBojSessionCookie() {
9895
9928
  const envCookie = process.env.PS_CLI_BOJ_COOKIE;
9896
9929
  if (envCookie) {
@@ -9902,6 +9935,10 @@ function setBojSessionCookie(cookie) {
9902
9935
  config.set("bojSessionCookie", cookie);
9903
9936
  }
9904
9937
  function getDefaultLanguage() {
9938
+ const projectConfig = getProjectConfigSync();
9939
+ if (projectConfig?.defaultLanguage) {
9940
+ return projectConfig.defaultLanguage;
9941
+ }
9905
9942
  return config.get("defaultLanguage") ?? "python";
9906
9943
  }
9907
9944
  function setDefaultLanguage(language) {
@@ -9914,23 +9951,48 @@ function setCodeOpen(open) {
9914
9951
  config.set("codeOpen", open);
9915
9952
  }
9916
9953
  function getEditor() {
9954
+ const projectConfig = getProjectConfigSync();
9955
+ if (projectConfig?.editor) {
9956
+ return projectConfig.editor;
9957
+ }
9917
9958
  return config.get("editor") ?? "code";
9918
9959
  }
9919
9960
  function setEditor(editor) {
9920
9961
  config.set("editor", editor);
9921
9962
  }
9922
9963
  function getAutoOpenEditor() {
9964
+ const projectConfig = getProjectConfigSync();
9965
+ if (projectConfig?.autoOpenEditor !== void 0) {
9966
+ return projectConfig.autoOpenEditor;
9967
+ }
9923
9968
  return config.get("autoOpenEditor") ?? false;
9924
9969
  }
9925
9970
  function setAutoOpenEditor(enabled) {
9926
9971
  config.set("autoOpenEditor", enabled);
9927
9972
  }
9928
9973
  function getSolvedAcHandle() {
9974
+ const projectConfig = getProjectConfigSync();
9975
+ if (projectConfig?.solvedAcHandle) {
9976
+ return projectConfig.solvedAcHandle;
9977
+ }
9929
9978
  return config.get("solvedAcHandle");
9930
9979
  }
9931
9980
  function setSolvedAcHandle(handle) {
9932
9981
  config.set("solvedAcHandle", handle);
9933
9982
  }
9983
+ function getProblemDir() {
9984
+ const projectConfig = getProjectConfigSync();
9985
+ if (projectConfig?.problemDir !== void 0) {
9986
+ return projectConfig.problemDir;
9987
+ }
9988
+ return config.get("problemDir") ?? "problems";
9989
+ }
9990
+ function setProblemDir(dir) {
9991
+ config.set("problemDir", dir);
9992
+ }
9993
+ function clearConfig() {
9994
+ config.clear();
9995
+ }
9934
9996
 
9935
9997
  export {
9936
9998
  getBojSessionCookie,
@@ -9944,5 +10006,8 @@ export {
9944
10006
  getAutoOpenEditor,
9945
10007
  setAutoOpenEditor,
9946
10008
  getSolvedAcHandle,
9947
- setSolvedAcHandle
10009
+ setSolvedAcHandle,
10010
+ getProblemDir,
10011
+ setProblemDir,
10012
+ clearConfig
9948
10013
  };
@@ -1,22 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ getSupportedLanguages,
4
+ getSupportedLanguagesString
5
+ } from "../chunk-TQXMB7XV.js";
6
+ import {
7
+ clearConfig,
3
8
  getAutoOpenEditor,
4
9
  getBojSessionCookie,
5
10
  getCodeOpen,
6
11
  getDefaultLanguage,
7
12
  getEditor,
13
+ getProblemDir,
8
14
  getSolvedAcHandle,
9
15
  setAutoOpenEditor,
10
16
  setBojSessionCookie,
11
17
  setCodeOpen,
12
18
  setDefaultLanguage,
13
19
  setEditor,
20
+ setProblemDir,
14
21
  setSolvedAcHandle
15
- } from "../chunk-KFQFQJYT.js";
16
- import {
17
- getSupportedLanguages,
18
- getSupportedLanguagesString
19
- } from "../chunk-TQXMB7XV.js";
22
+ } from "../chunk-63CK6URL.js";
20
23
  import "../chunk-FYS2JH42.js";
21
24
 
22
25
  // src/commands/config.tsx
@@ -28,6 +31,7 @@ function getConfigHelp() {
28
31
  $ ps config <\uD0A4> [\uAC12]
29
32
  $ ps config <\uD0A4> --get
30
33
  $ ps config --list
34
+ $ ps config clear
31
35
 
32
36
  \uC124\uBA85:
33
37
  \uC0AC\uC6A9\uC790 \uC124\uC815\uC744 \uAD00\uB9AC\uD569\uB2C8\uB2E4.
@@ -40,18 +44,23 @@ function getConfigHelp() {
40
44
  editor \uC5D0\uB514\uD130 \uBA85\uB839\uC5B4 (\uC608: code, vim, nano)
41
45
  auto-open-editor fetch \uD6C4 \uC790\uB3D9\uC73C\uB85C \uC5D0\uB514\uD130 \uC5F4\uAE30 (true/false)
42
46
  solved-ac-handle Solved.ac \uD578\uB4E4 (stats \uBA85\uB839\uC5B4\uC6A9)
47
+ problem-dir \uBB38\uC81C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C (\uAE30\uBCF8\uAC12: problems, "." \uB610\uB294 ""\uB294 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8)
43
48
 
44
49
  \uC635\uC158:
45
50
  --get \uC124\uC815 \uAC12 \uC870\uD68C
46
51
  --list \uBAA8\uB4E0 \uC124\uC815 \uC870\uD68C
52
+ clear \uBAA8\uB4E0 \uC124\uC815 \uCD08\uAE30\uD654
47
53
  --help, -h \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
48
54
 
49
55
  \uC608\uC81C:
50
56
  $ ps config boj-session-cookie "boj_session=xxx"
51
57
  $ ps config default-language python
52
58
  $ ps config solved-ac-handle myhandle
53
- $ ps config solved-ac-handle --get
59
+ $ ps config problem-dir "." # \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC9C1\uC811 \uC800\uC7A5
60
+ $ ps config problem-dir "problems" # problems \uB514\uB809\uD1A0\uB9AC \uC0AC\uC6A9 (\uAE30\uBCF8\uAC12)
61
+ $ ps config problem-dir --get
54
62
  $ ps config --list
63
+ $ ps config clear # \uBAA8\uB4E0 \uC124\uC815 \uCD08\uAE30\uD654
55
64
  `;
56
65
  }
57
66
  var configHelp = getConfigHelp();
@@ -60,8 +69,13 @@ function ConfigCommand({
60
69
  value,
61
70
  get,
62
71
  list,
72
+ clear,
63
73
  onComplete
64
74
  }) {
75
+ if (clear) {
76
+ clearConfig();
77
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 \uBAA8\uB4E0 \uC124\uC815\uC774 \uCD08\uAE30\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4." }) });
78
+ }
65
79
  if (list) {
66
80
  const bojCookie = getBojSessionCookie();
67
81
  const defaultLang = getDefaultLanguage();
@@ -96,6 +110,10 @@ function ConfigCommand({
96
110
  /* @__PURE__ */ jsxs(Text, { children: [
97
111
  "solved-ac-handle: ",
98
112
  /* @__PURE__ */ jsx(Text, { bold: true, children: handle || "\uC124\uC815 \uC548 \uB428" })
113
+ ] }),
114
+ /* @__PURE__ */ jsxs(Text, { children: [
115
+ "problem-dir: ",
116
+ /* @__PURE__ */ jsx(Text, { bold: true, children: getProblemDir() })
99
117
  ] })
100
118
  ] })
101
119
  ] });
@@ -121,6 +139,9 @@ function ConfigCommand({
121
139
  case "solved-ac-handle":
122
140
  configValue = getSolvedAcHandle();
123
141
  break;
142
+ case "problem-dir":
143
+ configValue = getProblemDir();
144
+ break;
124
145
  default:
125
146
  console.error(`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${configKey}`);
126
147
  process.exit(1);
@@ -155,6 +176,9 @@ function ConfigCommand({
155
176
  case "solved-ac-handle":
156
177
  setSolvedAcHandle(value);
157
178
  break;
179
+ case "problem-dir":
180
+ setProblemDir(value);
181
+ break;
158
182
  default:
159
183
  console.error(`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${configKey}`);
160
184
  process.exit(1);
@@ -168,7 +192,7 @@ function ConfigCommand({
168
192
  }
169
193
  return null;
170
194
  }
171
- async function configCommand(configKey, value, get, list) {
195
+ async function configCommand(configKey, value, get, list, clear) {
172
196
  return new Promise((resolve) => {
173
197
  const { unmount } = render(
174
198
  /* @__PURE__ */ jsx(
@@ -178,6 +202,7 @@ async function configCommand(configKey, value, get, list) {
178
202
  value,
179
203
  get,
180
204
  list,
205
+ clear,
181
206
  onComplete: () => {
182
207
  unmount();
183
208
  resolve();
@@ -197,6 +222,10 @@ async function configExecute(args, flags) {
197
222
  process.exit(0);
198
223
  return;
199
224
  }
225
+ if (args[0] === "clear") {
226
+ await configCommand(void 0, void 0, false, false, true);
227
+ return;
228
+ }
200
229
  if (flags.list) {
201
230
  await configCommand(void 0, void 0, false, true);
202
231
  return;
@@ -206,6 +235,7 @@ async function configExecute(args, flags) {
206
235
  if (!key) {
207
236
  console.error("\uC624\uB958: \uC124\uC815 \uD0A4\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
208
237
  console.error(`\uC0AC\uC6A9\uBC95: ps config <\uD0A4> [\uAC12]`);
238
+ console.error(`\uC0AC\uC6A9\uBC95: ps config clear`);
209
239
  console.error(`\uB3C4\uC6C0\uB9D0: ps config --help`);
210
240
  process.exit(1);
211
241
  }
@@ -7,12 +7,9 @@ import {
7
7
  source_default
8
8
  } from "../chunk-2E4VSP6O.js";
9
9
  import {
10
- getAutoOpenEditor,
11
- getEditor
12
- } from "../chunk-KFQFQJYT.js";
13
- import {
10
+ getProblemDirPath,
14
11
  getProblemId
15
- } from "../chunk-OOTPZD7O.js";
12
+ } from "../chunk-3TMQ74SA.js";
16
13
  import {
17
14
  getLanguageConfig,
18
15
  getSupportedLanguages,
@@ -21,6 +18,10 @@ import {
21
18
  import {
22
19
  LoadingSpinner
23
20
  } from "../chunk-IJLJBKLK.js";
21
+ import {
22
+ getAutoOpenEditor,
23
+ getEditor
24
+ } from "../chunk-63CK6URL.js";
24
25
  import "../chunk-FYS2JH42.js";
25
26
 
26
27
  // src/commands/fetch.tsx
@@ -290,7 +291,7 @@ function getProjectRoot() {
290
291
  return join(__dirname, "../..");
291
292
  }
292
293
  async function generateProblemFiles(problem, language = "python") {
293
- const problemDir = join(process.cwd(), "problems", problem.id.toString());
294
+ const problemDir = getProblemDirPath(problem.id);
294
295
  await mkdir(problemDir, { recursive: true });
295
296
  const langConfig = getLanguageConfig(language);
296
297
  const projectRoot = getProjectRoot();