| 255 | |
| 256 | |
| 257 | class FileMatcher: |
| 258 | def __init__( |
| 259 | self, |
| 260 | left_files: Iterable[ScanLocation], |
| 261 | right_files: Iterable[ScanLocation], |
| 262 | threshold: Optional[float]=None |
| 263 | ): |
| 264 | self.threshold = threshold or self.get_similarity_threshold() |
| 265 | self.left = set(left_files) |
| 266 | self.right = set(right_files) |
| 267 | |
| 268 | def find_file_modifications(self) -> list: |
| 269 | modified = [] |
| 270 | right = set(self.right) |
| 271 | |
| 272 | for l in self.left: |
| 273 | closest_ratio, closest_file = None, None |
| 274 | |
| 275 | for r in right: |
| 276 | ratio = l.is_renamed_file(other=r) |
| 277 | if type(ratio) == IdenticalName: |
| 278 | closest_ratio = ratio |
| 279 | closest_file = r |
| 280 | break |
| 281 | elif l.is_archive and r.is_archive and ratio > 0: |
| 282 | closest_ratio = ratio |
| 283 | closest_file = r |
| 284 | elif ratio < self.threshold: |
| 285 | continue |
| 286 | elif closest_ratio is None: |
| 287 | closest_ratio = ratio |
| 288 | closest_file = r |
| 289 | elif ratio > closest_ratio: |
| 290 | closest_ratio = ratio |
| 291 | closest_file = r |
| 292 | |
| 293 | if closest_file: |
| 294 | modified.append((l, closest_file, closest_ratio)) |
| 295 | right.remove(closest_file) |
| 296 | |
| 297 | return modified |
| 298 | |
| 299 | def get_closure(self): |
| 300 | modified = self.find_file_modifications() |
| 301 | modified_locations = [x[0] for x in modified] |
| 302 | modified_locations.extend(x[1] for x in modified) |
| 303 | removed = [x for x in self.left if x not in modified_locations] |
| 304 | added = [x for x in self.right if x not in modified_locations] |
| 305 | |
| 306 | return {"added": added, "modified": modified, "removed": removed} |
| 307 | |
| 308 | @classmethod |
| 309 | def get_similarity_threshold(cls) -> float: |
| 310 | return config.CFG["diff"].get("similarity_threshold", 0.60) |