#!/usr/bin/env python3 """Phase A sweep: mechanical import + token rewrites toward gpui-kit. Run from repo root: python3 script/migration/sweep.py Idempotent; only touches consumer crates. """ import re import sys from pathlib import Path FILES = [ "crates/settings/src/lib.rs", "crates/nip4e/src/lib.rs", "crates/nip29_ui/src/lib.rs", "crates/nip29_ui/src/message.rs", "crates/nip29_ui/src/details.rs", "crates/nip29_ui/src/invite.rs", "crates/nip29_ui/src/metadata.rs", "crates/nip29_ui/src/util.rs", "crates/chat_ui/src/lib.rs", "crates/chat_ui/src/text.rs", "crates/chat_ui/src/actions.rs", "crates/chat_ui/src/file.rs", "crates/workspace/src/lib.rs", "crates/workspace/src/dialogs/import.rs", "crates/workspace/src/dialogs/join_group.rs", "crates/workspace/src/dialogs/new_chat.rs", "crates/workspace/src/dialogs/new_group.rs", "crates/workspace/src/dialogs/profile.rs", "crates/workspace/src/dialogs/relays.rs", "crates/workspace/src/dialogs/restore.rs", "crates/workspace/src/dialogs/screening.rs", "crates/workspace/src/dialogs/settings.rs", "crates/workspace/src/panels/backup.rs", "crates/workspace/src/panels/browse.rs", "crates/workspace/src/panels/contact_list.rs", "crates/workspace/src/panels/greeter.rs", "crates/workspace/src/panels/inbox.rs", "crates/workspace/src/sidebar/group_view.rs", "crates/workspace/src/sidebar/header.rs", "crates/workspace/src/sidebar/inbox.rs", "crates/workspace/src/sidebar/list.rs", "crates/workspace/src/sidebar/mod.rs", "crates/workspace/src/sidebar/onboarding.rs", "crates/workspace/src/sidebar/rows.rs", "crates/workspace/src/sidebar/search.rs", ] KIT = "gpui_kit::component" # ui:: items that move to the kit's component root KIT_ITEMS = { "Icon", "IconName", "Sizable", "Size", "Disableable", "Selectable", "StyledExt", "h_flex", "v_flex", "WindowExt", "Notification", "NotificationType", "Button", "ButtonVariants", "ButtonVariant", "ButtonCustomVariant", "IconPosition", "Switch", "Avatar", "GroupBox", "GroupBoxVariants", "GroupBoxVariant", "Tab", "TabBar", "Scrollbar", "ScrollableElement", "Tooltip", "Input", "InputState", "Textarea", "TextareaState", "InputEvent", "PopupMenu", "PopupMenuItem", "DropdownMenu", "ContextMenu", "Dialog", "DialogButtonProps", "AlertDialog", "ActiveTheme", "Theme", "ThemeMode", "Spinner", "Kbd", "TitleBar", "DockArea", "DockLayout", "DockPlacement", "Panel", "PanelEvent", "PanelHandle", "ClosePanel", "Divider", "Separator", } # ui:: items that stay in the coop ui crate (utilities) UI_ITEMS = { "TextStyle", "Typeset", "Metrics", "base_text_size", "DEFAULT_BASE_TEXT_SIZE", "SPACE", "CONTENT_MARGIN", "EDGE", "BUTTON_HEIGHT", "CONTROL_HEIGHT_SMALL", "BASE_RADIUS", "bubble_radius", "surface_radius", "panel_radius", "button_radius", "control_radius", "inset_radius", "TABBAR_HEIGHT", "SIDEBAR_WIDTH", "TRAFFIC_LIGHT_PADDING", "CLIENT_SIDE_DECORATION_ROUNDING", "CLIENT_SIDE_DECORATION_SHADOW", "CLIENT_SIDE_DECORATION_BORDER", "title_bar_drag_handlers", } TOKEN_RENAMES = [ # order matters: longest first ("surface_raised_hover", "secondary_hover"), ("surface_raised", "secondary"), ("surface_dialog", "popover"), ("surface_overlay", "popover"), ("surface_card", "muted"), ("surface", "sidebar"), ("element_hover", "accent"), ("element_active", "list_active"), ("border_faint", "border"), ("border_strong", "border"), ("text_muted", "muted_foreground"), ("text_faint", "muted_foreground"), ("text_dim", "muted_foreground"), ("on_solid", "primary_foreground"), ("solid", "primary"), ("accent_strong", "primary"), ("accent", "primary"), ("danger_strong", "button_danger"), ("danger_muted", "danger"), ("warning_muted", "warning.opacity(0.15)"), ("drop_line", "drag_border"), ("input_bg", "input"), ("bg", "background"), ("text", "foreground"), ] def rewrite_use(line: str): """Rewrite one `use ui::...;` or `use theme::...;` line. Returns new lines.""" s = line.strip() if s.startswith("use ui::{") and s.endswith(";"): inner = s[len("use ui::{"):-2] items = [i.strip() for i in inner.split(",") if i.strip()] kit, ui = [], [] for item in items: name = item.split(" as ")[0].strip() (ui if name in UI_ITEMS else kit).append(item) out = [] if kit: out.append(f"use {KIT}::{{{', '.join(sorted(set(kit)))}}};") if ui: out.append(f"use ui::{{{', '.join(sorted(set(ui)))}}};") return out if s.startswith("use ui::") and s.endswith(";"): path = s[4:-1] item = path.split("::")[-1] if item in UI_ITEMS: return [f"use ui::{item};"] return [f"use {KIT}::{{{item}}};"] if s.startswith("use theme::{") and s.endswith(";"): inner = s[len("use theme::{"):-2] items = [i.strip() for i in inner.split(",") if i.strip()] kit, ui = [], [] for item in items: name = item.split("::")[-1] if name == "ActiveTheme" or name in KIT_ITEMS: kit.append(name) else: ui.append(name) out = [] if kit: out.append(f"use {KIT}::{{{', '.join(sorted(set(kit)))}}};") if ui: out.append(f"use ui::{{{', '.join(sorted(set(ui)))}}};") return out if s.startswith("use theme::") and s.endswith(";"): name = s[11:-1].split("::")[-1] if name == "ActiveTheme" or name in KIT_ITEMS: return [f"use {KIT}::{name};"] return [f"use ui::{name};"] return [line] def process(path: Path): src = path.read_text() lines = src.split("\n") out = [] for line in lines: s = line.strip() if s.startswith("use ui::") or s.startswith("use theme::"): out.extend(rewrite_use(line)) else: out.append(line) src = "\n".join(out) # theme token renames, applied to `theme().x` reads only for old, new in TOKEN_RENAMES: src = re.sub( re.escape("theme()." + old) + r"(?![a-z_0-9])", "theme()." + new, src, ) # method/type renames src = src.replace("WindowExtension", "WindowExt") src = src.replace(".clear_notification_by_id::<", ".remove_notification::<") src = src.replace(".clear_notification::<", ".remove_notification::<") src = src.replace("with_kind(NotificationKind::", "with_type(NotificationType::") src = src.replace("NotificationKind", "NotificationType") path.write_text(src) for f in FILES: p = Path(f) if p.exists(): process(p) print("swept", f) else: print("MISSING", f, file=sys.stderr)