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

69 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-09-07 09:27 +0000

1import textwrap 

2from functools import lru_cache 

3from typing import ( 

4 Optional, 

5 Any, 

6 Union, 

7 Sequence, 

8 TYPE_CHECKING, 

9 Type, 

10) 

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: Optional[ParserDocumentation] = 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 "CPE", 

213 str, 

214 inline_reference_documentation=reference_documentation( 

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

216 description=textwrap.dedent( 

217 """\ 

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

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

220 

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

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

223 """ 

224 ), 

225 ), 

226 ) 

227 add_keyword( 

228 pg, 

229 root_parser, 

230 plugin_metadata, 

231 "Documentation", 

232 str, 

233 inline_reference_documentation=reference_documentation( 

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

235 description=textwrap.dedent( 

236 """\ 

237 A URL to online documentation. 

238 """ 

239 ), 

240 ), 

241 ) 

242 add_keyword( 

243 pg, 

244 root_parser, 

245 plugin_metadata, 

246 "Donation", 

247 str, 

248 inline_reference_documentation=reference_documentation( 

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

250 description=textwrap.dedent( 

251 """\ 

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

253 """ 

254 ), 

255 ), 

256 ) 

257 add_keyword( 

258 pg, 

259 root_parser, 

260 plugin_metadata, 

261 "FAQ", 

262 str, 

263 inline_reference_documentation=reference_documentation( 

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

265 description=textwrap.dedent( 

266 """\ 

267 A URL to the online FAQ. 

268 """ 

269 ), 

270 ), 

271 ) 

272 add_keyword( 

273 pg, 

274 root_parser, 

275 plugin_metadata, 

276 "Funding", 

277 # Unsure of the format 

278 Any, 

279 inline_reference_documentation=reference_documentation( 

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

281 description=textwrap.dedent( 

282 """\ 

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

284 """ 

285 ), 

286 ), 

287 ) 

288 add_keyword( 

289 pg, 

290 root_parser, 

291 plugin_metadata, 

292 "Gallery", 

293 str, 

294 inline_reference_documentation=reference_documentation( 

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

296 description=textwrap.dedent( 

297 """\ 

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

299 """ 

300 ), 

301 ), 

302 ) 

303 add_keyword( 

304 pg, 

305 root_parser, 

306 plugin_metadata, 

307 "Other-References", 

308 str, 

309 inline_reference_documentation=reference_documentation( 

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

311 description=textwrap.dedent( 

312 """\ 

313 A URL to a upstream page containing more references. 

314 """ 

315 ), 

316 ), 

317 ) 

318 add_keyword( 

319 pg, 

320 root_parser, 

321 plugin_metadata, 

322 "Reference", 

323 # Complex type 

324 Any, 

325 inline_reference_documentation=reference_documentation( 

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

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

328 description=textwrap.dedent( 

329 """\ 

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

331 the one or more of the following keys. 

332  

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

334 entries must provide the same content. 

335 """ 

336 ), 

337 ), 

338 ) 

339 

340 # 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. 

341 # 

342 # 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). 

343 # 

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

345 # 

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

347 # 

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

349 # 

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

351 # 

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

353 # 

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

355 # 

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

357 # 

358 # Number:: Issue number. 

359 # 

360 # 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 ? 

361 # 

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

363 # 

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

365 # 

366 # Title:: Article title. 

367 # 

368 # 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. 

369 # 

370 # 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. 

371 # 

372 # Volume:: Journal volume. 

373 # 

374 # Year:: Year of publication 

375 # 

376 

377 add_keyword( 

378 pg, 

379 root_parser, 

380 plugin_metadata, 

381 "Registration", 

382 str, 

383 inline_reference_documentation=reference_documentation( 

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

385 description=textwrap.dedent( 

386 """\ 

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

388 accounts, registration for counting/contacting users etc. 

389 """ 

390 ), 

391 ), 

392 ) 

393 add_keyword( 

394 pg, 

395 root_parser, 

396 plugin_metadata, 

397 "Registry", 

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

399 Any, 

400 inline_reference_documentation=reference_documentation( 

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

402 description=textwrap.dedent( 

403 """\ 

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

405  

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

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

408 Example: 

409 ```yaml 

410 Registry: 

411 - Name: bio.tools 

412 Entry: clustalw 

413 - Name: OMICtools 

414 Entry: OMICS_02562 

415 - Name: SciCrunch 

416 Entry: SCR_002909 

417 ``` 

418 """ 

419 ), 

420 ), 

421 ) 

422 

423 add_keyword( 

424 pg, 

425 root_parser, 

426 plugin_metadata, 

427 "Repository", 

428 str, 

429 inline_reference_documentation=reference_documentation( 

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

431 description=textwrap.dedent( 

432 """\ 

433 URL to a repository containing the upstream sources. 

434 """ 

435 ), 

436 ), 

437 ) 

438 

439 add_keyword( 

440 pg, 

441 root_parser, 

442 plugin_metadata, 

443 "Repository-Browse", 

444 str, 

445 inline_reference_documentation=reference_documentation( 

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

447 description=textwrap.dedent( 

448 """\ 

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

450 """ 

451 ), 

452 ), 

453 ) 

454 add_keyword( 

455 pg, 

456 root_parser, 

457 plugin_metadata, 

458 "Screenshots", 

459 Any, 

460 inline_reference_documentation=reference_documentation( 

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

462 description=textwrap.dedent( 

463 """\ 

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

465 represented by a scalar or a sequence of scalars. 

466 """ 

467 ), 

468 ), 

469 ) 

470 add_keyword( 

471 pg, 

472 root_parser, 

473 plugin_metadata, 

474 "Security-Contact", 

475 str, 

476 inline_reference_documentation=reference_documentation( 

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

478 description=textwrap.dedent( 

479 """\ 

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

481 """ 

482 ), 

483 ), 

484 ) 

485 add_keyword( 

486 pg, 

487 root_parser, 

488 plugin_metadata, 

489 "Webservice", 

490 str, 

491 inline_reference_documentation=reference_documentation( 

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

493 description=textwrap.dedent( 

494 """\ 

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

496 """ 

497 ), 

498 ), 

499 ) 

500 return root_parser 

501 

502 

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

504 return LSPYAMLHelper( 

505 lint_state, 

506 lint_state.plugin_feature_set.manifest_parser_generator, 

507 None, 

508 ) 

509 

510 

511@lint_diagnostics(_DISPATCH_RULE) 

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

513 await generic_yaml_lint( 

514 lint_state, 

515 root_object_parser(), 

516 _initialize_yaml_helper, 

517 ) 

518 

519 

520@lsp_completer(_DISPATCH_RULE) 

521def debian_upstream_metadata_completer( 

522 ls: "DebputyLanguageServer", 

523 params: types.CompletionParams, 

524) -> Optional[Union[types.CompletionList, Sequence[types.CompletionItem]]]: 

525 return generic_yaml_completer( 

526 ls, 

527 params, 

528 root_object_parser(), 

529 ) 

530 

531 

532@lsp_hover(_DISPATCH_RULE) 

533def debputy_manifest_hover( 

534 ls: "DebputyLanguageServer", 

535 params: types.HoverParams, 

536) -> Optional[types.Hover]: 

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