#!/usr/bin/env python3 """ migrate_fa_to_lucide.py — Replace FontAwesome with Lucide components. Usage: python3 scripts/migrate_fa_to_lucide.py src/routes/events/[event_id]/\(pres_mgmt\)/ Skips content inside HTML comments. Adds/merges lucide-svelte imports. """ import re import sys import os from pathlib import Path # ── FA icon → Lucide component name ───────────────────────────────────────── FA_TO_LUCIDE = { 'fa-spinner': 'LoaderCircle', 'fa-cog': 'LoaderCircle', # only when fa-spin 'fa-sync-alt': 'RefreshCw', 'fa-times': 'X', 'fa-exclamation-triangle': 'TriangleAlert', 'fa-check': 'Check', 'fa-check-circle': 'CheckCircle', 'fa-plus': 'Plus', 'fa-minus': 'Minus', 'fa-save': 'Save', 'fa-edit': 'Pencil', 'fa-eye': 'Eye', 'fa-eye-slash': 'EyeOff', 'fa-toggle-on': 'ToggleRight', 'fa-toggle-off': 'ToggleLeft', 'fa-star-of-life': 'Asterisk', 'fa-id-card': 'IdCard', 'fa-paper-plane': 'Send', 'fa-map-marker-alt': 'MapPin', 'fa-file-alt': 'FileText', 'fa-envelope': 'Mail', 'fa-book': 'BookOpen', 'fa-angle-right': 'ChevronRight', 'fa-user': 'User', 'fa-tasks': 'ListChecks', 'fa-plane': 'Plane', 'fa-list': 'List', 'fa-link': 'Link', 'fa-file-archive': 'Archive', 'fa-comment-dots': 'MessageCircle', 'fa-chevron-up': 'ChevronUp', 'fa-chevron-down': 'ChevronDown', 'fa-camera': 'Camera', 'fa-barcode': 'Barcode', 'fa-upload': 'Upload', 'fa-search': 'Search', 'fa-mail-bulk': 'Mails', 'fa-laptop-code': 'Laptop', 'fa-copy': 'Copy', 'fa-user-tag': 'Tag', 'fa-user-secret': 'UserRound', 'fa-users': 'Users', 'fa-user-circle': 'CircleUser', 'fa-sort': 'ArrowUpDown', 'fa-question': 'HelpCircle', 'fa-map-marked': 'MapPinned', 'fa-list-ol': 'ListOrdered', 'fa-laptop': 'Laptop', 'fa-info': 'Info', 'fa-building': 'Building2', 'fa-user-slash': 'UserX', 'fa-user-check': 'UserCheck', 'fa-unlink': 'Unlink', 'fa-star': 'Star', 'fa-search-location': 'MapPin', 'fa-remove-format': 'RemoveFormatting', 'fa-qrcode': 'QrCode', 'fa-key': 'Key', 'fa-heartbeat': 'HeartPulse', 'fa-hat-wizard': 'Wand2', 'fa-fingerprint': 'Fingerprint', 'fa-file-csv': 'FileSpreadsheet', 'fa-file': 'File', 'fa-clock': 'Clock', 'fa-clipboard-list': 'ClipboardList', 'fa-chart-line': 'TrendingUp', 'fa-chalkboard-teacher': 'Presentation', 'fa-calendar-day': 'CalendarDays', 'fa-bell-slash': 'BellOff', 'fa-bell': 'Bell', } # Skip modifiers — not real icon names FA_MODIFIERS = {'fas', 'far', 'fab', 'fa-spin', 'fa-fw', 'fa-lg', 'fa-2x', 'fa-sm'} # ── Pattern: ─────────── # [^>]* matches newlines too (character class, not dot) SPAN_RE = re.compile( r']*>\s*' ) # ── Comment splitter ───────────────────────────────────────────────────────── COMMENT_RE = re.compile(r'()') # ── Lucide import line ──────────────────────────────────────────────────────── IMPORT_RE = re.compile(r"import\s*\{([^}]+)\}\s*from\s*'lucide-svelte'\s*;?") def parse_fa_class(class_str): """Return (icon_name, extra_classes, has_spin) from a FA class string.""" parts = class_str.split() icon_name = None has_spin = 'fa-spin' in parts extra = [] for p in parts: if p in FA_MODIFIERS: continue elif p.startswith('fa-'): if icon_name is None: icon_name = p # first real icon name wins else: extra.append(p) return icon_name, extra, has_spin def replace_span(m): """Regex sub callback: replace a single FA span with a Lucide component.""" icon_name, extra, has_spin = parse_fa_class(m.group(1)) if icon_name is None: return m.group(0) lucide = FA_TO_LUCIDE.get(icon_name) if lucide is None: print(f' ⚠ no mapping for {icon_name!r} — left as-is', file=sys.stderr) return m.group(0) classes = extra[:] if has_spin: classes.append('animate-spin') class_attr = f' class="{" ".join(classes)}"' if classes else '' return f'<{lucide} size="1em"{class_attr} />' def process_content(content): """Replace FA spans, skip HTML comments. Return (new_content, used_icons).""" used_icons = set() def track_and_replace(m): result = replace_span(m) if result != m.group(0): # Extract lucide name from result lucide_name = result.split()[0].lstrip('<') used_icons.add(lucide_name) return result # Split by comments; only process non-comment segments parts = COMMENT_RE.split(content) new_parts = [] for part in parts: if part.startswith('