Initial commit: 25 scripts Python sauvegardés
Date: 2026-08-28 11:43:34 EDT
This commit is contained in:
@@ -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()
|
||||
+68
@@ -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!")
|
||||
Reference in New Issue
Block a user