Coverage for src/somesy/cff/writer.py: 100%
41 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"""Citation File Format (CFF) parser and saver."""
2import json
3from pathlib import Path
4from typing import Optional
6from cffconvert.cli.create_citation import create_citation
7from ruamel.yaml import YAML
9from somesy.core.models import Person, ProjectMetadata
10from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter
13class CFF(ProjectMetadataWriter):
14 """Citation File Format (CFF) parser and saver."""
16 def __init__(
17 self,
18 path: Path,
19 create_if_not_exists: bool = True,
20 ):
21 """Citation File Format (CFF) parser.
23 See [somesy.core.writer.ProjectMetadataWriter.__init__][].
24 """
25 self._yaml = YAML()
26 self._yaml.preserve_quotes = True
28 mappings: FieldKeyMapping = {
29 "name": ["title"],
30 "description": ["abstract"],
31 "homepage": ["url"],
32 "repository": ["repository-code"],
33 "documentation": IgnoreKey(),
34 "maintainers": ["contact"],
35 }
36 super().__init__(
37 path, create_if_not_exists=create_if_not_exists, direct_mappings=mappings
38 )
40 def _init_new_file(self):
41 """Initialize new CFF file."""
42 self._data = {
43 "cff-version": "1.2.0",
44 "message": "If you use this software, please cite it using these metadata.",
45 "type": "software",
46 }
47 with open(self.path, "w") as f:
48 self._yaml.dump(self._data, f)
50 def _load(self):
51 """Load the CFF file."""
52 with open(self.path) as f:
53 self._data = self._yaml.load(f)
55 def _validate(self):
56 """Validate the CFF file."""
57 try:
58 citation = create_citation(self.path, None)
59 citation.validate()
60 except ValueError as e:
61 raise ValueError(f"CITATION.cff file is not valid!\n{e}") from e
63 def save(self, path: Optional[Path] = None) -> None:
64 """Save the CFF object to a file."""
65 path = path or self.path
66 self._yaml.dump(self._data, path)
68 def _sync_authors(self, metadata: ProjectMetadata) -> None:
69 """Ensure that publication authors are added all into author list."""
70 self.authors = self._sync_person_list(
71 self.authors, metadata.publication_authors()
72 )
74 @staticmethod
75 def _from_person(person: Person):
76 """Convert project metadata person object to cff dict for person format."""
77 json_str = person.model_dump_json(
78 exclude={
79 "contribution",
80 "contribution_types",
81 "contribution_begin",
82 "contribution_end",
83 "author",
84 "publication_author",
85 "maintainer",
86 },
87 by_alias=True, # e.g. family_names -> family-names, etc.
88 )
89 return json.loads(json_str)
91 @staticmethod
92 def _to_person(person_obj) -> Person:
93 """Parse CFF Person to a somesy Person."""
94 # construct (partial) Person while preserving key order from YAML
95 Person._aliases()
96 ret = Person.make_partial(person_obj)
97 ret.set_key_order(list(person_obj.keys()))
98 return ret