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

114 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-07-29 07:42 +0000

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

2 

3import logging 

4import xml.etree.ElementTree as ET 

5from pathlib import Path 

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

7 

8from somesy.core.models import Person 

9from somesy.core.writer import FieldKeyMapping, ProjectMetadataWriter 

10 

11from . import POM_ROOT_ATRS, POM_URL 

12from .xmlproxy import XMLProxy 

13 

14logger = logging.getLogger("somesy") 

15 

16 

17class POM(ProjectMetadataWriter): 

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

19 

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

21 # TODO: set up correct field name mappings 

22 

23 def __init__( 

24 self, 

25 path: Path, 

26 create_if_not_exists: bool = True, 

27 ): 

28 """Java Maven pom.xml parser. 

29 

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

31 """ 

32 mappings: FieldKeyMapping = { 

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

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

35 "license": ["licenses", "license"], 

36 "homepage": ["url"], 

37 "repository": ["scm"], 

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

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

40 "contributors": ["contributors", "contributor"], 

41 } 

42 super().__init__( 

43 path, create_if_not_exists=create_if_not_exists, direct_mappings=mappings 

44 ) 

45 

46 def _init_new_file(self): 

47 """Initialize new pom.xml file.""" 

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

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

50 pom.write(self.path) 

51 

52 def _load(self): 

53 """Load the POM file.""" 

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

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

56 

57 def _validate(self): 

58 """Validate the POM file.""" 

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

60 

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

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

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

64 

65 def _get_property( 

66 self, 

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

68 *, 

69 only_first: bool = False, 

70 remove: bool = False, 

71 ) -> Optional[Any]: 

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

73 if elem is not None: 

74 if isinstance(elem, list): 

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

76 else: 

77 return elem.to_jsonlike() 

78 return None 

79 

80 @staticmethod 

81 def _from_person(person: Person): 

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

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

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

85 ret["id"] = person_id 

86 ret["name"] = person.full_name 

87 ret["email"] = person.email 

88 if person.orcid: 

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

90 if person.contribution_types: 

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

92 return ret 

93 

94 @staticmethod 

95 def _to_person(person_obj) -> Person: 

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

97 print(person_obj) 

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

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

100 fname = names[-1] 

101 email = person_obj["email"] 

102 url = person_obj.get("url") 

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

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

105 contr = roles["role"] 

106 else: 

107 contr = None 

108 

109 return Person( 

110 given_names=gnames, 

111 family_names=fname, 

112 email=email, 

113 orcid=maybe_orcid, 

114 contribution_types=contr, 

115 ) 

116 

117 # no search keywords supported in POM 

118 @property 

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

120 """Return the keywords of the project.""" 

121 pass 

122 

123 @keywords.setter 

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

125 """Set the keywords of the project.""" 

126 pass 

127 

128 # authors must be a list 

129 @property 

130 def authors(self): 

131 """Return the authors of the project.""" 

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

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

134 

135 @authors.setter 

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

137 """Set the authors of the project.""" 

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

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

140 

141 # contributors must be a list 

142 @property 

143 def contributors(self): 

144 """Return the contributors of the project.""" 

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

146 if contr is None: 

147 return [] 

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

149 

150 @contributors.setter 

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

152 """Set the contributors of the project.""" 

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

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

155 

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

157 @property 

158 def maintainers(self): 

159 """Return the maintainers of the project.""" 

160 return [] 

161 

162 @maintainers.setter 

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

164 """Set the maintainers of the project.""" 

165 pass 

166 

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

168 @property 

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

170 """Return the license of the project.""" 

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

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

173 

174 @license.setter 

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

176 """Set the license of the project.""" 

177 self._set_property( 

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

179 ) 

180 

181 @property 

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

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

184 repo = super().repository 

185 if isinstance(repo, str): 

186 return repo 

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

188 

189 @repository.setter 

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

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

192 self._set_property( 

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

194 ) 

195 

196 @property 

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

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

199 docs = super().documentation 

200 if isinstance(docs, str): 

201 return docs 

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

203 

204 @documentation.setter 

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

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

207 self._set_property( 

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

209 ) 

210 

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

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

213 

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

215 """ 

216 super().sync(metadata) 

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