Coverage for src/somesy/pom_xml/writer.py: 92%

114 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-04-30 09:42 +0000

1"""Writer adapter for pom.xml files.""" 

2import logging 

3import xml.etree.ElementTree as ET 

4from pathlib import Path 

5from typing import Any, Dict, List, Optional, Union 

6 

7from somesy.core.models import Person 

8from somesy.core.writer import FieldKeyMapping, ProjectMetadataWriter 

9 

10from . import POM_ROOT_ATRS, POM_URL 

11from .xmlproxy import XMLProxy 

12 

13logger = logging.getLogger("somesy") 

14 

15 

16class POM(ProjectMetadataWriter): 

17 """Java Maven pom.xml parser and saver.""" 

18 

19 # TODO: write a wrapper for ElementTree that behaves like a dict 

20 # TODO: set up correct field name mappings 

21 

22 def __init__( 

23 self, 

24 path: Path, 

25 create_if_not_exists: bool = True, 

26 ): 

27 """Java Maven pom.xml parser. 

28 

29 See [somesy.core.writer.ProjectMetadataWriter.__init__][]. 

30 """ 

31 mappings: FieldKeyMapping = { 

32 # "year": ["inceptionYear"], # not supported by somesy + does not really change 

33 # "project_slug": ["artifactId"], # not supported by somesy for sync 

34 "license": ["licenses", "license"], 

35 "homepage": ["url"], 

36 "repository": ["scm"], 

37 "documentation": ["distributionManagement", "site"], 

38 "authors": ["developers", "developer"], 

39 "contributors": ["contributors", "contributor"], 

40 } 

41 super().__init__( 

42 path, create_if_not_exists=create_if_not_exists, direct_mappings=mappings 

43 ) 

44 

45 def _init_new_file(self): 

46 """Initialize new pom.xml file.""" 

47 pom = XMLProxy(ET.Element("project", POM_ROOT_ATRS)) 

48 pom["properties"] = {"info.versionScheme": "semver-spec"} 

49 pom.write(self.path) 

50 

51 def _load(self): 

52 """Load the POM file.""" 

53 ET.register_namespace("", POM_URL) # register POM as default xml namespace 

54 self._data = XMLProxy.parse(self.path, default_namespace=POM_URL) 

55 

56 def _validate(self): 

57 """Validate the POM file.""" 

58 logger.info("Cannot validate POM file, skipping validation.") 

59 

60 def save(self, path: Optional[Path] = None) -> None: 

61 """Save the POM DOM to a file.""" 

62 self._data.write(path or self.path, default_namespace=None) 

63 

64 def _get_property( 

65 self, 

66 key: Union[str, List[str]], 

67 *, 

68 only_first: bool = False, 

69 remove: bool = False, 

70 ) -> Optional[Any]: 

71 elem = super()._get_property(key, only_first=only_first, remove=remove) 

72 if elem is not None: 

73 if isinstance(elem, list): 

74 return [e.to_jsonlike() for e in elem] 

75 else: 

76 return elem.to_jsonlike() 

77 return None 

78 

79 @staticmethod 

80 def _from_person(person: Person): 

81 """Convert person object to dict for POM XML person format.""" 

82 ret: Dict[str, Any] = {} 

83 person_id = str(person.orcid) or person.to_name_email_string() 

84 ret["id"] = person_id 

85 ret["name"] = person.full_name 

86 ret["email"] = person.email 

87 if person.orcid: 

88 ret["url"] = str(person.orcid) 

89 if person.contribution_types: 

90 ret["roles"] = dict(role=[c.value for c in person.contribution_types]) 

91 return ret 

92 

93 @staticmethod 

94 def _to_person(person_obj) -> Person: 

95 """Parse POM XML person to a somesy Person.""" 

96 print(person_obj) 

97 names = person_obj["name"].split() 

98 gnames = " ".join(names[:-1]) 

99 fname = names[-1] 

100 email = person_obj["email"] 

101 url = person_obj.get("url") 

102 maybe_orcid = url if url.find("orcid.org") >= 0 else None 

103 if roles := person_obj.get("roles"): 

104 contr = roles["role"] 

105 else: 

106 contr = None 

107 

108 return Person( 

109 given_names=gnames, 

110 family_names=fname, 

111 email=email, 

112 orcid=maybe_orcid, 

113 contribution_types=contr, 

114 ) 

115 

116 # no search keywords supported in POM 

117 @property 

118 def keywords(self) -> Optional[List[str]]: 

119 """Return the keywords of the project.""" 

120 pass 

121 

122 @keywords.setter 

123 def keywords(self, keywords: List[str]) -> None: 

124 """Set the keywords of the project.""" 

125 pass 

126 

127 # authors must be a list 

128 @property 

129 def authors(self): 

130 """Return the authors of the project.""" 

131 authors = self._get_property(self._get_key("authors")) 

132 return authors if isinstance(authors, list) else [authors] 

133 

134 @authors.setter 

135 def authors(self, authors: List[Person]) -> None: 

136 """Set the authors of the project.""" 

137 authors = [self._from_person(c) for c in authors] 

138 self._set_property(self._get_key("authors"), authors) 

139 

140 # contributors must be a list 

141 @property 

142 def contributors(self): 

143 """Return the contributors of the project.""" 

144 contr = self._get_property(self._get_key("contributors")) 

145 if contr is None: 

146 return [] 

147 return contr if isinstance(contr, list) else [contr] 

148 

149 @contributors.setter 

150 def contributors(self, contributors: List[Person]) -> None: 

151 """Set the contributors of the project.""" 

152 contr = [self._from_person(c) for c in contributors] 

153 self._set_property(self._get_key("contributors"), contr) 

154 

155 # no maintainers supported im POM, only developers and contributors 

156 @property 

157 def maintainers(self): 

158 """Return the maintainers of the project.""" 

159 return [] 

160 

161 @maintainers.setter 

162 def maintainers(self, maintainers: List[Person]) -> None: 

163 """Set the maintainers of the project.""" 

164 pass 

165 

166 # only one project license supported in somesy (POM can have many) 

167 @property 

168 def license(self) -> Optional[str]: 

169 """Return the license of the project.""" 

170 lic = self._get_property(self._get_key("license"), only_first=True) 

171 return lic.get("name") if lic is not None else None 

172 

173 @license.setter 

174 def license(self, license: Optional[str]) -> None: 

175 """Set the license of the project.""" 

176 self._set_property( 

177 self._get_key("license"), dict(name=license, distribution="repo") 

178 ) 

179 

180 @property 

181 def repository(self) -> Optional[Union[str, dict]]: 

182 """Return the repository url of the project.""" 

183 repo = super().repository 

184 if isinstance(repo, str): 

185 return repo 

186 return repo.get("url") if repo is not None else None 

187 

188 @repository.setter 

189 def repository(self, value: Optional[Union[str, dict]]) -> None: 

190 """Set the repository url of the project.""" 

191 self._set_property( 

192 self._get_key("repository"), dict(name="git repository", url=value) 

193 ) 

194 

195 @property 

196 def documentation(self) -> Optional[Union[str, dict]]: 

197 """Return the documentation url of the project.""" 

198 docs = super().documentation 

199 if isinstance(docs, str): 

200 return docs 

201 return docs.get("url") if docs is not None else None 

202 

203 @documentation.setter 

204 def documentation(self, value: Optional[Union[str, dict]]) -> None: 

205 """Set the documentation url of the project.""" 

206 self._set_property( 

207 self._get_key("documentation"), dict(name="documentation site", url=value) 

208 ) 

209 

210 def sync(self, metadata) -> None: 

211 """Sync codemeta.json with project metadata. 

212 

213 Use existing sync function from ProjectMetadataWriter but update repository and contributors. 

214 """ 

215 super().sync(metadata) 

216 self.contributors = self._sync_person_list(self.contributors, metadata.people)