#!/usr/bin/env python3 """ doc2md — 통합 문서 변환기 (AI 에이전트용) 사용법: python convert.py -o [--json] python convert.py --scan -o [--json] 자세한 사용법: AGENT_GUIDE.md 참조 """ from __future__ import annotations import argparse import json import sys from pathlib import Path if sys.platform == 'win32': sys.stdout.reconfigure(encoding='utf-8', errors='replace') sys.stderr.reconfigure(encoding='utf-8', errors='replace') SUPPORTED = {'.pdf', '.hwp', '.hwpx', '.hml', '.html', '.htm'} SKIP_NAMES = {'README.md', 'CLAUDE.md', 'AGENT_GUIDE.md'} def convert_file(src: Path, output_dir: Path) -> dict: """파일 하나를 변환. AGENT_GUIDE 스펙 dict 반환.""" ext = src.suffix.lower() try: if ext == '.pdf': from converters.pdf import convert_pdf return convert_pdf(src, output_dir) elif ext == '.hwp': from converters.hwp import convert_hwp return convert_hwp(src, output_dir) elif ext == '.hwpx': from converters.hwpx import convert_hwpx return convert_hwpx(src, output_dir) elif ext == '.hml': from converters.hml import convert_hml return convert_hml(src, output_dir) elif ext in {'.html', '.htm'}: from converters.html import convert_html return convert_html(src, output_dir) else: return {"status": "skipped", "input": str(src), "reason": "unsupported_format"} except Exception as e: return {"status": "error", "input": str(src), "error": str(e)} def scan_and_convert(scan_dir: Path, output_dir: Path) -> dict: """폴더 스캔 후 변환 대상 일괄 처리.""" targets = [] for ext in SUPPORTED: targets.extend(scan_dir.rglob(f'*{ext}')) targets.sort() results = [] ok = fail = skipped = 0 for src in targets: if src.name in SKIP_NAMES: continue # 이미 .md 존재하면 스킵 if src.with_suffix('.md').exists(): results.append({"input": str(src), "output": None, "status": "skipped", "reason": "already_md"}) skipped += 1 continue out_dir = output_dir / src.parent.relative_to(scan_dir) r = convert_file(src, out_dir) results.append(r) if r['status'] == 'ok': ok += 1 elif r['status'] == 'error': fail += 1 else: skipped += 1 return { "status": "ok" if fail == 0 else ("error" if ok == 0 else "partial"), "total": len(results), "converted": ok, "skipped": skipped, "failed": fail, "results": results, } def main(): parser = argparse.ArgumentParser( description='doc2md — AI 에이전트용 문서 변환기', formatter_class=argparse.RawDescriptionHelpFormatter, epilog='자세한 사용법: AGENT_GUIDE.md' ) parser.add_argument('file', nargs='?', help='변환할 파일') parser.add_argument('-o', '--output', required=True, help='출력 폴더') parser.add_argument('--scan', metavar='DIR', help='폴더 일괄 변환 모드') parser.add_argument('--json', action='store_true', help='결과를 JSON으로 출력') args = parser.parse_args() output_dir = Path(args.output) if args.scan: result = scan_and_convert(Path(args.scan), output_dir) exit_code = 0 if result['status'] == 'ok' else (1 if result['status'] == 'partial' else 2) elif args.file: src = Path(args.file) if not src.exists(): err = {"status": "error", "input": str(src), "error": "파일 없음"} if args.json: print(json.dumps(err, ensure_ascii=False)) else: print(f"오류: 파일 없음 — {src}", file=sys.stderr) sys.exit(2) result = convert_file(src, output_dir) exit_code = 0 if result['status'] == 'ok' else 2 else: parser.print_help() sys.exit(1) if args.json: print(json.dumps(result, ensure_ascii=False, indent=2)) else: # 사람이 읽기 쉬운 출력 (에이전트가 --json 없이 호출 시) status = result.get('status', '') if 'results' in result: print(f"[doc2md] {result['converted']}개 변환 / {result['skipped']}개 스킵 / {result['failed']}개 실패") else: output = result.get('output', '') print(f"[doc2md] {status.upper()} — {output or result.get('error', '')}") if result.get('has_diagrams'): pages = result.get('diagram_pages', []) print(f"[doc2md] 다이어그램 페이지: {pages} → Vision AI 처리 필요") sys.exit(exit_code) if __name__ == '__main__': main()