BoilerWorks

boilerworks / boilerworks / renderer.py
Blame History Raw 165 lines
1
"""File renderer — string replacement across a cloned template directory."""
2
3
from __future__ import annotations
4
5
import os
6
from pathlib import Path
7
8
# Directories to skip entirely during rendering
9
_SKIP_DIRS: frozenset[str] = frozenset(
10
[".git", "node_modules", "vendor", "__pycache__", "_build", "deps", "target", ".venv"]
11
)
12
13
# Binary/lock file extensions to skip (don't attempt text replacement)
14
_SKIP_EXTENSIONS: frozenset[str] = frozenset(
15
[
16
".png",
17
".jpg",
18
".jpeg",
19
".gif",
20
".ico",
21
".woff",
22
".woff2",
23
".ttf",
24
".eot",
25
".lock",
26
".pyc",
27
".pdf",
28
".zip",
29
".tar",
30
".gz",
31
".bz2",
32
".7z",
33
".exe",
34
".bin",
35
".so",
36
".dylib",
37
".dll",
38
".wasm",
39
".db",
40
".sqlite",
41
".sqlite3",
42
]
43
)
44
45
46
def build_replacements(project: str) -> dict[str, str]:
47
"""Build the standard case-variant replacement map from a project name.
48
49
E.g. project='my-app' produces:
50
'boilerworks' → 'my-app'
51
'Boilerworks' → 'My-App'
52
'BOILERWORKS' → 'MY-APP'
53
'boilerworks_' → 'my_app_' (underscore variant for Python identifiers)
54
"""
55
# underscore variant (Python module names)
56
project_under = project.replace("-", "_")
57
project_title = project.replace("-", " ").title().replace(" ", "-")
58
project_upper = project.upper()
59
60
return {
61
"boilerworks_": f"{project_under}_",
62
"_boilerworks": f"_{project_under}",
63
"BOILERWORKS": project_upper,
64
"Boilerworks": project_title,
65
"boilerworks": project,
66
}
67
68
69
def render_file(path: Path, replacements: dict[str, str]) -> bool:
70
"""Apply string replacements to a single file in-place.
71
72
Returns True if the file was modified, False if skipped or unchanged.
73
"""
74
if path.suffix.lower() in _SKIP_EXTENSIONS:
75
return False
76
77
try:
78
original = path.read_text(encoding="utf-8", errors="strict")
79
except (UnicodeDecodeError, PermissionError):
80
# Binary or unreadable file — skip
81
return False
82
83
modified = original
84
for old, new in replacements.items():
85
modified = modified.replace(old, new)
86
87
if modified == original:
88
return False
89
90
path.write_text(modified, encoding="utf-8")
91
return True
92
93
94
def render_directory(
95
root: Path,
96
replacements: dict[str, str],
97
skip_dirs: frozenset[str] | None = None,
98
skip_extensions: frozenset[str] | None = None,
99
) -> list[Path]:
100
"""Apply replacements to all eligible files under root.
101
102
Returns the list of files that were modified.
103
"""
104
skip_d = skip_dirs if skip_dirs is not None else _SKIP_DIRS
105
skip_e = skip_extensions if skip_extensions is not None else _SKIP_EXTENSIONS
106
107
modified: list[Path] = []
108
109
for dirpath, dirnames, filenames in os.walk(root):
110
# Prune excluded dirs in-place so os.walk doesn't recurse into them
111
dirnames[:] = [d for d in dirnames if d not in skip_d]
112
113
for filename in filenames:
114
filepath = Path(dirpath) / filename
115
if filepath.suffix.lower() in skip_e:
116
continue
117
try:
118
was_changed = render_file(filepath, replacements)
119
except OSError:
120
continue
121
if was_changed:
122
modified.append(filepath)
123
124
return modified
125
126
127
def rename_boilerworks_paths(root: Path, project: str) -> list[tuple[Path, Path]]:
128
"""Rename any files or directories containing 'boilerworks' in their name.
129
130
Walks the tree bottom-up so child renames happen before parents.
131
Returns list of (old_path, new_path) pairs.
132
"""
133
project_under = project.replace("-", "_")
134
renames: list[tuple[Path, Path]] = []
135
136
for dirpath, dirnames, filenames in os.walk(root, topdown=False):
137
current_dir = Path(dirpath)
138
139
# Rename files
140
for filename in filenames:
141
if "boilerworks" in filename.lower():
142
old = current_dir / filename
143
new_name = filename.replace("boilerworks", project).replace("BOILERWORKS", project.upper())
144
new_name = new_name.replace("Boilerworks", project.replace("-", " ").title().replace(" ", "-"))
145
# Also handle underscore variant
146
new_name = new_name.replace("boilerworks_", f"{project_under}_")
147
new = current_dir / new_name
148
if old != new:
149
old.rename(new)
150
renames.append((old, new))
151
152
# Rename subdirectories
153
for dirname in dirnames:
154
if "boilerworks" in dirname.lower():
155
old = current_dir / dirname
156
new_dirname = dirname.replace("boilerworks", project).replace("BOILERWORKS", project.upper())
157
new_dirname = new_dirname.replace("Boilerworks", project.replace("-", " ").title().replace(" ", "-"))
158
new_dirname = new_dirname.replace("boilerworks_", f"{project_under}_")
159
new = current_dir / new_dirname
160
if old != new and old.exists():
161
old.rename(new)
162
renames.append((old, new))
163
164
return renames
165

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button