Coverage for src/glotter_core/source.py: 100%

84 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-03 02:09 +0000

1"""Source information""" 

2 

3import os 

4from dataclasses import dataclass, field 

5from pathlib import Path 

6 

7import yaml 

8 

9from glotter_core.project import CoreProjectMixin, NamingScheme 

10from glotter_core.testinfo import TestInfo 

11 

12 

13@dataclass(frozen=True) 

14class CoreSource: 

15 """Metadata about a source file 

16 

17 :param filename: filename including extension 

18 :param language: the language of the source 

19 :param path: path to the file excluding name 

20 :param str test_info: a string in yaml format containing testinfo for a directory 

21 :param project_type: name of project for this source 

22 

23 :ivar filename: filename including extension 

24 :ivar language: the language of the source 

25 :ivar path: path to the file excluding name 

26 :ivar TestInfo test_info: TestInfo object 

27 :param project_type: name of project for this source 

28 """ 

29 

30 filename: str 

31 language: str 

32 path: str 

33 test_info: str = field(repr=False) 

34 project_type: str 

35 

36 def __post_init__(self) -> None: 

37 object.__setattr__(self, "test_info", TestInfo.from_string(self.test_info, self)) 

38 

39 @property 

40 def full_path(self) -> str: 

41 """Returns the full path to the source including filename and extension""" 

42 return str(Path(self.path) / self.filename) 

43 

44 @property 

45 def name(self) -> str: 

46 """Returns the name of the source excluding the extension""" 

47 return self.filename.split(".")[0] 

48 

49 @property 

50 def extension(self) -> str: 

51 """Returns the extension of the source""" 

52 return "".join(Path(self.filename).suffixes) 

53 

54 

55@dataclass 

56class CoreLanguage: 

57 """ 

58 Information about a language 

59 

60 :ivar sources: list of source objects 

61 :ivar test_info: TestInfo object 

62 :ivar path: Path to TestInfo object file 

63 """ 

64 

65 sources: list[CoreSource] 

66 test_info: TestInfo 

67 test_info_path: Path 

68 

69 

70@dataclass 

71class CoreSourceCategories: 

72 """ 

73 Categories for sources 

74 

75 :ivar testable_by_project: dictionary whose key is the project type and 

76 whose value is a list of testable source object 

77 :ivar by_language: dictionary whose key is the language and whose 

78 value is a CoreLanguage object 

79 :ivar bad_sources: list of filenames that do not belong to a project 

80 """ 

81 

82 testable_by_project: dict[str, list[CoreSource]] = field(default_factory=dict) 

83 by_language: dict[str, list[CoreLanguage]] = field(default_factory=dict) 

84 bad_sources: list[str] = field(default_factory=list) 

85 

86 

87_IGNORED_FILENAMES = {"untestable.yml", "testinfo.yml", "README.md"} 

88 

89 

90def categorize_sources( 

91 path: str, projects: dict[str, CoreProjectMixin], source_cls: type 

92) -> CoreSourceCategories: 

93 """ 

94 Categorize sources 

95 

96 :param path: path to source directory 

97 :param projects: dictionary whose key is a project type and whose value is a 

98 CoreProjectMixin object 

99 :param source_cls: source object class 

100 :return: CoreSourceCategories object containing information of the source 

101 categories 

102 """ 

103 

104 categories = CoreSourceCategories() 

105 categories.testable_by_project = {k: [] for k in projects} 

106 orig_path = Path(path).resolve() 

107 for root, _, files in os.walk(path): 

108 current_path = Path(root).resolve() 

109 test_info_string = "" 

110 test_info_filename = "" 

111 if "testinfo.yml" in files: 

112 test_info_filename = "testinfo.yml" 

113 test_info_string = Path(current_path, test_info_filename).read_text(encoding="utf-8") 

114 elif "untestable.yml" in files: 

115 test_info_filename = "untestable.yml" 

116 test_info_string = _convert_untestable_to_testinfo(current_path, files, projects) 

117 

118 if test_info_string: 

119 language = current_path.name 

120 test_info = TestInfo.from_dict(yaml.safe_load(test_info_string), language) 

121 folder_info = test_info.file_info 

122 folder_project_names = folder_info.get_project_mappings( 

123 projects, include_extension=True 

124 ) 

125 sources = [] 

126 test_info_path = Path(current_path, test_info_filename) 

127 for project_type, project_name in folder_project_names.items(): 

128 if project_name in files: 

129 source = source_cls( 

130 filename=project_name, 

131 language=language, 

132 path=str(current_path), 

133 test_info=test_info_string, 

134 project_type=project_type, 

135 ) 

136 sources.append(source) 

137 if source.test_info.is_testable: 

138 categories.testable_by_project[project_type].append(source) 

139 

140 categories.by_language[language] = CoreLanguage(sources, test_info, test_info_path) 

141 

142 invalid_filenames = set(files) - ( 

143 set(folder_project_names.values()) | _IGNORED_FILENAMES 

144 ) 

145 categories.bad_sources += [ 

146 str(current_path.relative_to(orig_path) / filename) 

147 for filename in invalid_filenames 

148 ] 

149 

150 return categories 

151 

152 

153def _convert_untestable_to_testinfo( 

154 current_path: Path, files: list[str], projects: dict[str, CoreProjectMixin] 

155) -> str: 

156 with Path(current_path, "untestable.yml").open(encoding="utf-8") as f: 

157 untestable_data = yaml.safe_load(f) 

158 

159 notes = untestable_data[0]["reason"] 

160 for filename in files: 

161 if filename in _IGNORED_FILENAMES: 

162 continue 

163 

164 base_filename = filename.split(".")[0] 

165 extension = "".join(Path(filename).suffixes) 

166 project_type = base_filename.lower().replace("-", "").replace("_", "") 

167 if project_type in projects and len(projects[project_type].words) > 1: 

168 for naming_scheme in NamingScheme: 

169 expected_filename = ( 

170 projects[project_type].get_project_name_by_scheme(naming_scheme) + extension 

171 ) 

172 if filename == expected_filename: 

173 test_info_dict = { 

174 "folder": { 

175 "extension": extension, 

176 "naming": naming_scheme.value, 

177 }, 

178 "notes": [notes], 

179 } 

180 return yaml.dump(test_info_dict, sort_keys=False) 

181 

182 return "" 

183 

184 

185__all__ = ["CoreLanguage", "CoreSource", "CoreSourceCategories", "categorize_sources"]