Repository navigation
Expand file tree
/
Copy pathinit_project.py
More file actions
176 lines (132 loc) · 5.27 KB
/
Copy pathinit_project.py
File metadata and controls
176 lines (132 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""Renames a Python project by replacing the template name in files and directories."""
import sys
import shutil
import pathlib
OLD_NAME = "python-app-template"
# The contributing guide is shared by all projects created from this template:
# new projects link to it instead of keeping a copy.
CONTRIBUTING_GUIDE_URL = (
f"https://github.com/GlobalFishingWatch/{OLD_NAME}/blob/main/CONTRIBUTING.md"
)
FILES_TO_UPDATE = (
"cloudbuild/backend.tf",
"cloudbuild/main.tf",
"docker-compose.yml",
"Makefile",
"pyproject.toml",
"README.md",
"requirements.txt",
"requirements-test.txt",
)
PATHS_TO_REMOVE = (
"CONTRIBUTING.md",
"docs/contributing",
)
SOURCE_DIR = pathlib.Path("src")
TESTS_DIR = pathlib.Path("tests")
# Its tests use the template's name on purpose.
INIT_PROJECT_TESTS = "test_init_project.py"
class Name:
"""Encapsulates a project name with methods to convert between dash and underscore styles."""
def __init__(self, raw: str):
"""Initialize Name with a raw string.
Strips leading/trailing whitespace and replaces spaces with dashes.
"""
self._raw = raw.strip().replace(" ", "-")
@property
def with_dashes(self) -> str:
"""Return the project name with dashes instead of underscores."""
return self._raw.replace("_", "-")
@property
def with_underscores(self) -> str:
"""Returns the project name with underscores instead of dashes."""
return self._raw.replace("-", "_")
def __str__(self):
"""Returns a string representation showing both dash and underscore versions."""
return f"{self.with_dashes} / {self.with_underscores}"
def _replace_in_file(filepath: pathlib.Path, old_new_pairs: list[tuple[str, str]]) -> None:
# Links to the shared contributing guide must keep pointing to the template.
parts = filepath.read_text(encoding="utf-8").split(CONTRIBUTING_GUIDE_URL)
for old, new in old_new_pairs:
parts = [part.replace(old, new) for part in parts]
filepath.write_text(CONTRIBUTING_GUIDE_URL.join(parts), encoding="utf-8")
def _remove_path(path: pathlib.Path) -> None:
if not path.exists():
print(f"⏭️ Skipped (not found): {path}")
return
if path.is_dir():
shutil.rmtree(path)
else:
path.unlink()
print(f"🗑️ Removed: {path}")
def _rename_package_dir(src_dir: pathlib.Path, old_name: str, new_name: str) -> None:
old_path = src_dir / old_name
new_path = src_dir / new_name
if not old_path.exists():
print(f"⚠️ Warning: {old_path} does not exist.")
return
if new_path.exists():
print(f"⚠️ Warning: {new_path} already exists.")
return
old_path.rename(new_path)
print(f"📁 Renamed package dir: {old_path} → {new_path}")
def update_project_name(
old_name: Name,
new_name: Name,
src_dir: pathlib.Path,
files_to_update: tuple[str, ...] = (),
) -> None:
"""Updates the project name in all relevant files and rename the source directory."""
replacements = [
(old_name.with_dashes, new_name.with_dashes),
(old_name.with_underscores, new_name.with_underscores),
]
print("🔁 Replacing names:")
for o, n in replacements:
print(f" {o} → {n}")
for file in files_to_update:
path = pathlib.Path(file)
if path.exists():
_replace_in_file(path, replacements)
print(f"✅ Updated: {file}")
else:
print(f"⏭️ Skipped (not found): {file}")
_rename_package_dir(src_dir, old_name.with_underscores, new_name.with_underscores)
print("\n✅ Done!")
print(f"🎉 Project renamed to: {new_name.with_dashes}.")
def python_files(src_dir: pathlib.Path, tests_dir: pathlib.Path, package: str) -> list[str]:
"""Returns the Python files of the package and the tests, which import the package by name."""
files = sorted((src_dir / package).rglob("*.py")) + sorted(tests_dir.rglob("*.py"))
return [str(f) for f in files if f.name != INIT_PROJECT_TESTS]
def remove_template_only_paths(paths_to_remove: tuple[str, ...] = PATHS_TO_REMOVE) -> None:
"""Removes files that only belong to the template, like the shared contributing guide."""
for path in paths_to_remove:
_remove_path(pathlib.Path(path))
def main(
args: list[str] = sys.argv,
old_name: str = OLD_NAME,
files_to_update: tuple[str, ...] = FILES_TO_UPDATE,
src_dir: pathlib.Path = SOURCE_DIR,
paths_to_remove: tuple[str, ...] = PATHS_TO_REMOVE,
tests_dir: pathlib.Path = TESTS_DIR,
):
"""Entry point for the script.
Expects a single command-line argument: the new project name (with dashes or underscores).
"""
example = Name("my-project-name")
if len(args) != 2:
print(f"Usage: python {args[0]} {example.with_dashes} OR {example.with_underscores}")
sys.exit(1)
old_name = Name(old_name)
new_name = Name(args[1])
files_to_update = list(files_to_update)
files_to_update.extend(python_files(src_dir, tests_dir, old_name.with_underscores))
update_project_name(
old_name=old_name,
new_name=new_name,
files_to_update=tuple(files_to_update),
src_dir=src_dir
)
remove_template_only_paths(paths_to_remove)
if __name__ == "__main__":
main()