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

1import dataclasses 

2import os.path 

3import subprocess 

4from typing import Callable, Sequence, List, Optional, TypeVar 

5 

6from debian.debian_support import Version 

7 

8from debputy.util import _error 

9 

10 

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 

18 

19 

20LSP_CHECKS: List[LSPSelfCheck] = [] 

21 

22C = TypeVar("C", bound="Callable") 

23 

24 

25def lsp_import_check( 

26 packages: Sequence[str], 

27 *, 

28 feature_name: Optional[str] = None, 

29 is_mandatory: bool = False, 

30) -> Callable[[C], C]: 

31 

32 def _wrapper(func: C) -> C: 

33 

34 def _impl(): 

35 try: 

36 r = func() 

37 except ImportError: 

38 return False 

39 return r is None or bool(r) 

40 

41 suffix = "fix this issue" if is_mandatory else "enable this feature" 

42 

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 

53 

54 return _wrapper 

55 

56 

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]: 

64 

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 

76 

77 return _wrapper 

78 

79 

80def _feature_name(feature: Optional[str], func: Callable[[], None]) -> str: 

81 if feature is not None: 

82 return feature 

83 return func.__name__.replace("_", " ") 

84 

85 

86@lsp_import_check(["python3-lsprotocol", "python3-pygls"], is_mandatory=True) 

87def minimum_requirements() -> bool: 

88 import pygls.server 

89 

90 # The hasattr is irrelevant; but it avoids the import being flagged as redundant. 

91 return hasattr(pygls.server, "LanguageServer") 

92 

93 

94@lsp_import_check(["python3-levenshtein"]) 

95def typo_detection() -> bool: 

96 import Levenshtein 

97 

98 # The hasattr is irrelevant; but it avoids the import being flagged as redundant. 

99 return hasattr(Levenshtein, "distance") 

100 

101 

102@lsp_import_check(["hunspell-en-us", "python3-hunspell"]) 

103def spell_checking() -> bool: 

104 import Levenshtein 

105 

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 ) 

110 

111 

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~") 

136 

137 

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 

158 

159 return False 

160 

161 

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 )