Coverage for src/debputy/lsp/languages/lsp_debian_upstream_metadata.py: 93%

72 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-02-14 10:41 +0000

1import textwrap 

2from functools import lru_cache 

3from typing import ( 

4 Optional, 

5 Any, 

6 Union, 

7 TYPE_CHECKING, 

8 Type, 

9) 

10from collections.abc import Sequence 

11 

12from debputy.linting.lint_util import LintState 

13from debputy.lsp.lsp_features import ( 

14 lint_diagnostics, 

15 lsp_standard_handler, 

16 lsp_hover, 

17 lsp_completer, 

18 LanguageDispatchRule, 

19 SecondaryLanguage, 

20) 

21from debputy.lsp.lsp_generic_yaml import ( 

22 generic_yaml_hover, 

23 LSPYAMLHelper, 

24 generic_yaml_lint, 

25 generic_yaml_completer, 

26) 

27from debputy.manifest_parser.base_types import ( 

28 DebputyParsedContent, 

29) 

30from debputy.manifest_parser.declarative_parser import ( 

31 ParserGenerator, 

32) 

33from debputy.manifest_parser.parser_data import ParserContextData 

34from debputy.manifest_parser.util import AttributePath 

35from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin 

36from debputy.plugin.api.impl_types import ( 

37 DispatchingParserBase, 

38 DebputyPluginMetadata, 

39 DispatchingObjectParser, 

40) 

41from debputy.plugin.api.spec import ParserDocumentation, reference_documentation 

42from debputy.util import T 

43 

44try: 

45 from debputy.lsp.debputy_ls import DebputyLanguageServer 

46 from debputy.lsp.vendoring._deb822_repro.locatable import ( 

47 Position as TEPosition, 

48 Range as TERange, 

49 ) 

50except ImportError: 

51 pass 

52 

53if TYPE_CHECKING: 

54 import lsprotocol.types as types 

55else: 

56 import debputy.lsprotocol.types as types 

57 

58 

59_DISPATCH_RULE = LanguageDispatchRule.new_rule( 

60 "debian/upstream/metadata", 

61 None, 

62 "debian/upstream/metadata", 

63 [SecondaryLanguage("yaml", secondary_lookup="path-name")], 

64) 

65 

66 

67lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION) 

68lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL) 

69 

70 

71TT = type[T] 

72 

73 

74def _parser_handler( 

75 _key: str, 

76 value: Any, 

77 _attr_path: AttributePath, 

78 _context: Optional["ParserContextData"], 

79) -> Any: 

80 return value 

81 

82 

83def add_keyword( 

84 pg: ParserGenerator, 

85 root_parser: DispatchingParserBase[Any], 

86 plugin_metadata: DebputyPluginMetadata, 

87 keyword: str, 

88 value_type: TT, 

89 *, 

90 inline_reference_documentation: ParserDocumentation | None = None, 

91) -> None: 

92 class DebputyParsedContentWrapper(DebputyParsedContent): 

93 content: value_type # type: ignore 

94 

95 parser = pg.generate_parser( 

96 DebputyParsedContentWrapper, 

97 source_content=value_type, 

98 inline_reference_documentation=inline_reference_documentation, 

99 ) 

100 root_parser.register_parser( 

101 keyword, 

102 parser, 

103 _parser_handler, 

104 plugin_metadata, 

105 ) 

106 

107 

108@lru_cache 

109def root_object_parser() -> DispatchingObjectParser: 

110 plugin_metadata = plugin_metadata_for_debputys_own_plugin() 

111 pg = ParserGenerator() 

112 root_parser = pg.add_object_parser( 

113 "<ROOT>", 

114 unknown_keys_diagnostic_severity="warning", 

115 ) 

116 add_keyword( 

117 pg, 

118 root_parser, 

119 plugin_metadata, 

120 "Archive", 

121 str, 

122 inline_reference_documentation=reference_documentation( 

123 title="Archive (`Archive`)", 

124 description=textwrap.dedent( 

125 """\ 

126 The name of the large archive that the upstream work is part of, like CPAN. 

127 """ 

128 ), 

129 ), 

130 ) 

131 add_keyword( 

132 pg, 

133 root_parser, 

134 plugin_metadata, 

135 "ASCL-Id", 

136 str, 

137 inline_reference_documentation=reference_documentation( 

138 title="ASCL Identifier (`ASCL-Id`)", 

139 description=textwrap.dedent( 

140 """\ 

141 Identification code in the http://ascl.net 

142 """ 

143 ), 

144 ), 

145 ) 

146 add_keyword( 

147 pg, 

148 root_parser, 

149 plugin_metadata, 

150 "Bug-Database", 

151 str, 

152 inline_reference_documentation=reference_documentation( 

153 title="Bug database or tracker for the project (`Bug-Database`)", 

154 description=textwrap.dedent( 

155 """\ 

156 A URL to the list of known bugs for the project. 

157 """ 

158 ), 

159 ), 

160 ) 

161 add_keyword( 

162 pg, 

163 root_parser, 

164 plugin_metadata, 

165 "Bug-Submit", 

166 str, 

167 inline_reference_documentation=reference_documentation( 

168 title="Bug submission URL for the project (`Bug-Submit`)", 

169 description=textwrap.dedent( 

170 """\ 

171 A URL that is the place where new bug reports should be sent. 

172 """ 

173 ), 

174 ), 

175 ) 

176 add_keyword( 

177 pg, 

178 root_parser, 

179 plugin_metadata, 

180 "Cite-As", 

181 str, 

182 inline_reference_documentation=reference_documentation( 

183 title="Cite-As (`Cite-As`)", 

184 description=textwrap.dedent( 

185 """\ 

186 The way the authors want their software be cited in publications. 

187 

188 The value is a string which might contain a link in valid HTML syntax. 

189 """ 

190 ), 

191 ), 

192 ) 

193 add_keyword( 

194 pg, 

195 root_parser, 

196 plugin_metadata, 

197 "Changelog", 

198 str, 

199 inline_reference_documentation=reference_documentation( 

200 title="Changelog (`Changelog`)", 

201 description=textwrap.dedent( 

202 """\ 

203 URL to the upstream changelog. 

204 """ 

205 ), 

206 ), 

207 ) 

208 add_keyword( 

209 pg, 

210 root_parser, 

211 plugin_metadata, 

212 "Contact", 

213 str, 

214 inline_reference_documentation=reference_documentation( 

215 title="Contact (`Contact`)", 

216 description=textwrap.dedent( 

217 """\ 

218 Contact point for the upstream point. 

219 

220 Deprecated when using the machine readable format. The `Upstream-Contact` field in 

221 `debian/copyright` is the direct replacement in that case. 

222 """ 

223 ), 

224 ), 

225 ) 

226 add_keyword( 

227 pg, 

228 root_parser, 

229 plugin_metadata, 

230 "CPE", 

231 str, 

232 inline_reference_documentation=reference_documentation( 

233 title="CPE (`CPE`)", 

234 description=textwrap.dedent( 

235 """\ 

236 One or more space separated http://cpe.mitre.org/ values useful to look up relevant CVEs 

237 in the https://nvd.nist.gov/home.cfm and other CVE sources. 

238 

239 See `CPEtagPackagesDep` for information on how this information can be used. 

240 **Example**: `cpe:/a:ethereal_group:ethereal` 

241 """ 

242 ), 

243 ), 

244 ) 

245 add_keyword( 

246 pg, 

247 root_parser, 

248 plugin_metadata, 

249 "Documentation", 

250 str, 

251 inline_reference_documentation=reference_documentation( 

252 title="Documentation (`Documentation`)", 

253 description=textwrap.dedent( 

254 """\ 

255 A URL to online documentation. 

256 """ 

257 ), 

258 ), 

259 ) 

260 add_keyword( 

261 pg, 

262 root_parser, 

263 plugin_metadata, 

264 "Donation", 

265 str, 

266 inline_reference_documentation=reference_documentation( 

267 title="Donation (`Donation`)", 

268 description=textwrap.dedent( 

269 """\ 

270 A URL to a donation form (or instructions). 

271 """ 

272 ), 

273 ), 

274 ) 

275 add_keyword( 

276 pg, 

277 root_parser, 

278 plugin_metadata, 

279 "FAQ", 

280 str, 

281 inline_reference_documentation=reference_documentation( 

282 title="FAQ (`FAQ`)", 

283 description=textwrap.dedent( 

284 """\ 

285 A URL to the online FAQ. 

286 """ 

287 ), 

288 ), 

289 ) 

290 add_keyword( 

291 pg, 

292 root_parser, 

293 plugin_metadata, 

294 "Funding", 

295 # Unsure of the format 

296 Any, 

297 inline_reference_documentation=reference_documentation( 

298 title="Funding (`Funding`)", 

299 description=textwrap.dedent( 

300 """\ 

301 One or more sources of funding which have supported this project (e.g. NSF OCI-12345). 

302 """ 

303 ), 

304 ), 

305 ) 

306 add_keyword( 

307 pg, 

308 root_parser, 

309 plugin_metadata, 

310 "Gallery", 

311 str, 

312 inline_reference_documentation=reference_documentation( 

313 title="Gallery (`Gallery`)", 

314 description=textwrap.dedent( 

315 """\ 

316 A URL to a gallery of pictures made with the program (not screenshots). 

317 """ 

318 ), 

319 ), 

320 ) 

321 add_keyword( 

322 pg, 

323 root_parser, 

324 plugin_metadata, 

325 "Name", 

326 str, 

327 inline_reference_documentation=reference_documentation( 

328 title="Name (`Name`)", 

329 description=textwrap.dedent( 

330 """\ 

331 Name of the upstream project. 

332 

333 Deprecated when using the machine readable format. The `Upstream-Name` field in 

334 `debian/copyright` is the direct replacement in that case. 

335 """ 

336 ), 

337 ), 

338 ) 

339 add_keyword( 

340 pg, 

341 root_parser, 

342 plugin_metadata, 

343 "Other-References", 

344 str, 

345 inline_reference_documentation=reference_documentation( 

346 title="Other-References (`Other-References`)", 

347 description=textwrap.dedent( 

348 """\ 

349 A URL to a upstream page containing more references. 

350 """ 

351 ), 

352 ), 

353 ) 

354 add_keyword( 

355 pg, 

356 root_parser, 

357 plugin_metadata, 

358 "Reference", 

359 # Complex type 

360 Any, 

361 inline_reference_documentation=reference_documentation( 

362 title="Reference (`Reference`)", 

363 # FIXME: Add the fields below as a nested subobject or list of such objects 

364 description=textwrap.dedent( 

365 """\ 

366 One or more bibliographic references, represented as a mapping or sequence of mappings containing 

367 the one or more of the following keys. 

368  

369 The values for the keys are always scalars, and the keys that correspond to standard BibTeX 

370 entries must provide the same content. 

371 """ 

372 ), 

373 ), 

374 ) 

375 

376 # Reference:: One or more bibliographic references, represented as a mapping or sequence of mappings containing the one or more of the following keys. The values for the keys are always scalars, and the keys that correspond to standard BibTeX entries must provide the same content. 

377 # 

378 # Author:: Author list in BibTeX friendly syntax (separating multiple authors by the keyword "and" and using as few as possible abbreviations in the names, as proposed in http://nwalsh.com/tex/texhelp/bibtx-23.html). 

379 # 

380 # Booktitle:: Title of the book the article is published in 

381 # 

382 # DOI:: This is the digital object identifier of the academic publication describing the packaged work. 

383 # 

384 # Editor:: Editor of the book the article is published in 

385 # 

386 # Eprint:: Hyperlink to the PDF file of the article. 

387 # 

388 # ISBN:: International Standard Book Number of the book if the article is part of the book or the reference is a book 

389 # 

390 # ISSN:: International Standard Serial Number of the periodical publication if the article is part of a series 

391 # 

392 # Journal:: Abbreviated journal name [To be discussed: which standard to recommend ?]. 

393 # 

394 # Number:: Issue number. 

395 # 

396 # Pages:: Article page number(s). [To be discussed] Page number separator must be a single ASCII hyphen. What do we do with condensed notations like 401-10 ? 

397 # 

398 # PMID:: ID number in the https://www.ncbi.nlm.nih.gov/pubmed/ database. 

399 # 

400 # Publisher:: Publisher of the book containing the article 

401 # 

402 # Title:: Article title. 

403 # 

404 # Type:: A http://www.bibtex.org/Format indicating what is cited. Typical values are {{{article}}}, {{{book}}}, or {{{inproceedings}}}. [To be discussed]. In case this field is not present, {{{article}}} is assumed. 

405 # 

406 # URL:: Hyperlink to the abstract of the article. This should not point to the full version because this is specified by Eprint. Please also do not drop links to pubmed here because this would be redundant to PMID. 

407 # 

408 # Volume:: Journal volume. 

409 # 

410 # Year:: Year of publication 

411 # 

412 

413 add_keyword( 

414 pg, 

415 root_parser, 

416 plugin_metadata, 

417 "Registration", 

418 str, 

419 inline_reference_documentation=reference_documentation( 

420 title="Registration (`Registration`)", 

421 description=textwrap.dedent( 

422 """\ 

423 A URL to a registration form (or instructions). This could be registration of bug reporting 

424 accounts, registration for counting/contacting users etc. 

425 """ 

426 ), 

427 ), 

428 ) 

429 add_keyword( 

430 pg, 

431 root_parser, 

432 plugin_metadata, 

433 "Registry", 

434 # FIXME: Add List of `Name`, `Entry` objects 

435 Any, 

436 inline_reference_documentation=reference_documentation( 

437 title="Registry (`Registry`)", 

438 description=textwrap.dedent( 

439 """\ 

440 This field shall point to external catalogs/registries of software. 

441  

442 The field features an array of "Name (of registry) - Entry (ID of software in that catalog)" pairs. 

443 The names and entries shall only be names, not complete URIs, to avoid any bias on mirrors etc. 

444 Example: 

445 ```yaml 

446 Registry: 

447 - Name: bio.tools 

448 Entry: clustalw 

449 - Name: OMICtools 

450 Entry: OMICS_02562 

451 - Name: SciCrunch 

452 Entry: SCR_002909 

453 ``` 

454 """ 

455 ), 

456 ), 

457 ) 

458 

459 add_keyword( 

460 pg, 

461 root_parser, 

462 plugin_metadata, 

463 "Repository", 

464 str, 

465 inline_reference_documentation=reference_documentation( 

466 title="Repository (`Repository`)", 

467 description=textwrap.dedent( 

468 """\ 

469 URL to a repository containing the upstream sources. 

470 """ 

471 ), 

472 ), 

473 ) 

474 

475 add_keyword( 

476 pg, 

477 root_parser, 

478 plugin_metadata, 

479 "Repository-Browse", 

480 str, 

481 inline_reference_documentation=reference_documentation( 

482 title="Repository-Browse (`Repository-Browse`)", 

483 description=textwrap.dedent( 

484 """\ 

485 A URL to browse the repository containing the upstream sources. 

486 """ 

487 ), 

488 ), 

489 ) 

490 add_keyword( 

491 pg, 

492 root_parser, 

493 plugin_metadata, 

494 "Screenshots", 

495 Any, 

496 inline_reference_documentation=reference_documentation( 

497 title="Screenshots (`Screenshots`)", 

498 description=textwrap.dedent( 

499 """\ 

500 One or more URLs to upstream pages containing screenshots (not <https://screenshots.debian.net>), 

501 represented by a scalar or a sequence of scalars. 

502 """ 

503 ), 

504 ), 

505 ) 

506 add_keyword( 

507 pg, 

508 root_parser, 

509 plugin_metadata, 

510 "Security-Contact", 

511 str, 

512 inline_reference_documentation=reference_documentation( 

513 title="Security-Contact (`Security-Contact`)", 

514 description=textwrap.dedent( 

515 """\ 

516 Which person, mailing list, forum, etc. to send security-related messages in the first place. 

517 """ 

518 ), 

519 ), 

520 ) 

521 add_keyword( 

522 pg, 

523 root_parser, 

524 plugin_metadata, 

525 "Webservice", 

526 str, 

527 inline_reference_documentation=reference_documentation( 

528 title="Webservice (`Webservice`)", 

529 description=textwrap.dedent( 

530 """\ 

531 URL to a web page where the packaged program can also be used. 

532 """ 

533 ), 

534 ), 

535 ) 

536 return root_parser 

537 

538 

539def _initialize_yaml_helper(lint_state: LintState) -> LSPYAMLHelper[None]: 

540 return LSPYAMLHelper( 

541 lint_state, 

542 lint_state.plugin_feature_set.manifest_parser_generator, 

543 None, 

544 ) 

545 

546 

547@lint_diagnostics(_DISPATCH_RULE) 

548async def _lint_debian_upstream_metadata(lint_state: LintState) -> None: 

549 await generic_yaml_lint( 

550 lint_state, 

551 root_object_parser(), 

552 _initialize_yaml_helper, 

553 ) 

554 

555 

556@lsp_completer(_DISPATCH_RULE) 

557def debian_upstream_metadata_completer( 

558 ls: "DebputyLanguageServer", 

559 params: types.CompletionParams, 

560) -> types.CompletionList | Sequence[types.CompletionItem] | None: 

561 return generic_yaml_completer( 

562 ls, 

563 params, 

564 root_object_parser(), 

565 ) 

566 

567 

568@lsp_hover(_DISPATCH_RULE) 

569def debputy_manifest_hover( 

570 ls: "DebputyLanguageServer", 

571 params: types.HoverParams, 

572) -> types.Hover | None: 

573 return generic_yaml_hover(ls, params, lambda _: root_object_parser())