From 11180d7be49c1912b7bdcba23d932747041e5b5d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 22:18:23 +0200 Subject: Merging upstream version 0.1.28. Signed-off-by: Daniel Baumann --- .../d_6e57078c9ef7177d_plugin_cmds_py.html | 1295 ++++++++++++++++++++ 1 file changed, 1295 insertions(+) create mode 100644 coverage-report/d_6e57078c9ef7177d_plugin_cmds_py.html (limited to 'coverage-report/d_6e57078c9ef7177d_plugin_cmds_py.html') diff --git a/coverage-report/d_6e57078c9ef7177d_plugin_cmds_py.html b/coverage-report/d_6e57078c9ef7177d_plugin_cmds_py.html new file mode 100644 index 0000000..2f6123b --- /dev/null +++ b/coverage-report/d_6e57078c9ef7177d_plugin_cmds_py.html @@ -0,0 +1,1295 @@ + + + + + Coverage for src/debputy/commands/debputy_cmd/plugin_cmds.py: 13% + + + + + +
+
+

+ Coverage for src/debputy/commands/debputy_cmd/plugin_cmds.py: + 13% +

+ +

+ 541 statements   + + + + +

+

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

+ +
+
+
+

1import argparse 

+

2import itertools 

+

3import operator 

+

4import os 

+

5import sys 

+

6from itertools import chain 

+

7from typing import ( 

+

8 Sequence, 

+

9 Union, 

+

10 Tuple, 

+

11 Iterable, 

+

12 Any, 

+

13 Optional, 

+

14 Type, 

+

15 Mapping, 

+

16 Callable, 

+

17) 

+

18 

+

19from debputy import DEBPUTY_DOC_ROOT_DIR 

+

20from debputy.commands.debputy_cmd.context import ( 

+

21 CommandContext, 

+

22 add_arg, 

+

23 ROOT_COMMAND, 

+

24) 

+

25from debputy.commands.debputy_cmd.dc_util import flatten_ppfs 

+

26from debputy.commands.debputy_cmd.output import ( 

+

27 _stream_to_pager, 

+

28 _output_styling, 

+

29 OutputStylingBase, 

+

30) 

+

31from debputy.exceptions import DebputySubstitutionError 

+

32from debputy.filesystem_scan import build_virtual_fs 

+

33from debputy.manifest_parser.base_types import TypeMapping 

+

34from debputy.manifest_parser.declarative_parser import ( 

+

35 DeclarativeMappingInputParser, 

+

36 DeclarativeNonMappingInputParser, 

+

37 BASIC_SIMPLE_TYPES, 

+

38) 

+

39from debputy.manifest_parser.parser_data import ParserContextData 

+

40from debputy.manifest_parser.parser_doc import render_rule 

+

41from debputy.manifest_parser.util import unpack_type, AttributePath 

+

42from debputy.packager_provided_files import detect_all_packager_provided_files 

+

43from debputy.plugin.api.example_processing import ( 

+

44 process_discard_rule_example, 

+

45 DiscardVerdict, 

+

46) 

+

47from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin 

+

48from debputy.plugin.api.impl_types import ( 

+

49 PackagerProvidedFileClassSpec, 

+

50 PluginProvidedManifestVariable, 

+

51 DispatchingParserBase, 

+

52 DeclarativeInputParser, 

+

53 DebputyPluginMetadata, 

+

54 DispatchingObjectParser, 

+

55 SUPPORTED_DISPATCHABLE_TABLE_PARSERS, 

+

56 OPARSER_MANIFEST_ROOT, 

+

57 PluginProvidedDiscardRule, 

+

58 AutomaticDiscardRuleExample, 

+

59 MetadataOrMaintscriptDetector, 

+

60 PluginProvidedTypeMapping, 

+

61) 

+

62from debputy.plugin.api.spec import ( 

+

63 ParserDocumentation, 

+

64 reference_documentation, 

+

65 undocumented_attr, 

+

66 TypeMappingExample, 

+

67) 

+

68from debputy.substitution import Substitution 

+

69from debputy.util import _error, assume_not_none, _warn 

+

70 

+

71plugin_dispatcher = ROOT_COMMAND.add_dispatching_subcommand( 

+

72 "plugin", 

+

73 "plugin_subcommand", 

+

74 default_subcommand="--help", 

+

75 help_description="Interact with debputy plugins", 

+

76 metavar="command", 

+

77) 

+

78 

+

79plugin_list_cmds = plugin_dispatcher.add_dispatching_subcommand( 

+

80 "list", 

+

81 "plugin_subcommand_list", 

+

82 metavar="topic", 

+

83 default_subcommand="plugins", 

+

84 help_description="List plugins or things provided by plugins (unstable format)." 

+

85 " Pass `--help` *after* `list` get a topic listing", 

+

86) 

+

87 

+

88plugin_show_cmds = plugin_dispatcher.add_dispatching_subcommand( 

+

89 "show", 

+

90 "plugin_subcommand_show", 

+

91 metavar="topic", 

+

92 help_description="Show details about a plugin or things provided by plugins (unstable format)." 

+

93 " Pass `--help` *after* `show` get a topic listing", 

+

94) 

+

95 

+

96 

+

97def format_output_arg( 

+

98 default_format: str, 

+

99 allowed_formats: Sequence[str], 

+

100 help_text: str, 

+

101) -> Callable[[argparse.ArgumentParser], None]: 

+

102 if default_format not in allowed_formats: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true

+

103 raise ValueError("The default format must be in the allowed_formats...") 

+

104 

+

105 def _configurator(argparser: argparse.ArgumentParser) -> None: 

+

106 argparser.add_argument( 

+

107 "--output-format", 

+

108 dest="output_format", 

+

109 default=default_format, 

+

110 choices=allowed_formats, 

+

111 help=help_text, 

+

112 ) 

+

113 

+

114 return _configurator 

+

115 

+

116 

+

117# To let --output-format=... "always" work 

+

118TEXT_ONLY_FORMAT = format_output_arg( 

+

119 "text", 

+

120 ["text"], 

+

121 "Select a given output format (options and output are not stable between releases)", 

+

122) 

+

123 

+

124 

+

125TEXT_CSV_FORMAT_NO_STABILITY_PROMISE = format_output_arg( 

+

126 "text", 

+

127 ["text", "csv"], 

+

128 "Select a given output format (options and output are not stable between releases)", 

+

129) 

+

130 

+

131 

+

132@plugin_list_cmds.register_subcommand( 

+

133 "plugins", 

+

134 help_description="List known plugins with their versions", 

+

135 argparser=TEXT_CSV_FORMAT_NO_STABILITY_PROMISE, 

+

136) 

+

137def _plugin_cmd_list_plugins(context: CommandContext) -> None: 

+

138 plugin_metadata_entries = context.load_plugins().plugin_data.values() 

+

139 # Because the "plugins" part is optional, we are not guaranteed that TEXT_CSV_FORMAT applies 

+

140 output_format = getattr(context.parsed_args, "output_format", "text") 

+

141 assert output_format in {"text", "csv"} 

+

142 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

143 fo.print_list_table( 

+

144 ["Plugin Name", "Plugin Path"], 

+

145 [(p.plugin_name, p.plugin_path) for p in plugin_metadata_entries], 

+

146 ) 

+

147 

+

148 

+

149def _path(path: str) -> str: 

+

150 if path.startswith("./"): 

+

151 return path[1:] 

+

152 return path 

+

153 

+

154 

+

155def _ppf_flags(ppf: PackagerProvidedFileClassSpec) -> str: 

+

156 flags = [] 

+

157 if ppf.allow_name_segment: 

+

158 flags.append("named") 

+

159 if ppf.allow_architecture_segment: 

+

160 flags.append("arch") 

+

161 if ppf.supports_priority: 

+

162 flags.append(f"priority={ppf.default_priority}") 

+

163 if ppf.packageless_is_fallback_for_all_packages: 

+

164 flags.append("main-all-fallback") 

+

165 if ppf.post_formatting_rewrite: 

+

166 flags.append("post-format-hook") 

+

167 return ",".join(flags) 

+

168 

+

169 

+

170@plugin_list_cmds.register_subcommand( 

+

171 ["used-packager-provided-files", "uppf", "u-p-p-f"], 

+

172 help_description="List packager provided files used by this package (debian/pkg.foo)", 

+

173 argparser=TEXT_ONLY_FORMAT, 

+

174) 

+

175def _plugin_cmd_list_uppf(context: CommandContext) -> None: 

+

176 ppf_table = context.load_plugins().packager_provided_files 

+

177 all_ppfs = detect_all_packager_provided_files( 

+

178 ppf_table, 

+

179 context.debian_dir, 

+

180 context.binary_packages(), 

+

181 ) 

+

182 requested_plugins = set(context.requested_plugins()) 

+

183 requested_plugins.add("debputy") 

+

184 all_detected_ppfs = list(flatten_ppfs(all_ppfs)) 

+

185 

+

186 used_ppfs = [ 

+

187 p 

+

188 for p in all_detected_ppfs 

+

189 if p.definition.debputy_plugin_metadata.plugin_name in requested_plugins 

+

190 ] 

+

191 inactive_ppfs = [ 

+

192 p 

+

193 for p in all_detected_ppfs 

+

194 if p.definition.debputy_plugin_metadata.plugin_name not in requested_plugins 

+

195 ] 

+

196 

+

197 if not used_ppfs and not inactive_ppfs: 

+

198 print("No packager provided files detected; not even a changelog... ?") 

+

199 return 

+

200 

+

201 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

202 if used_ppfs: 

+

203 headers: Sequence[Union[str, Tuple[str, str]]] = [ 

+

204 "File", 

+

205 "Matched Stem", 

+

206 "Installed Into", 

+

207 "Installed As", 

+

208 ] 

+

209 fo.print_list_table( 

+

210 headers, 

+

211 [ 

+

212 ( 

+

213 ppf.path.path, 

+

214 ppf.definition.stem, 

+

215 ppf.package_name, 

+

216 "/".join(ppf.compute_dest()).lstrip("."), 

+

217 ) 

+

218 for ppf in sorted( 

+

219 used_ppfs, key=operator.attrgetter("package_name") 

+

220 ) 

+

221 ], 

+

222 ) 

+

223 

+

224 if inactive_ppfs: 

+

225 headers: Sequence[Union[str, Tuple[str, str]]] = [ 

+

226 "UNUSED FILE", 

+

227 "Matched Stem", 

+

228 "Installed Into", 

+

229 "Could Be Installed As", 

+

230 "If B-D Had", 

+

231 ] 

+

232 fo.print_list_table( 

+

233 headers, 

+

234 [ 

+

235 ( 

+

236 f"~{ppf.path.path}~", 

+

237 ppf.definition.stem, 

+

238 f"~{ppf.package_name}~", 

+

239 "/".join(ppf.compute_dest()).lstrip("."), 

+

240 f"debputy-plugin-{ppf.definition.debputy_plugin_metadata.plugin_name}", 

+

241 ) 

+

242 for ppf in sorted( 

+

243 inactive_ppfs, key=operator.attrgetter("package_name") 

+

244 ) 

+

245 ], 

+

246 ) 

+

247 

+

248 

+

249@plugin_list_cmds.register_subcommand( 

+

250 ["packager-provided-files", "ppf", "p-p-f"], 

+

251 help_description="List packager provided file definitions (debian/pkg.foo)", 

+

252 argparser=TEXT_CSV_FORMAT_NO_STABILITY_PROMISE, 

+

253) 

+

254def _plugin_cmd_list_ppf(context: CommandContext) -> None: 

+

255 ppfs: Iterable[PackagerProvidedFileClassSpec] 

+

256 ppfs = context.load_plugins().packager_provided_files.values() 

+

257 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

258 headers: Sequence[Union[str, Tuple[str, str]]] = [ 

+

259 "Stem", 

+

260 "Installed As", 

+

261 ("Mode", ">"), 

+

262 "Features", 

+

263 "Provided by", 

+

264 ] 

+

265 fo.print_list_table( 

+

266 headers, 

+

267 [ 

+

268 ( 

+

269 ppf.stem, 

+

270 _path(ppf.installed_as_format), 

+

271 "0" + oct(ppf.default_mode)[2:], 

+

272 _ppf_flags(ppf), 

+

273 ppf.debputy_plugin_metadata.plugin_name, 

+

274 ) 

+

275 for ppf in sorted(ppfs, key=operator.attrgetter("stem")) 

+

276 ], 

+

277 ) 

+

278 

+

279 if os.path.isdir("debian/") and fo.output_format == "text": 

+

280 fo.print() 

+

281 fo.print( 

+

282 "Hint: You can use `debputy plugin list used-packager-provided-files` to have `debputy`", 

+

283 ) 

+

284 fo.print("list all the files in debian/ that matches these definitions.") 

+

285 

+

286 

+

287@plugin_list_cmds.register_subcommand( 

+

288 ["metadata-detectors"], 

+

289 help_description="List metadata detectors", 

+

290 argparser=TEXT_CSV_FORMAT_NO_STABILITY_PROMISE, 

+

291) 

+

292def _plugin_cmd_list_metadata_detectors(context: CommandContext) -> None: 

+

293 mds = list( 

+

294 chain.from_iterable( 

+

295 context.load_plugins().metadata_maintscript_detectors.values() 

+

296 ) 

+

297 ) 

+

298 

+

299 def _sort_key(md: "MetadataOrMaintscriptDetector") -> Any: 

+

300 return md.plugin_metadata.plugin_name, md.detector_id 

+

301 

+

302 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

303 fo.print_list_table( 

+

304 ["Provided by", "Detector Id"], 

+

305 [ 

+

306 (md.plugin_metadata.plugin_name, md.detector_id) 

+

307 for md in sorted(mds, key=_sort_key) 

+

308 ], 

+

309 ) 

+

310 

+

311 

+

312def _resolve_variable_for_list( 

+

313 substitution: Substitution, 

+

314 variable: PluginProvidedManifestVariable, 

+

315) -> str: 

+

316 var = "{{" + variable.variable_name + "}}" 

+

317 try: 

+

318 value = substitution.substitute(var, "CLI request") 

+

319 except DebputySubstitutionError: 

+

320 value = None 

+

321 return _render_manifest_variable_value(value) 

+

322 

+

323 

+

324def _render_manifest_variable_flag(variable: PluginProvidedManifestVariable) -> str: 

+

325 flags = [] 

+

326 if variable.is_for_special_case: 

+

327 flags.append("special-use-case") 

+

328 if variable.is_internal: 

+

329 flags.append("internal") 

+

330 return ",".join(flags) 

+

331 

+

332 

+

333def _render_list_filter(v: Optional[bool]) -> str: 

+

334 if v is None: 

+

335 return "N/A" 

+

336 return "shown" if v else "hidden" 

+

337 

+

338 

+

339@plugin_list_cmds.register_subcommand( 

+

340 ["manifest-variables"], 

+

341 help_description="List plugin provided manifest variables (such as `{{path:FOO}}`)", 

+

342) 

+

343def plugin_cmd_list_manifest_variables(context: CommandContext) -> None: 

+

344 variables = context.load_plugins().manifest_variables 

+

345 substitution = context.substitution.with_extra_substitutions( 

+

346 PACKAGE="<package-name>" 

+

347 ) 

+

348 parsed_args = context.parsed_args 

+

349 show_special_case_vars = parsed_args.show_special_use_variables 

+

350 show_token_vars = parsed_args.show_token_variables 

+

351 show_all_vars = parsed_args.show_all_variables 

+

352 

+

353 def _include_var(var: PluginProvidedManifestVariable) -> bool: 

+

354 if show_all_vars: 

+

355 return True 

+

356 if var.is_internal: 

+

357 return False 

+

358 if var.is_for_special_case and not show_special_case_vars: 

+

359 return False 

+

360 if var.is_token and not show_token_vars: 

+

361 return False 

+

362 return True 

+

363 

+

364 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

365 fo.print_list_table( 

+

366 ["Variable (use via: `{{ NAME }}`)", "Value", "Flag", "Provided by"], 

+

367 [ 

+

368 ( 

+

369 k, 

+

370 _resolve_variable_for_list(substitution, var), 

+

371 _render_manifest_variable_flag(var), 

+

372 var.plugin_metadata.plugin_name, 

+

373 ) 

+

374 for k, var in sorted(variables.items()) 

+

375 if _include_var(var) 

+

376 ], 

+

377 ) 

+

378 

+

379 fo.print() 

+

380 

+

381 filters = [ 

+

382 ( 

+

383 "Token variables", 

+

384 show_token_vars if not show_all_vars else None, 

+

385 "--show-token-variables", 

+

386 ), 

+

387 ( 

+

388 "Special use variables", 

+

389 show_special_case_vars if not show_all_vars else None, 

+

390 "--show-special-case-variables", 

+

391 ), 

+

392 ] 

+

393 

+

394 fo.print_list_table( 

+

395 ["Variable type", "Value", "Option"], 

+

396 [ 

+

397 ( 

+

398 fname, 

+

399 _render_list_filter(value or show_all_vars), 

+

400 f"{option} OR --show-all-variables", 

+

401 ) 

+

402 for fname, value, option in filters 

+

403 ], 

+

404 ) 

+

405 

+

406 

+

407@plugin_cmd_list_manifest_variables.configure_handler 

+

408def list_manifest_variable_arg_parser( 

+

409 plugin_list_manifest_variables_parser: argparse.ArgumentParser, 

+

410) -> None: 

+

411 plugin_list_manifest_variables_parser.add_argument( 

+

412 "--show-special-case-variables", 

+

413 dest="show_special_use_variables", 

+

414 default=False, 

+

415 action="store_true", 

+

416 help="Show variables that are only used in special / niche cases", 

+

417 ) 

+

418 plugin_list_manifest_variables_parser.add_argument( 

+

419 "--show-token-variables", 

+

420 dest="show_token_variables", 

+

421 default=False, 

+

422 action="store_true", 

+

423 help="Show token (syntactical) variables like {{token:TAB}}", 

+

424 ) 

+

425 plugin_list_manifest_variables_parser.add_argument( 

+

426 "--show-all-variables", 

+

427 dest="show_all_variables", 

+

428 default=False, 

+

429 action="store_true", 

+

430 help="Show all variables regardless of type/kind (overrules other filter settings)", 

+

431 ) 

+

432 TEXT_ONLY_FORMAT(plugin_list_manifest_variables_parser) 

+

433 

+

434 

+

435def _parser_type_name(v: Union[str, Type[Any]]) -> str: 

+

436 if isinstance(v, str): 

+

437 return v if v != "<ROOT>" else "" 

+

438 return v.__name__ 

+

439 

+

440 

+

441@plugin_list_cmds.register_subcommand( 

+

442 ["pluggable-manifest-rules", "p-m-r", "pmr"], 

+

443 help_description="Pluggable manifest rules (such as install rules)", 

+

444 argparser=TEXT_CSV_FORMAT_NO_STABILITY_PROMISE, 

+

445) 

+

446def _plugin_cmd_list_manifest_rules(context: CommandContext) -> None: 

+

447 feature_set = context.load_plugins() 

+

448 

+

449 # Type hint to make the chain call easier for the type checker, which does not seem 

+

450 # to derive to this common base type on its own. 

+

451 base_type = Iterable[Tuple[Union[str, Type[Any]], DispatchingParserBase[Any]]] 

+

452 

+

453 parser_generator = feature_set.manifest_parser_generator 

+

454 table_parsers: base_type = parser_generator.dispatchable_table_parsers.items() 

+

455 object_parsers: base_type = parser_generator.dispatchable_object_parsers.items() 

+

456 

+

457 parsers = chain( 

+

458 table_parsers, 

+

459 object_parsers, 

+

460 ) 

+

461 

+

462 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

463 fo.print_list_table( 

+

464 ["Rule Name", "Rule Type", "Provided By"], 

+

465 [ 

+

466 ( 

+

467 rn, 

+

468 _parser_type_name(rt), 

+

469 pt.parser_for(rn).plugin_metadata.plugin_name, 

+

470 ) 

+

471 for rt, pt in parsers 

+

472 for rn in pt.registered_keywords() 

+

473 ], 

+

474 ) 

+

475 

+

476 

+

477@plugin_list_cmds.register_subcommand( 

+

478 ["automatic-discard-rules", "a-d-r"], 

+

479 help_description="List automatic discard rules", 

+

480 argparser=TEXT_CSV_FORMAT_NO_STABILITY_PROMISE, 

+

481) 

+

482def _plugin_cmd_list_automatic_discard_rules(context: CommandContext) -> None: 

+

483 auto_discard_rules = context.load_plugins().auto_discard_rules 

+

484 

+

485 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

486 fo.print_list_table( 

+

487 ["Name", "Provided By"], 

+

488 [ 

+

489 ( 

+

490 name, 

+

491 ppdr.plugin_metadata.plugin_name, 

+

492 ) 

+

493 for name, ppdr in auto_discard_rules.items() 

+

494 ], 

+

495 ) 

+

496 

+

497 

+

498def _render_manifest_variable_value(v: Optional[str]) -> str: 

+

499 if v is None: 

+

500 return "(N/A: Cannot resolve the variable)" 

+

501 v = v.replace("\n", "\\n").replace("\t", "\\t") 

+

502 return v 

+

503 

+

504 

+

505def _render_multiline_documentation( 

+

506 documentation: str, 

+

507 *, 

+

508 first_line_prefix: str = "Documentation: ", 

+

509 following_line_prefix: str = " ", 

+

510) -> None: 

+

511 current_prefix = first_line_prefix 

+

512 for line in documentation.splitlines(keepends=False): 

+

513 if line.isspace(): 

+

514 if not current_prefix.isspace(): 

+

515 print(current_prefix.rstrip()) 

+

516 current_prefix = following_line_prefix 

+

517 else: 

+

518 print() 

+

519 continue 

+

520 print(f"{current_prefix}{line}") 

+

521 current_prefix = following_line_prefix 

+

522 

+

523 

+

524@plugin_show_cmds.register_subcommand( 

+

525 ["manifest-variables"], 

+

526 help_description="Plugin provided manifest variables (such as `{{path:FOO}}`)", 

+

527 argparser=add_arg( 

+

528 "manifest_variable", 

+

529 metavar="manifest-variable", 

+

530 help="Name of the variable (such as `path:FOO` or `{{path:FOO}}`) to display details about", 

+

531 ), 

+

532) 

+

533def _plugin_cmd_show_manifest_variables(context: CommandContext) -> None: 

+

534 plugin_feature_set = context.load_plugins() 

+

535 variables = plugin_feature_set.manifest_variables 

+

536 substitution = context.substitution 

+

537 parsed_args = context.parsed_args 

+

538 variable_name = parsed_args.manifest_variable 

+

539 fo = _output_styling(context.parsed_args, sys.stdout) 

+

540 if variable_name.startswith("{{") and variable_name.endswith("}}"): 

+

541 variable_name = variable_name[2:-2] 

+

542 variable: Optional[PluginProvidedManifestVariable] 

+

543 if variable_name.startswith("env:") and len(variable_name) > 4: 

+

544 env_var = variable_name[4:] 

+

545 variable = PluginProvidedManifestVariable( 

+

546 plugin_feature_set.plugin_data["debputy"], 

+

547 variable_name, 

+

548 variable_value=None, 

+

549 is_context_specific_variable=False, 

+

550 is_documentation_placeholder=True, 

+

551 variable_reference_documentation=f'Environment variable "{env_var}"', 

+

552 ) 

+

553 else: 

+

554 variable = variables.get(variable_name) 

+

555 if variable is None: 

+

556 _error( 

+

557 f'Cannot resolve "{variable_name}" as a known variable from any of the available' 

+

558 f" plugins. Please use `debputy plugin list manifest-variables` to list all known" 

+

559 f" provided variables." 

+

560 ) 

+

561 

+

562 var_with_braces = "{{" + variable_name + "}}" 

+

563 try: 

+

564 source_value = substitution.substitute(var_with_braces, "CLI request") 

+

565 except DebputySubstitutionError: 

+

566 source_value = None 

+

567 binary_value = source_value 

+

568 print(f"Variable: {variable_name}") 

+

569 fo.print_visual_formatting(f"=========={'=' * len(variable_name)}") 

+

570 print() 

+

571 

+

572 if variable.is_context_specific_variable: 

+

573 try: 

+

574 binary_value = substitution.with_extra_substitutions( 

+

575 PACKAGE="<package-name>", 

+

576 ).substitute(var_with_braces, "CLI request") 

+

577 except DebputySubstitutionError: 

+

578 binary_value = None 

+

579 

+

580 doc = variable.variable_reference_documentation or "No documentation provided" 

+

581 _render_multiline_documentation(doc) 

+

582 

+

583 if source_value == binary_value: 

+

584 print(f"Resolved: {_render_manifest_variable_value(source_value)}") 

+

585 else: 

+

586 print("Resolved:") 

+

587 print(f" [source context]: {_render_manifest_variable_value(source_value)}") 

+

588 print(f" [binary context]: {_render_manifest_variable_value(binary_value)}") 

+

589 

+

590 if variable.is_for_special_case: 

+

591 print( 

+

592 'Special-case: The variable has been marked as a "special-case"-only variable.' 

+

593 ) 

+

594 

+

595 if not variable.is_documentation_placeholder: 

+

596 print(f"Plugin: {variable.plugin_metadata.plugin_name}") 

+

597 

+

598 if variable.is_internal: 

+

599 print() 

+

600 # I knew everything I felt was showing on my face, and I hate that. I grated out, 

+

601 print("That was private.") 

+

602 

+

603 

+

604def _determine_ppf( 

+

605 context: CommandContext, 

+

606) -> Tuple[PackagerProvidedFileClassSpec, bool]: 

+

607 feature_set = context.load_plugins() 

+

608 ppf_name = context.parsed_args.ppf_name 

+

609 try: 

+

610 return feature_set.packager_provided_files[ppf_name], False 

+

611 except KeyError: 

+

612 pass 

+

613 

+

614 orig_ppf_name = ppf_name 

+

615 if ( 

+

616 ppf_name.startswith("d/") 

+

617 and not os.path.lexists(ppf_name) 

+

618 and os.path.lexists("debian/" + ppf_name[2:]) 

+

619 ): 

+

620 ppf_name = "debian/" + ppf_name[2:] 

+

621 

+

622 if ppf_name in ("debian/control", "debian/debputy.manifest", "debian/rules"): 

+

623 if ppf_name == "debian/debputy.manifest": 

+

624 doc = f"{DEBPUTY_DOC_ROOT_DIR}/MANIFEST-FORMAT.md" 

+

625 else: 

+

626 doc = "Debian Policy Manual or a packaging tutorial" 

+

627 _error( 

+

628 f"Sorry. While {orig_ppf_name} is a well-defined packaging file, it does not match the definition of" 

+

629 f" a packager provided file. Please see {doc} for more information about this file" 

+

630 ) 

+

631 

+

632 if context.has_dctrl_file and os.path.lexists(ppf_name): 

+

633 basename = ppf_name[7:] 

+

634 if "/" not in basename: 

+

635 debian_dir = build_virtual_fs([basename]) 

+

636 all_ppfs = detect_all_packager_provided_files( 

+

637 feature_set.packager_provided_files, 

+

638 debian_dir, 

+

639 context.binary_packages(), 

+

640 ) 

+

641 if all_ppfs: 

+

642 matched = next(iter(all_ppfs.values())) 

+

643 if len(matched.auto_installable) == 1 and not matched.reserved_only: 

+

644 return matched.auto_installable[0].definition, True 

+

645 if not matched.auto_installable and len(matched.reserved_only) == 1: 

+

646 reserved = next(iter(matched.reserved_only.values())) 

+

647 if len(reserved) == 1: 

+

648 return reserved[0].definition, True 

+

649 

+

650 _error( 

+

651 f'Unknown packager provided file "{orig_ppf_name}". Please use' 

+

652 f" `debputy plugin list packager-provided-files` to see them all." 

+

653 ) 

+

654 

+

655 

+

656@plugin_show_cmds.register_subcommand( 

+

657 ["packager-provided-files", "ppf", "p-p-f"], 

+

658 help_description="Show details about a given packager provided file (debian/pkg.foo)", 

+

659 argparser=add_arg( 

+

660 "ppf_name", 

+

661 metavar="name", 

+

662 help="Name of the packager provided file (such as `changelog`) to display details about", 

+

663 ), 

+

664) 

+

665def _plugin_cmd_show_ppf(context: CommandContext) -> None: 

+

666 ppf, matched_file = _determine_ppf(context) 

+

667 

+

668 fo = _output_styling(context.parsed_args, sys.stdout) 

+

669 

+

670 fo.print(f"Packager Provided File: {ppf.stem}") 

+

671 fo.print_visual_formatting(f"========================{'=' * len(ppf.stem)}") 

+

672 fo.print() 

+

673 ref_doc = ppf.reference_documentation 

+

674 description = ref_doc.description if ref_doc else None 

+

675 doc_uris = ref_doc.format_documentation_uris if ref_doc else tuple() 

+

676 if description is None: 

+

677 fo.print( 

+

678 f"Sorry, no description provided by the plugin {ppf.debputy_plugin_metadata.plugin_name}." 

+

679 ) 

+

680 else: 

+

681 for line in description.splitlines(keepends=False): 

+

682 fo.print(line) 

+

683 

+

684 fo.print() 

+

685 fo.print("Features:") 

+

686 if ppf.packageless_is_fallback_for_all_packages: 

+

687 fo.print(f" * debian/{ppf.stem} is used for *ALL* packages") 

+

688 else: 

+

689 fo.print(f' * debian/{ppf.stem} is used for only for the "main" package') 

+

690 if ppf.allow_name_segment: 

+

691 fo.print(" * Supports naming segment (multiple files and custom naming).") 

+

692 else: 

+

693 fo.print( 

+

694 " * No naming support; at most one per package and it is named after the package." 

+

695 ) 

+

696 if ppf.allow_architecture_segment: 

+

697 fo.print(" * Supports architecture specific variants.") 

+

698 else: 

+

699 fo.print(" * No architecture specific variants.") 

+

700 if ppf.supports_priority: 

+

701 fo.print( 

+

702 f" * Has a priority system (default priority: {ppf.default_priority})." 

+

703 ) 

+

704 

+

705 fo.print() 

+

706 fo.print("Examples matches:") 

+

707 

+

708 if context.has_dctrl_file: 

+

709 first_pkg = next(iter(context.binary_packages())) 

+

710 else: 

+

711 first_pkg = "example-package" 

+

712 example_files = [ 

+

713 (f"debian/{ppf.stem}", first_pkg), 

+

714 (f"debian/{first_pkg}.{ppf.stem}", first_pkg), 

+

715 ] 

+

716 if ppf.allow_name_segment: 

+

717 example_files.append( 

+

718 (f"debian/{first_pkg}.my.custom.name.{ppf.stem}", "my.custom.name") 

+

719 ) 

+

720 if ppf.allow_architecture_segment: 

+

721 example_files.append((f"debian/{first_pkg}.{ppf.stem}.amd64", first_pkg)), 

+

722 if ppf.allow_name_segment: 

+

723 example_files.append( 

+

724 ( 

+

725 f"debian/{first_pkg}.my.custom.name.{ppf.stem}.amd64", 

+

726 "my.custom.name", 

+

727 ) 

+

728 ) 

+

729 fs_root = build_virtual_fs([x for x, _ in example_files]) 

+

730 priority = ppf.default_priority if ppf.supports_priority else None 

+

731 rendered_examples = [] 

+

732 for example_file, assigned_name in example_files: 

+

733 example_path = fs_root.lookup(example_file) 

+

734 assert example_path is not None and example_path.is_file 

+

735 dest = ppf.compute_dest( 

+

736 assigned_name, 

+

737 owning_package=first_pkg, 

+

738 assigned_priority=priority, 

+

739 path=example_path, 

+

740 ) 

+

741 dest_path = "/".join(dest).lstrip(".") 

+

742 rendered_examples.append((example_file, dest_path)) 

+

743 

+

744 fo.print_list_table(["Source file", "Installed As"], rendered_examples) 

+

745 

+

746 if doc_uris: 

+

747 fo.print() 

+

748 fo.print("Documentation URIs:") 

+

749 for uri in doc_uris: 

+

750 fo.print(f" * {fo.render_url(uri)}") 

+

751 

+

752 plugin_name = ppf.debputy_plugin_metadata.plugin_name 

+

753 fo.print() 

+

754 fo.print(f"Install Mode: 0{oct(ppf.default_mode)[2:]}") 

+

755 fo.print(f"Provided by plugin: {plugin_name}") 

+

756 if ( 

+

757 matched_file 

+

758 and plugin_name != "debputy" 

+

759 and plugin_name not in context.requested_plugins() 

+

760 ): 

+

761 fo.print() 

+

762 _warn( 

+

763 f"The file might *NOT* be used due to missing Build-Depends on debputy-plugin-{plugin_name}" 

+

764 ) 

+

765 

+

766 

+

767@plugin_show_cmds.register_subcommand( 

+

768 ["pluggable-manifest-rules", "p-m-r", "pmr"], 

+

769 help_description="Pluggable manifest rules (such as install rules)", 

+

770 argparser=add_arg( 

+

771 "pmr_rule_name", 

+

772 metavar="rule-name", 

+

773 help="Name of the rule (such as `install`) to display details about", 

+

774 ), 

+

775) 

+

776def _plugin_cmd_show_manifest_rule(context: CommandContext) -> None: 

+

777 feature_set = context.load_plugins() 

+

778 parsed_args = context.parsed_args 

+

779 req_rule_type = None 

+

780 rule_name = parsed_args.pmr_rule_name 

+

781 if "::" in rule_name and rule_name != "::": 

+

782 req_rule_type, rule_name = rule_name.split("::", 1) 

+

783 

+

784 matched = [] 

+

785 

+

786 base_type = Iterable[Tuple[Union[str, Type[Any]], DispatchingParserBase[Any]]] 

+

787 parser_generator = feature_set.manifest_parser_generator 

+

788 table_parsers: base_type = parser_generator.dispatchable_table_parsers.items() 

+

789 object_parsers: base_type = parser_generator.dispatchable_object_parsers.items() 

+

790 

+

791 parsers = chain( 

+

792 table_parsers, 

+

793 object_parsers, 

+

794 ) 

+

795 

+

796 for rule_type, dispatching_parser in parsers: 

+

797 if req_rule_type is not None and req_rule_type not in _parser_type_name( 

+

798 rule_type 

+

799 ): 

+

800 continue 

+

801 if dispatching_parser.is_known_keyword(rule_name): 

+

802 matched.append((rule_type, dispatching_parser)) 

+

803 

+

804 if len(matched) != 1 and (matched or rule_name != "::"): 

+

805 if not matched: 

+

806 _error( 

+

807 f"Could not find any pluggable manifest rule related to {parsed_args.pmr_rule_name}." 

+

808 f" Please use `debputy plugin list pluggable-manifest-rules` to see the list of rules." 

+

809 ) 

+

810 match_a = matched[0][0] 

+

811 match_b = matched[1][0] 

+

812 _error( 

+

813 f"The name {rule_name} was ambiguous and matched multiple rule types. Please use" 

+

814 f" <rule-type>::{rule_name} to clarify which rule to use" 

+

815 f" (such as {_parser_type_name(match_a)}::{rule_name} or {_parser_type_name(match_b)}::{rule_name})." 

+

816 f" Please use `debputy plugin list pluggable-manifest-rules` to see the list of rules." 

+

817 ) 

+

818 

+

819 if matched: 

+

820 rule_type, matched_dispatching_parser = matched[0] 

+

821 plugin_provided_parser = matched_dispatching_parser.parser_for(rule_name) 

+

822 if isinstance(rule_type, str): 

+

823 manifest_attribute_path = rule_type 

+

824 else: 

+

825 manifest_attribute_path = SUPPORTED_DISPATCHABLE_TABLE_PARSERS[rule_type] 

+

826 parser_type_name = _parser_type_name(rule_type) 

+

827 parser = plugin_provided_parser.parser 

+

828 plugin_metadata = plugin_provided_parser.plugin_metadata 

+

829 else: 

+

830 rule_name = "::" 

+

831 parser = parser_generator.dispatchable_object_parsers[OPARSER_MANIFEST_ROOT] 

+

832 parser_type_name = "" 

+

833 plugin_metadata = plugin_metadata_for_debputys_own_plugin() 

+

834 manifest_attribute_path = "" 

+

835 

+

836 is_root_rule = rule_name == "::" 

+

837 print( 

+

838 render_rule( 

+

839 rule_name, 

+

840 parser, 

+

841 plugin_metadata, 

+

842 is_root_rule=is_root_rule, 

+

843 ) 

+

844 ) 

+

845 

+

846 if not is_root_rule: 

+

847 print( 

+

848 f"Used in: {manifest_attribute_path if manifest_attribute_path != '<ROOT>' else 'The manifest root'}" 

+

849 ) 

+

850 print(f"Rule reference: {parser_type_name}::{rule_name}") 

+

851 print(f"Plugin: {plugin_metadata.plugin_name}") 

+

852 else: 

+

853 print(f"Rule reference: {rule_name}") 

+

854 

+

855 print() 

+

856 print( 

+

857 "PS: If you want to know more about a non-trivial type of an attribute such as `FileSystemMatchRule`," 

+

858 ) 

+

859 print( 

+

860 "you can use `debputy plugin show type-mappings FileSystemMatchRule` to look it up " 

+

861 ) 

+

862 

+

863 

+

864def _render_discard_rule_example( 

+

865 fo: OutputStylingBase, 

+

866 discard_rule: PluginProvidedDiscardRule, 

+

867 example: AutomaticDiscardRuleExample, 

+

868) -> None: 

+

869 processed = process_discard_rule_example(discard_rule, example) 

+

870 

+

871 if processed.inconsistent_paths: 

+

872 plugin_name = discard_rule.plugin_metadata.plugin_name 

+

873 _warn( 

+

874 f"This example is inconsistent with what the code actually does." 

+

875 f" Please consider filing a bug against the plugin {plugin_name}" 

+

876 ) 

+

877 

+

878 doc = example.description 

+

879 if doc: 

+

880 print(doc) 

+

881 

+

882 print("Consider the following source paths matched by a glob or directory match:") 

+

883 print() 

+

884 if fo.optimize_for_screen_reader: 

+

885 for p, _ in processed.rendered_paths: 

+

886 path_name = p.absolute 

+

887 print( 

+

888 f"The path {path_name} is a {'directory' if p.is_dir else 'file or symlink.'}" 

+

889 ) 

+

890 

+

891 print() 

+

892 if any(v.is_consistent and v.is_discarded for _, v in processed.rendered_paths): 

+

893 print("The following paths will be discarded by this rule:") 

+

894 for p, verdict in processed.rendered_paths: 

+

895 path_name = p.absolute 

+

896 if verdict.is_consistent and verdict.is_discarded: 

+

897 print() 

+

898 if p.is_dir: 

+

899 print(f"{path_name} along with anything beneath it") 

+

900 else: 

+

901 print(path_name) 

+

902 else: 

+

903 print("No paths will be discarded in this example.") 

+

904 

+

905 print() 

+

906 if any(v.is_consistent and v.is_kept for _, v in processed.rendered_paths): 

+

907 print("The following paths will be not be discarded by this rule:") 

+

908 for p, verdict in processed.rendered_paths: 

+

909 path_name = p.absolute 

+

910 if verdict.is_consistent and verdict.is_kept: 

+

911 print() 

+

912 print(path_name) 

+

913 

+

914 if any(not v.is_consistent for _, v in processed.rendered_paths): 

+

915 print() 

+

916 print( 

+

917 "The example was inconsistent with the code. These are the paths where the code disagrees with" 

+

918 " the provided example:" 

+

919 ) 

+

920 for p, verdict in processed.rendered_paths: 

+

921 path_name = p.absolute 

+

922 if not verdict.is_consistent: 

+

923 print() 

+

924 if verdict == DiscardVerdict.DISCARDED_BY_CODE: 

+

925 print( 

+

926 f"The path {path_name} was discarded by the code, but the example said it should" 

+

927 f" have been installed." 

+

928 ) 

+

929 else: 

+

930 print( 

+

931 f"The path {path_name} was not discarded by the code, but the example said it should" 

+

932 f" have been discarded." 

+

933 ) 

+

934 return 

+

935 

+

936 # Add +1 for dirs because we want trailing slashes in the output 

+

937 max_len = max( 

+

938 (len(p.absolute) + (1 if p.is_dir else 0)) for p, _ in processed.rendered_paths 

+

939 ) 

+

940 for p, verdict in processed.rendered_paths: 

+

941 path_name = p.absolute 

+

942 if p.is_dir: 

+

943 path_name += "/" 

+

944 

+

945 if not verdict.is_consistent: 

+

946 print(f" {path_name:<{max_len}} !! {verdict.message}") 

+

947 elif verdict.is_discarded: 

+

948 print(f" {path_name:<{max_len}} << {verdict.message}") 

+

949 else: 

+

950 print(f" {path_name:<{max_len}}") 

+

951 

+

952 

+

953def _render_discard_rule( 

+

954 context: CommandContext, 

+

955 discard_rule: PluginProvidedDiscardRule, 

+

956) -> None: 

+

957 fo = _output_styling(context.parsed_args, sys.stdout) 

+

958 print(fo.colored(f"Automatic Discard Rule: {discard_rule.name}", style="bold")) 

+

959 fo.print_visual_formatting( 

+

960 f"========================{'=' * len(discard_rule.name)}" 

+

961 ) 

+

962 print() 

+

963 doc = discard_rule.reference_documentation or "No documentation provided" 

+

964 _render_multiline_documentation(doc, first_line_prefix="", following_line_prefix="") 

+

965 

+

966 if len(discard_rule.examples) > 1: 

+

967 print() 

+

968 fo.print_visual_formatting("Examples") 

+

969 fo.print_visual_formatting("--------") 

+

970 print() 

+

971 for no, example in enumerate(discard_rule.examples, start=1): 

+

972 print( 

+

973 fo.colored( 

+

974 f"Example {no} of {len(discard_rule.examples)}", style="bold" 

+

975 ) 

+

976 ) 

+

977 fo.print_visual_formatting(f"........{'.' * len(str(no))}") 

+

978 _render_discard_rule_example(fo, discard_rule, example) 

+

979 elif discard_rule.examples: 

+

980 print() 

+

981 print(fo.colored("Example", style="bold")) 

+

982 fo.print_visual_formatting("-------") 

+

983 print() 

+

984 _render_discard_rule_example(fo, discard_rule, discard_rule.examples[0]) 

+

985 

+

986 

+

987@plugin_show_cmds.register_subcommand( 

+

988 ["automatic-discard-rules", "a-d-r"], 

+

989 help_description="Pluggable manifest rules (such as install rules)", 

+

990 argparser=add_arg( 

+

991 "discard_rule", 

+

992 metavar="automatic-discard-rule", 

+

993 help="Name of the automatic discard rule (such as `backup-files`)", 

+

994 ), 

+

995) 

+

996def _plugin_cmd_show_automatic_discard_rules(context: CommandContext) -> None: 

+

997 auto_discard_rules = context.load_plugins().auto_discard_rules 

+

998 name = context.parsed_args.discard_rule 

+

999 discard_rule = auto_discard_rules.get(name) 

+

1000 if discard_rule is None: 

+

1001 _error( 

+

1002 f'No automatic discard rule with the name "{name}". Please use' 

+

1003 f" `debputy plugin list automatic-discard-rules` to see the list of automatic discard rules" 

+

1004 ) 

+

1005 

+

1006 _render_discard_rule(context, discard_rule) 

+

1007 

+

1008 

+

1009def _render_source_type(t: Any) -> str: 

+

1010 _, origin_type, args = unpack_type(t, False) 

+

1011 if origin_type == Union: 

+

1012 at = ", ".join(_render_source_type(st) for st in args) 

+

1013 return f"One of: {at}" 

+

1014 name = BASIC_SIMPLE_TYPES.get(t) 

+

1015 if name is not None: 

+

1016 return name 

+

1017 try: 

+

1018 return t.__name__ 

+

1019 except AttributeError: 

+

1020 return str(t) 

+

1021 

+

1022 

+

1023@plugin_list_cmds.register_subcommand( 

+

1024 "type-mappings", 

+

1025 help_description="Registered type mappings/descriptions", 

+

1026) 

+

1027def _plugin_cmd_list_type_mappings(context: CommandContext) -> None: 

+

1028 type_mappings = context.load_plugins().mapped_types 

+

1029 

+

1030 with _stream_to_pager(context.parsed_args) as (fd, fo): 

+

1031 fo.print_list_table( 

+

1032 ["Type", "Base Type", "Provided By"], 

+

1033 [ 

+

1034 ( 

+

1035 target_type.__name__, 

+

1036 _render_source_type(type_mapping.mapped_type.source_type), 

+

1037 type_mapping.plugin_metadata.plugin_name, 

+

1038 ) 

+

1039 for target_type, type_mapping in type_mappings.items() 

+

1040 ], 

+

1041 ) 

+

1042 

+

1043 

+

1044@plugin_show_cmds.register_subcommand( 

+

1045 "type-mappings", 

+

1046 help_description="Register type mappings/descriptions", 

+

1047 argparser=add_arg( 

+

1048 "type_mapping", 

+

1049 metavar="type-mapping", 

+

1050 help="Name of the type", 

+

1051 ), 

+

1052) 

+

1053def _plugin_cmd_show_type_mappings(context: CommandContext) -> None: 

+

1054 type_mapping_name = context.parsed_args.type_mapping 

+

1055 type_mappings = context.load_plugins().mapped_types 

+

1056 

+

1057 matches = [] 

+

1058 for type_ in type_mappings: 

+

1059 if type_.__name__ == type_mapping_name: 

+

1060 matches.append(type_) 

+

1061 

+

1062 if not matches: 

+

1063 simple_types = set(BASIC_SIMPLE_TYPES.values()) 

+

1064 simple_types.update(t.__name__ for t in BASIC_SIMPLE_TYPES) 

+

1065 

+

1066 if type_mapping_name in simple_types: 

+

1067 print(f"The type {type_mapping_name} is a YAML scalar.") 

+

1068 return 

+

1069 if type_mapping_name == "Any": 

+

1070 print( 

+

1071 "The Any type is a placeholder for when no typing information is provided. Often this implies" 

+

1072 " custom parse logic." 

+

1073 ) 

+

1074 return 

+

1075 

+

1076 if type_mapping_name in ("List", "list"): 

+

1077 print( 

+

1078 f"The {type_mapping_name} is a YAML Sequence. Please see the YAML documentation for examples." 

+

1079 ) 

+

1080 return 

+

1081 

+

1082 if type_mapping_name in ("Mapping", "dict"): 

+

1083 print( 

+

1084 f"The {type_mapping_name} is a YAML mapping. Please see the YAML documentation for examples." 

+

1085 ) 

+

1086 return 

+

1087 

+

1088 if "[" in type_mapping_name: 

+

1089 _error( 

+

1090 f"No known matches for {type_mapping_name}. Note: It looks like a composite type. Try searching" 

+

1091 " for its component parts. As an example, replace List[FileSystemMatchRule] with FileSystemMatchRule." 

+

1092 ) 

+

1093 

+

1094 _error(f"Sorry, no known matches for {type_mapping_name}") 

+

1095 

+

1096 if len(matches) > 1: 

+

1097 _error( 

+

1098 f"Too many matches for {type_mapping_name}... Sorry, there is no way to avoid this right now :'(" 

+

1099 ) 

+

1100 

+

1101 match = matches[0] 

+

1102 _render_type(context, type_mappings[match]) 

+

1103 

+

1104 

+

1105def _render_type_example( 

+

1106 context: CommandContext, 

+

1107 fo: OutputStylingBase, 

+

1108 parser_context: ParserContextData, 

+

1109 type_mapping: TypeMapping[Any, Any], 

+

1110 example: TypeMappingExample, 

+

1111) -> Tuple[str, bool]: 

+

1112 attr_path = AttributePath.builtin_path()["CLI Request"] 

+

1113 v = _render_value(example.source_input) 

+

1114 try: 

+

1115 type_mapping.mapper( 

+

1116 example.source_input, 

+

1117 attr_path, 

+

1118 parser_context, 

+

1119 ) 

+

1120 except RuntimeError: 

+

1121 if context.parsed_args.debug_mode: 

+

1122 raise 

+

1123 fo.print( 

+

1124 fo.colored("Broken example: ", fg="red") 

+

1125 + f"Provided example input ({v})" 

+

1126 + " caused an exception when parsed. Please file a bug against the plugin." 

+

1127 + " Use --debug to see the stack trace" 

+

1128 ) 

+

1129 return fo.colored(v, fg="red") + " [Example value could not be parsed]", True 

+

1130 return fo.colored(v, fg="green"), False 

+

1131 

+

1132 

+

1133def _render_type( 

+

1134 context: CommandContext, 

+

1135 pptm: PluginProvidedTypeMapping, 

+

1136) -> None: 

+

1137 fo = _output_styling(context.parsed_args, sys.stdout) 

+

1138 type_mapping = pptm.mapped_type 

+

1139 target_type = type_mapping.target_type 

+

1140 ref_doc = pptm.reference_documentation 

+

1141 desc = ref_doc.description if ref_doc is not None else None 

+

1142 examples = ref_doc.examples if ref_doc is not None else tuple() 

+

1143 

+

1144 fo.print(fo.colored(f"# Type Mapping: {target_type.__name__}", style="bold")) 

+

1145 fo.print() 

+

1146 if desc is not None: 

+

1147 _render_multiline_documentation( 

+

1148 desc, first_line_prefix="", following_line_prefix="" 

+

1149 ) 

+

1150 else: 

+

1151 fo.print("No documentation provided.") 

+

1152 

+

1153 context.parse_manifest() 

+

1154 

+

1155 manifest_parser = context.manifest_parser() 

+

1156 

+

1157 if examples: 

+

1158 had_issues = False 

+

1159 fo.print() 

+

1160 fo.print(fo.colored("## Example values", style="bold")) 

+

1161 fo.print() 

+

1162 for no, example in enumerate(examples, start=1): 

+

1163 v, i = _render_type_example( 

+

1164 context, fo, manifest_parser, type_mapping, example 

+

1165 ) 

+

1166 fo.print(f" * {v}") 

+

1167 if i: 

+

1168 had_issues = True 

+

1169 else: 

+

1170 had_issues = False 

+

1171 

+

1172 fo.print() 

+

1173 fo.print(f"Provided by plugin: {pptm.plugin_metadata.plugin_name}") 

+

1174 

+

1175 if had_issues: 

+

1176 fo.print() 

+

1177 fo.print( 

+

1178 fo.colored( 

+

1179 "Examples had issues. Please file a bug against the plugin", fg="red" 

+

1180 ) 

+

1181 ) 

+

1182 fo.print() 

+

1183 fo.print("Use --debug to see the stacktrace") 

+

1184 

+

1185 

+

1186def _render_value(v: Any) -> str: 

+

1187 if isinstance(v, str) and '"' not in v: 

+

1188 return f'"{v}"' 

+

1189 return str(v) 

+

1190 

+

1191 

+

1192def ensure_plugin_commands_are_loaded(): 

+

1193 # Loading the module does the heavy lifting 

+

1194 # However, having this function means that we do not have an "unused" import that some tool 

+

1195 # gets tempted to remove 

+

1196 assert ROOT_COMMAND.has_command("plugin") 

+
+ + + -- cgit v1.2.3