Coverage for debputy/transformation_rules.py: 0%
121 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-22 14:29 +0100
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-22 14:29 +0100
1import os
2from typing import NoReturn, Iterable, Optional
4from debputy.filesystem_scan import FSPath, VirtualDirectoryFSPath, FSPathVirtualPath
5from debputy.manifest_conditions import ConditionContext, ManifestCondition
6from debputy.manifest_path_rules import ManifestPathRule, EnsureDirectoryPathRule
7from debputy.path_matcher import MatchRule
8from debputy.util import _error
11def _match_rule_had_no_matches(match_rule: MatchRule, definition_source: str) -> NoReturn:
12 _error(f'The match rule "{match_rule.describe_match_short()}" in transformation "{definition_source}" did not'
13 ' match any paths. Either the definition is redundant (and can be omitted) or the match rule is'
14 ' incorrect.')
17def _fs_path_as_dir(path: FSPath, force_convert: bool, definition_source: str) -> FSPath:
18 if path.is_dir:
19 return path
20 if not force_convert:
21 path_type = 'file' if path.is_file else 'symlink/"special file system object"'
22 _error(f"The path {path.path} was expected to be a directory (or non-existing) due to {definition_source}."
23 f"However that path existed and is a {path_type}.")
24 return VirtualDirectoryFSPath(path.basename, path.parent_dir)
27def _ensure_is_directory(fs_root: FSPath,
28 path_to_directory: str,
29 definition_source: str,
30 force_convert=False,
31 ) -> FSPath:
32 current, missing_parts = fs_root.attempt_lookup(path_to_directory)
33 current = _fs_path_as_dir(current, force_convert, definition_source)
34 for missing_part in missing_parts:
35 assert missing_part not in ('.', '..')
36 current = VirtualDirectoryFSPath(missing_part, current)
37 return current
40class TransformationRule:
42 @property
43 def is_builtin_rule(self) -> bool:
44 return False
46 def describe_transformation(self) -> str:
47 raise NotImplementedError
49 def transform_file_system(self, fs_root: FSPath, condition_context: ConditionContext) -> None:
50 raise NotImplementedError
53class ExclusionTransformationRule(TransformationRule):
55 def __init__(self, match_rule: MatchRule, definition_source: str) -> None:
56 self._match_rule = match_rule
57 self._definition_source = definition_source
59 def describe_transformation(self) -> str:
60 return f"Exclude {self._match_rule.describe_match_short()} (from: {self._definition_source})"
62 def transform_file_system(self, fs_root: FSPath, condition_context: ConditionContext) -> None:
63 matched_any = False
64 for m in self._match_rule.finditer(fs_root):
65 matched_any = True
66 parent = m.parent_dir
67 if parent is None:
68 _error(f"Cannot exclude the root directory (triggered by {self._definition_source}")
69 del parent[m.basename]
70 if not matched_any:
71 _match_rule_had_no_matches(self._match_rule, self._definition_source)
74class MoveTransformationRule(TransformationRule):
76 def __init__(self,
77 match_rule: MatchRule,
78 dest_path: str,
79 dest_is_dir: bool,
80 definition_source: str,
81 condition: Optional[ManifestCondition],
82 ) -> None:
83 self._match_rule = match_rule
84 self._dest_path = dest_path
85 self._dest_is_dir = dest_is_dir
86 self._definition_source = definition_source
87 self._condition = condition
89 def describe_transformation(self) -> str:
90 dest_path = self._dest_path
91 if self._dest_is_dir:
92 dest_path += '/'
93 return f'Rename {self._match_rule.describe_match_short()} -> "{dest_path}"' \
94 f" (from: {self._definition_source})"
96 def transform_file_system(self, fs_root: FSPath, condition_context: ConditionContext) -> None:
97 if self._condition is not None and not self._condition.evaluate(condition_context):
98 return
99 # Eager resolve is necessary to avoid "self-recursive" matching in special cases (e.g., **/*.la)
100 matches = list(self._match_rule.finditer(fs_root))
101 if not matches:
102 _match_rule_had_no_matches(self._match_rule, self._definition_source)
104 if self._dest_is_dir:
105 target_dir = _ensure_is_directory(fs_root, self._dest_path, self._definition_source)
106 else:
107 dir_part, basename = os.path.split(self._dest_path)
108 target_parent_dir = _ensure_is_directory(fs_root, dir_part, self._definition_source)
109 target_dir = target_parent_dir.get(basename)
111 if target_dir is None or not target_dir.is_dir:
112 if len(matches) > 1:
113 _error(f"Could not rename {self._match_rule.describe_match_short()} to {self._dest_path}"
114 f" (from: {self._definition_source}). Multiple paths matched the pattern and the destination"
115 " was not a directory. Either correct the pattern to only match ony source OR define the"
116 f' destination to be a directory (E.g., add a trailing slash - example:'
117 f' "{self._dest_path}/")')
118 p = matches[0]
119 if p.path == self._dest_path:
120 _error(f"Error in {self._definition_source}, the source {self._match_rule.describe_match_short()}"
121 f"matched {self._dest_path} making the rename redundant!?")
122 p.parent_dir = target_parent_dir
123 p.basename = basename
124 return
126 assert target_dir is not None and target_dir.is_dir
127 basenames = dict()
128 target_dir_path = target_dir.path
130 for m in matches:
131 if m.path == target_dir_path:
132 _error(f"Error in {self._definition_source}, the source {self._match_rule.describe_match_short()}"
133 f"matched {self._dest_path} (among other), but it is not possible to copy a directory into"
134 " itself")
135 if m.basename in basenames:
136 alt_path = basenames[m.basename]
137 # We document "two *distinct*" paths. However, as the glob matches are written, it should not be
138 # possible for a *single* glob to match the same path twice.
139 assert alt_path is not m
140 _error(f"Could not rename {self._match_rule.describe_match_short()} to {self._dest_path}"
141 f" (from: {self._definition_source}). Multiple paths matched the pattern had the same basename"
142 f' "{m.basename}" ("{m.path}" vs. "{alt_path.path}"). Please correct the pattern, so it only'
143 f' matches one path with that basename to avoid this conflict.')
144 existing = m.get(m.basename)
145 if existing and existing.is_dir:
146 _error(f"Could not rename {self._match_rule.describe_match_short()} to {self._dest_path}"
147 f" (from: {self._definition_source}). The pattern matched {m.path} which would replace"
148 f" the existing directory {existing.path}. If this replacement is intentional, then please"
149 f' exclude {existing.path} first (e.g., via `- exclude: "{existing.path}"`)')
150 basenames[m.basename] = m
151 m.parent_dir = target_dir
154class CreateDirectoryTransformationRule(TransformationRule):
156 def __init__(self, directories: Iterable[EnsureDirectoryPathRule]):
157 self._directories = directories
159 @property
160 def is_builtin_rule(self) -> bool:
161 return True
163 def describe_transformation(self) -> str:
164 return "<built-in: Insert implicitly and explicitly declared directories>"
166 def transform_file_system(self, fs_root: FSPath, condition_context: ConditionContext) -> None:
167 for path_definition in self._directories:
168 if path_definition.condition is not None and not path_definition.condition.evaluate(condition_context):
169 continue
170 _ensure_is_directory(fs_root,
171 path_definition.member_path,
172 '<Built-in; implementation of "directories">',
173 force_convert=True)
176class CreateVirtualPathTransformationRule(TransformationRule):
178 def __init__(self, path_definitions: Iterable[ManifestPathRule]):
179 self._path_definitions = path_definitions
181 @property
182 def is_builtin_rule(self) -> bool:
183 return True
185 def describe_transformation(self) -> str:
186 return "<built-in: Insert explicitly declared virtual paths>"
188 def transform_file_system(self, fs_root: FSPath, condition_context: ConditionContext) -> None:
189 for path_definition in self._path_definitions:
190 if path_definition.condition is not None and not path_definition.condition.evaluate(condition_context):
191 continue
192 parent_path = os.path.dirname(path_definition.member_path)
193 parent_dir = fs_root.lookup(parent_path)
194 assert parent_dir is not None and parent_dir.is_dir
195 FSPathVirtualPath(path_definition, parent_dir)