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

89 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-10-12 15:06 +0000

1import dataclasses 

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

3from collections.abc import Sequence, Iterable 

4 

5from debputy.manifest_parser.tagging_types import DebputyDispatchableType 

6from debputy.manifest_parser.util import AttributePath 

7 

8STD_CONTROL_SCRIPTS = frozenset( 

9 { 

10 "preinst", 

11 "prerm", 

12 "postinst", 

13 "postrm", 

14 } 

15) 

16UDEB_CONTROL_SCRIPTS = frozenset( 

17 { 

18 "postinst", 

19 "menutest", 

20 "isinstallable", 

21 } 

22) 

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

24 

25 

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

27class MaintscriptSnippet: 

28 definition_source: str 

29 snippet: str 

30 snippet_order: Literal["service"] | None = None 

31 

32 def script_content(self) -> str: 

33 lines = [ 

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

35 self.snippet, 

36 ] 

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

38 lines.append("\n") 

39 return "".join(lines) 

40 

41 

42class MaintscriptSnippetContainer: 

43 def __init__(self) -> None: 

44 self._generic_snippets: list[MaintscriptSnippet] = [] 

45 self._snippets_by_order: dict[Literal["service"], list[MaintscriptSnippet]] = {} 

46 

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

48 instance = self.__class__() 

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

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

51 return instance 

52 

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

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

55 self._generic_snippets.append(maintscript_snippet) 

56 else: 

57 if maintscript_snippet.snippet_order not in self._snippets_by_order: 

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

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

60 maintscript_snippet 

61 ) 

62 

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

64 if snippet_order is None: 

65 return bool(self._generic_snippets) 

66 if snippet_order not in self._snippets_by_order: 

67 return False 

68 return bool(self._snippets_by_order[snippet_order]) 

69 

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

71 yield from self._generic_snippets 

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

73 yield from snippets 

74 

75 def generate_snippet( 

76 self, 

77 tool_with_version: str | None = None, 

78 snippet_order: Literal["service"] | None = None, 

79 reverse: bool = False, 

80 ) -> str | None: 

81 inner_content = "" 

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

83 snippets = ( 

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

85 ) 

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

87 elif snippet_order in self._snippets_by_order: 

88 snippets = self._snippets_by_order[snippet_order] 

89 if reverse: 

90 snippets = reversed(snippets) 

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

92 

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

94 return None 

95 

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

97 return ( 

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

99 + inner_content 

100 + "# End automatically added section" 

101 ) 

102 return inner_content 

103 

104 

105class DpkgMaintscriptHelperCommand(DebputyDispatchableType): 

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

107 

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

109 super().__init__() 

110 self.cmdline = cmdline 

111 self.definition_source = definition_source 

112 

113 @classmethod 

114 def _finish_cmd( 

115 cls, 

116 definition_source: str, 

117 cmdline: list[str], 

118 prior_version: str | None, 

119 owning_package: str | None, 

120 ) -> Self: 

121 if prior_version is not None: 

122 cmdline.append(prior_version) 

123 if owning_package is not None: 

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

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

126 cmdline.append("") 

127 cmdline.append(owning_package) 

128 return cls( 

129 tuple(cmdline), 

130 definition_source, 

131 ) 

132 

133 @classmethod 

134 def rm_conffile( 

135 cls, 

136 definition_source: AttributePath, 

137 conffile: str, 

138 prior_version: str | None = None, 

139 owning_package: str | None = None, 

140 ) -> Self: 

141 cmdline = ["rm_conffile", conffile] 

142 return cls._finish_cmd( 

143 definition_source.path, cmdline, prior_version, owning_package 

144 ) 

145 

146 @classmethod 

147 def mv_conffile( 

148 cls, 

149 definition_source: AttributePath, 

150 old_conffile: str, 

151 new_confile: str, 

152 prior_version: str | None = None, 

153 owning_package: str | None = None, 

154 ) -> Self: 

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

156 return cls._finish_cmd( 

157 definition_source.path, cmdline, prior_version, owning_package 

158 ) 

159 

160 @classmethod 

161 def symlink_to_dir( 

162 cls, 

163 definition_source: AttributePath, 

164 pathname: str, 

165 old_target: str, 

166 prior_version: str | None = None, 

167 owning_package: str | None = None, 

168 ) -> Self: 

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

170 return cls._finish_cmd( 

171 definition_source.path, cmdline, prior_version, owning_package 

172 ) 

173 

174 @classmethod 

175 def dir_to_symlink( 

176 cls, 

177 definition_source: AttributePath, 

178 pathname: str, 

179 new_target: str, 

180 prior_version: str | None = None, 

181 owning_package: str | None = None, 

182 ) -> Self: 

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

184 return cls._finish_cmd( 

185 definition_source.path, cmdline, prior_version, owning_package 

186 )