-
-
Notifications
You must be signed in to change notification settings - Fork 270
Expand file tree
/
Copy path_subproject.py
More file actions
99 lines (81 loc) · 3.14 KB
/
Copy path_subproject.py
File metadata and controls
99 lines (81 loc) · 3.14 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
"""Objects to interact with subprojects.
A *subproject* is a project that gets rendered and/or updated with Copier.
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import field
from functools import cached_property
from pathlib import Path
from plumbum.machines import local
from pydantic.dataclasses import dataclass
from ._template import Template
from ._types import AbsolutePath, AnyByStrDict, VCSTypes
from ._user_data import load_answersfile_data
from ._vcs import get_git, is_in_git_repo, is_remote_url
@dataclass
class Subproject:
"""Object that represents the subproject and its current state.
Attributes:
local_abspath:
Absolute path on local disk pointing to the subproject root folder.
answers_relpath:
Relative path to [the answers file][the-copier-answersyml-file].
"""
local_abspath: AbsolutePath
answers_relpath: Path = Path(".copier-answers.yml")
_cleanup_hooks: list[Callable[[], None]] = field(default_factory=list, init=False)
def is_dirty(self) -> bool:
"""Indicate if the local template root is dirty.
Only applicable for VCS-tracked templates.
"""
if self.vcs == "git":
with local.cwd(self.local_abspath):
return bool(
get_git()("status", self.local_abspath, "--porcelain").strip()
)
return False
def _cleanup(self) -> None:
"""Remove temporary files and folders created by the subproject."""
for method in self._cleanup_hooks:
method()
@property
def _raw_answers(self) -> AnyByStrDict:
"""Get last answers, loaded raw as yaml."""
try:
return load_answersfile_data(self.local_abspath, self.answers_relpath)
except OSError:
return {}
@cached_property
def last_answers(self) -> AnyByStrDict:
"""Last answers, excluding private ones (except _src_path and _commit)."""
return {
key: value
for key, value in self._raw_answers.items()
if key in {"_src_path", "_commit"} or not key.startswith("_")
}
@cached_property
def template(self) -> Template | None:
"""Template, as it was used the last time."""
last_url = self.last_answers.get("_src_path")
last_ref = self.last_answers.get("_commit")
if last_url:
url = last_url
if not is_remote_url(last_url):
try:
path = Path(last_url)
if not path.is_absolute():
resolved_path = (self.local_abspath / path).resolve()
if resolved_path.is_dir():
url = str(resolved_path)
except OSError:
pass
result = Template(url=url, ref=last_ref)
self._cleanup_hooks.append(result._cleanup)
return result
return None
@cached_property
def vcs(self) -> VCSTypes | None:
"""VCS type of the subproject."""
if is_in_git_repo(self.local_abspath):
return "git"
return None