Coverage for lobster/common/file_collector.py: 85%
40 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-09-22 04:43 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-09-22 04:43 +0000
1# LOBSTER - Lightweight Open BMW Software Traceability Evidence Report
2# Copyright (C) 2025-2026 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as
6# published by the Free Software Foundation, either version 3 of the
7# License, or (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12# Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public
15# License along with this program. If not, see
16# <https://www.gnu.org/licenses/>.
18from pathlib import Path
19from re import Pattern
20from typing import Iterable, List, Optional
22from lobster.common.errors import PathError
25class FileCollector:
26 def __init__(
27 self,
28 extensions: Iterable[str],
29 directory_exclude_patterns: Optional[Iterable[Pattern]],
30 ) -> None:
31 if extensions is None:
32 raise ValueError("'extensions' must not be None")
33 if directory_exclude_patterns is None:
34 directory_exclude_patterns = []
35 self._extensions = extensions
36 self._files = []
37 self._directory_exclude_patterns = directory_exclude_patterns
38 for ext in self._extensions:
39 if not ext.startswith("."):
40 raise ValueError(f"Extension '{ext}' must start with a dot (.)")
42 @property
43 def files(self) -> List[str]:
44 return self._files
46 def add_file(self, file: str, throw_on_mismatch: bool) -> None:
47 if self._is_file_of_interest(file):
48 self._files.append(file)
49 elif throw_on_mismatch:
50 raise PathError(
51 f"File {file} does not have a valid extension. "
52 f"Expected one of {', '.join(self._extensions)}."
53 )
55 def _is_file_of_interest(self, file: str) -> bool:
56 if not self._extensions:
57 return True
58 name = Path(file).name.lower()
59 return any(name.endswith(ext.lower()) for ext in self._extensions)
61 def _is_dir_of_interest(self, dir_name: str) -> bool:
62 return not any(pattern.match(dir_name)
63 for pattern in self._directory_exclude_patterns)
65 def add_dir_recursively(self, dir_path: str) -> None:
66 """Recursively adds files from a directory, filtering by extensions."""
67 def walk_directory(path: Path):
68 for item in Path(path).iterdir():
69 if item.is_file(): 69 ↛ 71line 69 didn't jump to line 71 because the condition on line 69 was always true
70 self.add_file(str(item.as_posix()), throw_on_mismatch=False)
71 elif item.is_dir():
72 if self._is_dir_of_interest(item.name):
73 walk_directory(item)
75 walk_directory(Path(dir_path))