1use 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 fn set_vcs_url(&mut self, vcs_type: &str, url: &str);
38
39 fn get_vcs_url(&self, vcs_type: &str) -> Option<String>;
41}
42
43pub trait AbstractBinary {
45 fn name(&self) -> Option<String>;
47}
48
49use crate::debcargo::{DebcargoBinary, DebcargoEditor, DebcargoSource};
50use debian_control::{Binary as PlainBinary, Control as PlainControl, Source as PlainSource};
51
52impl AbstractControlEditor for DebcargoEditor {
53 fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>> {
54 Some(Box::new(DebcargoEditor::source(self)) as Box<dyn AbstractSource<'a>>)
55 }
56
57 fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>> {
58 DebcargoEditor::binaries(self)
59 .map(|b| Box::new(b) as Box<dyn AbstractBinary>)
60 .collect()
61 }
62
63 fn commit(&self) -> bool {
64 DebcargoEditor::commit(self).unwrap()
65 }
66
67 fn wrap_and_sort(&mut self) {}
68}
69
70impl AbstractBinary for PlainBinary {
71 fn name(&self) -> Option<String> {
72 self.name()
73 }
74}
75
76impl AbstractSource<'_> for PlainSource {
77 fn name(&self) -> Option<String> {
78 self.name()
79 }
80
81 fn ensure_build_dep(&mut self, dep: Entry) {
82 if let Some(mut build_deps) = self.build_depends() {
83 ensure_relation(&mut build_deps, dep);
84 self.set_build_depends(&build_deps);
85 } else {
86 self.set_build_depends(&Relations::from(vec![dep]));
87 }
88 }
89
90 fn set_maintainer(&mut self, maintainer: &str) {
91 (self as &mut debian_control::lossless::Source).set_maintainer(maintainer);
92 }
93
94 fn set_uploaders(&mut self, uploaders: &[&str]) {
95 (self as &mut debian_control::lossless::Source).set_uploaders(uploaders);
96 }
97
98 fn set_vcs_url(&mut self, vcs_type: &str, url: &str) {
99 let field_name = format!("Vcs-{}", vcs_type);
100 self.as_mut_deb822().set(&field_name, url);
101 }
102
103 fn get_vcs_url(&self, vcs_type: &str) -> Option<String> {
104 let field_name = format!("Vcs-{}", vcs_type);
105 self.as_deb822().get(&field_name)
106 }
107}
108
109impl AbstractBinary for DebcargoBinary<'_> {
110 fn name(&self) -> Option<String> {
111 Some(self.name().to_string())
112 }
113}
114
115impl<'a> AbstractSource<'a> for DebcargoSource<'a> {
116 fn name(&self) -> Option<String> {
117 self.name()
118 }
119
120 fn ensure_build_dep(&mut self, dep: Entry) {
121 if let Some(build_deps) = self
123 .toml_section_mut()
124 .get_mut("build_depends")
125 .and_then(|v| v.as_array_mut())
126 {
127 build_deps.push(dep.to_string());
128 }
129 }
130
131 fn set_maintainer(&mut self, maintainer: &str) {
132 (self as &mut crate::debcargo::DebcargoSource).set_maintainer(maintainer);
133 }
134
135 fn set_uploaders(&mut self, uploaders: &[&str]) {
136 (self as &mut crate::debcargo::DebcargoSource)
137 .set_uploaders(uploaders.iter().map(|s| s.to_string()).collect::<Vec<_>>());
138 }
139
140 fn set_vcs_url(&mut self, vcs_type: &str, url: &str) {
141 (self as &mut crate::debcargo::DebcargoSource).set_vcs_url(vcs_type, url);
142 }
143
144 fn get_vcs_url(&self, vcs_type: &str) -> Option<String> {
145 match vcs_type.to_lowercase().as_str() {
146 "git" => self.vcs_git(),
147 "browser" => self.vcs_browser(),
148 _ => self.get_extra_field(&format!("Vcs-{}", vcs_type)),
149 }
150 }
151}
152
153impl<E: crate::editor::Editor<PlainControl>> AbstractControlEditor for E {
154 fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>> {
155 PlainControl::source(self).map(|s| Box::new(s) as Box<dyn AbstractSource>)
156 }
157
158 fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>> {
159 PlainControl::binaries(self)
160 .map(|b| Box::new(b) as Box<dyn AbstractBinary>)
161 .collect()
162 }
163
164 fn commit(&self) -> bool {
165 !(self as &dyn crate::editor::Editor<PlainControl>)
166 .commit()
167 .unwrap()
168 .is_empty()
169 }
170
171 fn wrap_and_sort(&mut self) {
172 (self as &mut dyn crate::editor::Editor<PlainControl>).wrap_and_sort(
173 deb822_lossless::Indentation::Spaces(4),
174 false,
175 None,
176 )
177 }
178}
179
180pub fn edit_control<'a>(
182 tree: &dyn breezyshim::workingtree::WorkingTree,
183 subpath: &Path,
184) -> Result<Box<dyn AbstractControlEditor + 'a>, crate::editor::EditorError> {
185 if tree.has_filename(&subpath.join("debian/debcargo.toml")) {
186 Ok(Box::new(crate::debcargo::DebcargoEditor::from_directory(
187 &tree.abspath(subpath).unwrap(),
188 )?))
189 } else {
190 let control_path = tree.abspath(&subpath.join(std::path::Path::new("debian/control")));
191 Ok(Box::new(crate::control::TemplatedControlEditor::open(
192 control_path.unwrap(),
193 )?) as Box<dyn AbstractControlEditor>)
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat};
200 use breezyshim::prelude::*;
201 use std::path::Path;
202 use std::str::FromStr;
203
204 #[test]
205 fn test_edit_control_debcargo() {
206 let td = tempfile::tempdir().unwrap();
207 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
208 tree.mkdir(Path::new("debian")).unwrap();
210 std::fs::write(
211 td.path().join("debian/debcargo.toml"),
212 br#"
213maintainer = "Alice <alice@example.com>"
214homepage = "https://example.com"
215description = "Example package"
216"#,
217 )
218 .unwrap();
219
220 std::fs::write(
221 td.path().join("Cargo.toml"),
222 br#"
223[package]
224name = "example"
225version = "0.1.0"
226edition = "2018"
227"#,
228 )
229 .unwrap();
230
231 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
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_control_regular() {
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>
250Homepage: https://example.com
251
252Package: example
253Architecture: any
254Description: Example package
255"#,
256 )
257 .unwrap();
258
259 tree.add(&[(Path::new("debian")), (Path::new("debian/control"))])
260 .unwrap();
261
262 let editor = super::edit_control(&tree, Path::new("")).unwrap();
263
264 editor.commit();
265 }
266
267 #[test]
268 fn test_edit_source_ensure_build_depends() {
269 let td = tempfile::tempdir().unwrap();
270 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
271 tree.mkdir(Path::new("debian")).unwrap();
273 tree.put_file_bytes_non_atomic(
274 Path::new("debian/control"),
275 br#"
276Source: example
277Maintainer: Alice <alice@example.com>
278Build-Depends: libc6
279
280Package: example
281Architecture: any
282Description: Example package
283"#,
284 )
285 .unwrap();
286 tree.add(&[Path::new("debian/control")]).unwrap();
287
288 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
289 let mut source = editor.source().unwrap();
290 source.ensure_build_dep(
291 debian_control::lossless::relations::Entry::from_str("libssl-dev").unwrap(),
292 );
293 std::mem::drop(source);
294 editor.commit();
295
296 let text = tree.get_file_text(Path::new("debian/control")).unwrap();
297 assert_eq!(
298 std::str::from_utf8(&text).unwrap(),
299 r#"
300Source: example
301Maintainer: Alice <alice@example.com>
302Build-Depends: libc6, libssl-dev
303
304Package: example
305Architecture: any
306Description: Example package
307"#
308 );
309 }
310
311 #[test]
312 fn test_abstract_source_set_vcs_url_plain() {
313 let td = tempfile::tempdir().unwrap();
314 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
315 tree.mkdir(Path::new("debian")).unwrap();
317 tree.put_file_bytes_non_atomic(
318 Path::new("debian/control"),
319 br#"Source: example
320Maintainer: Alice <alice@example.com>
321
322Package: example
323Architecture: any
324Description: Example package
325"#,
326 )
327 .unwrap();
328 tree.add(&[Path::new("debian/control")]).unwrap();
329
330 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
331 let mut source = editor.source().unwrap();
332
333 source.set_vcs_url("Git", "https://github.com/example/repo.git");
335 source.set_vcs_url("Browser", "https://github.com/example/repo");
336
337 std::mem::drop(source);
338 editor.commit();
339
340 let text = tree.get_file_text(Path::new("debian/control")).unwrap();
341 assert_eq!(
342 std::str::from_utf8(&text).unwrap(),
343 r#"Source: example
344Maintainer: Alice <alice@example.com>
345Vcs-Git: https://github.com/example/repo.git
346Vcs-Browser: https://github.com/example/repo
347
348Package: example
349Architecture: any
350Description: Example package
351"#
352 );
353 }
354
355 #[test]
356 fn test_abstract_source_set_vcs_url_debcargo() {
357 let td = tempfile::tempdir().unwrap();
358 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
359 tree.mkdir(Path::new("debian")).unwrap();
361 std::fs::write(
362 td.path().join("debian/debcargo.toml"),
363 br#"maintainer = "Alice <alice@example.com>"
364"#,
365 )
366 .unwrap();
367
368 std::fs::write(
369 td.path().join("Cargo.toml"),
370 br#"[package]
371name = "example"
372version = "0.1.0"
373"#,
374 )
375 .unwrap();
376
377 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
378 .unwrap();
379
380 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
381 let mut source = editor.source().unwrap();
382
383 source.set_vcs_url("Git", "https://github.com/example/repo.git");
385 source.set_vcs_url("Browser", "https://github.com/example/repo");
386
387 source.set_vcs_url("Svn", "https://svn.example.com/repo");
389
390 std::mem::drop(source);
391 editor.commit();
392
393 let content = std::fs::read_to_string(td.path().join("debian/debcargo.toml")).unwrap();
395 assert_eq!(
396 content,
397 r#"maintainer = "Alice <alice@example.com>"
398
399[source]
400vcs_git = "https://github.com/example/repo.git"
401vcs_browser = "https://github.com/example/repo"
402extra_lines = ["Vcs-Svn: https://svn.example.com/repo"]
403"#
404 );
405 }
406
407 #[test]
408 fn test_abstract_source_get_vcs_url_plain() {
409 let td = tempfile::tempdir().unwrap();
410 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
411 tree.mkdir(Path::new("debian")).unwrap();
413 tree.put_file_bytes_non_atomic(
414 Path::new("debian/control"),
415 br#"Source: example
416Maintainer: Alice <alice@example.com>
417Vcs-Git: https://github.com/example/repo.git
418Vcs-Browser: https://github.com/example/repo
419Vcs-Svn: https://svn.example.com/repo
420
421Package: example
422Architecture: any
423Description: Example package
424"#,
425 )
426 .unwrap();
427 tree.add(&[Path::new("debian/control")]).unwrap();
428
429 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
430 let source = editor.source().unwrap();
431
432 assert_eq!(
434 source.get_vcs_url("Git"),
435 Some("https://github.com/example/repo.git".to_string())
436 );
437 assert_eq!(
438 source.get_vcs_url("Browser"),
439 Some("https://github.com/example/repo".to_string())
440 );
441 assert_eq!(
442 source.get_vcs_url("Svn"),
443 Some("https://svn.example.com/repo".to_string())
444 );
445 assert_eq!(source.get_vcs_url("Bzr"), None);
446 }
447
448 #[test]
449 fn test_abstract_source_get_vcs_url_debcargo() {
450 let td = tempfile::tempdir().unwrap();
451 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
452 tree.mkdir(Path::new("debian")).unwrap();
454 std::fs::write(
455 td.path().join("debian/debcargo.toml"),
456 br#"maintainer = "Alice <alice@example.com>"
457
458[source]
459vcs_git = "https://github.com/example/repo.git"
460vcs_browser = "https://github.com/example/repo"
461extra_lines = ["Vcs-Svn: https://svn.example.com/repo", "Vcs-Bzr: https://bzr.example.com/repo"]
462"#,
463 )
464 .unwrap();
465
466 std::fs::write(
467 td.path().join("Cargo.toml"),
468 br#"[package]
469name = "example"
470version = "0.1.0"
471"#,
472 )
473 .unwrap();
474
475 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
476 .unwrap();
477
478 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
479 let source = editor.source().unwrap();
480
481 assert_eq!(
483 source.get_vcs_url("Git"),
484 Some("https://github.com/example/repo.git".to_string())
485 );
486 assert_eq!(
487 source.get_vcs_url("Browser"),
488 Some("https://github.com/example/repo".to_string())
489 );
490
491 assert_eq!(
493 source.get_vcs_url("Svn"),
494 Some("https://svn.example.com/repo".to_string())
495 );
496 assert_eq!(
497 source.get_vcs_url("Bzr"),
498 Some("https://bzr.example.com/repo".to_string())
499 );
500
501 assert_eq!(source.get_vcs_url("Hg"), None);
503 }
504}