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

71 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-07-22 10:58 +0000

1import textwrap 

2from functools import lru_cache 

3from typing import ( 

4 Optional, 

5 Any, 

6 TYPE_CHECKING, 

7) 

8from collections.abc import Sequence 

9 

10from debputy.linting.lint_util import LintState 

11from debputy.lsp.lsp_features import ( 

12 lint_diagnostics, 

13 lsp_standard_handler, 

14 lsp_hover, 

15 lsp_completer, 

16 LanguageDispatchRule, 

17 SecondaryLanguage, 

18) 

19from debputy.lsp.lsp_generic_yaml import ( 

20 generic_yaml_hover, 

21 LSPYAMLHelper, 

22 generic_yaml_lint, 

23 generic_yaml_completer, 

24) 

25from debputy.manifest_parser.base_types import ( 

26 DebputyParsedContent, 

27) 

28from debputy.manifest_parser.declarative_parser import ( 

29 ParserGenerator, 

30) 

31from debputy.manifest_parser.parser_data import ParserContextData 

32from debputy.manifest_parser.util import AttributePath 

33from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin 

34from debputy.plugin.api.impl_types import ( 

35 DispatchingParserBase, 

36 DebputyPluginMetadata, 

37 DispatchingObjectParser, 

38) 

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

40from debputy.util import T 

41 

42try: 

43 from debputy.lsp.debputy_ls import DebputyLanguageServer 

44 from debian._deb822_repro.locatable import ( 

45 Position as TEPosition, 

46 Range as TERange, 

47 ) 

48except ImportError: 

49 pass 

50 

51if TYPE_CHECKING: 

52 import lsprotocol.types as types 

53else: 

54 import debputy.lsprotocol.types as types 

55 

56 

57_DISPATCH_RULE = LanguageDispatchRule.new_rule( 

58 "debian/upstream/metadata", 

59 None, 

60 "debian/upstream/metadata", 

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

62) 

63 

64 

65lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION) 

66lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL) 

67 

68 

69TT = type[T] 

70 

71 

72def _parser_handler( 

73 _key: str, 

74 value: Any, 

75 _attr_path: AttributePath, 

76 _context: Optional["ParserContextData"], 

77) -> Any: 

78 return value 

79 

80 

81def add_keyword( 

82 pg: ParserGenerator, 

83 root_parser: DispatchingParserBase[Any], 

84 plugin_metadata: DebputyPluginMetadata, 

85 keyword: str, 

86 value_type: TT, 

87 *, 

88 inline_reference_documentation: ParserDocumentation | None = None, 

89) -> None: 

90 class DebputyParsedContentWrapper(DebputyParsedContent): 

91 content: value_type # type: ignore 

92 

93 parser = pg.generate_parser( 

94 DebputyParsedContentWrapper, 

95 source_content=value_type, 

96 inline_reference_documentation=inline_reference_documentation, 

97 ) 

98 root_parser.register_parser( 

99 keyword, 

100 parser, 

101 _parser_handler, 

102 plugin_metadata, 

103 ) 

104 

105 

106@lru_cache 

107def root_object_parser() -> DispatchingObjectParser: 

108 plugin_metadata = plugin_metadata_for_debputys_own_plugin() 

109 pg = ParserGenerator() 

110 root_parser = pg.add_object_parser( 

111 "<ROOT>", 

112 unknown_keys_diagnostic_severity="warning", 

113 ) 

114 add_keyword( 

115 pg, 

116 root_parser, 

117 plugin_metadata, 

118 "Archive", 

119 str, 

120 inline_reference_documentation=reference_documentation( 

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

122 description=textwrap.dedent("""\ 

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

124 """), 

125 ), 

126 ) 

127 add_keyword( 

128 pg, 

129 root_parser, 

130 plugin_metadata, 

131 "ASCL-Id", 

132 str, 

133 inline_reference_documentation=reference_documentation( 

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

135 description=textwrap.dedent("""\ 

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

137 """), 

138 ), 

139 ) 

140 add_keyword( 

141 pg, 

142 root_parser, 

143 plugin_metadata, 

144 "Bug-Database", 

145 str, 

146 inline_reference_documentation=reference_documentation( 

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

148 description=textwrap.dedent("""\ 

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

150 """), 

151 ), 

152 ) 

153 add_keyword( 

154 pg, 

155 root_parser, 

156 plugin_metadata, 

157 "Bug-Submit", 

158 str, 

159 inline_reference_documentation=reference_documentation( 

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

161 description=textwrap.dedent("""\ 

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

163 """), 

164 ), 

165 ) 

166 add_keyword( 

167 pg, 

168 root_parser, 

169 plugin_metadata, 

170 "Cite-As", 

171 str, 

172 inline_reference_documentation=reference_documentation( 

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

174 description=textwrap.dedent("""\ 

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

176 

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

178 """), 

179 ), 

180 ) 

181 add_keyword( 

182 pg, 

183 root_parser, 

184 plugin_metadata, 

185 "Changelog", 

186 str, 

187 inline_reference_documentation=reference_documentation( 

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

189 description=textwrap.dedent("""\ 

190 URL to the upstream changelog. 

191 """), 

192 ), 

193 ) 

194 add_keyword( 

195 pg, 

196 root_parser, 

197 plugin_metadata, 

198 "Contact", 

199 str, 

200 inline_reference_documentation=reference_documentation( 

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

202 description=textwrap.dedent("""\ 

203 Contact point for the upstream point. 

204 

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

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

207 """), 

208 ), 

209 ) 

210 add_keyword( 

211 pg, 

212 root_parser, 

213 plugin_metadata, 

214 "CPE", 

215 str, 

216 inline_reference_documentation=reference_documentation( 

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

218 description=textwrap.dedent("""\ 

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

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

221 

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

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

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 A URL to online documentation. 

237 """), 

238 ), 

239 ) 

240 add_keyword( 

241 pg, 

242 root_parser, 

243 plugin_metadata, 

244 "Donation", 

245 str, 

246 inline_reference_documentation=reference_documentation( 

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

248 description=textwrap.dedent("""\ 

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

250 """), 

251 ), 

252 ) 

253 add_keyword( 

254 pg, 

255 root_parser, 

256 plugin_metadata, 

257 "FAQ", 

258 str, 

259 inline_reference_documentation=reference_documentation( 

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

261 description=textwrap.dedent("""\ 

262 A URL to the online FAQ. 

263 """), 

264 ), 

265 ) 

266 add_keyword( 

267 pg, 

268 root_parser, 

269 plugin_metadata, 

270 "Funding", 

271 # Unsure of the format 

272 Any, 

273 inline_reference_documentation=reference_documentation( 

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

275 description=textwrap.dedent("""\ 

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

277 """), 

278 ), 

279 ) 

280 add_keyword( 

281 pg, 

282 root_parser, 

283 plugin_metadata, 

284 "Gallery", 

285 str, 

286 inline_reference_documentation=reference_documentation( 

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

288 description=textwrap.dedent("""\ 

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

290 """), 

291 ), 

292 ) 

293 add_keyword( 

294 pg, 

295 root_parser, 

296 plugin_metadata, 

297 "Name", 

298 str, 

299 inline_reference_documentation=reference_documentation( 

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

301 description=textwrap.dedent("""\ 

302 Name of the upstream project. 

303 

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

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

306 """), 

307 ), 

308 ) 

309 add_keyword( 

310 pg, 

311 root_parser, 

312 plugin_metadata, 

313 "Other-References", 

314 str, 

315 inline_reference_documentation=reference_documentation( 

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

317 description=textwrap.dedent("""\ 

318 A URL to a upstream page containing more references. 

319 """), 

320 ), 

321 ) 

322 add_keyword( 

323 pg, 

324 root_parser, 

325 plugin_metadata, 

326 "Reference", 

327 # Complex type 

328 Any, 

329 inline_reference_documentation=reference_documentation( 

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

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

332 description=textwrap.dedent("""\ 

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

334 the one or more of the following keys. 

335  

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

337 entries must provide the same content. 

338 """), 

339 ), 

340 ) 

341 

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

343 # 

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

345 # 

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

347 # 

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

349 # 

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

351 # 

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

353 # 

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

355 # 

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

357 # 

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

359 # 

360 # Number:: Issue number. 

361 # 

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

363 # 

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

365 # 

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

367 # 

368 # Title:: Article title. 

369 # 

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

371 # 

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

373 # 

374 # Volume:: Journal volume. 

375 # 

376 # Year:: Year of publication 

377 # 

378 

379 add_keyword( 

380 pg, 

381 root_parser, 

382 plugin_metadata, 

383 "Registration", 

384 str, 

385 inline_reference_documentation=reference_documentation( 

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

387 description=textwrap.dedent("""\ 

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

389 accounts, registration for counting/contacting users etc. 

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 This field shall point to external catalogs/registries of software. 

404  

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

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

407 Example: 

408 ```yaml 

409 Registry: 

410 - Name: bio.tools 

411 Entry: clustalw 

412 - Name: OMICtools 

413 Entry: OMICS_02562 

414 - Name: SciCrunch 

415 Entry: SCR_002909 

416 ``` 

417 """), 

418 ), 

419 ) 

420 

421 add_keyword( 

422 pg, 

423 root_parser, 

424 plugin_metadata, 

425 "Repository", 

426 str, 

427 inline_reference_documentation=reference_documentation( 

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

429 description=textwrap.dedent("""\ 

430 URL to a repository containing the upstream sources. 

431 """), 

432 ), 

433 ) 

434 

435 add_keyword( 

436 pg, 

437 root_parser, 

438 plugin_metadata, 

439 "Repository-Browse", 

440 str, 

441 inline_reference_documentation=reference_documentation( 

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

443 description=textwrap.dedent("""\ 

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

445 """), 

446 ), 

447 ) 

448 add_keyword( 

449 pg, 

450 root_parser, 

451 plugin_metadata, 

452 "Screenshots", 

453 Any, 

454 inline_reference_documentation=reference_documentation( 

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

456 description=textwrap.dedent("""\ 

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

458 represented by a scalar or a sequence of scalars. 

459 """), 

460 ), 

461 ) 

462 add_keyword( 

463 pg, 

464 root_parser, 

465 plugin_metadata, 

466 "Security-Contact", 

467 str, 

468 inline_reference_documentation=reference_documentation( 

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

470 description=textwrap.dedent("""\ 

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

472 """), 

473 ), 

474 ) 

475 add_keyword( 

476 pg, 

477 root_parser, 

478 plugin_metadata, 

479 "Webservice", 

480 str, 

481 inline_reference_documentation=reference_documentation( 

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

483 description=textwrap.dedent("""\ 

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

485 """), 

486 ), 

487 ) 

488 return root_parser 

489 

490 

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

492 return LSPYAMLHelper( 

493 lint_state, 

494 lint_state.plugin_feature_set.manifest_parser_generator, 

495 None, 

496 ) 

497 

498 

499@lint_diagnostics(_DISPATCH_RULE) 

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

501 await generic_yaml_lint( 

502 lint_state, 

503 root_object_parser(), 

504 _initialize_yaml_helper, 

505 ) 

506 

507 

508@lsp_completer(_DISPATCH_RULE) 

509def debian_upstream_metadata_completer( 

510 ls: "DebputyLanguageServer", 

511 params: types.CompletionParams, 

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

513 return generic_yaml_completer( 

514 ls, 

515 params, 

516 root_object_parser(), 

517 ) 

518 

519 

520@lsp_hover(_DISPATCH_RULE) 

521def debputy_manifest_hover( 

522 ls: "DebputyLanguageServer", 

523 params: types.HoverParams, 

524) -> types.Hover | None: 

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