Skip to content

Commit 476e575

Browse files
committed
implemented POM adapter for somesy
1 parent 9451bfa commit 476e575

12 files changed

Lines changed: 386 additions & 34 deletions

File tree

src/somesy/codemeta/writer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,6 @@ def sync(self, metadata: ProjectMetadata) -> None:
167167
Use existing sync function from ProjectMetadataWriter but update repository and contributors.
168168
"""
169169
super().sync(metadata)
170-
self.contributors = self._sync_person_list(self.contributors, metadata.people)
170+
self.contributors = self._sync_person_list(
171+
self.contributors, metadata.contributors()
172+
)

src/somesy/core/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class SomesyBaseModel(BaseModel):
3636

3737
model_config = dict(
3838
extra="forbid",
39+
validate_assignment=True,
3940
populate_by_name=True,
4041
str_strip_whitespace=True,
4142
str_min_length=1,

src/somesy/core/writer.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __init__(
4949
direct_mappings: Dict with direct mappings of keys between somesy and target
5050
"""
5151
self._data: DictLike = {}
52-
self.path = path
52+
self.path = path if isinstance(path, Path) else Path(path)
5353
self.create_if_not_exists = create_if_not_exists
5454
self.direct_mappings = direct_mappings or {}
5555

@@ -100,7 +100,11 @@ def save(self, path: Optional[Path]) -> None:
100100
"""
101101

102102
def _get_property(
103-
self, key: Union[str, List[str]], *, remove: bool = False
103+
self,
104+
key: Union[str, List[str]],
105+
*,
106+
only_first: bool = False,
107+
remove: bool = False,
104108
) -> Optional[Any]:
105109
"""Get a property from the data.
106110
@@ -109,6 +113,7 @@ def _get_property(
109113
110114
Args:
111115
key: Name of the key or sequence of multiple keys to retrieve the value.
116+
only_first: If True, returns only first entry if the value is a list.
112117
remove: If True, will remove the retrieved value and clean up the dict.
113118
"""
114119
key_path = [key] if isinstance(key, str) else key
@@ -117,6 +122,7 @@ def _get_property(
117122
seq = [curr]
118123
for k in key_path:
119124
curr = curr.get(k)
125+
curr = curr[0] if isinstance(curr, list) and only_first else curr
120126
seq.append(curr)
121127
if curr is None:
122128
return None
@@ -129,11 +135,15 @@ def _get_property(
129135
if not dct.get(key):
130136
del dct[key]
131137

138+
if isinstance(curr, list) and only_first:
139+
return curr[0]
132140
return curr
133141

134142
def _set_property(self, key: Union[str, List[str], IgnoreKey], value: Any) -> None:
135143
"""Set a property in the data.
136144
145+
Note if there are lists along the path, they are cleared out.
146+
137147
Override this to e.g. rewrite the retrieved key
138148
(e.g. if everything relevant is in some subobject).
139149
"""
@@ -151,6 +161,7 @@ def _set_property(self, key: Union[str, List[str], IgnoreKey], value: Any) -> No
151161
if key not in curr:
152162
curr[key] = {}
153163
curr = curr[key]
164+
154165
curr[key_path[-1]] = value
155166

156167
# ----
@@ -253,6 +264,9 @@ def sync(self, metadata: ProjectMetadata) -> None:
253264
self.maintainers = self._sync_person_list(
254265
self.maintainers, metadata.maintainers()
255266
)
267+
# self.contributors = self._sync_person_list(
268+
# self.contributors, metadata.contributors()
269+
# )
256270

257271
self.license = metadata.license.value
258272

@@ -275,8 +289,6 @@ def _to_person(person_obj: Any) -> Person:
275289
@classmethod
276290
def _parse_people(cls, people: Optional[List[Any]]) -> List[Person]:
277291
"""Return a list of Persons parsed from list of format-specific people representations."""
278-
if not people:
279-
return []
280292
return list(map(cls._to_person, people or []))
281293

282294
# ----

src/somesy/package_json/writer.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from collections import OrderedDict
44
from pathlib import Path
5-
from typing import List, Optional
5+
from typing import Dict, List, Optional, Union
66

77
from rich.pretty import pretty_repr
88

@@ -112,5 +112,19 @@ def sync(self, metadata: ProjectMetadata) -> None:
112112
"""
113113
super().sync(metadata)
114114
self.contributors = self._sync_person_list(self.contributors, metadata.people)
115-
if metadata.repository:
116-
self.repository = {"type": "git", "url": str(metadata.repository)}
115+
116+
@property
117+
def repository(self) -> Optional[Union[str, Dict]]:
118+
"""Return the repository url of the project."""
119+
if repo := super().repository:
120+
if isinstance(repo, str):
121+
return repo
122+
else:
123+
return repo.get("url")
124+
else:
125+
return None
126+
127+
@repository.setter
128+
def repository(self, value: Optional[Union[str, Dict]]) -> None:
129+
"""Set the repository url of the project."""
130+
self._set_property(self._get_key("repository"), dict(type="git", url=value))

src/somesy/pom_xml/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# some POM-related constants and reusable objects
44
POM_URL = "http://maven.apache.org/POM/4.0.0"
55
POM_PREF = "{" + POM_URL + "}"
6-
POM_NS_MAP = dict(pom=POM_URL)
76
POM_ROOT_ATRS = {
87
"xmlns": "http://maven.apache.org/POM/4.0.0",
98
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",

src/somesy/pom_xml/writer.py

Lines changed: 149 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
import logging
33
import xml.etree.ElementTree as ET
44
from pathlib import Path
5-
from typing import Optional
5+
from typing import Any, Dict, List, Optional, Union
66

77
from somesy.core.models import Person
88
from somesy.core.writer import FieldKeyMapping, ProjectMetadataWriter
99

1010
from . import POM_ROOT_ATRS, POM_URL
1111
from .xmlproxy import XMLProxy
1212

13-
ET.register_namespace("pom", POM_URL) # globally register xml namespace for POM
1413
logger = logging.getLogger("somesy")
1514

1615

@@ -30,10 +29,12 @@ def __init__(
3029
See [somesy.core.writer.ProjectMetadataWriter.__init__][].
3130
"""
3231
mappings: FieldKeyMapping = {
33-
"year": ["inceptionYear"],
32+
# "year": ["inceptionYear"], # not supported by somesy + does not really change
33+
# "project_slug": ["artifactId"], # not supported by somesy for sync
3434
"license": ["licenses", "license"],
3535
"homepage": ["url"],
36-
"project_slug": ["artifactId"],
36+
"repository": ["scm"],
37+
"documentation": ["distributionManagement", "site"],
3738
"authors": ["developers", "developer"],
3839
"contributors": ["contributors", "contributor"],
3940
}
@@ -45,10 +46,11 @@ def _init_new_file(self):
4546
"""Initialize new pom.xml file."""
4647
pom = XMLProxy(ET.Element("project", POM_ROOT_ATRS))
4748
pom["properties"] = {"info.versionScheme": "semver-spec"}
48-
pom.write(self.path, default_namespace=POM_URL)
49+
pom.write(self.path)
4950

5051
def _load(self):
5152
"""Load the POM file."""
53+
ET.register_namespace("", POM_URL) # register POM as default xml namespace
5254
self._data = XMLProxy.parse(self.path, default_namespace=POM_URL)
5355

5456
def _validate(self):
@@ -57,30 +59,158 @@ def _validate(self):
5759

5860
def save(self, path: Optional[Path] = None) -> None:
5961
"""Save the POM DOM to a file."""
60-
path = path or self.path
61-
self._data.write(path)
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
6278

6379
@staticmethod
6480
def _from_person(person: Person):
65-
"""Convert project metadata person object to cff dict for person format."""
66-
ret = {}
67-
person_id = person.orcid or person.to_name_email_string()
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()
6884
ret["id"] = person_id
69-
ret["name"] = person.name
85+
ret["name"] = person.full_name
7086
ret["email"] = person.email
7187
if person.orcid:
72-
ret["url"] = person.orcid
88+
ret["url"] = str(person.orcid)
7389
if person.contribution_types:
74-
ret["roles"] = dict(role=person.contribution_types)
90+
ret["roles"] = dict(role=[c.value for c in person.contribution_types])
7591
return ret
7692

7793
@staticmethod
7894
def _to_person(person_obj) -> Person:
79-
"""Parse CFF Person to a somesy 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+
80108
return Person(
81-
name=person_obj["name"],
82-
email=person_obj["email"],
83-
orcid=person_obj["orcid"],
84-
contribution_types=person_obj["roles"]["role"],
109+
given_names=gnames,
110+
family_names=fname,
111+
email=email,
112+
orcid=maybe_orcid,
113+
contribution_types=contr,
85114
)
86-
raise NotImplementedError
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)

0 commit comments

Comments
 (0)