hwpx-kit-cli 0.1.1__py3-none-any.whl
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.
- hwpx_kit/__init__.py +1 -0
- hwpx_kit/adapter/__init__.py +0 -0
- hwpx_kit/adapter/base.py +43 -0
- hwpx_kit/adapter/hwpx_engine.py +545 -0
- hwpx_kit/adapter/kordoc_engine.py +79 -0
- hwpx_kit/adapter/office_readers.py +46 -0
- hwpx_kit/cli.py +259 -0
- hwpx_kit/commands/__init__.py +0 -0
- hwpx_kit/commands/analyze.py +126 -0
- hwpx_kit/commands/convert.py +52 -0
- hwpx_kit/commands/export.py +44 -0
- hwpx_kit/commands/fill.py +91 -0
- hwpx_kit/commands/fmt.py +29 -0
- hwpx_kit/commands/generate.py +12 -0
- hwpx_kit/commands/outline.py +13 -0
- hwpx_kit/commands/page_break.py +17 -0
- hwpx_kit/commands/read.py +42 -0
- hwpx_kit/commands/render.py +11 -0
- hwpx_kit/commands/row_height.py +39 -0
- hwpx_kit/commands/table_clear.py +22 -0
- hwpx_kit/commands/table_copy.py +24 -0
- hwpx_kit/commands/table_map_cmd.py +18 -0
- hwpx_kit/commands/table_new.py +29 -0
- hwpx_kit/commands/table_set.py +49 -0
- hwpx_kit/commands/validate.py +12 -0
- hwpx_kit/format.py +94 -0
- hwpx_kit/output.py +48 -0
- hwpx_kit_cli-0.1.1.dist-info/METADATA +190 -0
- hwpx_kit_cli-0.1.1.dist-info/RECORD +32 -0
- hwpx_kit_cli-0.1.1.dist-info/WHEEL +4 -0
- hwpx_kit_cli-0.1.1.dist-info/entry_points.txt +2 -0
- hwpx_kit_cli-0.1.1.dist-info/licenses/LICENSE +209 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""outline 명령 — 문단·표 배치 지도 (읽기 전용).
|
|
2
|
+
|
|
3
|
+
앵커 후보를 찾을 때 원시 XML을 뒤지지 말고 이걸 쓴다: 문단 인덱스,
|
|
4
|
+
텍스트, 표 위치(문서 순서 table_index)가 한 번에 나온다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_outline(path: str) -> dict:
|
|
12
|
+
ad = HwpxEngineAdapter.open(path)
|
|
13
|
+
return {"file": path, "paragraphs": ad.outline()}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""page-break 명령 — 문단(또는 표가 든 문단) 앞 쪽나눔.
|
|
2
|
+
|
|
3
|
+
hwpx 문단의 pageBreak 속성(기본 0)을 1로 — 끼워넣은 장 헤더가 페이지
|
|
4
|
+
중간에 떨어지는 문제의 해결. 대상은 문단 원문 또는 표 인덱스로 지정.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_page_break(path: str, at_text: str | None, table: int | None, out_path: str) -> dict:
|
|
12
|
+
if (at_text is None) == (table is None):
|
|
13
|
+
raise ValueError("--at-text 또는 --table 중 정확히 하나를 지정하세요.")
|
|
14
|
+
ad = HwpxEngineAdapter.open(path)
|
|
15
|
+
applied = ad.set_page_break(at_text=at_text, table_index=table)
|
|
16
|
+
out = ad.save_copy(out_path)
|
|
17
|
+
return {"file": path, "out": out, "at_text": at_text, "table": table, "applied": applied}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
6
|
+
from hwpx_kit.adapter.kordoc_engine import KordocAdapter, kordoc_available
|
|
7
|
+
from hwpx_kit.adapter.office_readers import read_docx, read_pdf, read_xlsx
|
|
8
|
+
|
|
9
|
+
# 순수 파이썬 리더 (kordoc 흡수, 2026-07-10) — Node 불필요
|
|
10
|
+
_PY_READERS = {
|
|
11
|
+
".pdf": read_pdf,
|
|
12
|
+
".docx": read_docx,
|
|
13
|
+
".xlsx": read_xlsx,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# 구형 한글 포맷 — kordoc(있으면)만 가능. 없으면 convert 안내
|
|
17
|
+
_LEGACY_HWP_EXTS = {".hwp", ".hwpml"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_read(path: str, fmt: str = "md") -> dict:
|
|
21
|
+
if fmt not in ("md", "text"):
|
|
22
|
+
raise ValueError(f"지원하지 않는 형식: {fmt} (md 또는 text)")
|
|
23
|
+
|
|
24
|
+
ext = os.path.splitext(path)[1].lower()
|
|
25
|
+
|
|
26
|
+
if ext in _PY_READERS:
|
|
27
|
+
content = _PY_READERS[ext](path)
|
|
28
|
+
return {"file": os.path.abspath(path), "format": "md", "content": content}
|
|
29
|
+
|
|
30
|
+
if ext in _LEGACY_HWP_EXTS or ext == ".xls":
|
|
31
|
+
if kordoc_available():
|
|
32
|
+
content = KordocAdapter.convert_to_markdown(path)
|
|
33
|
+
return {"file": os.path.abspath(path), "format": "md", "content": content}
|
|
34
|
+
raise RuntimeError(
|
|
35
|
+
"구형 포맷은 두 가지 방법으로 읽을 수 있습니다: "
|
|
36
|
+
"① Windows+한글 환경이면 'hwpx-kit convert'로 hwpx 변환 후 read "
|
|
37
|
+
"② 한글이 없는 환경(Mac 등)이면 kordoc 설치 (Node.js 18+ 필요: npm install -g kordoc)"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
ad = HwpxEngineAdapter.open(path)
|
|
41
|
+
content = ad.export_markdown() if fmt == "md" else ad.export_text()
|
|
42
|
+
return {"file": os.path.abspath(path), "format": fmt, "content": content}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from hwpx_kit.adapter.kordoc_engine import KordocAdapter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_render(path: str, out_path: str | None = None) -> dict:
|
|
9
|
+
out = out_path or os.path.splitext(path)[0] + ".svg"
|
|
10
|
+
saved = KordocAdapter.render_svg(path, out)
|
|
11
|
+
return {"file": os.path.abspath(path), "out": saved}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""row-height 명령 — 기준 행 높이를 지정 행들에 복사.
|
|
2
|
+
|
|
3
|
+
사용자가 한글에서 행을 추가하면 기본 높이로 붙어 표가 들쭉날쭉해지는
|
|
4
|
+
케이스의 도구측 정돈. 인덱스는 analyze 출력과 동일한 0-기준.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_rows_spec(spec: str) -> list[int]:
|
|
12
|
+
"""'1-3' 범위 / '1,3,5' 나열 / 혼합('1-3,5') → 정렬된 행 목록."""
|
|
13
|
+
rows: set[int] = set()
|
|
14
|
+
for part in spec.split(","):
|
|
15
|
+
part = part.strip()
|
|
16
|
+
if not part:
|
|
17
|
+
continue
|
|
18
|
+
if "-" in part:
|
|
19
|
+
lo, hi = part.split("-", 1)
|
|
20
|
+
rows.update(range(int(lo), int(hi) + 1))
|
|
21
|
+
else:
|
|
22
|
+
rows.add(int(part))
|
|
23
|
+
if not rows:
|
|
24
|
+
raise ValueError(f"행 지정이 비었습니다: {spec!r}")
|
|
25
|
+
return sorted(rows)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_row_height(path: str, table: int, like: int, rows: list[int], out_path: str) -> dict:
|
|
29
|
+
ad = HwpxEngineAdapter.open(path)
|
|
30
|
+
height = ad.copy_row_height(table_index=table, like=like, rows=rows)
|
|
31
|
+
out = ad.save_copy(out_path)
|
|
32
|
+
return {
|
|
33
|
+
"file": path,
|
|
34
|
+
"out": out,
|
|
35
|
+
"table_index": table,
|
|
36
|
+
"like": like,
|
|
37
|
+
"applied_rows": rows,
|
|
38
|
+
"height": height,
|
|
39
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""table-clear 명령 — 표의 지정 행 범위 셀 내용 비우기.
|
|
2
|
+
|
|
3
|
+
전면 교체 시 새 내용과 안 맞는 표를 잔존시키지 않고 비워서, 사용자가
|
|
4
|
+
빈 칸을 보고 행 삭제(한글)나 추가 내용 요청을 판단하게 한다.
|
|
5
|
+
행 자체는 남는다(구조 변경 아님). 인덱스는 analyze와 동일한 0-기준.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_table_clear(path: str, table: int, rows: list[int] | None, out_path: str) -> dict:
|
|
13
|
+
ad = HwpxEngineAdapter.open(path)
|
|
14
|
+
cleared = ad.clear_table_rows(table_index=table, rows=rows)
|
|
15
|
+
out = ad.save_copy(out_path)
|
|
16
|
+
return {
|
|
17
|
+
"file": path,
|
|
18
|
+
"out": out,
|
|
19
|
+
"table_index": table,
|
|
20
|
+
"rows": rows,
|
|
21
|
+
"cleared_cells": cleared,
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""table-copy 명령 — 표 통째 복제를 지정 문단에 삽입.
|
|
2
|
+
|
|
3
|
+
장(章) 헤더 박스는 1x3 표라서, 장을 임의 개수로 늘리는 요구는
|
|
4
|
+
"헤더 표 복제(table-copy) → 번호·제목 기입(table-set)" 조합으로 해결.
|
|
5
|
+
삽입 위치는 fill의 text: 철학대로 문단 원문으로 지정.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_table_copy(path: str, table: int, after_text: str | None, out_path: str,
|
|
13
|
+
after_table: int | None = None) -> dict:
|
|
14
|
+
ad = HwpxEngineAdapter.open(path)
|
|
15
|
+
info = ad.copy_table(table_index=table, anchor_text=after_text, after_table=after_table)
|
|
16
|
+
out = ad.save_copy(out_path)
|
|
17
|
+
return {
|
|
18
|
+
"file": path,
|
|
19
|
+
"out": out,
|
|
20
|
+
"copied_from": table,
|
|
21
|
+
"anchor_text": after_text,
|
|
22
|
+
"after_table": after_table,
|
|
23
|
+
**info,
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""table-map 명령 — 표 셀 상태 덤프 (좌표·텍스트·병합 anchor).
|
|
2
|
+
|
|
3
|
+
병합 셀 확인을 위해 원시 XML을 파싱하던 우회를 대체하는 정식 검사 명령.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_table_map(path: str, table: int | None = None) -> dict:
|
|
11
|
+
ad = HwpxEngineAdapter.open(path)
|
|
12
|
+
tm = ad.table_map()
|
|
13
|
+
tables = tm.get("tables", [])
|
|
14
|
+
if table is not None:
|
|
15
|
+
if not 0 <= table < len(tables):
|
|
16
|
+
raise ValueError(f"표 인덱스 범위 밖: {table} (표 {len(tables)}개)")
|
|
17
|
+
tables = [tables[table]]
|
|
18
|
+
return {"file": path, "tables": tables}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""table-new 명령 — 임의 R×C 새 표를 지정 문단에 생성.
|
|
2
|
+
|
|
3
|
+
table-copy(같은 크기 복제)와 달리 크기를 지정한다. --like-table로
|
|
4
|
+
기존 표의 테두리 서식 참조를 빌려 입히면 템플릿과 어울리는 표가 나온다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_table_new(
|
|
12
|
+
path: str, rows: int, cols: int, after_text: str | None = None,
|
|
13
|
+
like_table: int | None = None, out_path: str = "",
|
|
14
|
+
after_table: int | None = None,
|
|
15
|
+
) -> dict:
|
|
16
|
+
if rows < 1 or cols < 1:
|
|
17
|
+
raise ValueError("rows/cols는 1 이상이어야 합니다.")
|
|
18
|
+
ad = HwpxEngineAdapter.open(path)
|
|
19
|
+
ad.new_table(rows=rows, cols=cols, anchor_text=after_text,
|
|
20
|
+
like_table=like_table, after_table=after_table)
|
|
21
|
+
out = ad.save_copy(out_path)
|
|
22
|
+
return {
|
|
23
|
+
"file": path,
|
|
24
|
+
"out": out,
|
|
25
|
+
"rows": rows,
|
|
26
|
+
"cols": cols,
|
|
27
|
+
"like_table": like_table,
|
|
28
|
+
"anchor_text": after_text,
|
|
29
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""table-set 명령 — 좌표 지정 셀 쓰기 (table-clear의 짝).
|
|
2
|
+
|
|
3
|
+
비운 셀에는 라벨이 없어 fill의 table: 키가 못 닿는다 — 전면 교체 후
|
|
4
|
+
새 항목명을 좌표로 직접 기입. 인덱스는 analyze와 동일한 0-기준.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_assignments_file(path: str) -> list[tuple[int, int, str]]:
|
|
12
|
+
"""--data JSON 파일({"R,C": "값"}) → assignments. 셀 대량 기입 시
|
|
13
|
+
셸 명령 길이 한계를 피하는 경로 (fill --data와 같은 철학)."""
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
with open(path, encoding="utf-8") as fh:
|
|
17
|
+
raw = json.load(fh)
|
|
18
|
+
if not isinstance(raw, dict):
|
|
19
|
+
raise ValueError('--data 파일 형식은 {"R,C": "값"} 객체여야 합니다.')
|
|
20
|
+
return parse_assignments([f"{coord}={value}" for coord, value in raw.items()])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_assignments(specs: list[str]) -> list[tuple[int, int, str]]:
|
|
24
|
+
"""'R,C=값' 목록 → (row, col, value). 값 안의 '='는 그대로 허용."""
|
|
25
|
+
result = []
|
|
26
|
+
for spec in specs:
|
|
27
|
+
coord, sep, value = spec.partition("=")
|
|
28
|
+
parts = coord.split(",")
|
|
29
|
+
if not sep or len(parts) != 2:
|
|
30
|
+
raise ValueError(f"형식은 'R,C=값' 이어야 합니다: {spec!r}")
|
|
31
|
+
try:
|
|
32
|
+
result.append((int(parts[0].strip()), int(parts[1].strip()), value))
|
|
33
|
+
except ValueError as exc:
|
|
34
|
+
raise ValueError(f"좌표는 정수여야 합니다: {spec!r}") from exc
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def run_table_set(
|
|
39
|
+
path: str, table: int, assignments: list[tuple[int, int, str]], out_path: str
|
|
40
|
+
) -> dict:
|
|
41
|
+
ad = HwpxEngineAdapter.open(path)
|
|
42
|
+
count = ad.set_table_cells(table_index=table, assignments=assignments)
|
|
43
|
+
out = ad.save_copy(out_path)
|
|
44
|
+
return {
|
|
45
|
+
"file": path,
|
|
46
|
+
"out": out,
|
|
47
|
+
"table_index": table,
|
|
48
|
+
"set_cells": count,
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from hwpx_kit.adapter.hwpx_engine import HwpxEngineAdapter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_validate(path: str) -> dict:
|
|
9
|
+
ad = HwpxEngineAdapter.open(path)
|
|
10
|
+
report = ad.validate()
|
|
11
|
+
issues = list(report.get("issues", []))
|
|
12
|
+
return {"file": os.path.abspath(path), "valid": not issues, "issues": issues}
|
hwpx_kit/format.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""결정론 표기 변환 — 순수함수만. 엔진(python-hwpx/kordoc) 임포트 금지.
|
|
2
|
+
|
|
3
|
+
LLM이 미세하게 틀리는 계산(큰 금액 한글 읽기, 요일, 만나이)을 못박는
|
|
4
|
+
정확성 보험. 스킬은 이 값들을 손으로 계산하지 말고 CLI `fmt`로 얻는다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
_DIGITS = "영일이삼사오육칠팔구"
|
|
12
|
+
_SMALL_UNITS = ((1000, "천"), (100, "백"), (10, "십"), (1, ""))
|
|
13
|
+
_BIG_UNITS = ((10**16, "경"), (10**12, "조"), (10**8, "억"), (10**4, "만"), (1, ""))
|
|
14
|
+
_WEEKDAYS = "월화수목금토일"
|
|
15
|
+
|
|
16
|
+
_AMOUNT_STYLES = ("gongmun", "ilgeum")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _group_to_korean(n: int) -> str:
|
|
20
|
+
"""0~9999를 한글로. 위변조 방지 표기 — 십/백/천 앞 '일'을 생략하지 않는다
|
|
21
|
+
(시행규칙 예시 '일십일만삼천오백육십')."""
|
|
22
|
+
parts = []
|
|
23
|
+
for value, unit in _SMALL_UNITS:
|
|
24
|
+
digit, n = divmod(n, value)
|
|
25
|
+
if digit:
|
|
26
|
+
parts.append(_DIGITS[digit] + unit)
|
|
27
|
+
return "".join(parts)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def amount_to_korean(n: int) -> str:
|
|
31
|
+
"""정수 금액의 한글 읽기. 예: 113560 → 일십일만삼천오백육십."""
|
|
32
|
+
if n < 0:
|
|
33
|
+
raise ValueError("금액은 음수가 될 수 없습니다.")
|
|
34
|
+
if n == 0:
|
|
35
|
+
return "영"
|
|
36
|
+
parts = []
|
|
37
|
+
for value, unit in _BIG_UNITS:
|
|
38
|
+
group, n = divmod(n, value)
|
|
39
|
+
if group:
|
|
40
|
+
parts.append(_group_to_korean(group) + unit)
|
|
41
|
+
return "".join(parts)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def format_amount(n: int, style: str = "gongmun") -> str:
|
|
45
|
+
"""금액 표기.
|
|
46
|
+
|
|
47
|
+
- gongmun(기본): 법정 공문 형식 — 행정 효율과 협업 촉진에 관한 규정
|
|
48
|
+
시행규칙. 예: 금113,560원(금일십일만삼천오백육십원)
|
|
49
|
+
- ilgeum: 민간 관습 형식(영수증·차용증·계약서). '정(整)'은 금액 뒤에
|
|
50
|
+
숫자를 못 붙이게 하는 위변조 방지 관습.
|
|
51
|
+
예: 일금 12,340원정(일금 일만이천삼백사십원정)
|
|
52
|
+
"""
|
|
53
|
+
if style not in _AMOUNT_STYLES:
|
|
54
|
+
raise ValueError(f"style은 {'/'.join(_AMOUNT_STYLES)} 중 하나여야 합니다: {style}")
|
|
55
|
+
reading = amount_to_korean(n)
|
|
56
|
+
if style == "gongmun":
|
|
57
|
+
return f"금{n:,}원(금{reading}원)"
|
|
58
|
+
return f"일금 {n:,}원정(일금 {reading}원정)"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _parse_date(raw: str) -> datetime.date:
|
|
62
|
+
digits = re.sub(r"\D", "", raw)
|
|
63
|
+
if len(digits) != 8:
|
|
64
|
+
raise ValueError(f"날짜는 YYYYMMDD 8자리여야 합니다: {raw}")
|
|
65
|
+
try:
|
|
66
|
+
return datetime.date(int(digits[:4]), int(digits[4:6]), int(digits[6:8]))
|
|
67
|
+
except ValueError as exc:
|
|
68
|
+
raise ValueError(f"달력에 없는 날짜입니다: {raw}") from exc
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def gongmun_date(raw: str) -> str:
|
|
72
|
+
"""공문 날짜 표기. 예: 20260101 → 2026.1.1.(목).
|
|
73
|
+
월·일 앞 0 없음, 일 뒤 마침표, 요일 괄호 — 행안부 공문 관습."""
|
|
74
|
+
d = _parse_date(raw)
|
|
75
|
+
return f"{d.year}.{d.month}.{d.day}.({_WEEKDAYS[d.weekday()]})"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def korean_age(yymmdd: str, base: str | None = None) -> str:
|
|
79
|
+
"""주민번호 앞 6자리 → 'YYMMDD(만나이)'. YY는 19YY로 해석(주민번호 관습).
|
|
80
|
+
base: 기준일 YYYYMMDD (기본 오늘)."""
|
|
81
|
+
digits = re.sub(r"\D", "", yymmdd)
|
|
82
|
+
if len(digits) != 6:
|
|
83
|
+
raise ValueError(f"생년월일은 YYMMDD 6자리여야 합니다: {yymmdd}")
|
|
84
|
+
try:
|
|
85
|
+
birth = datetime.date(1900 + int(digits[:2]), int(digits[2:4]), int(digits[4:6]))
|
|
86
|
+
except ValueError as exc:
|
|
87
|
+
raise ValueError(f"달력에 없는 생년월일입니다: {yymmdd}") from exc
|
|
88
|
+
base_date = _parse_date(base) if base else datetime.date.today()
|
|
89
|
+
age = base_date.year - birth.year - (
|
|
90
|
+
(base_date.month, base_date.day) < (birth.month, birth.day)
|
|
91
|
+
)
|
|
92
|
+
if age < 0:
|
|
93
|
+
raise ValueError("기준일이 생년월일보다 앞섭니다.")
|
|
94
|
+
return f"{digits}({age})"
|
hwpx_kit/output.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""JSON 봉투와 stdout 위생.
|
|
2
|
+
|
|
3
|
+
python-hwpx는 manifest fallback 등 경고를 print()로 stdout에 쓴다.
|
|
4
|
+
--json 모드에서 stdout은 JSON 봉투 한 줄만 허용되므로, 엔진 호출은
|
|
5
|
+
quiet_engine()으로 감싸 노이즈를 warnings로 회수한다.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def envelope(command, ok, data=None, warnings=None, error=None):
|
|
16
|
+
env = {"ok": ok, "command": command}
|
|
17
|
+
if data is not None:
|
|
18
|
+
env["data"] = data
|
|
19
|
+
env["warnings"] = warnings or []
|
|
20
|
+
if error is not None:
|
|
21
|
+
env["error"] = error
|
|
22
|
+
return env
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@contextmanager
|
|
26
|
+
def quiet_engine():
|
|
27
|
+
captured: list[str] = []
|
|
28
|
+
buf = io.StringIO()
|
|
29
|
+
orig = sys.stdout
|
|
30
|
+
sys.stdout = buf
|
|
31
|
+
try:
|
|
32
|
+
yield captured
|
|
33
|
+
finally:
|
|
34
|
+
sys.stdout = orig
|
|
35
|
+
captured.extend(line for line in buf.getvalue().splitlines() if line.strip())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def print_result(env, as_json):
|
|
39
|
+
if as_json:
|
|
40
|
+
print(json.dumps(env, ensure_ascii=False))
|
|
41
|
+
return
|
|
42
|
+
status = "OK" if env["ok"] else "ERROR"
|
|
43
|
+
print(f"[{status}] {env['command']}")
|
|
44
|
+
for w in env["warnings"]:
|
|
45
|
+
print(f" warning: {w}")
|
|
46
|
+
if not env["ok"]:
|
|
47
|
+
err = env.get("error") or {}
|
|
48
|
+
print(f" {err.get('code', '?')}: {err.get('message', '')}")
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hwpx-kit-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: 한글(HWPX) 문서 자동화 CLI — Korean HWPX document automation for AI agents: analyze, fill, read, validate, table ops
|
|
5
|
+
Project-URL: Homepage, https://github.com/6aneffy/hwpx-kit
|
|
6
|
+
Project-URL: Repository, https://github.com/6aneffy/hwpx-kit
|
|
7
|
+
Project-URL: Issues, https://github.com/6aneffy/hwpx-kit/issues
|
|
8
|
+
Author-email: 6aneffy <tkdgus990809@gmail.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude-code,document-automation,hangul,hwp,hwpx,korean
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Office/Business :: Office Suites
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
17
|
+
Requires-Dist: pypdf>=6.14.2
|
|
18
|
+
Requires-Dist: python-docx>=1.2.0
|
|
19
|
+
Requires-Dist: python-hwpx<3,>=2.24
|
|
20
|
+
Provides-Extra: convert
|
|
21
|
+
Requires-Dist: pyhwpx; (sys_platform == 'win32') and extra == 'convert'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<div align="center">
|
|
25
|
+
|
|
26
|
+
# hwpx-kit
|
|
27
|
+
|
|
28
|
+
**한글(HWPX) 문서 자동화 — Claude Code 플러그인 & CLI**
|
|
29
|
+
|
|
30
|
+
*"이 양식에 채워줘" 한마디로, 서식 무손상 한글 문서가 나옵니다.*
|
|
31
|
+
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
[](https://www.python.org/)
|
|
34
|
+
[](https://claude.com/claude-code)
|
|
35
|
+
[](tests/)
|
|
36
|
+
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 무엇이 되나
|
|
42
|
+
|
|
43
|
+
한글(HWP/HWPX)은 한국 공공·기업 문서의 표준이지만, AI가 다룰 수 있는 도구가 사실상 없었습니다.
|
|
44
|
+
hwpx-kit은 **AI 에이전트가 한글 문서를 읽고, 채우고, 고치고, 새로 만들 수 있게** 하는 도구 모음입니다.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
👤 "이 양식에 채워줘" → 서식(폰트·표·조판) 무손상으로 값만 채운 사본
|
|
48
|
+
👤 "이 기획서를 ○○사업용으로 바꿔줘" → 기존 문서를 양식 삼아 내용 전면 교체
|
|
49
|
+
👤 "한글로 새 기획서 만들어줘" → 내장 템플릿으로 표지·장·표 갖춘 문서 생성
|
|
50
|
+
👤 "예산 앞에 장 하나 추가하고 표 넣어줘" → 장 헤더 복제·새 표 생성·쪽나눔까지
|
|
51
|
+
👤 "금액 한글로 병기해줘" → 금12,340원(금일만이천삼백사십원) — 계산 착오 0
|
|
52
|
+
👤 "이거 워드로도 줘" / "PPT로 만들어줘" → docx 변환 / 발표자료 재구성
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
모든 채우기는 **원본을 절대 수정하지 않고** 사본에만 씁니다. 실제 공공기관 서식
|
|
56
|
+
(보도자료·신청서·계획서·회의록·공고문)으로 왕복 검증했습니다.
|
|
57
|
+
|
|
58
|
+
| | hwpx-kit 없이 | hwpx-kit으로 |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| 양식 채우기 | 한글 열고 칸마다 복붙, 서식 깨지면 수작업 복구 | 파일 주고 한마디 — 서식 무손상 사본 |
|
|
61
|
+
| 새 기획서 | 백지에서 표지·표·조판 직접 | 내장 템플릿에 내용만 — 몇 분 완주 |
|
|
62
|
+
| 금액 병기 | 손으로 한글 표기 (오타 위험) | 결정론 계산 — `금12,340원(금일만이천삼백사십원)` |
|
|
63
|
+
| AI에게 한글 파일 | 못 읽음 · PDF 변환 우회 | hwpx·hwp 직접 읽기/쓰기 |
|
|
64
|
+
|
|
65
|
+
**이런 분들을 위해 만들었습니다**: 공공기관·학교의 서식 담당자, 사업계획서를 반복 작성하는
|
|
66
|
+
창업자·연구자, 한글 문서를 AI 워크플로에 넣고 싶은 개발자.
|
|
67
|
+
|
|
68
|
+
## 30초 설치
|
|
69
|
+
|
|
70
|
+
Claude Code에서:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
/plugin marketplace add 6aneffy/hwpx-kit
|
|
74
|
+
/plugin install hwpx-kit@hwpx-kit-market
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
이게 전부입니다. CLI 런타임은 첫 사용 때 스킬이 알아서 설치를 진행합니다
|
|
78
|
+
(PC에 Python 3.10+만 있으면 됩니다).
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary>수동 설치</summary>
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install git+https://github.com/6aneffy/hwpx-kit.git
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```powershell
|
|
88
|
+
# Windows 일괄 (CLI + 플러그인 등록)
|
|
89
|
+
irm https://raw.githubusercontent.com/6aneffy/hwpx-kit/main/install.ps1 | iex
|
|
90
|
+
```
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
## 스킬 (Claude Code 플러그인)
|
|
94
|
+
|
|
95
|
+
| 스킬 | 트리거 | 역할 |
|
|
96
|
+
|------|--------|------|
|
|
97
|
+
| **hwpx-form** | "이 양식에 채워줘" | 분석→채우기→검증 코어 워크플로. 표 조작·강조·워드 출력 포함 |
|
|
98
|
+
| **doc-create** | "기획서 만들어줘" (파일 없이) | 백지 생성 라우터 — 내장 템플릿(표지·장 헤더·표 골격) 기반 |
|
|
99
|
+
| **format-convert** | "금액 한글로", "요일 붙여줘" | 금액 병기·날짜 요일·만나이 — 결정론 계산 (LLM 암산 금지) |
|
|
100
|
+
| **gongmun-format** | "공문 형식으로" | 행안부 공문 규약 — 글머리 위계(□○-※\*), 표기, 텍스트 정리 |
|
|
101
|
+
| **table-calc** | "증감 채워줘" | 보고서 표 증감(△·%p)·비율·합계 계산과 관습 표기 |
|
|
102
|
+
| **office-export** | "워드로", "PPT로" | docx 변환(한글 COM) / pptx·xlsx 재구성 |
|
|
103
|
+
|
|
104
|
+
스킬은 사용자에게 **필수 정보를 먼저 묻고**(선택지 UI), 초안을 확정받은 뒤 작업합니다.
|
|
105
|
+
말하지 않은 값을 지어내지 않습니다.
|
|
106
|
+
|
|
107
|
+
## CLI
|
|
108
|
+
|
|
109
|
+
모든 명령은 `--json`으로 한 줄 JSON 봉투(`ok`/`data`/`warnings`/`error`)를 반환합니다.
|
|
110
|
+
종료 코드: `0` 성공 · `1` 오류 · `2` 부분 성공.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# 읽기·분석
|
|
114
|
+
hwpx-kit analyze 양식.hwpx --json # 채울 수 있는 필드 탐지 (fill_key 반환)
|
|
115
|
+
hwpx-kit read 문서 --format md # 본문 추출 — hwpx·PDF·DOCX·XLSX 내장 지원
|
|
116
|
+
hwpx-kit outline 문서.hwpx --json # 문단·표 배치 지도 (삽입 앵커 탐색)
|
|
117
|
+
hwpx-kit table-map 문서.hwpx --table 3 --json # 표 셀 좌표·병합 상태
|
|
118
|
+
hwpx-kit validate 문서.hwpx --json # 구조 검증
|
|
119
|
+
|
|
120
|
+
# 채우기·편집 (원본 불변 — 항상 --out 사본)
|
|
121
|
+
hwpx-kit fill 양식.hwpx --data 값.json --out 결과.hwpx
|
|
122
|
+
hwpx-kit table-set 문서.hwpx --table 3 --data 셀.json --out 결과.hwpx # 좌표 셀 쓰기
|
|
123
|
+
hwpx-kit table-clear 문서.hwpx --table 3 --rows 1-20 --out 결과.hwpx # 셀 내용 비우기
|
|
124
|
+
hwpx-kit table-copy 문서.hwpx --table 1 --after-table 4 --out 결과.hwpx # 표 통째 복제 (서식·병합 유지)
|
|
125
|
+
hwpx-kit table-new 문서.hwpx --rows 6 --cols 4 --like-table 3 --after-text "앵커" --out 결과.hwpx
|
|
126
|
+
hwpx-kit row-height 문서.hwpx --table 3 --like 1 --rows 2-5 --out 결과.hwpx # 행 높이 정돈
|
|
127
|
+
hwpx-kit page-break 문서.hwpx --table 5 --out 결과.hwpx # 쪽나눔 (새 장 시작)
|
|
128
|
+
|
|
129
|
+
# 변환·생성
|
|
130
|
+
hwpx-kit fmt --amount 12340 --json # 금12,340원(금일만이천삼백사십원)
|
|
131
|
+
hwpx-kit fmt --date 20260101 --json # 2026.1.1.(목)
|
|
132
|
+
hwpx-kit convert 문서.hwp --json # .hwp → .hwpx (Windows + 한글)
|
|
133
|
+
hwpx-kit export 문서.hwpx --to docx --json # 워드로 내보내기 (Windows + 한글)
|
|
134
|
+
hwpx-kit generate 초안.md --out 새문서.hwpx # Markdown → 공문서 (kordoc)
|
|
135
|
+
hwpx-kit render 문서.hwpx --out p.svg # SVG 미리보기 (kordoc)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## fill_key 계약
|
|
139
|
+
|
|
140
|
+
`analyze`가 주는 키를 그대로 데이터 JSON의 키로 씁니다.
|
|
141
|
+
|
|
142
|
+
| 키 | 대상 | 예시 |
|
|
143
|
+
|----|------|------|
|
|
144
|
+
| `clickhere:<이름>` | 누름틀 필드 | `"clickhere:신청자": "김철수"` |
|
|
145
|
+
| `marker:<키>` | `{{키}}` 마커 | `"marker:출장기간": "7/14~7/16"` |
|
|
146
|
+
| `table:<라벨>` | 라벨 오른쪽 셀 | `"table:성명": "김철수"` |
|
|
147
|
+
| `table:<라벨>#N` | 같은 라벨 N번째 | `"table:담당 부서#2": "운영과"` |
|
|
148
|
+
| `table:<라벨> > <방향>` | 방향 체인 | `"table:과 장 > right > right": "010-…"` |
|
|
149
|
+
| `text:<원문>` | 문장 교체 (서식 보존) | `"text:○○○ 사업": "청년 AI교육 사업"` |
|
|
150
|
+
| `delete:<문단원문>` | 안내문 삭제 | `"delete:(예시입니다)": ""` |
|
|
151
|
+
| `bold:` / `underline:<원문>` | 굵게·밑줄 | `"bold:핵심 성과": ""` |
|
|
152
|
+
|
|
153
|
+
## 요구 사항
|
|
154
|
+
|
|
155
|
+
| 기능 | 필요한 것 |
|
|
156
|
+
|------|-----------|
|
|
157
|
+
| 분석·채우기·검증·표 조작·표기 변환·PDF/DOCX/XLSX 읽기 | **Python 3.10+ 만** (순수 파이썬) |
|
|
158
|
+
| `.hwp` 변환, 워드(docx) 내보내기 | Windows + 한글(한컴오피스) |
|
|
159
|
+
| 한글 없는 환경(Mac 등)의 구형 `.hwp` 읽기, MD→HWPX, SVG | [kordoc](https://github.com/chrisryugj/kordoc) (Node 18+, 선택) |
|
|
160
|
+
|
|
161
|
+
## 아키텍처
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Claude Code 스킬 6종 ──→ hwpx-kit CLI (19 명령, JSON 봉투)
|
|
165
|
+
└─ adapter/ ← 엔진 격리 계층
|
|
166
|
+
├─ python-hwpx hwpx 분석·채우기·표 조작
|
|
167
|
+
├─ pypdf·python-docx·openpyxl 타 포맷 읽기
|
|
168
|
+
├─ pyhwpx(COM) .hwp 변환·docx 내보내기
|
|
169
|
+
└─ kordoc 구형 .hwp·생성·렌더 (선택)
|
|
170
|
+
templates/ 기본 계획서 템플릿 (표지·장 헤더·표 골격, 마커 내장)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
설계 불변 원칙: **① 원본 절대 수정 금지 ② `--json` stdout은 봉투 한 줄만
|
|
174
|
+
③ 서식은 항상 보존** (셀을 비워도 글자모양이 유지되어 재기입 시 원래 서식을 물려받음).
|
|
175
|
+
|
|
176
|
+
> 🚧 같은 코어를 MCP 서버로 노출하는 버전도 제작 중입니다 (Claude Desktop 등 MCP 클라이언트용).
|
|
177
|
+
|
|
178
|
+
## 검증
|
|
179
|
+
|
|
180
|
+
- 자동 테스트 145개 (pytest)
|
|
181
|
+
- 실서식 수렴 테스트: 보도자료·신청서·계획서·회의록·공고문 5계열,
|
|
182
|
+
실제 공공기관 문서 30여 종으로 analyze→fill→validate 왕복
|
|
183
|
+
- 실사용 시나리오 테스트 3회 (백지 생성 12분 완주, 장·표 동적 추가, 보도자료 변환)
|
|
184
|
+
|
|
185
|
+
## 라이선스
|
|
186
|
+
|
|
187
|
+
[Apache-2.0](LICENSE) · © 2026 [6aneffy](https://github.com/6aneffy)
|
|
188
|
+
|
|
189
|
+
이슈·PR 환영합니다. 특히 실제 기관 양식에서의 analyze/fill 실패 사례 제보가 가장 값집니다
|
|
190
|
+
(단, **기관 내부 문서를 이슈에 첨부하지 마세요** — 재현 가능한 합성 예시로 부탁드립니다).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
hwpx_kit/__init__.py,sha256=QTYqXqSTHFRkM9TEgpDFcHvwLbvqHDqvqfQ9EiXkcAM,23
|
|
2
|
+
hwpx_kit/cli.py,sha256=nMcbTcXFk4fbOv5ArXttEWDeFQiE3w0DNqhMtOcXdoc,13133
|
|
3
|
+
hwpx_kit/format.py,sha256=zzCoHnlm78LPF4yphM0a3s7XP3-DacZPJxUirWZl2Bc,3891
|
|
4
|
+
hwpx_kit/output.py,sha256=-9AY8rTxVpg2tAur-9XUkJHGXStEdriRKBdjq3E_RN0,1374
|
|
5
|
+
hwpx_kit/adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
hwpx_kit/adapter/base.py,sha256=ghWGUaKRhkk6vv4j-9UC2Yv0JUglwCazrNqIB9H3k28,1341
|
|
7
|
+
hwpx_kit/adapter/hwpx_engine.py,sha256=8Qi_UETncryutTpMg7uIygp7czNvf2OUPMtdhVSGFdU,25131
|
|
8
|
+
hwpx_kit/adapter/kordoc_engine.py,sha256=bdOMAGygIiK-H90BK9xxqFmFsRT-tNYJPV-xsxkh-mw,3008
|
|
9
|
+
hwpx_kit/adapter/office_readers.py,sha256=0dfiNOoqDYMdOM-kABDwlTgChr7sofgwJMIpA2cRQCY,1568
|
|
10
|
+
hwpx_kit/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
hwpx_kit/commands/analyze.py,sha256=b6RgMJ1G6hPIgQikQsCmgcyQRBjNo_o4f_OFZYX9o6Q,4578
|
|
12
|
+
hwpx_kit/commands/convert.py,sha256=uhatqTVywz-JEBE1zYxCH04Q-0w0vkxG30Ks0YrQdMk,2213
|
|
13
|
+
hwpx_kit/commands/export.py,sha256=mRXgzkL6OwzQlJy6NMmBTyn79OhHHAn3nHEkmuqZhZ0,1895
|
|
14
|
+
hwpx_kit/commands/fill.py,sha256=Hq2_252e4lgGrQ9LhuyWjqGMB2dX2xPSDx_w0nO6T3w,3944
|
|
15
|
+
hwpx_kit/commands/fmt.py,sha256=ozCH7boabBBIvlil7_Zy4vPqeLS97JtnF0nytSXYjMA,1201
|
|
16
|
+
hwpx_kit/commands/generate.py,sha256=M9cWEIAJRFueASvA8Ra-INLnL3lkFF8R8YGlYVJ-1zc,518
|
|
17
|
+
hwpx_kit/commands/outline.py,sha256=wBak6J6pgRfK4xy1xHEObZp1P2ldpucpakDpchHrUuU,474
|
|
18
|
+
hwpx_kit/commands/page_break.py,sha256=Fmz2xWH2y_Dyq_uDBHSINxdypbgw4NFTaPiCrOFu5hI,846
|
|
19
|
+
hwpx_kit/commands/read.py,sha256=rs416yvot1zx6InoqOz9CzCbwDmBXVIvh12k2cgxPHQ,1686
|
|
20
|
+
hwpx_kit/commands/render.py,sha256=zfdb0fvBqm0S3ETq1a5I_y3djpWc3Vc9ahYOlfbFLmc,343
|
|
21
|
+
hwpx_kit/commands/row_height.py,sha256=kJefyc5c14HbmE5qe9xAFgAqoYSBb8iwSWgpim2HHOg,1334
|
|
22
|
+
hwpx_kit/commands/table_clear.py,sha256=LO9Qk2hynUIBvAXKD5h5lC5QVcOFhoN7MBpVyKxzTz8,843
|
|
23
|
+
hwpx_kit/commands/table_copy.py,sha256=JRNuDRAz0kMbnHuNSLvz9GsajCVOTQ6dQMKQT36V9-k,927
|
|
24
|
+
hwpx_kit/commands/table_map_cmd.py,sha256=OxeZ6VOCTUMmGzmnv2glh4OinhT6lRbNlx55eWPDPNo,696
|
|
25
|
+
hwpx_kit/commands/table_new.py,sha256=TsUrF-DdfFl5xl4HWM3qRTc4FUV5aSwkIZ2jF-MPXvI,1049
|
|
26
|
+
hwpx_kit/commands/table_set.py,sha256=c1gu4ejDcEAV-E2k_pK8H5TOqzueKjBab2RIzBCLdEE,1942
|
|
27
|
+
hwpx_kit/commands/validate.py,sha256=iCQtk-ZFYzGmHf-DIyAzFsWjSxWIw7txzw8_gpkfDBM,348
|
|
28
|
+
hwpx_kit_cli-0.1.1.dist-info/METADATA,sha256=jzUbmxT_7o3rZzVaAoTJtWCV3o1DOlAT5Giuh-2mqwI,9863
|
|
29
|
+
hwpx_kit_cli-0.1.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
30
|
+
hwpx_kit_cli-0.1.1.dist-info/entry_points.txt,sha256=wMWfvC6GkxzVbWzcWjwqusESXzbZgup5-NURWtwiPFg,47
|
|
31
|
+
hwpx_kit_cli-0.1.1.dist-info/licenses/LICENSE,sha256=dF6iw8X7b4BkcR9Z6CNirWS_EHfkE4b8KlDBfYsZ2HY,11751
|
|
32
|
+
hwpx_kit_cli-0.1.1.dist-info/RECORD,,
|