Compare commits

...

1 Commits

Author SHA1 Message Date
Elijah
ce1886d413 reflex rename 2025-01-20 16:02:25 +00:00
2 changed files with 124 additions and 0 deletions

View File

@ -551,6 +551,22 @@ def deploy(
) )
@cli.command()
def rename(
new_name: str = typer.Argument(
help="The new name of the app.",
),
loglevel: constants.LogLevel = typer.Option(
config.loglevel, help="The log level to use."
),
):
"""Rename the app."""
from reflex.utils import prerequisites
new_name = prerequisites.validate_app_name(new_name)
prerequisites.rename_app(new_name)
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.") cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.") cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
cli.add_typer( cli.add_typer(

View File

@ -2,11 +2,13 @@
from __future__ import annotations from __future__ import annotations
import ast
import contextlib import contextlib
import dataclasses import dataclasses
import functools import functools
import importlib import importlib
import importlib.metadata import importlib.metadata
import importlib.util
import json import json
import os import os
import platform import platform
@ -23,6 +25,7 @@ from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import Callable, List, Optional from typing import Callable, List, Optional
import astor
import httpx import httpx
import typer import typer
from alembic.util.exc import CommandError from alembic.util.exc import CommandError
@ -427,6 +430,111 @@ def validate_app_name(app_name: str | None = None) -> str:
return app_name return app_name
class ImportRenamer(ast.NodeTransformer):
"""Rename imports in a tree."""
def __init__(self, old_name, new_name):
"""Initialize the ImportRenamer."""
self.old_name = old_name
self.new_name = new_name
def visit_Import(self, node):
"""Rename imports of the form `import foo`."""
for alias in node.names:
if alias.name == self.old_name:
alias.name = self.new_name
return node
def visit_ImportFrom(self, node):
"""Rename imports of the form `from foo import bar`."""
if node.module == self.old_name:
node.module = self.new_name
return node
def visit_Assign(self, node):
"""Handle assignments like `config = rx.Config(app_name='foo')`."""
if (
isinstance(node.targets[0], ast.Name)
and node.targets[0].id == "config"
and isinstance(node.value, ast.Call)
and isinstance(node.value.func, ast.Attribute)
and node.value.func.attr == "Config"
):
for kw in node.value.keywords:
if kw.arg == "app_name" and isinstance(kw.value, ast.Constant):
if kw.value.value == self.old_name:
kw.value = ast.Constant(value=self.new_name)
# Handle positional arguments
if node.value.args and isinstance(node.value.args[0], ast.Constant):
if node.value.args[0].value == self.old_name:
node.value.args[0] = ast.Constant(value=self.new_name)
return node
def rename_imports_and_app_name_in_file(file_path, old_name, new_name):
"""Rename imports and update the app_name in rxconfig.py."""
file_path = Path(file_path)
content = file_path.read_text()
tree = ast.parse(content)
transformer = ImportRenamer(old_name, new_name)
new_tree = transformer.visit(tree)
ast.fix_missing_locations(new_tree)
modified_content = astor.to_source(new_tree)
file_path.write_text(modified_content)
def process_directory(directory, old_name, new_name, exclude_dirs=None):
"""Process all Python files in a directory, excluding specified directories."""
exclude_dirs = exclude_dirs or []
directory = Path(directory)
for root in directory.rglob("*.py"):
if not any(root.parts[i] in exclude_dirs for i in range(len(root.parts))):
rename_imports_and_app_name_in_file(root, old_name, new_name)
def rename_path_up_tree(full_path, old_name, new_name):
"""Rename all instances of `old_name` in the path (file and directories) to `new_name`."""
current_path = Path(full_path)
new_path = None
while True:
directory, base = current_path.parent, current_path.name
if old_name in base:
new_base = base.replace(old_name, new_name)
new_path = directory / new_base
current_path.rename(new_path)
current_path = new_path
else:
new_path = current_path
# Stop if we've reached the root package
if old_name not in directory.name:
break
# Move up the directory tree
current_path = directory
return new_path
def rename_app(app_name: str):
"""Rename the app directory."""
config = get_config()
process_directory(
Path.cwd(), config.app_name, app_name, exclude_dirs=["assets", ".web"]
)
full_path = importlib.util.find_spec(config.module).origin
rename_path_up_tree(full_path, config.app_name, app_name)
def create_config(app_name: str): def create_config(app_name: str):
"""Create a new rxconfig file. """Create a new rxconfig file.