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

65 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-03-14 13:01 +0000

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

2 

3import logging 

4from pathlib import Path 

5from typing import List, Optional, Union 

6 

7from rich.pretty import pretty_repr 

8from ruamel.yaml import YAML 

9from ruamel.yaml.scalarstring import LiteralScalarString 

10 

11from somesy.core.models import Entity, Person, ProjectMetadata 

12from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter 

13from somesy.mkdocs.models import MkDocsConfig 

14 

15logger = logging.getLogger("somesy") 

16 

17 

18class MkDocs(ProjectMetadataWriter): 

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

20 

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. 

28 

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

30 """ 

31 self._yaml = YAML() 

32 self._yaml.preserve_quotes = True 

33 

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 ) 

52 

53 def _load(self): 

54 """Load the MkDocs file.""" 

55 with open(self.path) as f: 

56 self._data = self._yaml.load(f) 

57 

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) 

67 

68 def save(self, path: Optional[Path] = None) -> None: 

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

70 path = path or self.path 

71 

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) 

80 

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

82 

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] 

91 

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) 

97 

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

102 

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.") 

110 

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 

116 

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