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
« 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
7from somesy.core.models import Person
8from somesy.core.writer import FieldKeyMapping, ProjectMetadataWriter
10from . import POM_ROOT_ATRS, POM_URL
11from .xmlproxy import XMLProxy
13logger = logging.getLogger("somesy")
16class POM(ProjectMetadataWriter):
17 """Java Maven pom.xml parser and saver."""
19 # TODO: write a wrapper for ElementTree that behaves like a dict
20 # TODO: set up correct field name mappings
22 def __init__(
23 self,
24 path: Path,
25 create_if_not_exists: bool = True,
26 ):
27 """Java Maven pom.xml parser.
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 )
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)
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)
56 def _validate(self):
57 """Validate the POM file."""
58 logger.info("Cannot validate POM file, skipping validation.")
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)
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
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
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
108 return Person(
109 given_names=gnames,
110 family_names=fname,
111 email=email,
112 orcid=maybe_orcid,
113 contribution_types=contr,
114 )
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
122 @keywords.setter
123 def keywords(self, keywords: List[str]) -> None:
124 """Set the keywords of the project."""
125 pass
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]
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)
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]
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)
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 []
161 @maintainers.setter
162 def maintainers(self, maintainers: List[Person]) -> None:
163 """Set the maintainers of the project."""
164 pass
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
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 )
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
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 )
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
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 )
210 def sync(self, metadata) -> None:
211 """Sync codemeta.json with project metadata.
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)