101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
#!/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()
|