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

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/>. 

17 

18from pathlib import Path 

19from re import Pattern 

20from typing import Iterable, List, Optional 

21 

22from lobster.common.errors import PathError 

23 

24 

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 (.)") 

41 

42 @property 

43 def files(self) -> List[str]: 

44 return self._files 

45 

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 ) 

54 

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) 

60 

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) 

64 

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) 

74 

75 walk_directory(Path(dir_path))