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
« 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
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
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)
42class MaintscriptSnippetContainer:
43 def __init__(self) -> None:
44 self._generic_snippets: list[MaintscriptSnippet] = []
45 self._snippets_by_order: dict[Literal["service"], list[MaintscriptSnippet]] = {}
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
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 )
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])
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
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)
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
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
105class DpkgMaintscriptHelperCommand(DebputyDispatchableType):
106 __slots__ = ("cmdline", "definition_source")
108 def __init__(self, cmdline: Sequence[str], definition_source: str) -> None:
109 super().__init__()
110 self.cmdline = cmdline
111 self.definition_source = definition_source
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 )
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 )
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 )
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 )
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 )