Coverage for src/somesy/fortran/writer.py: 84%
92 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-04 11:35 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-04 11:35 +0000
1"""Fortran writer."""
3import logging
4from pathlib import Path
6import tomlkit
7from rich.pretty import pretty_repr
8from tomlkit.items import InlineTable
10from somesy.core.models import Entity, Person, ProjectMetadata
11from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter
13from .models import FortranConfig
15logger = logging.getLogger("somesy")
18class Fortran(ProjectMetadataWriter):
19 """Fortran config file handler parsed from fpm.toml."""
21 def __init__(
22 self,
23 path: Path,
24 pass_validation: bool | None = False,
25 ):
26 """Fortran config file handler parsed from fpm.toml.
28 See [somesy.core.writer.ProjectMetadataWriter.__init__][].
29 """
30 mappings: FieldKeyMapping = {
31 "authors": ["author"],
32 "maintainers": ["maintainer"],
33 "documentation": IgnoreKey(),
34 }
35 super().__init__(
36 path,
37 create_if_not_exists=False,
38 direct_mappings=mappings,
39 pass_validation=pass_validation,
40 )
42 @property
43 def authors(self):
44 """Return the only author of the fpm.toml file as list."""
45 authors = []
46 try:
47 self._to_person(self._get_property(self._get_key("authors")))
48 authors = [self._get_property(self._get_key("authors"))]
49 except ValueError:
50 logger.warning("Cannot convert authors to Person object.")
51 return authors
53 @authors.setter
54 def authors(self, authors: list[Person | Entity]) -> None:
55 """Set the authors of the project."""
56 self._set_property(self._get_key("authors"), self._from_person(authors[0]))
58 @property
59 def maintainers(self):
60 """Return the only author of the fpm.toml file as list."""
61 maintainers = self._get_property(self._get_key("maintainers"))
62 if maintainers:
63 return [self._get_property(self._get_key("maintainers"))]
64 return []
66 @maintainers.setter
67 def maintainers(self, maintainers: list[Person | Entity]) -> None:
68 """Set the maintainers of the project."""
69 maintainer = self._from_person(maintainers[0])
70 self._set_property(self._get_key("maintainers"), maintainer)
72 def _load(self) -> None:
73 """Load fpm.toml file."""
74 with open(self.path) as f:
75 self._data = tomlkit.load(f)
77 def _validate(self) -> None:
78 """Validate poetry config using pydantic class.
80 In order to preserve toml comments and structure, tomlkit library is used.
81 Pydantic class only used for validation.
82 """
83 if self.pass_validation:
84 return
85 config = dict(self._get_property([]))
86 logger.debug(
87 f"Validating config using {FortranConfig.__name__}: {pretty_repr(config)}"
88 )
89 FortranConfig(**config)
91 def save(self, path: Path | None = None) -> None:
92 """Save the fpm file."""
93 path = path or self.path
94 if "description" in self._data and "\n" in self._data["description"]:
95 self._data["description"] = tomlkit.string(
96 self._data["description"], multiline=True
97 )
99 # Handle arrays with proper formatting
100 for key, value in self._data.items():
101 if isinstance(value, list):
102 array = tomlkit.array()
103 array.extend(value)
104 array.multiline(True)
105 # Ensure whitespace after commas in inline tables
106 for item in array:
107 if isinstance(item, InlineTable):
108 # Rebuild the inline table with desired formatting
109 formatted_item = tomlkit.inline_table()
110 for k, v in item.value.items():
111 formatted_item[k] = v
112 formatted_item.trivia.trail = " " # Add space after each comma
113 array[array.index(item)] = formatted_item
114 self._data[key] = array
115 else:
116 self._data[key] = value
118 with open(path, "w") as f:
119 tomlkit.dump(self._data, f)
121 @staticmethod
122 def _from_person(person: Person | Entity):
123 """Convert project metadata person/entity object to poetry string for person format "full name <email>."""
124 return person.to_name_email_string()
126 @staticmethod
127 def _to_person(person_obj: str) -> Person | Entity | None:
128 """Convert from free string to person or entity object."""
129 try:
130 return Person.from_name_email_string(person_obj)
131 except (ValueError, AttributeError):
132 logger.info(f"Cannot convert {person_obj} to Person object, trying Entity.")
134 try:
135 return Entity.from_name_email_string(person_obj)
136 except (ValueError, AttributeError):
137 logger.warning(f"Cannot convert {person_obj} to Entity.")
138 return None
140 def sync(self, metadata: ProjectMetadata) -> None:
141 """Sync output file with other metadata files."""
142 self.name = metadata.name
143 self.description = metadata.description
145 if metadata.version:
146 self.version = metadata.version
148 if metadata.keywords:
149 self.keywords = metadata.keywords
151 self.authors = metadata.authors()
152 maintainers = metadata.maintainers()
154 # set if not empty
155 if maintainers:
156 # only one maintainer is allowed
157 self.maintainers = maintainers
159 licenses = metadata.license
160 self.license = (
161 " OR ".join(license.value for license in licenses)
162 if isinstance(licenses, list)
163 else licenses.value
164 )
166 self.homepage = str(metadata.homepage) if metadata.homepage else None