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
« 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
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
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
51if TYPE_CHECKING:
52 import lsprotocol.types as types
53else:
54 import debputy.lsprotocol.types as types
57_DISPATCH_RULE = LanguageDispatchRule.new_rule(
58 "debian/upstream/metadata",
59 None,
60 "debian/upstream/metadata",
61 [SecondaryLanguage("yaml", secondary_lookup="path-name")],
62)
65lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION)
66lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)
69TT = type[T]
72def _parser_handler(
73 _key: str,
74 value: Any,
75 _attr_path: AttributePath,
76 _context: Optional["ParserContextData"],
77) -> Any:
78 return value
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
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 )
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.
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.
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.
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.
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.
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 )
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 #
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.
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 )
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 )
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
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 )
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 )
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 )
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())