# -*- coding: utf-8 -*- """gate_core.py — 四個機械閘門的共用外殼(2026-07-28 抽出) script_gate / plan_gate / shorts_gate / interview_gate 長年是同一個三件套: gate_x(spec) -> (ok, {"fails": [...], "warns": [...], ...}) assert_x(spec) -> 不過就 raise AssertionError(訊息格式一致) _selftest() -> 逐條印 [PASS]/[FAIL],末行 SELFTEST GREEN/RED,回 0/1 本檔只收「外殼」(回傳結構 / raise 格式 / self-test 印法)。 **判定規則一律留在各 gate 自己的檔案**——規則不集中,才不會互相污染, 也才守得住「重構不是重寫」(既有 assert 的邏輯與訊息語意一字不改)。 API: report(fails, warns, **extra) -> dict # 標準回傳 dict raise_if_failed(rep, label, title) # "[label] title:\n - f1\n - f2" make_assert(gate_fn, label_fn, title, print_warns=False, post=None) -> assert_x selftest_runner(cases, width=50, list_fails=False) -> 0/1 cp950 安全:本檔 print 只有 ASCII 標記;不做任何檔案 I/O。 """ from __future__ import annotations # ────────────────────────────────────────────── 回傳結構 def report(fails=None, warns=None, **extra) -> dict: """建構標準 gate 回傳 dict:ok/fails/warns(+ 各 gate 自己的欄位)。""" fails = list(fails or []) warns = list(warns or []) rep = {"ok": not fails, "fails": fails, "warns": warns} rep.update(extra) return rep # ────────────────────────────────────────────── assert 包裝 def raise_if_failed(rep: dict, label: str, title: str) -> None: """fails 非空就 raise,訊息格式全系統統一: [