Coverage for src/somesy/mkdocs/writer.py: 82%
65 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-03-14 13:02 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-03-14 13:02 +0000
1"""Project documentation with Markdown (MkDocs) parser and saver."""
3import logging
4from pathlib import Path
5from typing import List, Optional, Union
7from rich.pretty import pretty_repr
8from ruamel.yaml import YAML
9from ruamel.yaml.scalarstring import LiteralScalarString
11from somesy.core.models import Entity, Person, ProjectMetadata
12from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter
13from somesy.mkdocs.models import MkDocsConfig
15logger = logging.getLogger("somesy")
18class MkDocs(ProjectMetadataWriter):
19 """Project documentation with Markdown (MkDocs) parser and saver."""
21 def __init__(
22 self,
23 path: Path,
24 create_if_not_exists: bool = False,
25 pass_validation: Optional[bool] = False,
26 ):
27 """Project documentation with Markdown (MkDocs) parser.
29 See [somesy.core.writer.ProjectMetadataWriter.__init__][].
30 """
31 self._yaml = YAML()
32 self._yaml.preserve_quotes = True
34 mappings: FieldKeyMapping = {
35 "name": ["site_name"],
36 "description": ["site_description"],
37 "homepage": ["site_url"],
38 "repository": ["repo_url"],
39 "authors": ["site_author"],
40 "documentation": IgnoreKey(),
41 "version": IgnoreKey(),
42 "maintainers": IgnoreKey(),
43 "license": IgnoreKey(),
44 "keywords": IgnoreKey(),
45 }
46 super().__init__(
47 path,
48 create_if_not_exists=create_if_not_exists,
49 direct_mappings=mappings,
50 pass_validation=pass_validation,
51 )
53 def _load(self):
54 """Load the MkDocs file."""
55 with open(self.path) as f:
56 self._data = self._yaml.load(f)
58 def _validate(self) -> None:
59 """Validate the MkDocs file."""
60 if self.pass_validation:
61 return
62 config = dict(self._get_property([]))
63 logger.debug(
64 f"Validating config using {MkDocsConfig.__name__}: {pretty_repr(config)}"
65 )
66 MkDocsConfig(**config)
68 def save(self, path: Optional[Path] = None) -> None:
69 """Save the MkDocs object to a file."""
70 path = path or self.path
72 # if description have new line characters, it should be saved as multiline string
73 if self._data is not None and "site_description" in self._data:
74 if "\n" in self._data["site_description"]:
75 self._data["site_description"] = LiteralScalarString(
76 self._data["site_description"]
77 )
78 else:
79 self._data["site_description"] = str(self.description)
81 self._yaml.dump(self._data, path)
83 @property
84 def authors(self):
85 """Return the only author from the source file as list."""
86 authors = self._get_property(self._get_key("authors"))
87 if authors is None or self._to_person(authors) is None:
88 return []
89 else:
90 return [authors]
92 @authors.setter
93 def authors(self, authors: List[Union[Entity, Person]]) -> None:
94 """Set the authors of the project."""
95 authors = self._from_person(authors[0])
96 self._set_property(self._get_key("authors"), authors)
98 @staticmethod
99 def _from_person(person: Union[Entity, Person]):
100 """MkDocs Person is a string with full name."""
101 return person.to_name_email_string()
103 @staticmethod
104 def _to_person(person: str) -> Optional[Union[Entity, Person]]:
105 """MkDocs Person is a string with full name."""
106 try:
107 return Person.from_name_email_string(person)
108 except (ValueError, AttributeError):
109 logger.info(f"Cannot convert {person} to Person object, trying Entity.")
111 try:
112 return Entity.from_name_email_string(person)
113 except (ValueError, AttributeError):
114 logger.warning(f"Cannot convert {person} to Entity.")
115 return None
117 def sync(self, metadata: ProjectMetadata) -> None:
118 """Sync the MkDocs object with the ProjectMetadata object."""
119 self.name = metadata.name
120 self.description = metadata.description
121 # no author merge since it is a free text field
122 self.authors = metadata.authors()
123 if metadata.homepage:
124 self.homepage = str(metadata.homepage)
125 if metadata.repository:
126 self.repository = str(metadata.repository)
127 self.repo_name = metadata.repository.path