import re import sys from pathlib import Path MEDIA = re.compile(r'@media([^{;]*prefers-reduced-motion[^{;]*)\{') MOTION = re.compile(r'\(\s*prefers-reduced-motion\s*:\s*(reduce|no-preference)\s*\)') def block_end(css, start): depth, i, quote = 1, start, None while depth: ch = css[i] if quote: if ch == '\\': i += 1 elif ch == quote: quote = None elif ch in '"\'': quote = ch elif ch == '{': depth += 1 elif ch == '}': depth -= 1 i += 1 return i def without_motion(prelude): if ',' in prelude or re.search(r'\b(or|not|only)\b', prelude): raise ValueError(f'@media{prelude}') parts = [part.strip() for part in re.split(r'\band\b', prelude)] wanted = [MOTION.fullmatch(part).group(1) for part in parts if MOTION.fullmatch(part)] if len(wanted) != 1 or any('prefers-reduced-motion' in part and not MOTION.fullmatch(part) for part in parts): raise ValueError(f'@media{prelude}') return wanted[0], ' and '.join(part for part in parts if not MOTION.fullmatch(part)) def everyone_moves(css): out, i = [], 0 while match := MEDIA.search(css, i): out.append(css[i:match.start()]) end = block_end(css, match.end()) wanted, rest = without_motion(match.group(1)) if wanted == 'no-preference': inner = everyone_moves(css[match.end():end - 1]) if not rest and inner.strip() and not inner.rstrip().endswith((';', '}')): inner += ';' out.append(f'@media {rest}{{{inner}}}' if rest else inner) i = end out.append(css[i:]) return ''.join(out) failed = False for name in sys.argv[1:]: path = Path(name) before = path.read_text(encoding='utf-8') try: after = everyone_moves(before) except ValueError as query: print(f'{name}: cannot rewrite {query}', file=sys.stderr) failed = True continue if 'prefers-reduced-motion' in after: print(f'{name}: a prefers-reduced-motion query is still there', file=sys.stderr) failed = True continue if after != before: path.write_text(after, encoding='utf-8') print(f'{name}: motion now plays for everyone') sys.exit(1 if failed else 0)