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
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:42 +0000
1"""Writer adapter for pom.xml files."""
3import logging
4import xml.etree.ElementTree as ET
5from pathlib import Path
6from typing import Any, Dict, List, Optional, Union
8from somesy.core.models import Person
9from somesy.core.writer import FieldKeyMapping, ProjectMetadataWriter
11from . import POM_ROOT_ATRS, POM_URL
12from .xmlproxy import XMLProxy
14logger = logging.getLogger("somesy")
17class POM(ProjectMetadataWriter):
18 """Java Maven pom.xml parser and saver."""
20 # TODO: write a wrapper for ElementTree that behaves like a dict
21 # TODO: set up correct field name mappings
23 def __init__(
24 self,
25 path: Path,
26 create_if_not_exists: bool = True,
27 ):
28 """Java Maven pom.xml parser.
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 )
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)
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)
57 def _validate(self):
58 """Validate the POM file."""
59 logger.info("Cannot validate POM file, skipping validation.")
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)
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
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
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
109 return Person(
110 given_names=gnames,
111 family_names=fname,
112 email=email,
113 orcid=maybe_orcid,
114 contribution_types=contr,
115 )
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
123 @keywords.setter
124 def keywords(self, keywords: List[str]) -> None:
125 """Set the keywords of the project."""
126 pass
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]
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)
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]
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)
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 []
162 @maintainers.setter
163 def maintainers(self, maintainers: List[Person]) -> None:
164 """Set the maintainers of the project."""
165 pass
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
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 )
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
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 )
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
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 )
211 def sync(self, metadata) -> None:
212 """Sync codemeta.json with project metadata.
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)