Coverage for src/somesy/mkdocs/writer.py: 97%
59 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-03 13:13 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-03 13:13 +0000
1"""Project documentation with Markdown (MkDocs) parser and saver."""
3import logging
4from pathlib import Path
6from rich.pretty import pretty_repr
7from ruamel.yaml import YAML
8from ruamel.yaml.scalarstring import LiteralScalarString
10from somesy.core.models import Entity, Person, ProjectMetadata, parse_name_email
11from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter
12from somesy.mkdocs.models import MkDocsConfig
14logger = logging.getLogger("somesy")
17class MkDocs(ProjectMetadataWriter):
18 """Project documentation with Markdown (MkDocs) parser and saver."""
20 def __init__(
21 self,
22 path: Path,
23 create_if_not_exists: bool = False,
24 pass_validation: bool | None = False,
25 ):
26 """Project documentation with Markdown (MkDocs) parser.
28 See [somesy.core.writer.ProjectMetadataWriter.__init__][].
29 """
30 self._yaml = YAML()
31 self._yaml.preserve_quotes = True
33 mappings: FieldKeyMapping = {
34 "name": ["site_name"],
35 "description": ["site_description"],
36 "homepage": ["site_url"],
37 "repository": ["repo_url"],
38 "authors": ["site_author"],
39 "documentation": IgnoreKey(),
40 "version": IgnoreKey(),
41 "maintainers": IgnoreKey(),
42 "license": IgnoreKey(),
43 "keywords": IgnoreKey(),
44 }
45 super().__init__(
46 path,
47 create_if_not_exists=create_if_not_exists,
48 direct_mappings=mappings,
49 pass_validation=pass_validation,
50 )
52 def _load(self):
53 """Load the MkDocs file."""
54 with open(self.path, encoding="utf-8") as f:
55 self._data = self._yaml.load(f)
57 def _validate(self) -> None:
58 """Validate the MkDocs file."""
59 config = dict(self._get_property([]))
60 logger.debug(
61 f"Validating config using {MkDocsConfig.__name__}: {pretty_repr(config)}"
62 )
63 MkDocsConfig(**config)
65 def save(self, path: Path | None = None) -> None:
66 """Save the MkDocs object to a file."""
67 path = path or self.path
69 # if description have new line characters, it should be saved as multiline string
70 if self._data is not None and "site_description" in self._data:
71 if isinstance(self._data["site_description"], str) and (
72 "\n" in self._data["site_description"]
73 ):
74 self._data["site_description"] = LiteralScalarString(
75 self._data["site_description"]
76 )
77 else:
78 self._data["site_description"] = str(self.description)
80 self._yaml.dump(self._data, path)
82 @property
83 def authors(self):
84 """Return the only author from the source file as list."""
85 authors = self._get_property(self._get_key("authors"))
86 if authors is None or self._to_person(authors) is None:
87 return []
88 else:
89 return [authors]
91 @authors.setter
92 def authors(self, authors: list[Entity | Person]) -> None:
93 """Set the authors of the project."""
94 author = self._from_person(authors[0])
95 self._set_property(self._get_key("authors"), author)
97 @staticmethod
98 def _from_person(person: Entity | Person):
99 """MkDocs Person is a string with full name."""
100 return person.to_name_email_string()
102 @staticmethod
103 def _to_person(person_obj: str) -> Entity | Person | None:
104 """MkDocs Person is a string with full name."""
105 return parse_name_email(person_obj)
107 def sync(self, metadata: ProjectMetadata) -> None:
108 """Sync the MkDocs object with the ProjectMetadata object."""
109 if metadata.name is not None:
110 self.name = metadata.name
111 if metadata.description is not None:
112 self.description = metadata.description
113 # no author merge since it is a free text field
114 if metadata.authors():
115 self.authors = metadata.authors()
116 if metadata.homepage:
117 self.homepage = str(metadata.homepage)
118 if metadata.repository:
119 self.repository = str(metadata.repository)
120 # display name like "org/repo", only if the user did not set one
121 if not self._get_property("repo_name"):
122 repo_name = metadata.repository.path or ""
123 self._set_property(
124 "repo_name", repo_name.strip("/").removesuffix(".git")
125 )