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
« 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
4from debputy.manifest_parser.tagging_types import DebputyDispatchableType
5from debputy.manifest_parser.util import AttributePath
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"}
25@dataclasses.dataclass(slots=True, frozen=True)
26class MaintscriptSnippet:
27 definition_source: str
28 snippet: str
29 snippet_order: Optional[Literal["service"]] = None
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)
41class MaintscriptSnippetContainer:
42 def __init__(self) -> None:
43 self._generic_snippets: List[MaintscriptSnippet] = []
44 self._snippets_by_order: Dict[Literal["service"], List[MaintscriptSnippet]] = {}
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
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 )
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])
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
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)
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
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
104class DpkgMaintscriptHelperCommand(DebputyDispatchableType):
105 __slots__ = ("cmdline", "definition_source")
107 def __init__(self, cmdline: Sequence[str], definition_source: str) -> None:
108 super().__init__()
109 self.cmdline = cmdline
110 self.definition_source = definition_source
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 )
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 )
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 )
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 )
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 )