612c417024
gates / gates (push) Successful in 19s
Message bundles (internal/i18n) expanded into templates before parsing, one template set per language. Launcher, /backups, /apps/<slug> and the layout converted; household language setting, POST /settings/language, ?lang= override, report field. Parity test against fixtures captured from unconverted templates; copy gates read templates expanded; new i18n_missing_gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""D1 §10 Python emoji gate — Windows grep silently fails to match multibyte emoji (the D0
|
||
grep-gate zero was a false negative). This scans templates by Unicode codepoint.
|
||
|
||
Run from controller/: python scripts/emoji_gate.py
|
||
Exit 1 if any emoji/pictograph remains in web+setup templates.
|
||
"""
|
||
import io, os, sys, unicodedata
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
import i18n_bundle # noqa: E402
|
||
|
||
ROOTS = [
|
||
os.path.join("internal", "web", "templates"),
|
||
os.path.join("internal", "setup", "templates"),
|
||
]
|
||
|
||
# Codepoint ranges that count as "emoji / pictographs / dingbats" for UI-copy purposes.
|
||
# Deliberately does NOT flag Hungarian letters, typographic quotes/dashes, the middle dot (·),
|
||
# arrows used as affordances (→ ↗ ↻ ↑ ↓), the multiplication sign (×), or box-drawing.
|
||
def is_emoji(ch):
|
||
o = ord(ch)
|
||
ranges = [
|
||
(0x1F300, 0x1FAFF), # Misc symbols & pictographs, emoticons, transport, supplemental, symbols-ext
|
||
(0x2600, 0x26FF), # Misc symbols (☀ ⚙ ⚠ ☁ …)
|
||
(0x2700, 0x27BF), # Dingbats (✅ ✂ ✈ ✏ ✓? no — see allow)
|
||
(0x1F000, 0x1F0FF), # Mahjong/dominoes/cards
|
||
(0xFE00, 0xFE0F), # Variation selectors (emoji presentation)
|
||
(0x1F1E6, 0x1F1FF), # Regional indicators
|
||
]
|
||
if any(a <= o <= b for a, b in ranges):
|
||
return True
|
||
return False
|
||
|
||
# Dingbat codepoints that are legitimate UI glyphs (checkmarks/crosses used as plain text marks,
|
||
# not emoji). We keep these OUT of the ban — they render as monochrome text, not color emoji.
|
||
ALLOW = set("✓✗✔✘•●○■▶") # ✓ ✗ ✔ ✘ • ● ○ ■ ▶
|
||
|
||
|
||
def scan(path):
|
||
# v0.247.0: the copy of a converted template lives in the i18n bundle -- scan every language's
|
||
# expansion, so an emoji in a translation is caught where it would render.
|
||
hits = []
|
||
for lang in i18n_bundle.LANGS:
|
||
for h in _scan_text(i18n_bundle.read_template(path, lang)):
|
||
if h not in hits:
|
||
hits.append(h)
|
||
return hits
|
||
|
||
|
||
def _scan_text(text):
|
||
hits = []
|
||
for lineno, line in enumerate(text.split("\n"), 1):
|
||
for ch in line:
|
||
if ch in ALLOW:
|
||
continue
|
||
if is_emoji(ch):
|
||
try:
|
||
name = unicodedata.name(ch)
|
||
except ValueError:
|
||
name = "U+%04X" % ord(ch)
|
||
hits.append((lineno, ch, name))
|
||
return hits
|
||
|
||
|
||
def _html_files(root):
|
||
# R-421 (2026-09-01): AT ANY DEPTH. This was `os.listdir`, one level only. There are no
|
||
# template subdirectories today, so the gate was green and correct — and would have stayed
|
||
# green the moment anyone added `templates/partials/`, which is an ordinary thing to do.
|
||
# Measured: a planted template in a new partials/ directory passed every listdir-based gate
|
||
# and was caught by the two that already used os.walk. See AUDIT-gate-decoys-2026-09-01.md.
|
||
out = []
|
||
for dirpath, _dirs, names in os.walk(root):
|
||
for fn in sorted(names):
|
||
if fn.endswith('.html'):
|
||
out.append(os.path.join(dirpath, fn))
|
||
return sorted(out)
|
||
|
||
|
||
def main():
|
||
total = 0
|
||
for root in ROOTS:
|
||
for path in _html_files(root):
|
||
fn = os.path.relpath(path, root)
|
||
for lineno, ch, name in scan(path):
|
||
total += 1
|
||
print("%s:%d %s %s" % (fn, lineno, ch, name))
|
||
if total:
|
||
print("EMOJI GATE FAILED: %d emoji found" % total)
|
||
sys.exit(1)
|
||
print("emoji gate OK — no emoji in web/setup templates")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|