Coverage for src/griffe2md/main.py: 78.31%

65 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-20 11:44 +0100

1"""Main function of the program.""" 

2 

3from __future__ import annotations 

4 

5import re 

6import sys 

7from pathlib import Path 

8from typing import IO, TYPE_CHECKING 

9 

10import mdformat 

11from griffe.docstrings import Parser 

12from griffe.loader import GriffeLoader 

13from jinja2 import Environment, FileSystemLoader 

14 

15from griffe2md import rendering 

16 

17if TYPE_CHECKING: 

18 from griffe import Object 

19 

20 

21def _output(text: str, to: IO | str | None = None) -> None: 

22 if isinstance(to, str): 22 ↛ 26line 22 didn't jump to line 26, because the condition on line 22 was never false

23 with open(to, "w") as output: 

24 output.write(text) 

25 else: 

26 if to is None: 

27 to = sys.stdout 

28 to.write(text) 

29 

30 

31def prepare_context(obj: Object, config: dict | None = None) -> dict: 

32 """Prepare Jinja context. 

33 

34 Parameters: 

35 obj: A Griffe object. 

36 config: The configuration options. 

37 

38 Returns: 

39 The Jinja context. 

40 """ 

41 config = config or dict(rendering.default_config) 

42 if config["filters"]: 42 ↛ 45line 42 didn't jump to line 45, because the condition on line 42 was never false

43 config["filters"] = [(re.compile(filtr.lstrip("!")), filtr.startswith("!")) for filtr in config["filters"]] 

44 

45 heading_level = config["heading_level"] 

46 try: 

47 config["members_order"] = rendering.Order(config["members_order"]) 

48 except ValueError as error: 

49 choices = "', '".join(item.value for item in rendering.Order) 

50 raise ValueError( 

51 f"Unknown members_order '{config['members_order']}', choose between '{choices}'.", 

52 ) from error 

53 

54 summary = config["summary"] 

55 if summary is True: 55 ↛ 62line 55 didn't jump to line 62, because the condition on line 55 was never false

56 config["summary"] = { 

57 "attributes": True, 

58 "functions": True, 

59 "classes": True, 

60 "modules": True, 

61 } 

62 elif summary is False: 

63 config["summary"] = { 

64 "attributes": False, 

65 "functions": False, 

66 "classes": False, 

67 "modules": False, 

68 } 

69 else: 

70 config["summary"] = { 

71 "attributes": summary.get("attributes", False), 

72 "functions": summary.get("functions", False), 

73 "classes": summary.get("classes", False), 

74 "modules": summary.get("modules", False), 

75 } 

76 

77 return { 

78 "config": config, 

79 obj.kind.value: obj, 

80 "heading_level": heading_level, 

81 "root": True, 

82 } 

83 

84 

85def prepare_env(env: Environment | None = None) -> Environment: 

86 """Prepare Jinja environment. 

87 

88 Parameters: 

89 env: A Jinja environment. 

90 

91 Returns: 

92 The Jinja environment. 

93 """ 

94 env = env or Environment( 

95 autoescape=False, # noqa: S701 

96 loader=FileSystemLoader([Path(__file__).parent / "templates"]), 

97 auto_reload=False, 

98 ) 

99 env.filters["any"] = rendering.do_any 

100 env.filters["heading"] = rendering.do_heading 

101 env.filters["as_attributes_section"] = rendering.do_as_attributes_section 

102 env.filters["as_classes_section"] = rendering.do_as_classes_section 

103 env.filters["as_functions_section"] = rendering.do_as_functions_section 

104 env.filters["as_modules_section"] = rendering.do_as_modules_section 

105 env.filters["filter_objects"] = rendering.do_filter_objects 

106 env.filters["format_code"] = rendering.do_format_code 

107 env.filters["format_signature"] = rendering.do_format_signature 

108 env.filters["format_attribute"] = rendering.do_format_attribute 

109 env.filters["order_members"] = rendering.do_order_members 

110 env.filters["split_path"] = rendering.do_split_path 

111 env.filters["stash_crossref"] = lambda ref, length: ref 

112 env.filters["from_private_package"] = rendering.from_private_package 

113 

114 return env 

115 

116 

117def render_object_docs(obj: Object, config: dict | None = None) -> str: 

118 """Render docs for a given object. 

119 

120 Parameters: 

121 obj: The Griffe object to render docs for. 

122 config: The rendering configuration. 

123 

124 Returns: 

125 Markdown. 

126 """ 

127 env = prepare_env() 

128 context = prepare_context(obj, config) 

129 rendered = env.get_template(f"{obj.kind.value}.md.jinja").render(**context) 

130 return mdformat.text(rendered) 

131 

132 

133def render_package_docs(package: str, config: dict | None = None) -> str: 

134 """Render docs for a given package. 

135 

136 Parameters: 

137 package: The package (name) to render docs for. 

138 config: The rendering configuration. 

139 

140 Returns: 

141 Markdown. 

142 """ 

143 config = config or dict(rendering.default_config) 

144 parser = config["docstring_style"] and Parser(config["docstring_style"]) 

145 loader = GriffeLoader(docstring_parser=parser) 

146 module = loader.load(package) 

147 loader.resolve_aliases(external=True) 

148 return render_object_docs(module, config) 

149 

150 

151def write_package_docs(package: str, config: dict | None = None, output: IO | str | None = None) -> None: 

152 """Write docs for a given package to a file or stdout. 

153 

154 Parameters: 

155 package: The package to render docs for. 

156 config: The rendering configuration. 

157 output: The file to write to. 

158 """ 

159 _output(render_package_docs(package, config), to=output)