mirror of
https://github.com/Sun-ZhenXing/mcp-template-python.git
synced 2026-02-04 02:03:32 +00:00
256 lines
5.8 KiB
Python
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())
|