Coverage for src/debputy/lsp/lsp_debian_upstream_metadata.py: 30%
234 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
1import textwrap
2from functools import lru_cache
3from typing import (
4 Optional,
5 Any,
6 Union,
7 Sequence,
8 TYPE_CHECKING,
9)
11from debputy.highlevel_manifest import MANIFEST_YAML
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 error_range_at_position,
23 insert_complete_marker_snippet,
24 YAML_COMPLETION_HINT_KEY,
25 yaml_flag_unknown_key,
26 _trace_cursor,
27 DEBPUTY_PLUGIN_METADATA,
28 resolve_keyword,
29 generic_yaml_hover,
30 completion_from_attr,
31)
32from debputy.manifest_parser.base_types import (
33 DebputyParsedContent,
34)
35from debputy.manifest_parser.declarative_parser import (
36 AttributeDescription,
37 ParserGenerator,
38 DeclarativeNonMappingInputParser,
39)
40from debputy.manifest_parser.declarative_parser import DeclarativeMappingInputParser
41from debputy.manifest_parser.parser_data import ParserContextData
42from debputy.manifest_parser.tagging_types import DebputyDispatchableType
43from debputy.manifest_parser.util import AttributePath
44from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin
45from debputy.plugin.api.impl_types import (
46 DeclarativeInputParser,
47 DispatchingParserBase,
48 DebputyPluginMetadata,
49 ListWrappedDeclarativeInputParser,
50 InPackageContextParser,
51 DeclarativeValuelessKeywordInputParser,
52 DispatchingObjectParser,
53)
54from debputy.plugin.api.spec import ParserDocumentation, reference_documentation
55from debputy.util import _info
56from debputy.yaml.compat import (
57 CommentedMap,
58 CommentedSeq,
59 MarkedYAMLError,
60 YAMLError,
61)
63try:
64 from debputy.lsp.debputy_ls import DebputyLanguageServer
65 from debputy.lsp.vendoring._deb822_repro.locatable import (
66 Position as TEPosition,
67 Range as TERange,
68 )
69except ImportError:
70 pass
72if TYPE_CHECKING:
73 import lsprotocol.types as types
74else:
75 import debputy.lsprotocol.types as types
78_DISPATCH_RULE = LanguageDispatchRule.new_rule(
79 "debian/upstream/metadata",
80 "debian/upstream/metadata",
81 [SecondaryLanguage("yaml", filename_based_lookup=True)],
82)
85lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION)
86lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)
89class StrDebputyParsedContent(DebputyParsedContent):
90 content: str
93def _parser_handler(
94 _key: str,
95 value: Any,
96 _attr_path: AttributePath,
97 _context: Optional["ParserContextData"],
98) -> Any:
99 return value
102def add_keyword(
103 pg: ParserGenerator,
104 root_parser: DispatchingParserBase[Any],
105 plugin_metadata: DebputyPluginMetadata,
106 keyword: str,
107 *,
108 inline_reference_documentation: Optional[ParserDocumentation] = None,
109) -> None:
110 parser = pg.generate_parser(
111 StrDebputyParsedContent,
112 source_content=str,
113 inline_reference_documentation=inline_reference_documentation,
114 )
115 root_parser.register_parser(
116 keyword,
117 parser,
118 _parser_handler,
119 plugin_metadata,
120 )
123@lru_cache
124def root_object_parser() -> DispatchingObjectParser:
125 plugin_metadata = plugin_metadata_for_debputys_own_plugin()
126 pg = ParserGenerator()
127 pg.add_object_parser(
128 "<ROOT>",
129 unknown_keys_diagnostic_severity="warning",
130 )
131 root_parser = pg.dispatchable_object_parsers["<ROOT>"]
132 add_keyword(
133 pg,
134 root_parser,
135 plugin_metadata,
136 "Archive",
137 inline_reference_documentation=reference_documentation(
138 title="Archive (`Archive`)",
139 description=textwrap.dedent(
140 """\
141 The name of the large archive that the upstream work is part of, like CPAN.
142 """
143 ),
144 ),
145 )
146 add_keyword(
147 pg,
148 root_parser,
149 plugin_metadata,
150 "ASCL-Id",
151 inline_reference_documentation=reference_documentation(
152 title="ASCL Identifier (`ASCL-Id`)",
153 description=textwrap.dedent(
154 """\
155 Identification code in the http://ascl.net
156 """
157 ),
158 ),
159 )
160 add_keyword(
161 pg,
162 root_parser,
163 plugin_metadata,
164 "Bug-Database",
165 inline_reference_documentation=reference_documentation(
166 title="Bug database or tracker for the project (`Bug-Database`)",
167 description=textwrap.dedent(
168 """\
169 A URL to the list of known bugs for the project.
170 """
171 ),
172 ),
173 )
174 add_keyword(
175 pg,
176 root_parser,
177 plugin_metadata,
178 "Bug-Submit",
179 inline_reference_documentation=reference_documentation(
180 title="Bug submission URL for the project (`Bug-Submit`)",
181 description=textwrap.dedent(
182 """\
183 A URL that is the place where new bug reports should be sent.
184 """
185 ),
186 ),
187 )
188 add_keyword(
189 pg,
190 root_parser,
191 plugin_metadata,
192 "Cite-As",
193 inline_reference_documentation=reference_documentation(
194 title="Cite-As (`Cite-As`)",
195 description=textwrap.dedent(
196 """\
197 The way the authors want their software be cited in publications.
199 The value is a string which might contain a link in valid HTML syntax.
200 """
201 ),
202 ),
203 )
204 add_keyword(
205 pg,
206 root_parser,
207 plugin_metadata,
208 "Changelog",
209 inline_reference_documentation=reference_documentation(
210 title="Changelog (`Changelog`)",
211 description=textwrap.dedent(
212 """\
213 URL to the upstream changelog.
214 """
215 ),
216 ),
217 )
218 add_keyword(
219 pg,
220 root_parser,
221 plugin_metadata,
222 "CPE",
223 inline_reference_documentation=reference_documentation(
224 title="CPE (`CPE`)",
225 description=textwrap.dedent(
226 """\
227 One or more space separated http://cpe.mitre.org/ values useful to look up relevant CVEs
228 in the https://nvd.nist.gov/home.cfm and other CVE sources.
230 See `CPEtagPackagesDep` for information on how this information can be used.
231 **Example**: `cpe:/a:ethereal_group:ethereal`
232 """
233 ),
234 ),
235 )
236 add_keyword(
237 pg,
238 root_parser,
239 plugin_metadata,
240 "Documentation",
241 inline_reference_documentation=reference_documentation(
242 title="Documentation (`Documentation`)",
243 description=textwrap.dedent(
244 """\
245 A URL to online documentation.
246 """
247 ),
248 ),
249 )
250 add_keyword(
251 pg,
252 root_parser,
253 plugin_metadata,
254 "Donation",
255 inline_reference_documentation=reference_documentation(
256 title="Donation (`Donation`)",
257 description=textwrap.dedent(
258 """\
259 A URL to a donation form (or instructions).
260 """
261 ),
262 ),
263 )
264 add_keyword(
265 pg,
266 root_parser,
267 plugin_metadata,
268 "FAQ",
269 inline_reference_documentation=reference_documentation(
270 title="FAQ (`FAQ`)",
271 description=textwrap.dedent(
272 """\
273 A URL to the online FAQ.
274 """
275 ),
276 ),
277 )
278 add_keyword(
279 pg,
280 root_parser,
281 plugin_metadata,
282 "Funding",
283 inline_reference_documentation=reference_documentation(
284 title="Funding (`Funding`)",
285 description=textwrap.dedent(
286 """\
287 One or more sources of funding which have supported this project (e.g. NSF OCI-12345).
288 """
289 ),
290 ),
291 )
292 add_keyword(
293 pg,
294 root_parser,
295 plugin_metadata,
296 "Gallery",
297 inline_reference_documentation=reference_documentation(
298 title="Gallery (`Gallery`)",
299 description=textwrap.dedent(
300 """\
301 A URL to a gallery of pictures made with the program (not screenshots).
302 """
303 ),
304 ),
305 )
306 add_keyword(
307 pg,
308 root_parser,
309 plugin_metadata,
310 "Other-References",
311 inline_reference_documentation=reference_documentation(
312 title="Other-References (`Other-References`)",
313 description=textwrap.dedent(
314 """\
315 A URL to a upstream page containing more references.
316 """
317 ),
318 ),
319 )
320 add_keyword(
321 pg,
322 root_parser,
323 plugin_metadata,
324 "Reference",
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.
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 )
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 #
377 add_keyword(
378 pg,
379 root_parser,
380 plugin_metadata,
381 "Registration",
382 inline_reference_documentation=reference_documentation(
383 title="Registration (`Registration`)",
384 description=textwrap.dedent(
385 """\
386 A URL to a registration form (or instructions). This could be registration of bug reporting
387 accounts, registration for counting/contacting users etc.
388 """
389 ),
390 ),
391 )
392 add_keyword(
393 pg,
394 root_parser,
395 plugin_metadata,
396 "Registry",
397 # FIXME: Add List of `Name`, `Entry` objects
398 inline_reference_documentation=reference_documentation(
399 title="Registry (`Registry`)",
400 description=textwrap.dedent(
401 """\
402 This field shall point to external catalogs/registries of software.
404 The field features an array of "Name (of registry) - Entry (ID of software in that catalog)" pairs.
405 The names and entries shall only be names, not complete URIs, to avoid any bias on mirrors etc.
406 Example:
407 ```yaml
408 Registry:
409 - Name: bio.tools
410 Entry: clustalw
411 - Name: OMICtools
412 Entry: OMICS_02562
413 - Name: SciCrunch
414 Entry: SCR_002909
415 ```
416 """
417 ),
418 ),
419 )
421 add_keyword(
422 pg,
423 root_parser,
424 plugin_metadata,
425 "Repository",
426 inline_reference_documentation=reference_documentation(
427 title="Repository (`Repository`)",
428 description=textwrap.dedent(
429 """\
430 URL to a repository containing the upstream sources.
431 """
432 ),
433 ),
434 )
436 add_keyword(
437 pg,
438 root_parser,
439 plugin_metadata,
440 "Repository-Browse",
441 inline_reference_documentation=reference_documentation(
442 title="Repository-Browse (`Repository-Browse`)",
443 description=textwrap.dedent(
444 """\
445 A URL to browse the repository containing the upstream sources.
446 """
447 ),
448 ),
449 )
450 add_keyword(
451 pg,
452 root_parser,
453 plugin_metadata,
454 "Screenshots",
455 inline_reference_documentation=reference_documentation(
456 title="Screenshots (`Screenshots`)",
457 description=textwrap.dedent(
458 """\
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 )
465 add_keyword(
466 pg,
467 root_parser,
468 plugin_metadata,
469 "Security-Contact",
470 inline_reference_documentation=reference_documentation(
471 title="Security-Contact (`Security-Contact`)",
472 description=textwrap.dedent(
473 """\
474 Which person, mailing list, forum, etc. to send security-related messages in the first place.
475 """
476 ),
477 ),
478 )
479 add_keyword(
480 pg,
481 root_parser,
482 plugin_metadata,
483 "Webservice",
484 inline_reference_documentation=reference_documentation(
485 title="Webservice (`Webservice`)",
486 description=textwrap.dedent(
487 """\
488 URL to a web page where the packaged program can also be used.
489 """
490 ),
491 ),
492 )
493 return root_parser
496@lint_diagnostics(_DISPATCH_RULE)
497def _lint_debian_upstream_metadata(
498 lint_state: LintState,
499) -> None:
500 lines = lint_state.lines
502 try:
503 content = MANIFEST_YAML.load("".join(lines))
504 except MarkedYAMLError as e:
505 if e.context_mark:
506 line = e.context_mark.line
507 column = e.context_mark.column
508 else:
509 line = e.problem_mark.line
510 column = e.problem_mark.column
511 error_range = error_range_at_position(
512 lines,
513 line,
514 column,
515 )
516 lint_state.emit_diagnostic(
517 error_range,
518 f"YAML parse error: {e}",
519 "error",
520 "debputy",
521 )
522 except YAMLError as e:
523 error_range = TERange(
524 TEPosition(0, 0),
525 TEPosition(0, len(lines[0])),
526 )
527 lint_state.emit_diagnostic(
528 error_range,
529 f"Unknown YAML parse error: {e} [{e!r}]",
530 "error",
531 "debputy",
532 )
533 else:
534 feature_set = lint_state.plugin_feature_set
535 pg = feature_set.manifest_parser_generator
536 root_parser = root_object_parser()
537 _lint_content(
538 lint_state,
539 pg,
540 root_parser,
541 content,
542 )
545def _conflicting_key(
546 lint_state: LintState,
547 key_a: str,
548 key_b: str,
549 key_a_line: int,
550 key_a_col: int,
551 key_b_line: int,
552 key_b_col: int,
553) -> None:
554 key_a_range = TERange(
555 TEPosition(
556 key_a_line,
557 key_a_col,
558 ),
559 TEPosition(
560 key_a_line,
561 key_a_col + len(key_a),
562 ),
563 )
564 key_b_range = TERange(
565 TEPosition(
566 key_b_line,
567 key_b_col,
568 ),
569 TEPosition(
570 key_b_line,
571 key_b_col + len(key_b),
572 ),
573 )
574 lint_state.emit_diagnostic(
575 key_a_range,
576 f'The "{key_a}" cannot be used with "{key_b}".',
577 "error",
578 "debputy",
579 related_information=[
580 types.DiagnosticRelatedInformation(
581 location=types.Location(
582 lint_state.doc_uri,
583 key_b_range,
584 ),
585 message=f'The attribute "{key_b}" is used here.',
586 )
587 ],
588 )
590 lint_state.emit_diagnostic(
591 key_b_range,
592 f'The "{key_b}" cannot be used with "{key_a}".',
593 "error",
594 "debputy",
595 related_information=[
596 types.DiagnosticRelatedInformation(
597 location=types.Location(
598 lint_state.doc_uri,
599 key_a_range,
600 ),
601 message=f'The attribute "{key_a}" is used here.',
602 )
603 ],
604 )
607def _lint_attr_value(
608 lint_state: LintState,
609 attr: AttributeDescription,
610 pg: ParserGenerator,
611 value: Any,
612) -> None:
613 attr_type = attr.attribute_type
614 if isinstance(attr_type, type) and issubclass(attr_type, DebputyDispatchableType):
615 parser = pg.dispatch_parser_table_for(attr_type)
616 _lint_content(
617 lint_state,
618 pg,
619 parser,
620 value,
621 )
624def _lint_declarative_mapping_input_parser(
625 lint_state: LintState,
626 pg: ParserGenerator,
627 parser: DeclarativeMappingInputParser,
628 content: Any,
629) -> None:
630 if not isinstance(content, CommentedMap):
631 return
632 lc = content.lc
633 for key, value in content.items():
634 attr = parser.manifest_attributes.get(key)
635 line, col = lc.key(key)
636 if attr is None:
637 corrected_key = yaml_flag_unknown_key(
638 lint_state,
639 key,
640 parser.manifest_attributes,
641 line,
642 col,
643 )
644 if corrected_key:
645 key = corrected_key
646 attr = parser.manifest_attributes.get(corrected_key)
647 if attr is None:
648 continue
650 _lint_attr_value(
651 lint_state,
652 attr,
653 pg,
654 value,
655 )
657 for forbidden_key in attr.conflicting_attributes:
658 if forbidden_key in content:
659 con_line, con_col = lc.key(forbidden_key)
660 _conflicting_key(
661 lint_state,
662 key,
663 forbidden_key,
664 line,
665 col,
666 con_line,
667 con_col,
668 )
669 for mx in parser.mutually_exclusive_attributes:
670 matches = content.keys() & mx
671 if len(matches) < 2:
672 continue
673 key, *others = list(matches)
674 line, col = lc.key(key)
675 for other in others:
676 con_line, con_col = lc.key(other)
677 _conflicting_key(
678 lint_state,
679 key,
680 other,
681 line,
682 col,
683 con_line,
684 con_col,
685 )
688def _lint_content(
689 lint_state: LintState,
690 pg: ParserGenerator,
691 parser: DeclarativeInputParser[Any],
692 content: Any,
693) -> None:
694 if isinstance(parser, DispatchingParserBase): 694 ↛ 723line 694 didn't jump to line 723 because the condition on line 694 was always true
695 if not isinstance(content, CommentedMap): 695 ↛ 696line 695 didn't jump to line 696 because the condition on line 695 was never true
696 return
697 lc = content.lc
698 for key, value in content.items():
699 is_known = parser.is_known_keyword(key)
700 if not is_known: 700 ↛ 714line 700 didn't jump to line 714 because the condition on line 700 was always true
701 line, col = lc.key(key)
702 corrected_key = yaml_flag_unknown_key(
703 lint_state,
704 key,
705 parser.registered_keywords(),
706 line,
707 col,
708 unknown_keys_diagnostic_severity=parser.unknown_keys_diagnostic_severity,
709 )
710 if corrected_key is not None: 710 ↛ 711line 710 didn't jump to line 711 because the condition on line 710 was never true
711 key = corrected_key
712 is_known = True
714 if is_known: 714 ↛ 715line 714 didn't jump to line 715 because the condition on line 714 was never true
715 subparser = parser.parser_for(key)
716 assert subparser is not None
717 _lint_content(
718 lint_state,
719 pg,
720 subparser.parser,
721 value,
722 )
723 elif isinstance(parser, ListWrappedDeclarativeInputParser):
724 if not isinstance(content, CommentedSeq):
725 return
726 subparser = parser.delegate
727 for value in content:
728 _lint_content(lint_state, pg, subparser, value)
729 elif isinstance(parser, InPackageContextParser):
730 if not isinstance(content, CommentedMap):
731 return
732 print(lint_state)
733 known_packages = lint_state.binary_packages
734 lc = content.lc
735 for k, v in content.items():
736 if "{{" not in k and known_packages is not None and k not in known_packages:
737 line, col = lc.key(k)
738 yaml_flag_unknown_key(
739 lint_state,
740 k,
741 known_packages,
742 line,
743 col,
744 message_format='Unknown package "{key}".',
745 )
746 _lint_content(lint_state, pg, parser.delegate, v)
747 elif isinstance(parser, DeclarativeMappingInputParser):
748 _lint_declarative_mapping_input_parser(
749 lint_state,
750 pg,
751 parser,
752 content,
753 )
756@lsp_completer(_DISPATCH_RULE)
757def debian_upstream_metadata_completer(
758 ls: "DebputyLanguageServer",
759 params: types.CompletionParams,
760) -> Optional[Union[types.CompletionList, Sequence[types.CompletionItem]]]:
761 doc = ls.workspace.get_text_document(params.text_document.uri)
762 lines = doc.lines
763 server_position = doc.position_codec.position_from_client_units(
764 lines, params.position
765 )
766 added_key = insert_complete_marker_snippet(lines, server_position)
767 attempts = 1 if added_key else 2
768 content = None
770 while attempts > 0:
771 attempts -= 1
772 try:
773 content = MANIFEST_YAML.load("".join(lines))
774 break
775 except MarkedYAMLError as e:
776 context_line = (
777 e.context_mark.line if e.context_mark else e.problem_mark.line
778 )
779 if (
780 e.problem_mark.line != server_position.line
781 and context_line != server_position.line
782 ):
783 l_data = (
784 lines[e.problem_mark.line].rstrip()
785 if e.problem_mark.line < len(lines)
786 else "N/A (OOB)"
787 )
789 _info(f"Parse error on line: {e.problem_mark.line}: {l_data}")
790 return None
792 if attempts > 0:
793 # Try to make it a key and see if that fixes the problem
794 new_line = (
795 lines[server_position.line].rstrip() + YAML_COMPLETION_HINT_KEY
796 )
797 lines[server_position.line] = new_line
798 except YAMLError:
799 break
800 if content is None:
801 context = lines[server_position.line].replace("\n", "\\n")
802 _info(f"Completion failed: parse error: Line in question: {context}")
803 return None
804 attribute_root_path = AttributePath.root_path(content)
805 m = _trace_cursor(content, attribute_root_path, server_position)
807 if m is None:
808 _info("No match")
809 return None
810 matched_key, attr_path, matched, parent = m
811 _info(f"Matched path: {matched} (path: {attr_path.path}) [{matched_key=}]")
812 feature_set = ls.plugin_feature_set
813 root_parser = root_object_parser()
814 segments = list(attr_path.path_segments())
815 km = resolve_keyword(
816 root_parser,
817 DEBPUTY_PLUGIN_METADATA,
818 segments,
819 0,
820 feature_set.manifest_parser_generator,
821 is_completion_attempt=True,
822 )
823 if km is None:
824 return None
825 parser, _, at_depth_idx = km
826 _info(f"Match leaf parser {at_depth_idx} -- {parser.__class__}")
827 items = []
828 if at_depth_idx + 1 >= len(segments):
829 if isinstance(parser, DispatchingParserBase):
830 if matched_key:
831 items = [
832 types.CompletionItem(f"{k}:")
833 for k in parser.registered_keywords()
834 if k not in parent
835 and not isinstance(
836 parser.parser_for(k).parser,
837 DeclarativeValuelessKeywordInputParser,
838 )
839 ]
840 else:
841 items = [
842 types.CompletionItem(k)
843 for k in parser.registered_keywords()
844 if k not in parent
845 and isinstance(
846 parser.parser_for(k).parser,
847 DeclarativeValuelessKeywordInputParser,
848 )
849 ]
850 elif isinstance(parser, InPackageContextParser):
851 binary_packages = ls.lint_state(doc).binary_packages
852 if binary_packages is not None:
853 items = [
854 types.CompletionItem(f"{p}:")
855 for p in binary_packages
856 if p not in parent
857 ]
858 elif isinstance(parser, DeclarativeMappingInputParser):
859 if matched_key:
860 _info("Match attributes")
861 locked = set(parent)
862 for mx in parser.mutually_exclusive_attributes:
863 if not mx.isdisjoint(parent.keys()):
864 locked.update(mx)
865 for attr_name, attr in parser.manifest_attributes.items():
866 if not attr.conflicting_attributes.isdisjoint(parent.keys()):
867 locked.add(attr_name)
868 break
869 items = [
870 types.CompletionItem(f"{k}:")
871 for k in parser.manifest_attributes
872 if k not in locked
873 ]
874 else:
875 # Value
876 key = segments[at_depth_idx] if len(segments) > at_depth_idx else None
877 attr = parser.manifest_attributes.get(key)
878 if attr is not None:
879 _info(f"Expand value / key: {key} -- {attr.attribute_type}")
880 items = completion_from_attr(
881 attr,
882 feature_set.manifest_parser_generator,
883 matched,
884 )
885 else:
886 _info(
887 f"Expand value / key: {key} -- !! {list(parser.manifest_attributes)}"
888 )
889 elif isinstance(parser, DeclarativeNonMappingInputParser):
890 attr = parser.alt_form_parser
891 items = completion_from_attr(
892 attr,
893 feature_set.manifest_parser_generator,
894 matched,
895 )
896 return items
899@lsp_hover(_DISPATCH_RULE)
900def debputy_manifest_hover(
901 ls: "DebputyLanguageServer",
902 params: types.HoverParams,
903) -> Optional[types.Hover]:
904 return generic_yaml_hover(ls, params, lambda _: root_object_parser())