Coverage for src/debputy/maintscript_snippet.py: 60%

88 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-09-07 09:27 +0000

1import dataclasses 

2from typing import Sequence, Optional, List, Literal, Iterable, Dict, Self 

3 

4from debputy.manifest_parser.tagging_types import DebputyDispatchableType 

5from debputy.manifest_parser.util import AttributePath 

6 

7STD_CONTROL_SCRIPTS = frozenset( 

8 { 

9 "preinst", 

10 "prerm", 

11 "postinst", 

12 "postrm", 

13 } 

14) 

15UDEB_CONTROL_SCRIPTS = frozenset( 

16 { 

17 "postinst", 

18 "menutest", 

19 "isinstallable", 

20 } 

21) 

22ALL_CONTROL_SCRIPTS = STD_CONTROL_SCRIPTS | UDEB_CONTROL_SCRIPTS | {"config"} 

23 

24 

25@dataclasses.dataclass(slots=True, frozen=True) 

26class MaintscriptSnippet: 

27 definition_source: str 

28 snippet: str 

29 snippet_order: Optional[Literal["service"]] = None 

30 

31 def script_content(self) -> str: 

32 lines = [ 

33 f"# Snippet source: {self.definition_source}\n", 

34 self.snippet, 

35 ] 

36 if not self.snippet.endswith("\n"): 36 ↛ 37line 36 didn't jump to line 37 because the condition on line 36 was never true

37 lines.append("\n") 

38 return "".join(lines) 

39 

40 

41class MaintscriptSnippetContainer: 

42 def __init__(self) -> None: 

43 self._generic_snippets: List[MaintscriptSnippet] = [] 

44 self._snippets_by_order: Dict[Literal["service"], List[MaintscriptSnippet]] = {} 

45 

46 def copy(self) -> "MaintscriptSnippetContainer": 

47 instance = self.__class__() 

48 instance._generic_snippets = self._generic_snippets.copy() 

49 instance._snippets_by_order = self._snippets_by_order.copy() 

50 return instance 

51 

52 def append(self, maintscript_snippet: MaintscriptSnippet) -> None: 

53 if maintscript_snippet.snippet_order is None: 53 ↛ 56line 53 didn't jump to line 56 because the condition on line 53 was always true

54 self._generic_snippets.append(maintscript_snippet) 

55 else: 

56 if maintscript_snippet.snippet_order not in self._snippets_by_order: 

57 self._snippets_by_order[maintscript_snippet.snippet_order] = [] 

58 self._snippets_by_order[maintscript_snippet.snippet_order].append( 

59 maintscript_snippet 

60 ) 

61 

62 def has_content(self, snippet_order: Optional[Literal["service"]] = None) -> bool: 

63 if snippet_order is None: 

64 return bool(self._generic_snippets) 

65 if snippet_order not in self._snippets_by_order: 

66 return False 

67 return bool(self._snippets_by_order[snippet_order]) 

68 

69 def all_snippets(self) -> Iterable[MaintscriptSnippet]: 

70 yield from self._generic_snippets 

71 for snippets in self._snippets_by_order.values(): 

72 yield from snippets 

73 

74 def generate_snippet( 

75 self, 

76 tool_with_version: Optional[str] = None, 

77 snippet_order: Optional[Literal["service"]] = None, 

78 reverse: bool = False, 

79 ) -> Optional[str]: 

80 inner_content = "" 

81 if snippet_order is None: 81 ↛ 86line 81 didn't jump to line 86 because the condition on line 81 was always true

82 snippets = ( 

83 reversed(self._generic_snippets) if reverse else self._generic_snippets 

84 ) 

85 inner_content = "".join(s.script_content() for s in snippets) 

86 elif snippet_order in self._snippets_by_order: 

87 snippets = self._snippets_by_order[snippet_order] 

88 if reverse: 

89 snippets = reversed(snippets) 

90 inner_content = "".join(s.script_content() for s in snippets) 

91 

92 if not inner_content: 92 ↛ 93line 92 didn't jump to line 93 because the condition on line 92 was never true

93 return None 

94 

95 if tool_with_version: 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 return ( 

97 f"# Automatically added by {tool_with_version}\n" 

98 + inner_content 

99 + "# End automatically added section" 

100 ) 

101 return inner_content 

102 

103 

104class DpkgMaintscriptHelperCommand(DebputyDispatchableType): 

105 __slots__ = ("cmdline", "definition_source") 

106 

107 def __init__(self, cmdline: Sequence[str], definition_source: str) -> None: 

108 super().__init__() 

109 self.cmdline = cmdline 

110 self.definition_source = definition_source 

111 

112 @classmethod 

113 def _finish_cmd( 

114 cls, 

115 definition_source: str, 

116 cmdline: List[str], 

117 prior_version: Optional[str], 

118 owning_package: Optional[str], 

119 ) -> Self: 

120 if prior_version is not None: 

121 cmdline.append(prior_version) 

122 if owning_package is not None: 

123 if prior_version is None: 123 ↛ 125line 123 didn't jump to line 125 because the condition on line 123 was never true

124 # Empty is allowed according to `man dpkg-maintscript-helper` 

125 cmdline.append("") 

126 cmdline.append(owning_package) 

127 return cls( 

128 tuple(cmdline), 

129 definition_source, 

130 ) 

131 

132 @classmethod 

133 def rm_conffile( 

134 cls, 

135 definition_source: AttributePath, 

136 conffile: str, 

137 prior_version: Optional[str] = None, 

138 owning_package: Optional[str] = None, 

139 ) -> Self: 

140 cmdline = ["rm_conffile", conffile] 

141 return cls._finish_cmd( 

142 definition_source.path, cmdline, prior_version, owning_package 

143 ) 

144 

145 @classmethod 

146 def mv_conffile( 

147 cls, 

148 definition_source: AttributePath, 

149 old_conffile: str, 

150 new_confile: str, 

151 prior_version: Optional[str] = None, 

152 owning_package: Optional[str] = None, 

153 ) -> Self: 

154 cmdline = ["mv_conffile", old_conffile, new_confile] 

155 return cls._finish_cmd( 

156 definition_source.path, cmdline, prior_version, owning_package 

157 ) 

158 

159 @classmethod 

160 def symlink_to_dir( 

161 cls, 

162 definition_source: AttributePath, 

163 pathname: str, 

164 old_target: str, 

165 prior_version: Optional[str] = None, 

166 owning_package: Optional[str] = None, 

167 ) -> Self: 

168 cmdline = ["symlink_to_dir", pathname, old_target] 

169 return cls._finish_cmd( 

170 definition_source.path, cmdline, prior_version, owning_package 

171 ) 

172 

173 @classmethod 

174 def dir_to_symlink( 

175 cls, 

176 definition_source: AttributePath, 

177 pathname: str, 

178 new_target: str, 

179 prior_version: Optional[str] = None, 

180 owning_package: Optional[str] = None, 

181 ) -> Self: 

182 cmdline = ["dir_to_symlink", pathname, new_target] 

183 return cls._finish_cmd( 

184 definition_source.path, cmdline, prior_version, owning_package 

185 )