Coverage for src/somesy/mkdocs/writer.py: 97%

59 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-11-03 13:31 +0000

1"""Project documentation with Markdown (MkDocs) parser and saver.""" 

2 

3import logging 

4from pathlib import Path 

5 

6from rich.pretty import pretty_repr 

7from ruamel.yaml import YAML 

8from ruamel.yaml.scalarstring import LiteralScalarString 

9 

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 

13 

14logger = logging.getLogger("somesy") 

15 

16 

17class MkDocs(ProjectMetadataWriter): 

18 """Project documentation with Markdown (MkDocs) parser and saver.""" 

19 

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. 

27 

28 See [somesy.core.writer.ProjectMetadataWriter.__init__][]. 

29 """ 

30 self._yaml = YAML() 

31 self._yaml.preserve_quotes = True 

32 

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 ) 

51 

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) 

56 

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) 

64 

65 def save(self, path: Path | None = None) -> None: 

66 """Save the MkDocs object to a file.""" 

67 path = path or self.path 

68 

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) 

79 

80 self._yaml.dump(self._data, path) 

81 

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] 

90 

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) 

96 

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() 

101 

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) 

106 

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 )