Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,12 @@
read_cache_meta,
write_cache_meta,
)
from create_python_app_core.paths import default_cache_dir, resolve_source
from create_python_app_core.loaders import load_layer, merge_layers
from create_python_app_core.paths import (
default_cache_dir,
get_template_dir_path,
resolve_source,
)

__all__ = [
"__version__",
Expand All@@ -42,6 +47,9 @@
"CacheMeta",
"read_cache_meta",
"write_cache_meta",
"merge_layers",
"load_layer",
"get_template_dir_path",
"CPA_USER_AGENT",
"check_for_latest_version",
"check_python_version",
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
"""Template/extension file loaders and merge model."""

from __future__ import annotations

import shutil
from pathlib import Path

from create_python_app_core.errors import ManifestLoadError
from create_python_app_core.paths import ResolvedSource, get_template_dir_path


def copy_tree(src: Path, dest: Path, *, overwrite: bool = True) -> list[Path]:
"""Copy files from src into dest. Returns list of written paths."""
written: list[Path] = []
if not src.is_dir():
raise ManifestLoadError(f"template directory not found: {src}")
dest.mkdir(parents=True, exist_ok=True)
for path in src.rglob("*"):
if path.is_dir():
continue
rel = path.relative_to(src)
target = dest / rel
if target.exists() and not overwrite:
continue
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, target)
written.append(target)
return written


def load_layer(
source: ResolvedSource,
root: Path,
dest: Path,
*,
overwrite: bool = True,
) -> list[Path]:
"""Load one template/extension layer into dest."""
template_root = get_template_dir_path(source, root)
return copy_tree(template_root, dest, overwrite=overwrite)


def merge_layers(
layers: list[tuple[ResolvedSource, Path]],
dest: Path,
) -> list[Path]:
"""Apply layers in order: template → addons → extend (later wins)."""
written: list[Path] = []
for source, root in layers:
written.extend(load_layer(source, root, dest, overwrite=True))
return written
24 changes: 24 additions & 0 deletions packages/create-python-app-core/tests/test_loaders.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
from pathlib import Path

from create_python_app_core.loaders import merge_layers
from create_python_app_core.paths import ResolvedSource


def _layer(tmp: Path, name: str, files: dict[str, str]) -> tuple[ResolvedSource, Path]:
root = tmp / name
tpl = root / "template"
for rel, content in files.items():
path = tpl / rel
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
src = ResolvedSource(kind="file", url=f"file://{root}", local_path=root)
return src, root


def test_merge_later_wins(tmp_path: Path) -> None:
a = _layer(tmp_path, "a", {"README.md": "a", "keep.txt": "keep"})
b = _layer(tmp_path, "b", {"README.md": "b"})
dest = tmp_path / "out"
merge_layers([a, b], dest)
assert (dest / "README.md").read_text() == "b"
assert (dest / "keep.txt").read_text() == "keep"