repolish 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ .codeguide*
2
+ .venv
3
+ build/
4
+ dist/
5
+ __pycache__/
6
+ *.egg-info/
7
+ *.pyc
8
+ .coverage
9
+ .coverage.*
10
+ .coverage-files/
11
+ test_ignored.py
12
+ htmlcov/
13
+ book/
14
+ .pytest_cache/*
15
+ .pkglink
16
+ tmp*
17
+ /.editorconfig
18
+ .repolish
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: repolish
3
+ Version: 0.1.0
4
+ Summary: Maintain consistency across repositories
5
+ Author: hotdog-werx
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: cookiecutter>=2.6.0
9
+ Requires-Dist: hotlog>=0.1.1
10
+ Description-Content-Type: text/markdown
11
+
12
+ # repolish
13
+
14
+ > Repolish is a hybrid of templating and diff/patch systems, useful for
15
+ > maintaining repo consistency while allowing local customizations. It uses
16
+ > templates with placeholders that can be filled from a context, and regex
17
+ > patterns to preserve existing local content in files.
18
+
19
+ ## Why this exists
20
+
21
+ Teams often need to enforce repository-level conventions (CI config, build
22
+ tools, metadata, common docs) while letting individual projects keep local
23
+ customizations. The naive approaches are painful:
24
+
25
+ - Copying templates into many repos means drift over time and manual syncs.
26
+ - Running destructive templating can overwrite local changes developers rely on.
27
+
28
+ Repolish solves this by combining templating (to generate canonical files) with
29
+ a set of careful, reversible operations that preserve useful local content.
30
+ Instead of blindly replacing files, Repolish can:
31
+
32
+ - Fill placeholders from provider-supplied context.
33
+ - Apply anchor-driven replacements to keep developer-customized sections.
34
+ - Track provider-specified deletions and record provenance so reviewers can see
35
+ _why_ a path was requested for deletion.
36
+
37
+ ## Design overview
38
+
39
+ Key concepts:
40
+
41
+ - Providers (templates): Each provider lives in a template directory and may
42
+ include a `repolish.py` module that exports `create_context()`,
43
+ `create_anchors()`, and/or `create_delete_files()` helpers. Providers supply
44
+ cookiecutter context and may indicate files that should be removed from a
45
+ project.
46
+ - Anchors: A small markup syntax placed in templates (and optionally in project
47
+ files) that marks blocks or regex lines to preserve. Examples:
48
+ - Block anchors: `## repolish-start[readme]` ... `repolish-end[readme]`
49
+ - Regex anchors: `## repolish-regex[keep]: ^important=.*` The processors use
50
+ these anchors to replace or merge the template content with the local
51
+ project file while preserving the parts marked with anchors.
52
+ - Delete semantics: Providers can request deletions using POSIX-style paths. A
53
+ `!` prefix acts as a negation (keep). Config-level `delete_files` are applied
54
+ last and recorded in provenance.
55
+ - Provenance: Repolish records a `delete_history` mapping that stores, for each
56
+ candidate path, a list of decisions (which provider or config requested a
57
+ delete or a keep). This helps reviewers and automation understand why a path
58
+ was flagged.
59
+
60
+ ## How it works (high level)
61
+
62
+ 1. Load providers configured in `repolish.yaml` (or the default config).
63
+ 2. Merge provider contexts; config-level context overrides provider values.
64
+ 3. Merge anchors from providers and config.
65
+ 4. Stage all provider template directories into a single cookiecutter template
66
+ (adjacent to the config under `.repolish/setup-input`).
67
+ 5. Preprocess staged templates by applying anchor-driven replacements using
68
+ local project files (looked up relative to the config location).
69
+ 6. Render the merged cookiecutter template once into `.repolish/setup-output`.
70
+ 7. In `--check` mode: compare generated files to project files and report either
71
+ diffs, missing files, or paths that providers wanted deleted but which are
72
+ still present.
73
+ 8. In apply mode: copy generated files into the project and apply deletions as
74
+ the final step.
75
+
76
+ ## Example usage
77
+
78
+ repolish.yaml (simple example):
79
+
80
+ ```yaml
81
+ directories:
82
+ - ./templates/template_a
83
+ - ./templates/template_b
84
+ context: {}
85
+ anchors: {}
86
+ delete_files: []
87
+ ```
88
+
89
+ Run a dry-run check (useful for CI):
90
+
91
+ ```bash
92
+ repolish --check --config repolish.yaml
93
+ ```
94
+
95
+ This will produce structured logs that include:
96
+
97
+ - The merged provider `context` and `delete_paths` (so you can see what was
98
+ requested).
99
+ - A `check_result` listing per-path diffs or deletion warnings like
100
+ `PRESENT_BUT_SHOULD_BE_DELETED`.
101
+
102
+ ## Processor story (anchors)
103
+
104
+ We iterated on preserving local file semantics and landed on a simple, explicit
105
+ anchor-based system. Anchors are easy for template authors to add and for
106
+ maintainers to reason about:
107
+
108
+ - Block anchors allow entire sections of a file to be preserved or replaced
109
+ while keeping the surrounding template-driven structure.
110
+ - Regex anchors can mark single lines or patterns to keep (useful for
111
+ maintainer-inserted keys or comments that should survive templating).
112
+
113
+ Anchors are processed in staging before cookiecutter runs, so the generated
114
+ output already reflects local overrides while still taking canonical values from
115
+ templates when needed.
116
+
117
+ ## How do I add anchors?
118
+
119
+ Anchors are intentionally simple so template authors and maintainers can reason
120
+ about them easily. There are two primary forms:
121
+
122
+ - Block anchors mark a named section to preserve or replace between
123
+ `repolish-start[...]` and `repolish-end[...]` markers. Use them for multi-line
124
+ sections such as README snippets, install blocks, or long descriptions.
125
+ - Regex anchors mark single-line patterns to keep using a regular expression.
126
+ They are useful when you want to preserve a line that follows a predictable
127
+ pattern (version lines, keys, simple single-line edits).
128
+
129
+ Below are two practical examples you can copy into templates and projects.
130
+
131
+ Dockerfile (block anchor)
132
+
133
+ Template (templates/template_a/Dockerfile):
134
+
135
+ ```dockerfile
136
+ # base image
137
+ FROM python:3.11-slim
138
+
139
+ ## repolish-start[install]
140
+ # install system deps
141
+ RUN apt-get update && apt-get install -y build-essential libssl-dev
142
+ ## repolish-end[install]
143
+
144
+ # copy + install python deps
145
+ COPY pyproject.toml .
146
+ RUN pip install --no-cache-dir .
147
+ ```
148
+
149
+ Project Dockerfile (local override) — developer has custom install needs:
150
+
151
+ ```dockerfile
152
+ FROM python:3.11-slim
153
+
154
+ ## repolish-start[install]
155
+ # custom build deps for project X
156
+ RUN apt-get update && apt-get install -y locales libpq-dev
157
+ ## repolish-end[install]
158
+
159
+ # copy + install python deps
160
+ COPY pyproject.toml .
161
+ RUN pip install --no-cache-dir .
162
+ ```
163
+
164
+ When Repolish runs its preprocessing, the `install` block from the local project
165
+ will be preserved in the staged template (so the generated output keeps the
166
+ local custom `RUN` command), while the rest of the Dockerfile comes from the
167
+ template.
168
+
169
+ pyproject.toml (regex anchor + block anchor)
170
+
171
+ Template (templates/template_a/pyproject.toml):
172
+
173
+ ```toml
174
+ [tool.poetry]
175
+ name = "{{ cookiecutter.package_name }}"
176
+ version = "0.1.0"
177
+ ## repolish-regex[keep]: ^version\s*=\s*".*"
178
+
179
+ description = "A short description"
180
+
181
+ ## repolish-start[extra-deps]
182
+ # optional extra deps (preserved when present)
183
+ ## repolish-end[extra-deps]
184
+ ```
185
+
186
+ Project pyproject.toml (developer bumped version and added extras):
187
+
188
+ ```toml
189
+ [tool.poetry]
190
+ name = "myproj"
191
+ version = "0.2.0"
192
+
193
+ description = "Local project description"
194
+
195
+ ## repolish-start[extra-deps]
196
+ requests = "^2.30"
197
+ ## repolish-end[extra-deps]
198
+ ```
199
+
200
+ In this example the `## repolish-regex[keep]: ^version\s*=\s*".*"` anchor
201
+ ensures the local `version = "0.2.0"` line is preserved instead of being
202
+ replaced by the template's `0.1.0`. The `extra-deps` block is preserved
203
+ whole-cloth when present, letting projects keep local dependency additions.
204
+
205
+ Notes and tips
206
+
207
+ - Use meaningful anchor names (e.g., `install`, `readme`, `extra-deps`) so
208
+ reviewers immediately understand the preserved section's intent.
209
+ - Regex anchors are applied line-by-line; prefer anchoring to a simple, easy to
210
+ read pattern to avoid surprises.
211
+ - Anchors are processed before cookiecutter rendering, so template substitutions
212
+ still work around preserved sections.
213
+
214
+ ### Where anchors are declared and uniqueness
215
+
216
+ Anchors can come from three places (and are merged in this order):
217
+
218
+ 1. Provider templates: any `## repolish-start[...]` / `## repolish-regex[...]`
219
+ markers present inside the provider's template files.
220
+ 2. Provider code: a provider's `create_anchors()` callable can return an anchors
221
+ mapping (key -> replacement text) used during preprocessing.
222
+ 3. Config-level anchors: the `anchors` mapping in `repolish.yaml` applies last
223
+ and can be used to override or add anchor values.
224
+
225
+ When anchors are merged, later sources override earlier ones (config wins).
226
+ Anchor keys must be unique across the whole merged template set — keys are
227
+ global identifiers used to find matching `repolish-start[...]` blocks or
228
+ `repolish-regex[...]` declarations. If two different template files (or
229
+ providers) use the same anchor key, the later provider's value will override the
230
+ earlier one, which can produce surprising results.
231
+
232
+ Example conflict
233
+
234
+ Two provider templates accidentally use the same anchor key `init`:
235
+
236
+ - `templates/a/Dockerfile` contains `## repolish-start[init]` …
237
+ `## repolish-end[init]`
238
+ - `templates/b/README.md` also contains `## repolish-start[init]` …
239
+ `## repolish-end[init]`
240
+
241
+ Because anchor keys are merged globally, the `init` block from the provider that
242
+ is processed later will replace (or be used in place of) the other one. That may
243
+ not be what you want — for predictable behavior, choose anchor keys scoped to
244
+ the file or the provider, e.g. `docker-install` or `readme-intro`.
245
+
246
+ Best practice: prefix anchor keys with the file or provider name when the
247
+ content is file-scoped. This avoids accidental collisions when multiple
248
+ providers contribute templates that contain similarly-named sections.
249
+
250
+ ## Why this is useful
251
+
252
+ - Safe consistency: teams get centralized templates without forcing destructive,
253
+ manual rollouts.
254
+ - Clear explainability: the `delete_history` provenance makes it easy to review
255
+ why a file was targeted for deletion or kept.
256
+ - CI-friendly: `--check` can be run in CI to detect drift; logs and diffs make
257
+ it straightforward to require PRs to run repolish before merging.
258
+
259
+ ## Final notes
260
+
261
+ Repolish is intentionally small and composable. If you need per-file log
262
+ artifacts, or slightly different merge rules, the processors and cookiecutter
263
+ helpers are isolated so you can adapt them safely.
264
+
265
+ Contributions and issues are welcome — see the test-suite for practical examples
266
+ of how the system behaves.
@@ -0,0 +1,255 @@
1
+ # repolish
2
+
3
+ > Repolish is a hybrid of templating and diff/patch systems, useful for
4
+ > maintaining repo consistency while allowing local customizations. It uses
5
+ > templates with placeholders that can be filled from a context, and regex
6
+ > patterns to preserve existing local content in files.
7
+
8
+ ## Why this exists
9
+
10
+ Teams often need to enforce repository-level conventions (CI config, build
11
+ tools, metadata, common docs) while letting individual projects keep local
12
+ customizations. The naive approaches are painful:
13
+
14
+ - Copying templates into many repos means drift over time and manual syncs.
15
+ - Running destructive templating can overwrite local changes developers rely on.
16
+
17
+ Repolish solves this by combining templating (to generate canonical files) with
18
+ a set of careful, reversible operations that preserve useful local content.
19
+ Instead of blindly replacing files, Repolish can:
20
+
21
+ - Fill placeholders from provider-supplied context.
22
+ - Apply anchor-driven replacements to keep developer-customized sections.
23
+ - Track provider-specified deletions and record provenance so reviewers can see
24
+ _why_ a path was requested for deletion.
25
+
26
+ ## Design overview
27
+
28
+ Key concepts:
29
+
30
+ - Providers (templates): Each provider lives in a template directory and may
31
+ include a `repolish.py` module that exports `create_context()`,
32
+ `create_anchors()`, and/or `create_delete_files()` helpers. Providers supply
33
+ cookiecutter context and may indicate files that should be removed from a
34
+ project.
35
+ - Anchors: A small markup syntax placed in templates (and optionally in project
36
+ files) that marks blocks or regex lines to preserve. Examples:
37
+ - Block anchors: `## repolish-start[readme]` ... `repolish-end[readme]`
38
+ - Regex anchors: `## repolish-regex[keep]: ^important=.*` The processors use
39
+ these anchors to replace or merge the template content with the local
40
+ project file while preserving the parts marked with anchors.
41
+ - Delete semantics: Providers can request deletions using POSIX-style paths. A
42
+ `!` prefix acts as a negation (keep). Config-level `delete_files` are applied
43
+ last and recorded in provenance.
44
+ - Provenance: Repolish records a `delete_history` mapping that stores, for each
45
+ candidate path, a list of decisions (which provider or config requested a
46
+ delete or a keep). This helps reviewers and automation understand why a path
47
+ was flagged.
48
+
49
+ ## How it works (high level)
50
+
51
+ 1. Load providers configured in `repolish.yaml` (or the default config).
52
+ 2. Merge provider contexts; config-level context overrides provider values.
53
+ 3. Merge anchors from providers and config.
54
+ 4. Stage all provider template directories into a single cookiecutter template
55
+ (adjacent to the config under `.repolish/setup-input`).
56
+ 5. Preprocess staged templates by applying anchor-driven replacements using
57
+ local project files (looked up relative to the config location).
58
+ 6. Render the merged cookiecutter template once into `.repolish/setup-output`.
59
+ 7. In `--check` mode: compare generated files to project files and report either
60
+ diffs, missing files, or paths that providers wanted deleted but which are
61
+ still present.
62
+ 8. In apply mode: copy generated files into the project and apply deletions as
63
+ the final step.
64
+
65
+ ## Example usage
66
+
67
+ repolish.yaml (simple example):
68
+
69
+ ```yaml
70
+ directories:
71
+ - ./templates/template_a
72
+ - ./templates/template_b
73
+ context: {}
74
+ anchors: {}
75
+ delete_files: []
76
+ ```
77
+
78
+ Run a dry-run check (useful for CI):
79
+
80
+ ```bash
81
+ repolish --check --config repolish.yaml
82
+ ```
83
+
84
+ This will produce structured logs that include:
85
+
86
+ - The merged provider `context` and `delete_paths` (so you can see what was
87
+ requested).
88
+ - A `check_result` listing per-path diffs or deletion warnings like
89
+ `PRESENT_BUT_SHOULD_BE_DELETED`.
90
+
91
+ ## Processor story (anchors)
92
+
93
+ We iterated on preserving local file semantics and landed on a simple, explicit
94
+ anchor-based system. Anchors are easy for template authors to add and for
95
+ maintainers to reason about:
96
+
97
+ - Block anchors allow entire sections of a file to be preserved or replaced
98
+ while keeping the surrounding template-driven structure.
99
+ - Regex anchors can mark single lines or patterns to keep (useful for
100
+ maintainer-inserted keys or comments that should survive templating).
101
+
102
+ Anchors are processed in staging before cookiecutter runs, so the generated
103
+ output already reflects local overrides while still taking canonical values from
104
+ templates when needed.
105
+
106
+ ## How do I add anchors?
107
+
108
+ Anchors are intentionally simple so template authors and maintainers can reason
109
+ about them easily. There are two primary forms:
110
+
111
+ - Block anchors mark a named section to preserve or replace between
112
+ `repolish-start[...]` and `repolish-end[...]` markers. Use them for multi-line
113
+ sections such as README snippets, install blocks, or long descriptions.
114
+ - Regex anchors mark single-line patterns to keep using a regular expression.
115
+ They are useful when you want to preserve a line that follows a predictable
116
+ pattern (version lines, keys, simple single-line edits).
117
+
118
+ Below are two practical examples you can copy into templates and projects.
119
+
120
+ Dockerfile (block anchor)
121
+
122
+ Template (templates/template_a/Dockerfile):
123
+
124
+ ```dockerfile
125
+ # base image
126
+ FROM python:3.11-slim
127
+
128
+ ## repolish-start[install]
129
+ # install system deps
130
+ RUN apt-get update && apt-get install -y build-essential libssl-dev
131
+ ## repolish-end[install]
132
+
133
+ # copy + install python deps
134
+ COPY pyproject.toml .
135
+ RUN pip install --no-cache-dir .
136
+ ```
137
+
138
+ Project Dockerfile (local override) — developer has custom install needs:
139
+
140
+ ```dockerfile
141
+ FROM python:3.11-slim
142
+
143
+ ## repolish-start[install]
144
+ # custom build deps for project X
145
+ RUN apt-get update && apt-get install -y locales libpq-dev
146
+ ## repolish-end[install]
147
+
148
+ # copy + install python deps
149
+ COPY pyproject.toml .
150
+ RUN pip install --no-cache-dir .
151
+ ```
152
+
153
+ When Repolish runs its preprocessing, the `install` block from the local project
154
+ will be preserved in the staged template (so the generated output keeps the
155
+ local custom `RUN` command), while the rest of the Dockerfile comes from the
156
+ template.
157
+
158
+ pyproject.toml (regex anchor + block anchor)
159
+
160
+ Template (templates/template_a/pyproject.toml):
161
+
162
+ ```toml
163
+ [tool.poetry]
164
+ name = "{{ cookiecutter.package_name }}"
165
+ version = "0.1.0"
166
+ ## repolish-regex[keep]: ^version\s*=\s*".*"
167
+
168
+ description = "A short description"
169
+
170
+ ## repolish-start[extra-deps]
171
+ # optional extra deps (preserved when present)
172
+ ## repolish-end[extra-deps]
173
+ ```
174
+
175
+ Project pyproject.toml (developer bumped version and added extras):
176
+
177
+ ```toml
178
+ [tool.poetry]
179
+ name = "myproj"
180
+ version = "0.2.0"
181
+
182
+ description = "Local project description"
183
+
184
+ ## repolish-start[extra-deps]
185
+ requests = "^2.30"
186
+ ## repolish-end[extra-deps]
187
+ ```
188
+
189
+ In this example the `## repolish-regex[keep]: ^version\s*=\s*".*"` anchor
190
+ ensures the local `version = "0.2.0"` line is preserved instead of being
191
+ replaced by the template's `0.1.0`. The `extra-deps` block is preserved
192
+ whole-cloth when present, letting projects keep local dependency additions.
193
+
194
+ Notes and tips
195
+
196
+ - Use meaningful anchor names (e.g., `install`, `readme`, `extra-deps`) so
197
+ reviewers immediately understand the preserved section's intent.
198
+ - Regex anchors are applied line-by-line; prefer anchoring to a simple, easy to
199
+ read pattern to avoid surprises.
200
+ - Anchors are processed before cookiecutter rendering, so template substitutions
201
+ still work around preserved sections.
202
+
203
+ ### Where anchors are declared and uniqueness
204
+
205
+ Anchors can come from three places (and are merged in this order):
206
+
207
+ 1. Provider templates: any `## repolish-start[...]` / `## repolish-regex[...]`
208
+ markers present inside the provider's template files.
209
+ 2. Provider code: a provider's `create_anchors()` callable can return an anchors
210
+ mapping (key -> replacement text) used during preprocessing.
211
+ 3. Config-level anchors: the `anchors` mapping in `repolish.yaml` applies last
212
+ and can be used to override or add anchor values.
213
+
214
+ When anchors are merged, later sources override earlier ones (config wins).
215
+ Anchor keys must be unique across the whole merged template set — keys are
216
+ global identifiers used to find matching `repolish-start[...]` blocks or
217
+ `repolish-regex[...]` declarations. If two different template files (or
218
+ providers) use the same anchor key, the later provider's value will override the
219
+ earlier one, which can produce surprising results.
220
+
221
+ Example conflict
222
+
223
+ Two provider templates accidentally use the same anchor key `init`:
224
+
225
+ - `templates/a/Dockerfile` contains `## repolish-start[init]` …
226
+ `## repolish-end[init]`
227
+ - `templates/b/README.md` also contains `## repolish-start[init]` …
228
+ `## repolish-end[init]`
229
+
230
+ Because anchor keys are merged globally, the `init` block from the provider that
231
+ is processed later will replace (or be used in place of) the other one. That may
232
+ not be what you want — for predictable behavior, choose anchor keys scoped to
233
+ the file or the provider, e.g. `docker-install` or `readme-intro`.
234
+
235
+ Best practice: prefix anchor keys with the file or provider name when the
236
+ content is file-scoped. This avoids accidental collisions when multiple
237
+ providers contribute templates that contain similarly-named sections.
238
+
239
+ ## Why this is useful
240
+
241
+ - Safe consistency: teams get centralized templates without forcing destructive,
242
+ manual rollouts.
243
+ - Clear explainability: the `delete_history` provenance makes it easy to review
244
+ why a file was targeted for deletion or kept.
245
+ - CI-friendly: `--check` can be run in CI to detect drift; logs and diffs make
246
+ it straightforward to require PRs to run repolish before merging.
247
+
248
+ ## Final notes
249
+
250
+ Repolish is intentionally small and composable. If you need per-file log
251
+ artifacts, or slightly different merge rules, the processors and cookiecutter
252
+ helpers are isolated so you can adapt them safely.
253
+
254
+ Contributions and issues are welcome — see the test-suite for practical examples
255
+ of how the system behaves.
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "repolish"
3
+ description = "Maintain consistency across repositories"
4
+ authors = [{ name = "hotdog-werx" }]
5
+ readme = "README.md"
6
+ license = 'MIT'
7
+ requires-python = ">=3.11"
8
+ dynamic = ["version"]
9
+
10
+ dependencies = [
11
+ "hotlog >= 0.1.1",
12
+ "cookiecutter >= 2.6.0",
13
+ ]
14
+
15
+ [project.scripts]
16
+ repolish = "repolish.cli:main"
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "tbelt >= 0.0.3",
21
+ "pydantic >= 2.0.0",
22
+ "pytest >= 8.0.0",
23
+ "pytest-mock >= 3.12.0",
24
+ "pytest-cov >= 4.0.0",
25
+ ]
26
+
27
+ [tool.hatch]
28
+ build.packages = ["repolish"]
29
+ metadata.allow-direct-references = true
30
+ version.path = 'repolish/version.py'
31
+
32
+ [tool]
33
+ pyright.extends = ".codeguide/configs/pyrightconfig.json"
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
File without changes
@@ -0,0 +1,45 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+
5
+ def create_cookiecutter_template(
6
+ staging_dir: Path,
7
+ template_directories: list[Path],
8
+ ) -> Path:
9
+ """Create a cookiecutter template in a staging directory.
10
+
11
+ Args:
12
+ staging_dir: Path to the staging directory to create the templates.
13
+ template_directories: List of template directories to copy into the
14
+ staging directory. If multiple directories are provided, later
15
+ directories will overwrite files from earlier ones.
16
+
17
+ Returns:
18
+ The Path to the staging directory containing the combined templates.
19
+ """
20
+ if staging_dir.exists():
21
+ shutil.rmtree(staging_dir)
22
+ staging_dir.mkdir(parents=True, exist_ok=True)
23
+ for template_dir in template_directories:
24
+ _copy_template_dir(template_dir, staging_dir)
25
+ return staging_dir
26
+
27
+
28
+ def _copy_template_dir(template_dir: Path, staging_dir: Path) -> None:
29
+ """Copy the contents of a template directory into the staging directory.
30
+
31
+ Each provider is expected to have a `repolish/` subdirectory containing
32
+ the project layout files. These will be copied over to the staging dir under
33
+ the special folder `{{cookiecutter._repolish_project}}`.
34
+ """
35
+ repolish_dir = template_dir / 'repolish'
36
+ if repolish_dir.exists() and repolish_dir.is_dir():
37
+ dest_root = staging_dir / '{{cookiecutter._repolish_project}}'
38
+ for item in repolish_dir.rglob('*'):
39
+ rel = item.relative_to(repolish_dir)
40
+ dest = dest_root / rel
41
+ if item.is_dir():
42
+ dest.mkdir(parents=True, exist_ok=True)
43
+ else:
44
+ dest.parent.mkdir(parents=True, exist_ok=True)
45
+ shutil.copy2(item, dest)