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-04-19 20:37 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2026-04-19 20:37 +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
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
44try:
45 from debputy.lsp.debputy_ls import DebputyLanguageServer
46 from debian._deb822_repro.locatable import (
47 Position as TEPosition,
48 Range as TERange,
49 )
50except ImportError:
51 pass
53if TYPE_CHECKING:
54 import lsprotocol.types as types
55else:
56 import debputy.lsprotocol.types as types
59_DISPATCH_RULE = LanguageDispatchRule.new_rule(
60 "debian/upstream/metadata",
61 None,
62 "debian/upstream/metadata",
63 [SecondaryLanguage("yaml", secondary_lookup="path-name")],
64)
67lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION)
68lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)
71TT = type[T]
74def _parser_handler(
75 _key: str,
76 value: Any,
77 _attr_path: AttributePath,
78 _context: Optional["ParserContextData"],
79) -> Any:
80 return value
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
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 )
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 The name of the large archive that the upstream work is part of, like CPAN.
126 """),
127 ),
128 )
129 add_keyword(
130 pg,
131 root_parser,
132 plugin_metadata,
133 "ASCL-Id",
134 str,
135 inline_reference_documentation=reference_documentation(
136 title="ASCL Identifier (`ASCL-Id`)",
137 description=textwrap.dedent("""\
138 Identification code in the http://ascl.net
139 """),
140 ),
141 )
142 add_keyword(
143 pg,
144 root_parser,
145 plugin_metadata,
146 "Bug-Database",
147 str,
148 inline_reference_documentation=reference_documentation(
149 title="Bug database or tracker for the project (`Bug-Database`)",
150 description=textwrap.dedent("""\
151 A URL to the list of known bugs for the project.
152 """),
153 ),
154 )
155 add_keyword(
156 pg,
157 root_parser,
158 plugin_metadata,
159 "Bug-Submit",
160 str,
161 inline_reference_documentation=reference_documentation(
162 title="Bug submission URL for the project (`Bug-Submit`)",
163 description=textwrap.dedent("""\
164 A URL that is the place where new bug reports should be sent.
165 """),
166 ),
167 )
168 add_keyword(
169 pg,
170 root_parser,
171 plugin_metadata,
172 "Cite-As",
173 str,
174 inline_reference_documentation=reference_documentation(
175 title="Cite-As (`Cite-As`)",
176 description=textwrap.dedent("""\
177 The way the authors want their software be cited in publications.
179 The value is a string which might contain a link in valid HTML syntax.
180 """),
181 ),
182 )
183 add_keyword(
184 pg,
185 root_parser,
186 plugin_metadata,
187 "Changelog",
188 str,
189 inline_reference_documentation=reference_documentation(
190 title="Changelog (`Changelog`)",
191 description=textwrap.dedent("""\
192 URL to the upstream changelog.
193 """),
194 ),
195 )
196 add_keyword(
197 pg,
198 root_parser,
199 plugin_metadata,
200 "Contact",
201 str,
202 inline_reference_documentation=reference_documentation(
203 title="Contact (`Contact`)",
204 description=textwrap.dedent("""\
205 Contact point for the upstream point.
207 Deprecated when using the machine readable format. The `Upstream-Contact` field in
208 `debian/copyright` is the direct replacement in that case.
209 """),
210 ),
211 )
212 add_keyword(
213 pg,
214 root_parser,
215 plugin_metadata,
216 "CPE",
217 str,
218 inline_reference_documentation=reference_documentation(
219 title="CPE (`CPE`)",
220 description=textwrap.dedent("""\
221 One or more space separated http://cpe.mitre.org/ values useful to look up relevant CVEs
222 in the https://nvd.nist.gov/home.cfm and other CVE sources.
224 See `CPEtagPackagesDep` for information on how this information can be used.
225 **Example**: `cpe:/a:ethereal_group:ethereal`
226 """),
227 ),
228 )
229 add_keyword(
230 pg,
231 root_parser,
232 plugin_metadata,
233 "Documentation",
234 str,
235 inline_reference_documentation=reference_documentation(
236 title="Documentation (`Documentation`)",
237 description=textwrap.dedent("""\
238 A URL to online documentation.
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 A URL to a donation form (or instructions).
252 """),
253 ),
254 )
255 add_keyword(
256 pg,
257 root_parser,
258 plugin_metadata,
259 "FAQ",
260 str,
261 inline_reference_documentation=reference_documentation(
262 title="FAQ (`FAQ`)",
263 description=textwrap.dedent("""\
264 A URL to the online FAQ.
265 """),
266 ),
267 )
268 add_keyword(
269 pg,
270 root_parser,
271 plugin_metadata,
272 "Funding",
273 # Unsure of the format
274 Any,
275 inline_reference_documentation=reference_documentation(
276 title="Funding (`Funding`)",
277 description=textwrap.dedent("""\
278 One or more sources of funding which have supported this project (e.g. NSF OCI-12345).
279 """),
280 ),
281 )
282 add_keyword(
283 pg,
284 root_parser,
285 plugin_metadata,
286 "Gallery",
287 str,
288 inline_reference_documentation=reference_documentation(
289 title="Gallery (`Gallery`)",
290 description=textwrap.dedent("""\
291 A URL to a gallery of pictures made with the program (not screenshots).
292 """),
293 ),
294 )
295 add_keyword(
296 pg,
297 root_parser,
298 plugin_metadata,
299 "Name",
300 str,
301 inline_reference_documentation=reference_documentation(
302 title="Name (`Name`)",
303 description=textwrap.dedent("""\
304 Name of the upstream project.
306 Deprecated when using the machine readable format. The `Upstream-Name` field in
307 `debian/copyright` is the direct replacement in that case.
308 """),
309 ),
310 )
311 add_keyword(
312 pg,
313 root_parser,
314 plugin_metadata,
315 "Other-References",
316 str,
317 inline_reference_documentation=reference_documentation(
318 title="Other-References (`Other-References`)",
319 description=textwrap.dedent("""\
320 A URL to a upstream page containing more references.
321 """),
322 ),
323 )
324 add_keyword(
325 pg,
326 root_parser,
327 plugin_metadata,
328 "Reference",
329 # Complex type
330 Any,
331 inline_reference_documentation=reference_documentation(
332 title="Reference (`Reference`)",
333 # FIXME: Add the fields below as a nested subobject or list of such objects
334 description=textwrap.dedent("""\
335 One or more bibliographic references, represented as a mapping or sequence of mappings containing
336 the one or more of the following keys.
338 The values for the keys are always scalars, and the keys that correspond to standard BibTeX
339 entries must provide the same content.
340 """),
341 ),
342 )
344 # 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.
345 #
346 # 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).
347 #
348 # Booktitle:: Title of the book the article is published in
349 #
350 # DOI:: This is the digital object identifier of the academic publication describing the packaged work.
351 #
352 # Editor:: Editor of the book the article is published in
353 #
354 # Eprint:: Hyperlink to the PDF file of the article.
355 #
356 # ISBN:: International Standard Book Number of the book if the article is part of the book or the reference is a book
357 #
358 # ISSN:: International Standard Serial Number of the periodical publication if the article is part of a series
359 #
360 # Journal:: Abbreviated journal name [To be discussed: which standard to recommend ?].
361 #
362 # Number:: Issue number.
363 #
364 # 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 ?
365 #
366 # PMID:: ID number in the https://www.ncbi.nlm.nih.gov/pubmed/ database.
367 #
368 # Publisher:: Publisher of the book containing the article
369 #
370 # Title:: Article title.
371 #
372 # 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.
373 #
374 # 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.
375 #
376 # Volume:: Journal volume.
377 #
378 # Year:: Year of publication
379 #
381 add_keyword(
382 pg,
383 root_parser,
384 plugin_metadata,
385 "Registration",
386 str,
387 inline_reference_documentation=reference_documentation(
388 title="Registration (`Registration`)",
389 description=textwrap.dedent("""\
390 A URL to a registration form (or instructions). This could be registration of bug reporting
391 accounts, registration for counting/contacting users etc.
392 """),
393 ),
394 )
395 add_keyword(
396 pg,
397 root_parser,
398 plugin_metadata,
399 "Registry",
400 # FIXME: Add List of `Name`, `Entry` objects
401 Any,
402 inline_reference_documentation=reference_documentation(
403 title="Registry (`Registry`)",
404 description=textwrap.dedent("""\
405 This field shall point to external catalogs/registries of software.
407 The field features an array of "Name (of registry) - Entry (ID of software in that catalog)" pairs.
408 The names and entries shall only be names, not complete URIs, to avoid any bias on mirrors etc.
409 Example:
410 ```yaml
411 Registry:
412 - Name: bio.tools
413 Entry: clustalw
414 - Name: OMICtools
415 Entry: OMICS_02562
416 - Name: SciCrunch
417 Entry: SCR_002909
418 ```
419 """),
420 ),
421 )
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 URL to a repository containing the upstream sources.
433 """),
434 ),
435 )
437 add_keyword(
438 pg,
439 root_parser,
440 plugin_metadata,
441 "Repository-Browse",
442 str,
443 inline_reference_documentation=reference_documentation(
444 title="Repository-Browse (`Repository-Browse`)",
445 description=textwrap.dedent("""\
446 A URL to browse the repository containing the upstream sources.
447 """),
448 ),
449 )
450 add_keyword(
451 pg,
452 root_parser,
453 plugin_metadata,
454 "Screenshots",
455 Any,
456 inline_reference_documentation=reference_documentation(
457 title="Screenshots (`Screenshots`)",
458 description=textwrap.dedent("""\
459 One or more URLs to upstream pages containing screenshots (not <https://screenshots.debian.net>),
460 represented by a scalar or a sequence of scalars.
461 """),
462 ),
463 )
464 add_keyword(
465 pg,
466 root_parser,
467 plugin_metadata,
468 "Security-Contact",
469 str,
470 inline_reference_documentation=reference_documentation(
471 title="Security-Contact (`Security-Contact`)",
472 description=textwrap.dedent("""\
473 Which person, mailing list, forum, etc. to send security-related messages in the first place.
474 """),
475 ),
476 )
477 add_keyword(
478 pg,
479 root_parser,
480 plugin_metadata,
481 "Webservice",
482 str,
483 inline_reference_documentation=reference_documentation(
484 title="Webservice (`Webservice`)",
485 description=textwrap.dedent("""\
486 URL to a web page where the packaged program can also be used.
487 """),
488 ),
489 )
490 return root_parser
493def _initialize_yaml_helper(lint_state: LintState) -> LSPYAMLHelper[None]:
494 return LSPYAMLHelper(
495 lint_state,
496 lint_state.plugin_feature_set.manifest_parser_generator,
497 None,
498 )
501@lint_diagnostics(_DISPATCH_RULE)
502async def _lint_debian_upstream_metadata(lint_state: LintState) -> None:
503 await generic_yaml_lint(
504 lint_state,
505 root_object_parser(),
506 _initialize_yaml_helper,
507 )
510@lsp_completer(_DISPATCH_RULE)
511def debian_upstream_metadata_completer(
512 ls: "DebputyLanguageServer",
513 params: types.CompletionParams,
514) -> types.CompletionList | Sequence[types.CompletionItem] | None:
515 return generic_yaml_completer(
516 ls,
517 params,
518 root_object_parser(),
519 )
522@lsp_hover(_DISPATCH_RULE)
523def debputy_manifest_hover(
524 ls: "DebputyLanguageServer",
525 params: types.HoverParams,
526) -> types.Hover | None:
527 return generic_yaml_hover(ls, params, lambda _: root_object_parser())