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