Files
decePubClient/tools/css/motion.py
T

71 lines
2.3 KiB
Python
Raw Normal View History

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)