debian_analyzer/
abstract_control.rs1use crate::relations::ensure_relation;
4use debian_control::lossless::relations::{Entry, Relations};
5use std::path::Path;
6
7pub trait AbstractControlEditor {
9 fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>>;
11
12 fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>>;
14
15 fn commit(&self) -> bool;
17
18 fn wrap_and_sort(&mut self);
20}
21
22pub trait AbstractSource<'a> {
24 fn name(&self) -> Option<String>;
26
27 fn ensure_build_dep(&mut self, dep: Entry);
29
30 fn set_maintainer(&mut self, maintainer: &str);
32
33 fn set_uploaders(&mut self, uploaders: &[&str]);
35}
36
37pub trait AbstractBinary {
39 fn name(&self) -> Option<String>;
41}
42
43use crate::debcargo::{DebcargoBinary, DebcargoEditor, DebcargoSource};
44use debian_control::{Binary as PlainBinary, Control as PlainControl, Source as PlainSource};
45
46impl AbstractControlEditor for DebcargoEditor {
47 fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>> {
48 Some(Box::new(DebcargoEditor::source(self)) as Box<dyn AbstractSource<'a>>)
49 }
50
51 fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>> {
52 DebcargoEditor::binaries(self)
53 .map(|b| Box::new(b) as Box<dyn AbstractBinary>)
54 .collect()
55 }
56
57 fn commit(&self) -> bool {
58 DebcargoEditor::commit(self).unwrap()
59 }
60
61 fn wrap_and_sort(&mut self) {}
62}
63
64impl AbstractBinary for PlainBinary {
65 fn name(&self) -> Option<String> {
66 self.name()
67 }
68}
69
70impl AbstractSource<'_> for PlainSource {
71 fn name(&self) -> Option<String> {
72 self.name()
73 }
74
75 fn ensure_build_dep(&mut self, dep: Entry) {
76 if let Some(mut build_deps) = self.build_depends() {
77 ensure_relation(&mut build_deps, dep);
78 self.set_build_depends(&build_deps);
79 } else {
80 self.set_build_depends(&Relations::from(vec![dep]));
81 }
82 }
83
84 fn set_maintainer(&mut self, maintainer: &str) {
85 (self as &mut debian_control::lossless::Source).set_maintainer(maintainer);
86 }
87
88 fn set_uploaders(&mut self, uploaders: &[&str]) {
89 (self as &mut debian_control::lossless::Source).set_uploaders(uploaders);
90 }
91}
92
93impl AbstractBinary for DebcargoBinary<'_> {
94 fn name(&self) -> Option<String> {
95 Some(self.name().to_string())
96 }
97}
98
99impl<'a> AbstractSource<'a> for DebcargoSource<'a> {
100 fn name(&self) -> Option<String> {
101 self.name()
102 }
103
104 fn ensure_build_dep(&mut self, dep: Entry) {
105 if let Some(build_deps) = self
107 .toml_section_mut()
108 .get_mut("build_depends")
109 .and_then(|v| v.as_array_mut())
110 {
111 build_deps.push(dep.to_string());
112 }
113 }
114
115 fn set_maintainer(&mut self, maintainer: &str) {
116 (self as &mut crate::debcargo::DebcargoSource).set_maintainer(maintainer);
117 }
118
119 fn set_uploaders(&mut self, uploaders: &[&str]) {
120 (self as &mut crate::debcargo::DebcargoSource)
121 .set_uploaders(uploaders.iter().map(|s| s.to_string()).collect::<Vec<_>>());
122 }
123}
124
125impl<E: crate::editor::Editor<PlainControl>> AbstractControlEditor for E {
126 fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>> {
127 PlainControl::source(self).map(|s| Box::new(s) as Box<dyn AbstractSource>)
128 }
129
130 fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>> {
131 PlainControl::binaries(self)
132 .map(|b| Box::new(b) as Box<dyn AbstractBinary>)
133 .collect()
134 }
135
136 fn commit(&self) -> bool {
137 !(self as &dyn crate::editor::Editor<PlainControl>)
138 .commit()
139 .unwrap()
140 .is_empty()
141 }
142
143 fn wrap_and_sort(&mut self) {
144 (self as &mut dyn crate::editor::Editor<PlainControl>).wrap_and_sort(
145 deb822_lossless::Indentation::Spaces(4),
146 false,
147 None,
148 )
149 }
150}
151
152pub fn edit_control<'a>(
154 tree: &dyn breezyshim::workingtree::WorkingTree,
155 subpath: &Path,
156) -> Result<Box<dyn AbstractControlEditor + 'a>, crate::editor::EditorError> {
157 if tree.has_filename(&subpath.join("debian/debcargo.toml")) {
158 Ok(Box::new(crate::debcargo::DebcargoEditor::from_directory(
159 &tree.abspath(subpath).unwrap(),
160 )?))
161 } else {
162 let control_path = tree.abspath(&subpath.join(std::path::Path::new("debian/control")));
163 Ok(Box::new(crate::control::TemplatedControlEditor::open(
164 control_path.unwrap(),
165 )?) as Box<dyn AbstractControlEditor>)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat};
172 use breezyshim::prelude::*;
173 use std::path::Path;
174 use std::str::FromStr;
175
176 #[test]
177 fn test_edit_control_debcargo() {
178 let td = tempfile::tempdir().unwrap();
179 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
180 tree.mkdir(Path::new("debian")).unwrap();
182 std::fs::write(
183 td.path().join("debian/debcargo.toml"),
184 br#"
185maintainer = "Alice <alice@example.com>"
186homepage = "https://example.com"
187description = "Example package"
188"#,
189 )
190 .unwrap();
191
192 std::fs::write(
193 td.path().join("Cargo.toml"),
194 br#"
195[package]
196name = "example"
197version = "0.1.0"
198edition = "2018"
199"#,
200 )
201 .unwrap();
202
203 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
204 .unwrap();
205
206 let editor = super::edit_control(&tree, Path::new("")).unwrap();
207
208 editor.commit();
209 }
210
211 #[test]
212 fn test_edit_control_regular() {
213 let td = tempfile::tempdir().unwrap();
214 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
215 tree.mkdir(Path::new("debian")).unwrap();
217 tree.put_file_bytes_non_atomic(
218 Path::new("debian/control"),
219 br#"
220Source: example
221Maintainer: Alice <alice@example.com>
222Homepage: https://example.com
223
224Package: example
225Architecture: any
226Description: Example package
227"#,
228 )
229 .unwrap();
230
231 tree.add(&[(Path::new("debian")), (Path::new("debian/control"))])
232 .unwrap();
233
234 let editor = super::edit_control(&tree, Path::new("")).unwrap();
235
236 editor.commit();
237 }
238
239 #[test]
240 fn test_edit_source_ensure_build_depends() {
241 let td = tempfile::tempdir().unwrap();
242 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
243 tree.mkdir(Path::new("debian")).unwrap();
245 tree.put_file_bytes_non_atomic(
246 Path::new("debian/control"),
247 br#"
248Source: example
249Maintainer: Alice <alice@example.com>
250Build-Depends: libc6
251
252Package: example
253Architecture: any
254Description: Example package
255"#,
256 )
257 .unwrap();
258 tree.add(&[Path::new("debian/control")]).unwrap();
259
260 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
261 let mut source = editor.source().unwrap();
262 source.ensure_build_dep(
263 debian_control::lossless::relations::Entry::from_str("libssl-dev").unwrap(),
264 );
265 std::mem::drop(source);
266 editor.commit();
267
268 let text = tree.get_file_text(Path::new("debian/control")).unwrap();
269 assert_eq!(
270 std::str::from_utf8(&text).unwrap(),
271 r#"
272Source: example
273Maintainer: Alice <alice@example.com>
274Build-Depends: libc6, libssl-dev
275
276Package: example
277Architecture: any
278Description: Example package
279"#
280 );
281 }
282}