# -*- coding: utf-8 -*- """STMATE 프로젝트 원천 읽기 — STC/폴더 → (대문자 이름 → 바이트) · 보조 표(KONGJONG·COSTA·QCODE) · 설정 전수. 근거: STC_컨테이너_구조.md §2·§5 · 02_데이터셋과_파일형식.md §3 · 25_설정키_전수와_식템플릿.md §1 · 20_설정파일과_보고서_카탈로그.md §2(Start8x.Ini) """ from __future__ import annotations import pathlib from dbf_memo import SixMemo, ini_items from dbf_rw import DbfTable from stc_archive import files_of, read_entries class Source: """원천 한 곳 — files(대문자 이름 → 바이트) · names(대문자 → 원래 이름) · entries(STC 머리 값).""" def __init__(self, path: str): p = pathlib.Path(path) if not p.exists(): raise FileNotFoundError(path) self.path = p self.entries: list[dict] = [] self.names: dict[str, str] = {} if p.is_file() and p.suffix.upper() == ".STC": self.entries = read_entries(p.read_bytes()) self.files = files_of(self.entries) self.names = {pathlib.Path(e["name"]).name.upper(): e["name"] for e in self.entries} elif p.is_dir(): self.files = {} for fp in sorted(p.iterdir()): if fp.is_file(): self.files[fp.name.upper()] = fp.read_bytes() self.names[fp.name.upper()] = fp.name else: raise ValueError("지원하지 않는 경로 형식") self.kind = "stc" if self.entries else "folder" def table_rows(b: bytes) -> list[dict]: """평문 DBF 행(삭제 제외) — 암호 표(0x06)는 빈 목록.""" if not b or b[0] == 0x06: return [] t = DbfTable(b) return [dict(t.row(i), _recno=i) for i in range(len(t)) if not t.deleted(i)] def ini_sections(text: str) -> dict[str, dict[str, str]]: out: dict[str, dict[str, str]] = {} for sec, k, v in ini_items(text): out.setdefault(sec, {})[k] = v return out def prjlst_smt_all(files: dict[str, bytes]) -> list[dict]: """PRJLST 행마다 PRJ_MEMO 본문 전체(섹션별 키=값) — PCODE 로 BOQ1n 과 짝.""" if "PRJLST.DBF" not in files: return [] t = DbfTable(files["PRJLST.DBF"]) memo = SixMemo(files.get("PRJLST.SMT")) out = [] for i in range(len(t)): ptr = t.raw(i, "PRJ_MEMO") if "PRJ_MEMO" in t.fields else b"" text = memo.read(ptr).decode("cp949", "replace") out.append( { "recno": i, "pcode": t.get(i, "PCODE") if "PCODE" in t.fields else "", "sections": ini_sections(text), "key_count": len(ini_items(text)), } ) return out def qcode_rows(files: dict[str, bytes]) -> list[dict]: """QCODE.DBF 행 + IFORM·XCODE 메모 본문(QCODE.SMT).""" b = files.get("QCODE.DBF") if not b: return [] t = DbfTable(b) memo = SixMemo(files.get("QCODE.SMT")) rows = [] for i in range(len(t)): if t.deleted(i): continue r = dict(t.row(i), _recno=i) for f in ("IFORM", "XCODE"): if f in t.fields: r[f] = memo.read(t.raw(i, f)).decode("cp949", "replace") rows.append(r) return rows def extra_tables(files: dict[str, bytes]) -> dict: """api_load 덧붙임 — KONGJONG 공종 · COSTA(암호면 건수만) · QCODE · PRJLST.SMT 전수 · Start8x.Ini 섹션.""" costa = files.get("COSTA.DBF", b"") ini = files.get("START8X.INI") or files.get("STINI.MEM") or b"" return { "kongjong_rows": table_rows(files.get("KONGJONG.DBF", b"")), "costa_rows": table_rows(costa), "costa_count": len(DbfTable(costa)) if costa else 0, "qcode_rows": qcode_rows(files), "prjlst_smt_all": prjlst_smt_all(files), "start_ini_sections": ini_sections(ini.decode("cp949", "replace")), }