- 엔진 — 로직 줄의 묶음 `끝수`{대상, 자리, 방법}를 계산 끝에 붙임(비목 합마다 끊고 계 = 그 합 · 돈 아닌 로직은 결과) · 글자 끝수는 설명 그대로 · 검사가 묶음 모양을 봄
- 서버 — `GET /sections`(원문 본문 절 목록 + 그 절의 표·로직 수) · `/tables` 의 `section`(13-4 가 13-40 에 안 걸림) · `GET /table/options`(조건 값만) · `/logics` 의 `owner`
- 자체 로직 — `POST /logic/new|copy|edit|delete` · 파일 `로직_자체.json` · 키 GX 서버 발급(원문번호 같아도 줄마다 다른 키) · 정본은 403 · 저장 안 한 초안은 `/calc` 에 `key` "" 로
- 저장소가 700줄을 넘어 쓰기 모양(`keep_shape`·`dump`)을 `M01_MasterData_Store_Shape.py` 로 가름
- 시험 `resources/tester/test_m01_make.py` 8건 통과 · 기존 M01 시험 25건 통과 · 일괄 시험 계산 1,291 그대로
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
"""M01 저장 글 모양 — 고친 줄만 새로 쓰고 나머지는 옛 글 그대로(`_틀.md` 2장 · 들여쓰기 2칸).
|
|
|
|
`M01_MasterData_Store.save` 가 씀 — 파일을 통째로 다시 쓰면 손으로 맞춘 줄 모양이 사라짐.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from decimal import Decimal
|
|
|
|
|
|
def _atom(v) -> str:
|
|
if isinstance(v, Decimal):
|
|
return str(v)
|
|
return json.dumps(v, ensure_ascii=False)
|
|
|
|
|
|
def _flat(v, tight: bool = False, inner: bool | None = None) -> str:
|
|
"""한 줄 글 — tight = 바깥 묶음을 붙여 씀 · inner = 안쪽 묶음을 붙여 씀(None 이면 바깥과 같음)."""
|
|
inner = tight if inner is None else inner
|
|
if isinstance(v, dict):
|
|
if not v:
|
|
return "{}"
|
|
body = ", ".join(f"{_atom(k)}: {_flat(x, inner)}" for k, x in v.items())
|
|
return "{" + body + "}" if tight else "{ " + body + " }"
|
|
if isinstance(v, list):
|
|
return "[" + ", ".join(_flat(x, inner) for x in v) + "]"
|
|
return _atom(v)
|
|
|
|
|
|
def _skip(text: str, pos: int) -> int:
|
|
while text[pos] in " \t\r\n":
|
|
pos += 1
|
|
return pos
|
|
|
|
|
|
def keep_shape(old_text: str, before: dict, after: dict, key: str) -> str | None:
|
|
"""고친 요소만 새로 씀 — 안 바뀐 요소는 옛 글 그대로(한 줄 요소는 한 줄 · 들여쓰기 · 줄바꿈).
|
|
머리가 바뀌었거나 옛 글을 못 읽으면 None(통째로 dump)."""
|
|
head = lambda d: {k: v for k, v in d.items() if k != key} # noqa: E731
|
|
old_items, new_items = before.get(key) or [], after.get(key) or []
|
|
if head(before) != head(after) or not old_items or not new_items:
|
|
return None
|
|
for m in re.finditer(rf'"{key}"\s*:\s*\[', old_text): # 머리 속 같은 이름 칸은 건너뜀
|
|
spans, pos, dec = [], m.end(), json.JSONDecoder()
|
|
try:
|
|
while old_text[pos := _skip(old_text, pos)] != "]":
|
|
_, end = dec.raw_decode(old_text, pos)
|
|
spans.append((pos, end))
|
|
pos = _skip(old_text, end)
|
|
pos += old_text[pos] == ","
|
|
except (IndexError, ValueError):
|
|
return None
|
|
if len(spans) == len(old_items):
|
|
break
|
|
else:
|
|
return None
|
|
nl = "\r\n" if "\r\n" in old_text else "\n"
|
|
lead = old_text[m.end() : spans[0][0]]
|
|
indent = len(lead) - len(lead.rstrip(" "))
|
|
olds = {str(r.get("키")): (r, old_text[s:e]) for r, (s, e) in zip(old_items, spans)}
|
|
shape, parts = old_text[spans[0][0] : spans[0][1]], []
|
|
for row in new_items:
|
|
was, text = olds.get(str(row.get("키")), (None, None))
|
|
shape = text or shape
|
|
if was == row:
|
|
parts.append(text)
|
|
elif "\n" not in shape:
|
|
tight = shape.startswith('{"')
|
|
parts.append(_flat(row, tight, tight or ': {"' in shape))
|
|
else:
|
|
parts.append(dump(row, indent, expand=True).replace("\n", nl))
|
|
out = old_text[: m.end()] + lead + ("," + lead).join(parts) + old_text[spans[-1][1] :]
|
|
same = json.loads(out, parse_float=Decimal, parse_int=Decimal) == after
|
|
return out if same else None
|
|
|
|
|
|
def dump(v, indent: int = 0, width: int = 160, expand: bool = False) -> str:
|
|
"""UTF-8 · 들여쓰기 2칸 · 한 줄에 들어가는 묶음은 한 줄로(expand 면 첫 층은 펼침)."""
|
|
flat = _flat(v)
|
|
if (
|
|
not isinstance(v, (dict, list))
|
|
or not v
|
|
or (not expand and indent and len(flat) + indent <= width)
|
|
):
|
|
return flat + ("" if indent else "\n")
|
|
pad = " " * (indent + 2)
|
|
if isinstance(v, dict):
|
|
body = [f"{pad}{_atom(k)}: {dump(x, indent + 2, width)}" for k, x in v.items()]
|
|
text = "{\n" + ",\n".join(body) + "\n" + " " * indent + "}"
|
|
else:
|
|
text = (
|
|
"[\n"
|
|
+ ",\n".join(pad + dump(x, indent + 2, width) for x in v)
|
|
+ "\n"
|
|
+ " " * indent
|
|
+ "]"
|
|
)
|
|
return text + ("" if indent else "\n")
|