Files
mcp-template-python/tools/rename.py
2025-10-10 10:21:29 +08:00

256 lines
5.8 KiB
Python

import argparse
import re
import shutil
from pathlib import Path
from typing import List
IGNORED_DIRS = {
".venv",
"venv",
".git",
"__pycache__",
".pytest_cache",
".mypy_cache",
".ruff_cache",
"node_modules",
".idea",
".vscode",
"dist",
"build",
"*.egg-info",
}
IGNORED_EXTENSIONS = {
".pyc",
".pyo",
".pyd",
".so",
".dll",
".dylib",
".exe",
".bin",
".dat",
".db",
".sqlite",
".lock",
".jpg",
".jpeg",
".png",
".gif",
".ico",
".svg",
".pdf",
".zip",
".tar",
".gz",
".bz2",
".xz",
}
TEXT_EXTENSIONS = {
".py",
".txt",
".md",
".rst",
".toml",
".yaml",
".yml",
".json",
".ini",
".cfg",
".conf",
".sh",
".bat",
".ps1",
".html",
".css",
".js",
".ts",
".xml",
".Dockerfile",
".gitignore",
".dockerignore",
"Makefile",
"Dockerfile",
}
def normalize_name(name: str) -> tuple[str, str]:
"""Normalize name to underscore and hyphen versions."""
underscore_name = name.replace("-", "_")
hyphen_name = name.replace("_", "-")
return underscore_name, hyphen_name
def should_ignore_path(path: Path, root: Path) -> bool:
try:
relative = path.relative_to(root)
parts = relative.parts
for part in parts:
for ignored in IGNORED_DIRS:
if ignored.endswith("*"):
pattern = ignored.replace("*", ".*")
if re.match(pattern, part):
return True
elif part == ignored:
return True
return False
except ValueError:
return True
def should_process_file(file_path: Path) -> bool:
if file_path.suffix in IGNORED_EXTENSIONS:
return False
if file_path.suffix in TEXT_EXTENSIONS:
return True
if not file_path.suffix and file_path.name in TEXT_EXTENSIONS:
return True
return False
def collect_paths_to_rename(
root: Path, old_underscore: str, old_hyphen: str
) -> List[Path]:
"""Collect all paths that need to be renamed."""
paths_to_rename = []
for path in root.rglob("*"):
if should_ignore_path(path, root):
continue
name = path.name
if old_underscore in name or old_hyphen in name:
paths_to_rename.append(path)
paths_to_rename.sort(key=lambda p: len(p.parts), reverse=True)
return paths_to_rename
def rename_path(
old_path: Path,
old_underscore: str,
old_hyphen: str,
new_underscore: str,
new_hyphen: str,
dry_run: bool = False,
) -> Path:
"""Rename a single path."""
old_name = old_path.name
new_name = old_name
new_name = new_name.replace(old_underscore, new_underscore)
new_name = new_name.replace(old_hyphen, new_hyphen)
if new_name != old_name:
new_path = old_path.parent / new_name
if not dry_run:
if old_path.exists() and not new_path.exists():
shutil.move(str(old_path), str(new_path))
return new_path
return old_path
def replace_in_file(
file_path: Path,
old_underscore: str,
old_hyphen: str,
new_underscore: str,
new_hyphen: str,
dry_run: bool = False,
) -> int:
"""Replace old name in file content."""
if not should_process_file(file_path):
return 0
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except (UnicodeDecodeError, PermissionError):
return 0
new_content = content
new_content = new_content.replace(old_underscore, new_underscore)
new_content = new_content.replace(old_hyphen, new_hyphen)
changes = content.count(old_underscore) + content.count(old_hyphen)
if new_content != content and changes > 0:
if not dry_run:
with open(file_path, "w", encoding="utf-8") as f:
f.write(new_content)
return changes
return 0
def rename_project(root: Path, old_name: str, new_name: str, dry_run: bool = False):
"""Rename the entire project."""
old_underscore, old_hyphen = normalize_name(old_name)
new_underscore, new_hyphen = normalize_name(new_name)
print(f"\nRenaming project{' (dry-run)' if dry_run else ''}:")
print(f" {old_underscore} / {old_hyphen} -> {new_underscore} / {new_hyphen}")
paths_to_rename = collect_paths_to_rename(root, old_underscore, old_hyphen)
if paths_to_rename:
for path in paths_to_rename:
rename_path(
path, old_underscore, old_hyphen, new_underscore, new_hyphen, dry_run
)
total_changes = 0
for file_path in root.rglob("*"):
if not file_path.is_file():
continue
if should_ignore_path(file_path, root):
continue
changes = replace_in_file(
file_path, old_underscore, old_hyphen, new_underscore, new_hyphen, dry_run
)
if changes > 0:
total_changes += changes
print(f"\nCompleted: {total_changes} replacements")
def main():
"""Main function."""
parser = argparse.ArgumentParser(description="Rename project package name")
parser.add_argument("old_name", help="Old project name (xx_xx_xx or xx-xx-xx)")
parser.add_argument("new_name", help="New project name (yy_yy_yy or yy-yy-yy)")
parser.add_argument("--dry-run", action="store_true", help="Preview mode only")
parser.add_argument("--root", type=str, default=".", help="Project root directory")
args = parser.parse_args()
root = Path(args.root).resolve()
if not root.exists():
print(f"Error: Directory not found: {root}")
return 1
rename_project(root, args.old_name, args.new_name, args.dry_run)
return 0
if __name__ == "__main__":
exit(main())