파일마다 포맷 폭이 달라(≈80 대 100) 한 줄만 고쳐도 포맷터가 무관한 줄을 대량 재포맷했음. 사용자 지시로 전체를 한 번에 맞춤. 코드 동작 변경 없음 — 포맷만. - 프론트엔드 `.ts/.css/.html` → 저장소 prettier (`.prettierrc`, printWidth 100) - `B07_DesignDetail/openwebcad/**` → 자체 biome (tab 들여쓰기·single quote·lineWidth 100). `biome format` 만 사용 — `biome lint --write` 는 포맷 아닌 코드 수정까지 하므로 제외 - 파이썬 → `ruff format` (엔진 코드는 이미 정합, resources·scratch 스크립트 24개만 변경) 두 포맷터가 서로 되돌리지 않도록 `.prettierignore` 신규 — openwebcad 와 빌드·산출물 폴더를 prettier 대상에서 뺌. `.prettierrc` 에 `endOfLine: "auto"` 추가 — 기본값 `lf` 가 `core.autocrlf=true` 로 받은 CRLF 파일을 매번 전부 다시 써서 `--list-different` 가 실제 포맷 차이를 가리고 있었음. 검증: `tsc --noEmit` 통과(루트·openwebcad 둘 다), pytest 349 passed / 17 skipped / 0 failed, CAD vitest 87건 중 81 passed / 6 failed(laptop-sub 기준선과 동일, 회귀 없음). 포맷터 재실행 시 prettier·biome 모두 변경 0건. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""법령 본문 md의 원시 <img src="law.go.kr/..."> 태그를 pic/로 내려받아 md 이미지 참조로 교체.
|
|
|
|
법령 XML 조문·개정문에 인라인으로 박힌 수식·그림 이미지 처리.
|
|
이미지는 각 명칭 폴더의 pic/ 에 저장하고 md에서 ../pic 상대참조.
|
|
"""
|
|
|
|
import re, sys, time, urllib.request
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
UA = {"User-Agent": "Mozilla/5.0"}
|
|
# src="URL" 형식과 id="flSeq" 형식(내부 이미지) 모두 처리
|
|
IMG = re.compile(r"<img\b([^>]*?)/?>")
|
|
SRC = re.compile(r'src="([^"]+)"')
|
|
IID = re.compile(r'id="(\d+)"')
|
|
EXT = {
|
|
b"\x89PNG": ".png",
|
|
b"\xff\xd8\xff": ".jpg",
|
|
b"GIF8": ".gif",
|
|
b"BM": ".bmp",
|
|
b"II*\x00": ".tif",
|
|
b"MM\x00*": ".tif",
|
|
}
|
|
|
|
|
|
def sniff(b):
|
|
for sig, ext in EXT.items():
|
|
if b.startswith(sig):
|
|
return ext
|
|
return ".png"
|
|
|
|
|
|
def download(url):
|
|
u = url.replace("http://", "https://")
|
|
for _ in range(3):
|
|
try:
|
|
with urllib.request.urlopen(urllib.request.Request(u, headers=UA), timeout=40) as r:
|
|
b = r.read()
|
|
if b and len(b) > 100:
|
|
return b
|
|
except Exception:
|
|
time.sleep(1.5)
|
|
return None
|
|
|
|
|
|
def fix(md_path):
|
|
"""md 파일: <img> → pic/ 저장 +  참조. 폴더는 명칭 폴더의 pic/."""
|
|
text = md_path.read_text(encoding="utf-8")
|
|
if "<img" not in text and "</img" not in text:
|
|
return 0
|
|
# 명칭 폴더 = md가 명칭폴더 바로 아래(현행_/교본시점_/CHANGELOG)면 그 폴더, 별표/첨부면 상위
|
|
folder = md_path.parent
|
|
picdir = folder / "pic"
|
|
seq = [0]
|
|
|
|
def repl(m):
|
|
attrs = m.group(1)
|
|
ms = SRC.search(attrs)
|
|
mi = IID.search(attrs)
|
|
if ms:
|
|
url = ms.group(1)
|
|
elif mi:
|
|
url = f"https://www.law.go.kr/LSW/flDownload.do?flSeq={mi.group(1)}"
|
|
else:
|
|
return m.group(0)
|
|
blob = download(url)
|
|
if not blob:
|
|
return "〔그림: 다운로드 실패〕"
|
|
seq[0] += 1
|
|
picdir.mkdir(parents=True, exist_ok=True)
|
|
fn = f"{md_path.stem}_img{seq[0]}{sniff(blob)}"
|
|
(picdir / fn).write_bytes(blob)
|
|
return f""
|
|
|
|
new = IMG.sub(repl, text)
|
|
# 부칙 등에서 여는 태그와 분리돼 남은 고아 닫는 태그 제거(단독 줄/인용줄 포함)
|
|
new = re.sub(r"^>?\s*</img>\s*$", ">", new, flags=re.M)
|
|
new = new.replace("</img>", "")
|
|
if new != text:
|
|
md_path.write_text(new, encoding="utf-8")
|
|
return seq[0]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
mds = (
|
|
list(ROOT.rglob("현행_*.md"))
|
|
+ list(ROOT.rglob("교본시점_*.md"))
|
|
+ list(ROOT.rglob("CHANGELOG.md"))
|
|
)
|
|
mds = [m for m in mds if "임도기술교본" not in str(m) and "_pipeline" not in str(m)]
|
|
tot = 0
|
|
for md in sorted(mds):
|
|
n = fix(md)
|
|
if n:
|
|
tot += n
|
|
print(f" {n}장 {md.parent.parent.name[:30]}/{md.name}")
|
|
print(f"\n법령 본문 이미지 {tot}장 → pic/ 로컬화")
|