Coverage for src/debputy/lsp/lsp_self_check.py: 56%
74 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 dataclasses
2import os.path
3import subprocess
4from typing import Callable, Sequence, List, Optional, TypeVar
6from debian.debian_support import Version
8from debputy.util import _error
11@dataclasses.dataclass(slots=True, frozen=True)
12class LSPSelfCheck:
13 feature: str
14 test: Callable[[], bool]
15 problem: str
16 how_to_fix: str
17 is_mandatory: bool = False
20LSP_CHECKS: List[LSPSelfCheck] = []
22C = TypeVar("C", bound="Callable")
25def lsp_import_check(
26 packages: Sequence[str],
27 *,
28 feature_name: Optional[str] = None,
29 is_mandatory: bool = False,
30) -> Callable[[C], C]:
32 def _wrapper(func: C) -> C:
34 def _impl():
35 try:
36 r = func()
37 except ImportError:
38 return False
39 return r is None or bool(r)
41 suffix = "fix this issue" if is_mandatory else "enable this feature"
43 LSP_CHECKS.append(
44 LSPSelfCheck(
45 _feature_name(feature_name, func),
46 _impl,
47 "Missing dependencies",
48 f"Run `apt satisfy '{', '.join(packages)}'` to {suffix}",
49 is_mandatory=is_mandatory,
50 )
51 )
52 return func
54 return _wrapper
57def lsp_generic_check(
58 problem: str,
59 how_to_fix: str,
60 *,
61 feature_name: Optional[str] = None,
62 is_mandatory: bool = False,
63) -> Callable[[C], C]:
65 def _wrapper(func: C) -> C:
66 LSP_CHECKS.append(
67 LSPSelfCheck(
68 _feature_name(feature_name, func),
69 func,
70 problem,
71 how_to_fix,
72 is_mandatory=is_mandatory,
73 )
74 )
75 return func
77 return _wrapper
80def _feature_name(feature: Optional[str], func: Callable[[], None]) -> str:
81 if feature is not None:
82 return feature
83 return func.__name__.replace("_", " ")
86@lsp_import_check(["python3-lsprotocol", "python3-pygls"], is_mandatory=True)
87def minimum_requirements() -> bool:
88 import pygls.server
90 # The hasattr is irrelevant; but it avoids the import being flagged as redundant.
91 return hasattr(pygls.server, "LanguageServer")
94@lsp_import_check(["python3-levenshtein"])
95def typo_detection() -> bool:
96 import Levenshtein
98 # The hasattr is irrelevant; but it avoids the import being flagged as redundant.
99 return hasattr(Levenshtein, "distance")
102@lsp_import_check(["hunspell-en-us", "python3-hunspell"])
103def spell_checking() -> bool:
104 import Levenshtein
106 # The hasattr is irrelevant; but it avoids the import being flagged as redundant.
107 return hasattr(Levenshtein, "distance") and os.path.exists(
108 "/usr/share/hunspell/en_US.dic"
109 )
112@lsp_generic_check(
113 feature_name="extra dh support",
114 problem="Missing dependencies",
115 how_to_fix="Run `apt satisfy debhelper (>= 13.16~)` to enable this feature",
116)
117def check_dh_version() -> bool:
118 try:
119 output = subprocess.check_output(
120 [
121 "dpkg-query",
122 "-W",
123 "--showformat=${Version} ${db:Status-Status}\n",
124 "debhelper",
125 ]
126 ).decode("utf-8")
127 except (FileNotFoundError, subprocess.CalledProcessError):
128 return False
129 else:
130 parts = output.split()
131 if len(parts) != 2:
132 return False
133 if parts[1] != "installed":
134 return False
135 return Version(parts[0]) >= Version("13.16~")
138@lsp_generic_check(
139 feature_name="apt cache packages",
140 problem="Missing apt or empty apt cache",
141 how_to_fix="",
142)
143def check_apt_cache() -> bool:
144 try:
145 output = subprocess.check_output(
146 [
147 "apt-get",
148 "indextargets",
149 "--format",
150 "$(IDENTIFIER)",
151 ]
152 ).decode("utf-8")
153 except (FileNotFoundError, subprocess.CalledProcessError):
154 return False
155 for line in output.splitlines():
156 if line.strip() == "Packages":
157 return True
159 return False
162def assert_can_start_lsp() -> None:
163 for self_check in LSP_CHECKS:
164 if self_check.is_mandatory and not self_check.test():
165 _error(
166 f"Cannot start the language server. {self_check.problem}. {self_check.how_to_fix}"
167 )