From 1e5c28f36b0fd2d5ac1683c88d48e3d7c243e993 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 22:18:22 +0200 Subject: Adding upstream version 0.1.28. Signed-off-by: Daniel Baumann --- .../d_4f754ff76d8638bb_parser_doc_py.html | 372 +++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 coverage-report/d_4f754ff76d8638bb_parser_doc_py.html (limited to 'coverage-report/d_4f754ff76d8638bb_parser_doc_py.html') diff --git a/coverage-report/d_4f754ff76d8638bb_parser_doc_py.html b/coverage-report/d_4f754ff76d8638bb_parser_doc_py.html new file mode 100644 index 0000000..3ce54b7 --- /dev/null +++ b/coverage-report/d_4f754ff76d8638bb_parser_doc_py.html @@ -0,0 +1,372 @@ + + + + + Coverage for src/debputy/manifest_parser/parser_doc.py: 79% + + + + + +
+
+

+ Coverage for src/debputy/manifest_parser/parser_doc.py: + 79% +

+ +

+ 132 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.2.7, + created at 2024-04-07 12:14 +0200 +

+ +
+
+
+

1import itertools 

+

2from typing import Optional, Iterable, Any, Tuple, Mapping, Sequence, FrozenSet 

+

3 

+

4from debputy import DEBPUTY_DOC_ROOT_DIR 

+

5from debputy.manifest_parser.declarative_parser import ( 

+

6 DeclarativeMappingInputParser, 

+

7 DeclarativeNonMappingInputParser, 

+

8 AttributeDescription, 

+

9) 

+

10from debputy.plugin.api.impl_types import ( 

+

11 DebputyPluginMetadata, 

+

12 DeclarativeInputParser, 

+

13 DispatchingObjectParser, 

+

14 ListWrappedDeclarativeInputParser, 

+

15 InPackageContextParser, 

+

16) 

+

17from debputy.plugin.api.spec import ( 

+

18 ParserDocumentation, 

+

19 reference_documentation, 

+

20 undocumented_attr, 

+

21) 

+

22from debputy.util import assume_not_none 

+

23 

+

24 

+

25def _provide_placeholder_parser_doc( 

+

26 parser_doc: Optional[ParserDocumentation], 

+

27 attributes: Iterable[str], 

+

28) -> ParserDocumentation: 

+

29 if parser_doc is None: 29 ↛ 30line 29 didn't jump to line 30, because the condition on line 29 was never true

+

30 parser_doc = reference_documentation() 

+

31 changes = {} 

+

32 if parser_doc.attribute_doc is None: 

+

33 changes["attribute_doc"] = [undocumented_attr(attr) for attr in attributes] 

+

34 

+

35 if changes: 

+

36 return parser_doc.replace(**changes) 

+

37 return parser_doc 

+

38 

+

39 

+

40def doc_args_for_parser_doc( 

+

41 rule_name: str, 

+

42 declarative_parser: DeclarativeInputParser[Any], 

+

43 plugin_metadata: DebputyPluginMetadata, 

+

44) -> Tuple[Mapping[str, str], ParserDocumentation]: 

+

45 attributes: Iterable[str] 

+

46 if isinstance(declarative_parser, DeclarativeMappingInputParser): 

+

47 attributes = declarative_parser.source_attributes.keys() 

+

48 else: 

+

49 attributes = [] 

+

50 doc_args = { 

+

51 "RULE_NAME": rule_name, 

+

52 "MANIFEST_FORMAT_DOC": f"{DEBPUTY_DOC_ROOT_DIR}/MANIFEST-FORMAT.md", 

+

53 "PLUGIN_NAME": plugin_metadata.plugin_name, 

+

54 } 

+

55 parser_doc = _provide_placeholder_parser_doc( 

+

56 declarative_parser.inline_reference_documentation, 

+

57 attributes, 

+

58 ) 

+

59 return doc_args, parser_doc 

+

60 

+

61 

+

62def render_attribute_doc( 

+

63 parser: Any, 

+

64 attributes: Mapping[str, "AttributeDescription"], 

+

65 required_attributes: FrozenSet[str], 

+

66 conditionally_required_attributes: FrozenSet[FrozenSet[str]], 

+

67 parser_doc: ParserDocumentation, 

+

68 doc_args: Mapping[str, str], 

+

69 *, 

+

70 rule_name: str = "<unset>", 

+

71 is_root_rule: bool = False, 

+

72 is_interactive: bool = False, 

+

73) -> Iterable[Tuple[FrozenSet[str], Sequence[str]]]: 

+

74 provided_attribute_docs = ( 

+

75 parser_doc.attribute_doc if parser_doc.attribute_doc is not None else [] 

+

76 ) 

+

77 

+

78 for attr_doc in assume_not_none(provided_attribute_docs): 

+

79 attr_description = attr_doc.description 

+

80 rendered_doc = [] 

+

81 

+

82 for parameter in sorted(attr_doc.attributes): 

+

83 parameter_details = attributes.get(parameter) 

+

84 if parameter_details is not None: 84 ↛ 88line 84 didn't jump to line 88, because the condition on line 84 was never false

+

85 source_name = parameter_details.source_attribute_name 

+

86 describe_type = parameter_details.type_validator.describe_type() 

+

87 else: 

+

88 assert isinstance(parser, DispatchingObjectParser) 

+

89 source_name = parameter 

+

90 subparser = parser.parser_for(source_name).parser 

+

91 if isinstance(subparser, InPackageContextParser): 

+

92 if is_interactive: 

+

93 describe_type = "PackageContext" 

+

94 else: 

+

95 rule_prefix = rule_name if not is_root_rule else "" 

+

96 describe_type = f"PackageContext (chains to `{rule_prefix}::{subparser.manifest_attribute_path_template}`)" 

+

97 

+

98 elif isinstance(subparser, DispatchingObjectParser): 

+

99 if is_interactive: 

+

100 describe_type = "Object" 

+

101 else: 

+

102 rule_prefix = rule_name if not is_root_rule else "" 

+

103 describe_type = f"Object (see `{rule_prefix}::{subparser.manifest_attribute_path_template}`)" 

+

104 elif isinstance(subparser, DeclarativeMappingInputParser): 

+

105 describe_type = "<Type definition not implemented yet>" # TODO: Derive from subparser 

+

106 elif isinstance(subparser, DeclarativeNonMappingInputParser): 

+

107 describe_type = ( 

+

108 subparser.alt_form_parser.type_validator.describe_type() 

+

109 ) 

+

110 else: 

+

111 describe_type = f"<Unknown: Non-introspectable subparser - {subparser.__class__.__name__}>" 

+

112 

+

113 if source_name in required_attributes: 

+

114 req_str = "required" 

+

115 elif any(source_name in s for s in conditionally_required_attributes): 

+

116 req_str = "conditional" 

+

117 else: 

+

118 req_str = "optional" 

+

119 rendered_doc.append(f"`{source_name}` ({req_str}): {describe_type}") 

+

120 

+

121 if attr_description: 121 ↛ 130line 121 didn't jump to line 130, because the condition on line 121 was never false

+

122 rendered_doc.append("") 

+

123 rendered_doc.extend( 

+

124 line 

+

125 for line in attr_description.format(**doc_args).splitlines( 

+

126 keepends=False 

+

127 ) 

+

128 ) 

+

129 rendered_doc.append("") 

+

130 yield attr_doc.attributes, rendered_doc 

+

131 

+

132 

+

133def render_rule( 

+

134 rule_name: str, 

+

135 declarative_parser: DeclarativeInputParser[Any], 

+

136 plugin_metadata: DebputyPluginMetadata, 

+

137 *, 

+

138 is_root_rule: bool = False, 

+

139) -> str: 

+

140 doc_args, parser_doc = doc_args_for_parser_doc( 

+

141 "the manifest root" if is_root_rule else rule_name, 

+

142 declarative_parser, 

+

143 plugin_metadata, 

+

144 ) 

+

145 t = assume_not_none(parser_doc.title).format(**doc_args) 

+

146 r = [ 

+

147 t, 

+

148 "=" * len(t), 

+

149 "", 

+

150 assume_not_none(parser_doc.description).format(**doc_args).rstrip(), 

+

151 "", 

+

152 ] 

+

153 

+

154 alt_form_parser = getattr(declarative_parser, "alt_form_parser", None) 

+

155 is_list_wrapped = False 

+

156 unwrapped_parser = declarative_parser 

+

157 if isinstance(declarative_parser, ListWrappedDeclarativeInputParser): 

+

158 is_list_wrapped = True 

+

159 unwrapped_parser = declarative_parser.delegate 

+

160 

+

161 if isinstance( 

+

162 unwrapped_parser, (DeclarativeMappingInputParser, DispatchingObjectParser) 

+

163 ): 

+

164 

+

165 if isinstance(unwrapped_parser, DeclarativeMappingInputParser): 165 ↛ 171line 165 didn't jump to line 171, because the condition on line 165 was never false

+

166 attributes = unwrapped_parser.source_attributes 

+

167 required = unwrapped_parser.input_time_required_parameters 

+

168 conditionally_required = unwrapped_parser.at_least_one_of 

+

169 mutually_exclusive = unwrapped_parser.mutually_exclusive_attributes 

+

170 else: 

+

171 attributes = {} 

+

172 required = frozenset() 

+

173 conditionally_required = frozenset() 

+

174 mutually_exclusive = frozenset() 

+

175 if is_list_wrapped: 

+

176 r.append("List where each element has the following attributes:") 

+

177 else: 

+

178 r.append("Attributes:") 

+

179 

+

180 rendered_attr_doc = render_attribute_doc( 

+

181 unwrapped_parser, 

+

182 attributes, 

+

183 required, 

+

184 conditionally_required, 

+

185 parser_doc, 

+

186 doc_args, 

+

187 is_root_rule=is_root_rule, 

+

188 rule_name=rule_name, 

+

189 is_interactive=False, 

+

190 ) 

+

191 for _, rendered_doc in rendered_attr_doc: 

+

192 prefix = " - " 

+

193 for line in rendered_doc: 

+

194 if line: 

+

195 r.append(f"{prefix}{line}") 

+

196 else: 

+

197 r.append("") 

+

198 prefix = " " 

+

199 

+

200 if ( 

+

201 bool(conditionally_required) 

+

202 or bool(mutually_exclusive) 

+

203 or any(pd.conflicting_attributes for pd in attributes.values()) 

+

204 ): 

+

205 r.append("") 

+

206 if is_list_wrapped: 

+

207 r.append( 

+

208 "This rule enforces the following restrictions on each element in the list:" 

+

209 ) 

+

210 else: 

+

211 r.append("This rule enforces the following restrictions:") 

+

212 

+

213 if conditionally_required or mutually_exclusive: 213 ↛ 231line 213 didn't jump to line 231, because the condition on line 213 was never false

+

214 all_groups = set( 

+

215 itertools.chain(conditionally_required, mutually_exclusive) 

+

216 ) 

+

217 for g in all_groups: 

+

218 anames = "`, `".join(g) 

+

219 is_mx = g in mutually_exclusive 

+

220 is_cr = g in conditionally_required 

+

221 if is_mx and is_cr: 

+

222 r.append(f" - The rule must use exactly one of: `{anames}`") 

+

223 elif is_cr: 223 ↛ 224line 223 didn't jump to line 224, because the condition on line 223 was never true

+

224 r.append(f" - The rule must use at least one of: `{anames}`") 

+

225 else: 

+

226 assert is_mx 

+

227 r.append( 

+

228 f" - The following attributes are mutually exclusive: `{anames}`" 

+

229 ) 

+

230 

+

231 if mutually_exclusive or any( 231 ↛ exit,   231 ↛ 2482 missed branches: 1) line 231 didn't run the generator expression on line 231, 2) line 231 didn't jump to line 248, because the condition on line 231 was never false

+

232 pd.conflicting_attributes for pd in attributes.values() 

+

233 ): 

+

234 for parameter, parameter_details in sorted(attributes.items()): 

+

235 source_name = parameter_details.source_attribute_name 

+

236 conflicts = set(parameter_details.conflicting_attributes) 

+

237 for mx in mutually_exclusive: 

+

238 if parameter in mx and mx not in conditionally_required: 238 ↛ 239line 238 didn't jump to line 239, because the condition on line 238 was never true

+

239 conflicts |= mx 

+

240 if conflicts: 

+

241 conflicts.discard(parameter) 

+

242 cnames = "`, `".join( 

+

243 attributes[a].source_attribute_name for a in conflicts 

+

244 ) 

+

245 r.append( 

+

246 f" - The attribute `{source_name}` cannot be used with any of: `{cnames}`" 

+

247 ) 

+

248 r.append("") 

+

249 if alt_form_parser is not None: 

+

250 # FIXME: Mapping[str, Any] ends here, which is ironic given the headline. 

+

251 r.append( 

+

252 f"Non-mapping format: {alt_form_parser.type_validator.describe_type()}" 

+

253 ) 

+

254 alt_parser_desc = parser_doc.alt_parser_description 

+

255 if alt_parser_desc: 

+

256 r.extend( 

+

257 f" {line}" 

+

258 for line in alt_parser_desc.format(**doc_args).splitlines( 

+

259 keepends=False 

+

260 ) 

+

261 ) 

+

262 r.append("") 

+

263 

+

264 if declarative_parser.reference_documentation_url is not None: 

+

265 r.append( 

+

266 f"Reference documentation: {declarative_parser.reference_documentation_url}" 

+

267 ) 

+

268 else: 

+

269 r.append( 

+

270 "Reference documentation: No reference documentation link provided by the plugin" 

+

271 ) 

+

272 

+

273 return "\n".join(r) 

+
+ + + -- cgit v1.2.3