Coverage for src/debputy/maintscript_snippet.py: 74%
92 statements
« prev ^ index » next coverage.py v7.8.2, created at 2026-05-11 16:06 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2026-05-11 16:06 +0000
1import dataclasses
2from typing import Literal, Self
3from collections.abc import Sequence, Iterable
5from debputy.manifest_parser.tagging_types import DebputyDispatchableType
6from debputy.manifest_parser.util import AttributePath
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"}
26@dataclasses.dataclass(slots=True, frozen=True)
27class MaintscriptSnippet:
28 definition_source: str
29 snippet: str
30 snippet_order: Literal["service"] | None = None
31 uses_debconf: bool = False
33 def script_content(self) -> str:
34 lines = [
35 f"# Snippet source: {self.definition_source}\n",
36 self.snippet,
37 ]
38 if not self.snippet.endswith("\n"): 38 ↛ 39line 38 didn't jump to line 39 because the condition on line 38 was never true
39 lines.append("\n")
40 return "".join(lines)
43class MaintscriptSnippetContainer:
44 def __init__(self) -> None:
45 self._generic_snippets: list[MaintscriptSnippet] = []
46 self._snippets_by_order: dict[Literal["service"], list[MaintscriptSnippet]] = {}
48 def copy(self) -> "MaintscriptSnippetContainer":
49 instance = self.__class__()
50 instance._generic_snippets = self._generic_snippets.copy()
51 instance._snippets_by_order = self._snippets_by_order.copy()
52 return instance
54 def append(self, maintscript_snippet: MaintscriptSnippet) -> None:
55 if maintscript_snippet.snippet_order is None:
56 self._generic_snippets.append(maintscript_snippet)
57 else:
58 if maintscript_snippet.snippet_order not in self._snippets_by_order: 58 ↛ 60line 58 didn't jump to line 60 because the condition on line 58 was always true
59 self._snippets_by_order[maintscript_snippet.snippet_order] = []
60 self._snippets_by_order[maintscript_snippet.snippet_order].append(
61 maintscript_snippet
62 )
64 def has_content(self, snippet_order: Literal["service"] | None = None) -> bool:
65 if snippet_order is None:
66 return bool(self._generic_snippets)
67 if snippet_order not in self._snippets_by_order:
68 return False
69 return bool(self._snippets_by_order[snippet_order])
71 def all_snippets(self) -> Iterable[MaintscriptSnippet]:
72 yield from self._generic_snippets
73 for snippets in self._snippets_by_order.values():
74 yield from snippets
76 def needs_debconf(self) -> bool:
77 return any(s.uses_debconf for s in self._generic_snippets) or any(
78 s.uses_debconf
79 for snippet_lists in self._snippets_by_order.values()
80 for s in snippet_lists
81 )
83 def generate_snippet(
84 self,
85 tool_with_version: str | None = None,
86 snippet_order: Literal["service"] | None = None,
87 reverse: bool = False,
88 ) -> str | None:
89 inner_content = ""
90 if snippet_order is None:
91 snippets = (
92 reversed(self._generic_snippets) if reverse else self._generic_snippets
93 )
94 inner_content = "".join(s.script_content() for s in snippets)
95 elif snippet_order in self._snippets_by_order:
96 snippets = self._snippets_by_order[snippet_order]
97 if reverse: 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true
98 snippets = reversed(snippets)
99 inner_content = "".join(s.script_content() for s in snippets)
101 if not inner_content:
102 return None
104 if tool_with_version: 104 ↛ 105line 104 didn't jump to line 105 because the condition on line 104 was never true
105 return (
106 f"# Automatically added by {tool_with_version}\n"
107 + inner_content
108 + "# End automatically added section"
109 )
110 return inner_content
113class DpkgMaintscriptHelperCommand(DebputyDispatchableType):
114 __slots__ = ("cmdline", "definition_source")
116 def __init__(self, cmdline: Sequence[str], definition_source: str) -> None:
117 super().__init__()
118 self.cmdline = cmdline
119 self.definition_source = definition_source
121 @classmethod
122 def _finish_cmd(
123 cls,
124 definition_source: str,
125 cmdline: list[str],
126 prior_version: str | None,
127 owning_package: str | None,
128 ) -> Self:
129 if prior_version is not None:
130 cmdline.append(prior_version)
131 if owning_package is not None:
132 if prior_version is None: 132 ↛ 134line 132 didn't jump to line 134 because the condition on line 132 was never true
133 # Empty is allowed according to `man dpkg-maintscript-helper`
134 cmdline.append("")
135 cmdline.append(owning_package)
136 return cls(
137 tuple(cmdline),
138 definition_source,
139 )
141 @classmethod
142 def rm_conffile(
143 cls,
144 definition_source: AttributePath,
145 conffile: str,
146 prior_version: str | None = None,
147 owning_package: str | None = None,
148 ) -> Self:
149 cmdline = ["rm_conffile", conffile]
150 return cls._finish_cmd(
151 definition_source.path, cmdline, prior_version, owning_package
152 )
154 @classmethod
155 def mv_conffile(
156 cls,
157 definition_source: AttributePath,
158 old_conffile: str,
159 new_confile: str,
160 prior_version: str | None = None,
161 owning_package: str | None = None,
162 ) -> Self:
163 cmdline = ["mv_conffile", old_conffile, new_confile]
164 return cls._finish_cmd(
165 definition_source.path, cmdline, prior_version, owning_package
166 )
168 @classmethod
169 def symlink_to_dir(
170 cls,
171 definition_source: AttributePath,
172 pathname: str,
173 old_target: str,
174 prior_version: str | None = None,
175 owning_package: str | None = None,
176 ) -> Self:
177 cmdline = ["symlink_to_dir", pathname, old_target]
178 return cls._finish_cmd(
179 definition_source.path, cmdline, prior_version, owning_package
180 )
182 @classmethod
183 def dir_to_symlink(
184 cls,
185 definition_source: AttributePath,
186 pathname: str,
187 new_target: str,
188 prior_version: str | None = None,
189 owning_package: str | None = None,
190 ) -> Self:
191 cmdline = ["dir_to_symlink", pathname, new_target]
192 return cls._finish_cmd(
193 definition_source.path, cmdline, prior_version, owning_package
194 )