Initial commit: 25 scripts Python sauvegardés

Date: 2026-08-28 11:43:34 EDT
This commit is contained in:
2026-08-28 11:43:34 -04:00
parent 0ba9701e0f
commit 994ea86d47
204 changed files with 70104 additions and 2 deletions
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Audit Alpine.js templates for semicolons in event handler expressions.
Alpine evaluates expressions via `return <expr>`. Semicolons inside
event handler expressions terminate the return prematurely:
Expression: "a = 1; b = 2"
→ return a = 1; b = 2
→ SyntaxError: Unexpected token ';'
When this happens in a PARENT component, ALL child x-data components
fail to initialize → cascading ReferenceError for every variable.
Usage:
python3 scripts/audit-semicolons.py app/templates/
Returns exit code 1 if any offending ; are found.
"""
import re
import sys
from pathlib import Path
def audit_file(filepath: Path) -> int:
with open(filepath) as f:
content = f.read()
count = 0
for m in re.finditer(r'@\w+(?:\.\w+)*="([^"]*)"', content):
val = m.group(1)
if ";" in val and not val.startswith("$nextTick"):
count += 1
line_no = content[: m.start()].count("\n") + 1
print(f" {filepath}:{line_no} {val[:100]}")
return count
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <template_file_or_dir> [more files...]")
sys.exit(1)
total = 0
for arg in sys.argv[1:]:
p = Path(arg)
files = [p] if p.is_file() else sorted(p.rglob("*.html"))
for f in files:
n = audit_file(f)
if n:
print(f" → {n} issue(s) in {f.name}")
total += n
if total:
print(f"\nTOTAL: {total} semicolon issue(s) across all templates")
print("Fix: replace ';' with ',' (comma operator) in Alpine @event expressions")
sys.exit(1)
else:
print("CLEAN — no semicolons in Alpine expressions")
sys.exit(0)
if __name__ == "__main__":
main()
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""WCAG AA contrast ratio verification for period palettes."""
def srgb_to_linear(c):
s = c / 255.0
return s / 12.92 if s <= 0.04045 else ((s + 0.055) / 1.055) ** 2.4
def relative_luminance(hex_color):
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
return 0.2126 * srgb_to_linear(r) + 0.7152 * srgb_to_linear(g) + 0.0722 * srgb_to_linear(b)
def contrast_ratio(h1, h2):
l1, l2 = relative_luminance(h1), relative_luminance(h2)
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
def rgb_distance(h1, h2):
r1, g1, b1 = int(h1[0:2], 16), int(h1[2:4], 16), int(h1[4:6], 16)
r2, g2, b2 = int(h2[0:2], 16), int(h2[2:4], 16), int(h2[4:6], 16)
return ((r1-r2)**2 + (g1-g2)**2 + (b1-b2)**2) ** 0.5
# ============ EDIT THESE FOR YOUR PALETTE ============
DAY_TEXT = "3D2B1F"
NIGHT_TEXT = "D0D8E0"
periods = {
"DEJEUNER": ("FFA726", "FFE0B2", DAY_TEXT),
"DOUCHE": ("4DB6AC", "E0F2F1", DAY_TEXT),
"AVANT-MIDI": ("AED581", "F1F8E9", DAY_TEXT),
"DINER": ("FFD54F", "FFF9C4", DAY_TEXT),
"APRES-MIDI": ("FFB74D", "FFE0B2", DAY_TEXT),
"SOUPER": ("FF8A65", "FFCCBC", DAY_TEXT),
"SOIREE": ("311B92", "4527A0", NIGHT_TEXT),
"DODO": ("1A237E", "0D1520", NIGHT_TEXT),
}
period_order = ["DEJEUNER", "DOUCHE", "AVANT-MIDI", "DINER", "APRES-MIDI", "SOUPER", "SOIREE", "DODO"]
print("=" * 75)
print(f"{'Period':<14} {'Text':<8} {'Bg Top':<10} {'Ratio':>6} {'Bg Bot':<10} {'Ratio':>6} {'WCAG':>7}")
print("=" * 75)
all_pass = True
for name, (top, bottom, text) in periods.items():
r_top = contrast_ratio(text, top)
r_bot = contrast_ratio(text, bottom)
aa_top = "AA+ " if r_top >= 4.5 else "AA-lg" if r_top >= 3.0 else "FAIL!"
aa_bot = "AA+ " if r_bot >= 4.5 else "AA-lg" if r_bot >= 3.0 else "FAIL!"
if r_top < 3.0 or r_bot < 3.0:
all_pass = False
print(f"{name:<14} #{text} #{top} {r_top:>5.1f}:1 {aa_top} #{bottom} {r_bot:>5.1f}:1 {aa_bot}")
print()
print("=" * 75)
print("Adjacent period distinction (RGB distance)")
print("=" * 75)
for i in range(len(period_order) - 1):
curr, next_p = period_order[i], period_order[i+1]
_, curr_bot, _ = periods[curr]
next_top, _, _ = periods[next_p]
dist = rgb_distance(curr_bot, next_top)
l1, l2 = relative_luminance(curr_bot), relative_luminance(next_top)
lum_diff = abs(l1 - l2)
status = "OK" if dist > 80 or lum_diff > 0.15 else "PROCHE" if dist > 40 else "TROP PROCHE!"
print(f" {curr:<12} bas #{curr_bot} → {next_p:<12} haut #{next_top} dist={dist:5.0f} Δlum={lum_diff:.3f} [{status}]")
print()
print("WCAG AA (4.5:1 normal / 3:1 large):", "ALL PASS" if all_pass else "ISSUES FOUND!")