|
| 1 | +"""문서 생성 (v0.15+) — 확장자 디스패치 진입점. |
| 2 | +
|
| 3 | + from document_adapter import create_document |
| 4 | +
|
| 5 | + create_document("회의록.docx", markdown="# 주간 회의록\\n...", lang="ko") |
| 6 | + create_document("보고.pptx", markdown="# 표지\\n---\\n# 슬라이드2\\n- 불릿") |
| 7 | + create_document("공문.hwpx", markdown="# 제목\\n본문...") |
| 8 | + create_document("매출.xlsx", sheets=[{"name": "...", "headers": [...], "rows": [...]}]) |
| 9 | +
|
| 10 | +설계 원칙: |
| 11 | +- LLM 산출물은 markdown / sheet spec(dict) 뿐 — XML 을 직접 쓰는 경로 없음. |
| 12 | +- 렌더러가 곧 검증기: 잘못된 산출물은 이중어 ValueError. 재시도는 호출 |
| 13 | + 레이어(도구/에이전트)의 몫이다 (1회 재시도 계약). |
| 14 | +- 생성-편집 왕복 보장: 반환 즉시 ``load(path)`` 가 성립해 기존 편집 도구 |
| 15 | + (inspect_document → set_cell / insert_row / ...)로 이어진다. |
| 16 | +- markdown 렌더러 3종(.docx/.pptx/.hwpx)은 같은 파서(markdown_parser)를 |
| 17 | + 공유한다 — .pptx 는 ``---``/레벨 1~2 헤딩으로 슬라이드를 나눈다. |
| 18 | +""" |
| 19 | +from __future__ import annotations |
| 20 | + |
| 21 | +from pathlib import Path |
| 22 | +from typing import Any |
| 23 | + |
| 24 | +from .docx_writer import base_font_for_lang, docx_from_markdown |
| 25 | +from .hwpx_writer import hwpx_from_markdown |
| 26 | +from .markdown_parser import Block, Span, parse_inline, parse_markdown |
| 27 | +from .pptx_writer import pptx_from_markdown |
| 28 | +from .xlsx_writer import xlsx_from_sheets |
| 29 | + |
| 30 | +__all__ = [ |
| 31 | + "create_document", |
| 32 | + "docx_from_markdown", |
| 33 | + "pptx_from_markdown", |
| 34 | + "hwpx_from_markdown", |
| 35 | + "xlsx_from_sheets", |
| 36 | + "parse_markdown", |
| 37 | + "parse_inline", |
| 38 | + "base_font_for_lang", |
| 39 | + "Block", |
| 40 | + "Span", |
| 41 | +] |
| 42 | + |
| 43 | +_MARKDOWN_EXTS = {".docx", ".pptx", ".hwpx"} |
| 44 | +_SHEETS_EXTS = {".xlsx"} |
| 45 | +_SUPPORTED = sorted(_MARKDOWN_EXTS | _SHEETS_EXTS) |
| 46 | + |
| 47 | +_MARKDOWN_RENDERERS = { |
| 48 | + ".docx": docx_from_markdown, |
| 49 | + ".pptx": pptx_from_markdown, |
| 50 | + ".hwpx": hwpx_from_markdown, |
| 51 | +} |
| 52 | + |
| 53 | + |
| 54 | +def create_document( |
| 55 | + path: str | Path, |
| 56 | + *, |
| 57 | + markdown: str | None = None, |
| 58 | + sheets: list[dict[str, Any]] | None = None, |
| 59 | + lang: str = "ko", |
| 60 | + overwrite: bool = False, |
| 61 | +) -> Path: |
| 62 | + """확장자에 맞는 렌더러로 새 문서를 생성해 path 에 저장한다. |
| 63 | +
|
| 64 | + Args: |
| 65 | + path: 출력 경로. 확장자가 렌더러를 고른다 (.docx/.pptx/.hwpx=markdown, |
| 66 | + .xlsx=sheets). 부모 디렉토리는 자동 생성. |
| 67 | + markdown: .docx/.pptx/.hwpx 용 본문 (제약된 markdown 서브셋 — |
| 68 | + .pptx 는 ``---`` 또는 레벨 1~2 헤딩이 슬라이드 구분). |
| 69 | + sheets: .xlsx 용 sheet spec 리스트 (xlsx_writer 스키마). |
| 70 | + lang: 기본 폰트 스택 선택 (ko → 맑은 고딕). |
| 71 | + overwrite: False(기본)면 기존 파일이 있을 때 ValueError. |
| 72 | +
|
| 73 | + Returns: |
| 74 | + 생성된 파일 경로. 반환 즉시 ``document_adapter.load()`` 가능. |
| 75 | +
|
| 76 | + Raises: |
| 77 | + ValueError: 확장자-인자 불일치 / 스펙 검증 실패 / 파일 존재. |
| 78 | + 전부 이중어 메시지 — 호출 레이어의 재시도 계약용. |
| 79 | + """ |
| 80 | + out = Path(path) |
| 81 | + suffix = out.suffix.lower() |
| 82 | + |
| 83 | + if suffix not in _MARKDOWN_EXTS | _SHEETS_EXTS: |
| 84 | + raise ValueError( |
| 85 | + f"unsupported extension {suffix!r} for create_document " |
| 86 | + f"(supported: {', '.join(_SUPPORTED)}). " |
| 87 | + f"create_document 가 지원하지 않는 확장자입니다: {suffix!r} " |
| 88 | + f"(지원: {', '.join(_SUPPORTED)})." |
| 89 | + ) |
| 90 | + |
| 91 | + if markdown is not None and sheets is not None: |
| 92 | + raise ValueError( |
| 93 | + "pass either `markdown` or `sheets`, not both. " |
| 94 | + "`markdown` 과 `sheets` 는 동시에 줄 수 없습니다." |
| 95 | + ) |
| 96 | + |
| 97 | + if suffix in _MARKDOWN_EXTS: |
| 98 | + if markdown is None: |
| 99 | + raise ValueError( |
| 100 | + f"{suffix} generation requires `markdown`. " |
| 101 | + f"{suffix} 생성에는 `markdown` 인자가 필요합니다." |
| 102 | + ) |
| 103 | + content = _MARKDOWN_RENDERERS[suffix](markdown, lang=lang) |
| 104 | + else: # _SHEETS_EXTS |
| 105 | + if sheets is None: |
| 106 | + raise ValueError( |
| 107 | + f"{suffix} generation requires `sheets` (sheet spec list). " |
| 108 | + f"{suffix} 생성에는 `sheets`(sheet spec 리스트) 인자가 필요합니다." |
| 109 | + ) |
| 110 | + content = xlsx_from_sheets(sheets) |
| 111 | + |
| 112 | + if out.exists() and not overwrite: |
| 113 | + raise ValueError( |
| 114 | + f"file already exists: {out} — pass overwrite=True to replace. " |
| 115 | + f"파일이 이미 존재합니다: {out} — 덮어쓰려면 overwrite=True 를 전달하세요." |
| 116 | + ) |
| 117 | + |
| 118 | + out.parent.mkdir(parents=True, exist_ok=True) |
| 119 | + out.write_bytes(content) |
| 120 | + return out |
0 commit comments