Skip to content

Commit 7e91d1a

Browse files
DanMeonclaude
andcommitted
docs: v0.3.0 rhwp-py CLI 재도입 로드맵 및 설계 리서치 문서 추가
변경사항: - docs/roadmap/v0.3.0/cli.md 신규 — 이름 선정(rhwp-py), 커맨드 트리(parse/ir/blocks/chunks/schema/version), 서브커맨드 스펙, 의존성(cli/cli-chunks extras), 4단계 구현 스테이지 분할, 테스트 전략, 결정사항 6건 - docs/design/v0.3.0/cli-design-research.md 신규 — 이름 선정·업스트림 overlap=0 정책·기본 출력 포맷(NDJSON/JSON) 3건의 업계 선례(lxml/pyarrow/grpcio-tools, docker/podman/gh, kubectl/aws/gh)·대안 비교·실패 시나리오·1차 소스 URL 기록 - docs/roadmap/README.md: v0.3.0 항목을 "IR 확장 + rhwp-py CLI" 병기로 업데이트, 경고 박스를 "재도입" 맥락으로 전환 - docs/roadmap/phase-2.md: v0.3.0 이 IR 확장과 CLI 두 축임을 명시, cli.md 병행 문서 cross-link Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4c81639 commit 7e91d1a

4 files changed

Lines changed: 430 additions & 3 deletions

File tree

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# v0.3.0 `rhwp-py` CLI — 설계 의사결정 리서치 요약
2+
3+
[v0.3.0/cli.md](../../roadmap/v0.3.0/cli.md) §결정 사항 중 외부 독자가 "왜?" 를 던질 만한 3건 (이름 선정 · 업스트림 overlap=0 정책 · 기본 출력 포맷) 의 업계 선례·대안·실패 시나리오를 기록한다. cli.md 본문이 최종 결정을 기술하고, 본 문서는 그 결정의 근거를 담는다.
4+
5+
## 결정 매트릭스
6+
7+
| # | 이슈 | 결정 | 핵심 근거 |
8+
|---|---|---|---|
9+
| 1 | Python 바인딩 명령어 이름 | **`rhwp-py`** | Python 생태계 관행 (`python-docx` / `pyarrow` / `lxml`) + 명령 이름 충돌 회피 |
10+
| 2 | 상류 바이너리와의 overlap 정책 | **완전 분리 (overlap = 0)** | `docker` vs `podman` 의 "동일 표면" 비용 교훈, `gh` + `git` 의 "부분 중복" 도 우리 맥락에서 비적합 |
11+
| 3 | 기본 출력 포맷 (`ir`/`blocks`) | **JSON 계열 (`blocks`: NDJSON, `ir`: JSON)** | kubectl/aws/gh 의 `-o` 관행 + jq streaming 친화 |
12+
13+
---
14+
15+
## 1. 이름 — `rhwp-py`
16+
17+
### 조사: Python 생태계의 "언어 suffix" binding 작명 실례
18+
19+
| 프로젝트 | PyPI 이름 | `import` 이름 | 설치 시 깔리는 명령 (있다면) | 원본 라이브러리 |
20+
|---|---|---|---|---|
21+
| lxml | `lxml` | `lxml` | 없음 | libxml2 (C) |
22+
| pyarrow | `pyarrow` | `pyarrow` | 없음 (`plasma-store-server` 만, renamed) | Apache Arrow (C++) |
23+
| python-docx | `python-docx` | `docx` | 없음 | Office Open XML 포맷 |
24+
| protobuf (Python) | `protobuf` | `google.protobuf` | 없음 (`protoc` 은 C++) | protoc-gen-* 플러그인 |
25+
| grpcio | `grpcio` | `grpc` | 없음 | gRPC C core |
26+
| grpcio-tools | `grpcio-tools` | `grpc_tools` | 없음 (`python -m grpc_tools.protoc` 로 간접 호출) | protoc |
27+
| pyyaml | `PyYAML` | `yaml` | 없음 | libyaml (C, 선택) |
28+
| uv | `uv` || `uv` | Rust 도구 (자체 바이너리 재배포) |
29+
| ruff | `ruff` | `ruff` | `ruff` | Rust 도구 (자체 바이너리 재배포) |
30+
31+
### 관찰
32+
33+
1. **Python 바인딩은 대부분 command 를 깔지 않는다** — 상류 도구가 이미 명령을 제공하면 Python 쪽은 라이브러리만 노출. `grpcio-tools``python -m grpc_tools.protoc` 패턴이 "우회" 해법으로 정착.
34+
2. **자체 Rust 도구가 본체인 경우만 동일 이름 command 제공**`uv`, `ruff` 는 "Python 생태계 도구" 이지만 실체는 Rust. PyPI 는 단순 배포 채널 역할. 본 프로젝트는 이와 달리 상류 `rhwp` Rust 바이너리가 이미 존재 — 동일 이름 경쟁이 아니라 **보완** 위치.
35+
3. **`python-` 접두 / `-py` 접미 패턴은 이름 충돌 방지 용도**`python-docx``docx` 이름이 이미 선점 (Microsoft) 된 상황 회피. `grpc-web` (JS) vs `grpcio` (Python) 도 같은 패턴.
36+
37+
### 대안 평가
38+
39+
- **`rhwp`**: 상류 바이너리와 PATH 충돌 — `pip install rhwp-python``rhwp` 가 어느 쪽을 가리키는지 OS/shell/설치 순서에 따라 비결정적. 업스트림 메인테이너 동의 없이 이름 선점하면 [PEP 541](https://peps.python.org/pep-0541/) dispute 소지.
40+
- **`hwp`**: 포맷명만 표시 — 다른 HWP 도구 (`pyhwpx`, `pyhwp`) 도 이름 욕심 낼 여지 있고 "어느 언어 구현인지" 불명. shell 자동완성에서 구분 불가.
41+
- **`pyrhwp`**: 접두 패턴. `pyOpenSSL` / `pyyaml` 과 비슷하지만, 우리 PyPI 패키지명 `rhwp-python`**접두·접미 방향이 역전** — 일관성 붕괴.
42+
- **`hwprag`**: RAG 강조. 기능 범위가 "RAG 전용" 이 아니고 (Document IR / Schema export 등 범용), 이름이 스코프를 오도.
43+
- **`rhwp-py`**: `rhwp-python` 패키지명과 접미가 일관. Python 레이어임을 명시. 상류와 문자 단위로 구분 (`rhwp``rhwp-py` 는 tab completion 에서도 섞이지 않음).
44+
45+
### 실패 시나리오 (선택 후에도 감시 필요)
46+
47+
- **업스트림이 추후 PyPI `rhwp-python` 을 가져감** — 이 패키지는 `pip install` 시 obsolete 되고, `rhwp-py` 명령 소유권도 이전. 현재 커뮤니티 배포 지위를 README 에 명시해 둔 상태.
48+
- **Homebrew `rhwp-py` formula 충돌** — tap 에 같은 이름이 있으면 업스트림 `rhwp` formula 와 공존 가능하나 사용자 혼동 여지. 필요 시 formula 이름을 `rhwp-python` 로 분리.
49+
50+
### 출처
51+
52+
- PEP 541 (Package Index Name Retention): <https://peps.python.org/pep-0541/>
53+
- python-docx naming rationale: <https://python-docx.readthedocs.io/en/latest/>
54+
- grpcio-tools `python -m grpc_tools.protoc` 패턴: <https://grpc.io/docs/languages/python/quickstart/>
55+
56+
---
57+
58+
## 2. 상류 바이너리와 overlap=0
59+
60+
### 조사: "동일/유사 기능 도구의 이름·스코프 공존 패턴"
61+
62+
| 사례 | 이름 관계 | 기능 overlap | 결과 |
63+
|---|---|---|---|
64+
| `docker` vs `podman` | 완전 분리 | **거의 100%** (의도적으로 호환) | podman 이 `alias docker=podman` 권장 — 이름 경쟁, 사용자 혼동 지속 |
65+
| `gh` vs `git` | 완전 분리 | **부분** (gh 가 일부 git 명령을 상위 개념으로 래핑) | 성공적 — 역할 분담 명확 (git=VCS, gh=GitHub 플랫폼) |
66+
| `kubectl` vs `oc` (OpenShift) | 완전 분리 | **~90%** (oc 는 kubectl 의 superset) | OpenShift 고유 기능만 쓰는 사용자에게 혼란, 문서 중복 |
67+
| `aws` vs `aws-shell` | 접미 분리 | 부분 | aws-shell 은 REPL 레이어만 — 명확한 층위 분리 |
68+
| `npm` vs `yarn` vs `pnpm` | 완전 분리 | **거의 100%** | 생태계 분화, 세 도구 모두 문서·CI·IDE 통합 비용 부담 |
69+
| `pip` vs `uv pip` | namespace 분리 | **거의 100%** | uv 가 pip 호환성을 명시적으로 광고 — overlap 을 feature 로 |
70+
71+
### 교훈
72+
73+
- **Overlap 100% 는 "대체재 경쟁"** — 유지보수 중복, 버그 프로필 divergence, 사용자가 "어느 쪽 이슈에 리포트해야 하는지" 헷갈림.
74+
- **Partial overlap 은 추상화 레이어 차이가 명확할 때만 성립**`gh` 는 "GitHub 플랫폼 명령" 이라는 상위 개념. 우리가 만약 `rhwp-py export-svg` 를 제공하면 내부적으로 상류 `rhwp` 를 호출하거나 rhwp 의 Rust 렌더러를 FFI 로 재노출해야 하는데, **어느 쪽이든 버그 표면이 두 배**.
75+
- **Overlap 0 이 "역할 분담" 메시지를 가장 명확히 전달** — 사용자는 "렌더는 `rhwp`, 구조 추출은 `rhwp-py`" 로 즉시 학습.
76+
77+
### 본 프로젝트의 특수성
78+
79+
- **`rhwp``rhwp-py` 는 동일 Rust 코어를 공유** (`external/rhwp` submodule). 즉 동일 파싱 결과.
80+
- `rhwp` 가 이미 제공하는 것: `export-svg` / `export-pdf` / `info` / `dump` / `dump-pages` / `diag` / `ir-diff` / `convert` / `thumbnail`.
81+
- Python 레이어 고유: **Pydantic IR 모델 · JSON Schema export · LangChain Document 매핑 · shell-friendly NDJSON 스트리밍**. 이 중 어느 것도 상류 Rust 바이너리가 제공하지 **않는다**.
82+
83+
따라서 **완전 분리가 "두 도구가 서로 대체 관계가 아님" 을 가장 분명하게 알린다**. README 에 권장 조합 워크플로우 한 섹션만 추가하면 사용자 학습 부담 최소.
84+
85+
### 실패 시나리오
86+
87+
- **사용자가 `rhwp-py render` 를 기대하고 혼란** — README / `rhwp-py --help` 초반에 "렌더링은 업스트림 `rhwp export-svg` 사용" 을 명시해 redirect.
88+
- **상류가 Python IR export 기능을 직접 추가** — 그 시점에 overlap 이 생기며, `rhwp-py` 는 deprecate 경로를 시작. 현재로서는 상류가 Python 레이어를 공식 채택할 계획 없음이 [edwardkim/rhwp#227](https://github.com/edwardkim/rhwp/issues/227) 합의에 명시.
89+
90+
### 출처
91+
92+
- gh CLI scope rationale: <https://cli.github.com/manual/>
93+
- podman-docker 호환성 논의: <https://podman.io/docs/installation#podman-docker>
94+
- pip vs uv pip 호환성 전략: <https://docs.astral.sh/uv/pip/compatibility/>
95+
96+
---
97+
98+
## 3. 기본 출력 포맷 — JSON 계열
99+
100+
### 조사: 구조화 출력 CLI 의 기본값 관행
101+
102+
| CLI | 기본 포맷 | 대체 옵션 | 비고 |
103+
|---|---|---|---|
104+
| `kubectl get` | table (사람용) | `-o json` / `yaml` / `jsonpath` | 기본은 terminal UX, 스크립팅은 명시 플래그 |
105+
| `aws` | JSON | `--output table` / `text` / `yaml` | 기본이 JSON — AWS 는 "스크립팅이 primary use case" 로 설계 |
106+
| `gh` | table-like | `--json <fields>` | 기본은 사람용, JSON 은 필드 선택 강제 |
107+
| `docker ps` | table | `--format '{{json .}}'` | Go 템플릿으로 변형 |
108+
| `jq` | JSON (가공) | `--raw-output` | JSON 이 자연 |
109+
| `hugr-cli` / `pipx` etc. | mixed || 소규모 도구는 혼재 |
110+
111+
### 교훈
112+
113+
- **"대화형 탐색" primary use case → table/text 기본** (kubectl, gh, docker)
114+
- **"스크립팅/pipeline" primary use case → JSON 기본** (aws, jq)
115+
- 우리 `rhwp-py` 의 예상 use case:
116+
- `parse` / `version` — 빠른 확인용 → **text 기본**
117+
- `ir` — 파일 변환 후 jq/스크립트 소비 → **JSON 기본**
118+
- `blocks` — shell pipeline 에서 필터·변환 → **NDJSON 기본** (줄 단위 스트리밍)
119+
- `chunks` — vector indexer 로 직접 투입 → **NDJSON 기본**
120+
- `schema` — 파일 리다이렉션이 전형 → **JSON 기본**
121+
122+
### NDJSON vs JSON array 선택
123+
124+
- **NDJSON (한 줄 한 객체)**
125+
- jq streaming: `rhwp-py blocks file.hwp | jq -c 'select(.kind == "table")'`
126+
- `wc -l`, `head`, `tail` 에 자연 매핑
127+
- 파이프 상대방이 스트리밍 소비 (vector-indexer 가 한 블록씩 임베딩) 가능
128+
- **JSON array (`[{...}, {...}]`)**
129+
- `json.load()` 한 방에 파싱 가능, 일부 도구가 array 만 수용
130+
- 단점: 전체를 버퍼링해야 — 대형 문서 (수천 블록) 에서 메모리 압박, 스트리밍 불가
131+
132+
`ir` 서브커맨드는 "문서 전체를 하나의 객체로" 출고하므로 JSON array 가 아닌 **단일 object (JSON)** 가 자연. `blocks` / `chunks` 는 열 단위 스트림이라 NDJSON.
133+
134+
### kubectl/aws/gh 기준 추가 검증
135+
136+
- `kubectl get pods -o json` — array 기본, `-o jsonpath` 로 단일 값 추출
137+
- `aws s3api list-objects``Contents` 배열 안에 객체 — 하지만 `--output json` 은 하나의 객체로
138+
- `gh issue list --json number,title` — JSON array
139+
- 세 도구 모두 "복수 아이템" 을 array 로, "단일 문서" 를 object 로. 우리 설계와 일치.
140+
141+
### 실패 시나리오
142+
143+
- **사용자가 `rhwp-py blocks file.hwp | python -c "import json, sys; json.load(sys.stdin)"` 기대**
144+
- NDJSON 이라 `json.load` 는 첫 줄만 파싱
145+
- 해법: `--format json` 플래그로 array 출력 가능. 문서에 예시 명시.
146+
- **Windows CMD 의 파이프 문자 인코딩 이슈** — NDJSON 은 모든 플랫폼에서 줄바꿈 안전 (`\n` 고정). PowerShell 은 `ConvertFrom-Json -AsHashtable` 로 NDJSON 스트리밍 지원.
147+
148+
### 출처
149+
150+
- NDJSON 표준: <http://ndjson.org/>
151+
- kubectl output options: <https://kubernetes.io/docs/reference/kubectl/#output-options>
152+
- aws CLI output format: <https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-output-format.html>
153+
- gh `--json` design rationale: <https://github.blog/2020-09-17-github-cli-1-0-is-now-available/#scripting-with-gh>
154+
155+
---
156+
157+
## 변경 파급 — cli.md 본문 교정 목록
158+
159+
본 리서치 결과를 cli.md 본문과 대조할 때 교정/보강할 지점:
160+
161+
1. **§이름 선정** — 현재 대안 비교 테이블에 "Python 생태계 binding 작명 패턴" 근거 한 문장 추가 (본 문서 §1 참조)
162+
2. **§업스트림 경계** — overlap=0 이 "유지보수 중복 회피" 이외에도 "역할 분담 메시지 명확성" 이라는 근거가 있음을 명시
163+
3. **§서브커맨드 스펙**`blocks` 의 기본 포맷이 NDJSON 인 이유를 한 줄 추가 (streaming pipeline 친화)
164+
4. **§결정 사항 테이블 #3** — "shell pipeline 과 CI 친화" 표현을 "kubectl/aws/gh 의 스크립팅-primary 관행 준수" 로 구체화
165+
166+
본 문서를 cli.md 에서 `상세 증거: [cli-design-research.md](../../design/v0.3.0/cli-design-research.md)` 로 cross-link — v0.2.0 ir.md ↔ ir-design-research.md 와 동일 패턴.
167+
168+
## 참조
169+
170+
- [roadmap/v0.3.0/cli.md](../../roadmap/v0.3.0/cli.md) — 본 리서치의 결정 요약
171+
- [roadmap/v0.2.0/ir.md](../../roadmap/v0.2.0/ir.md) §방향 전환 배경 — CLI 폐기→재도입 맥락
172+
- [design/v0.2.0/ir-design-research.md](../v0.2.0/ir-design-research.md) — 리서치 문서 포맷 선례

docs/roadmap/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ rhwp-python 의 버전별 로드맵. 완료된 단계는 참고 자료, 미래
1616
|---|---|---|
1717
| v0.1.0 / v0.1.1 | rhwp-python 분사 + PyPI 런칭 (sdist 보정 포함) | [v0.1.0/rhwp-python.md](v0.1.0/rhwp-python.md) |
1818
| v0.2.0 | Document IR v1 — Pydantic 모델 + JSON Schema 공개 | [v0.2.0/ir.md](v0.2.0/ir.md) |
19-
| Phase 2 (v0.3.0) | IR 확장 (이미지·수식·각주·머리글꼬리·TocEntry·Field) | [phase-2.md](phase-2.md) |
19+
| Phase 2 (v0.3.0) | IR 확장 (이미지·수식·각주·머리글꼬리·TocEntry·Field) + `rhwp-py` CLI | [phase-2.md](phase-2.md) · [v0.3.0/cli.md](v0.3.0/cli.md) |
2020
| Phase 3 (v0.4~0.6) | view 렌더러 + RAG 로더 확장 | [phase-3.md](phase-3.md) |
2121
| Phase 4 (v0.7~1.0) | JSON IR → HWP 역생성 | [phase-4.md](phase-4.md) |
2222

23-
> CLI 는 v0.2.0 원안이었으나 상류 Rust 크레이트(`edwardkim/rhwp`)가 이미 동일 이름의 `rhwp` 바이너리를 제공하여 폐기. 상세 경위와 재검토 조건은 [v0.2.0/ir.md § 방향 전환 배경](v0.2.0/ir.md).
23+
> CLI 는 v0.2.0 원안이었으나 상류 Rust 크레이트(`edwardkim/rhwp`)`rhwp` 바이너리와 이름 충돌로 폐기. v0.3.0 에서 `rhwp-py` 라는 별도 이름으로 Python 고유 영역만 노출하는 얇은 CLI 를 재도입한다 (상세: [v0.3.0/cli.md](v0.3.0/cli.md)). 폐기 경위는 [v0.2.0/ir.md § 방향 전환 배경](v0.2.0/ir.md).
2424
2525
## 원칙
2626

docs/roadmap/phase-2.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Phase 2 — Document IR 확장
22

3-
**대상 버전**: v0.3.0
3+
**대상 버전**: v0.3.0 (IR 확장 + `rhwp-py` CLI 동시 진행)
44
**선행 조건**: v0.2.0 Document IR v1 (기본 스키마) GA — [v0.2.0/ir.md](v0.2.0/ir.md)
5+
**병행 문서**: [v0.3.0/cli.md](v0.3.0/cli.md) — CLI 재도입 설계
56

67
## 포지셔닝 변경 안내
78

0 commit comments

Comments
 (0)