#!/usr/bin/env python3 """ Normaliza nombres de archivos multimedia a snake_case y actualiza referencias. Uso: python3 scripts/normalize-media-filenames.py --apply python3 scripts/normalize-media-filenames.py --dry-run """ import argparse import os import re import unicodedata from pathlib import Path MEDIA_EXTS = {'.png', '.jpg', '.jpeg', '.webp', '.svg', '.gif', '.mp4', '.webm', '.mov', '.avi', '.mkv'} TEXT_EXTS = {'.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', '.sass', '.less', '.md', '.mdx', '.json', '.txt'} SKIP_DIRS = {'node_modules', '.git', 'dist', 'build', '.next', 'coverage'} def to_snake_case(filename: str) -> str: base, ext = os.path.splitext(filename) base = unicodedata.normalize('NFKD', base).encode('ascii', 'ignore').decode('ascii') base = base.lower() base = re.sub(r'[^a-z0-9]+', '_', base).strip('_') base = re.sub(r'_+', '_', base) return base + ext.lower() def iter_files(root: Path): for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] for filename in filenames: yield Path(dirpath) / filename def main(): parser = argparse.ArgumentParser() parser.add_argument('--apply', action='store_true', help='Aplicar cambios') parser.add_argument('--dry-run', action='store_true', help='Solo mostrar cambios') args = parser.parse_args() if not args.apply and not args.dry_run: args.dry_run = True root = Path(__file__).resolve().parents[1] media_files = [p for p in iter_files(root) if p.suffix.lower() in MEDIA_EXTS] rename_map = {} for path in media_files: new_name = to_snake_case(path.name) if new_name != path.name: new_path = path.with_name(new_name) if new_path.exists() and new_path.resolve() != path.resolve(): stem, ext = os.path.splitext(new_name) i = 2 while True: candidate = path.with_name(f"{stem}_{i}{ext}") if not candidate.exists(): new_path = candidate break i += 1 rename_map[path] = new_path # Print plan print(f"Archivos a renombrar: {len(rename_map)}") for old, new in rename_map.items(): print(f"{old} -> {new}") if args.dry_run: return # Apply renames for old, new in rename_map.items(): old.rename(new) # Update references repl_pairs = [] for old, new in rename_map.items(): repl_pairs.append((str(old), str(new))) repl_pairs.append((old.name, new.name)) repl_pairs.sort(key=lambda x: len(x[0]), reverse=True) for path in iter_files(root): if path.suffix.lower() not in TEXT_EXTS: continue try: text = path.read_text(encoding='utf-8', errors='ignore') except Exception: continue new_text = text for old, new in repl_pairs: if old in new_text: new_text = new_text.replace(old, new) if new_text != text: path.write_text(new_text, encoding='utf-8') if __name__ == '__main__': main()