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(&mut 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(&mut 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(&mut self) -> bool {
165 !(self as &mut 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<deb822_lossless::Error>>
185{
186 if tree.has_filename(&subpath.join("debian/debcargo.toml")) {
187 Ok(Box::new(crate::debcargo::DebcargoEditor::from_directory(
188 &tree.abspath(subpath).unwrap(),
189 )?))
190 } else {
191 let control_path = tree.abspath(&subpath.join(std::path::Path::new("debian/control")));
192 Ok(Box::new(crate::control::TemplatedControlEditor::open(
193 control_path.unwrap(),
194 )?) as Box<dyn AbstractControlEditor>)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat};
201 use breezyshim::prelude::*;
202 use std::path::Path;
203 use std::str::FromStr;
204
205 #[test]
206 fn test_edit_control_debcargo() {
207 let td = tempfile::tempdir().unwrap();
208 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
209 tree.mkdir(Path::new("debian")).unwrap();
211 std::fs::write(
212 td.path().join("debian/debcargo.toml"),
213 br#"
214maintainer = "Alice <alice@example.com>"
215homepage = "https://example.com"
216description = "Example package"
217"#,
218 )
219 .unwrap();
220
221 std::fs::write(
222 td.path().join("Cargo.toml"),
223 br#"
224[package]
225name = "example"
226version = "0.1.0"
227edition = "2018"
228"#,
229 )
230 .unwrap();
231
232 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
233 .unwrap();
234
235 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
236
237 editor.commit();
238 }
239
240 #[test]
241 fn test_edit_control_regular() {
242 let td = tempfile::tempdir().unwrap();
243 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
244 tree.mkdir(Path::new("debian")).unwrap();
246 tree.put_file_bytes_non_atomic(
247 Path::new("debian/control"),
248 br#"
249Source: example
250Maintainer: Alice <alice@example.com>
251Homepage: https://example.com
252
253Package: example
254Architecture: any
255Description: Example package
256"#,
257 )
258 .unwrap();
259
260 tree.add(&[(Path::new("debian")), (Path::new("debian/control"))])
261 .unwrap();
262
263 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
264
265 editor.commit();
266 }
267
268 #[test]
269 fn test_edit_source_ensure_build_depends() {
270 let td = tempfile::tempdir().unwrap();
271 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
272 tree.mkdir(Path::new("debian")).unwrap();
274 tree.put_file_bytes_non_atomic(
275 Path::new("debian/control"),
276 br#"
277Source: example
278Maintainer: Alice <alice@example.com>
279Build-Depends: libc6
280
281Package: example
282Architecture: any
283Description: Example package
284"#,
285 )
286 .unwrap();
287 tree.add(&[Path::new("debian/control")]).unwrap();
288
289 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
290 let mut source = editor.source().unwrap();
291 source.ensure_build_dep(
292 debian_control::lossless::relations::Entry::from_str("libssl-dev").unwrap(),
293 );
294 std::mem::drop(source);
295 editor.commit();
296
297 let text = tree.get_file_text(Path::new("debian/control")).unwrap();
298 assert_eq!(
299 std::str::from_utf8(&text).unwrap(),
300 r#"
301Source: example
302Maintainer: Alice <alice@example.com>
303Build-Depends: libc6, libssl-dev
304
305Package: example
306Architecture: any
307Description: Example package
308"#
309 );
310 }
311
312 #[test]
313 fn test_abstract_source_set_vcs_url_plain() {
314 let td = tempfile::tempdir().unwrap();
315 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
316 tree.mkdir(Path::new("debian")).unwrap();
318 tree.put_file_bytes_non_atomic(
319 Path::new("debian/control"),
320 br#"Source: example
321Maintainer: Alice <alice@example.com>
322
323Package: example
324Architecture: any
325Description: Example package
326"#,
327 )
328 .unwrap();
329 tree.add(&[Path::new("debian/control")]).unwrap();
330
331 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
332 let mut source = editor.source().unwrap();
333
334 source.set_vcs_url("Git", "https://github.com/example/repo.git");
336 source.set_vcs_url("Browser", "https://github.com/example/repo");
337
338 std::mem::drop(source);
339 editor.commit();
340
341 let text = tree.get_file_text(Path::new("debian/control")).unwrap();
342 assert_eq!(
343 std::str::from_utf8(&text).unwrap(),
344 r#"Source: example
345Maintainer: Alice <alice@example.com>
346Vcs-Git: https://github.com/example/repo.git
347Vcs-Browser: https://github.com/example/repo
348
349Package: example
350Architecture: any
351Description: Example package
352"#
353 );
354 }
355
356 #[test]
357 fn test_abstract_source_set_vcs_url_debcargo() {
358 let td = tempfile::tempdir().unwrap();
359 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
360 tree.mkdir(Path::new("debian")).unwrap();
362 std::fs::write(
363 td.path().join("debian/debcargo.toml"),
364 br#"maintainer = "Alice <alice@example.com>"
365"#,
366 )
367 .unwrap();
368
369 std::fs::write(
370 td.path().join("Cargo.toml"),
371 br#"[package]
372name = "example"
373version = "0.1.0"
374"#,
375 )
376 .unwrap();
377
378 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
379 .unwrap();
380
381 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
382 let mut source = editor.source().unwrap();
383
384 source.set_vcs_url("Git", "https://github.com/example/repo.git");
386 source.set_vcs_url("Browser", "https://github.com/example/repo");
387
388 source.set_vcs_url("Svn", "https://svn.example.com/repo");
390
391 std::mem::drop(source);
392 editor.commit();
393
394 let content = std::fs::read_to_string(td.path().join("debian/debcargo.toml")).unwrap();
396 assert_eq!(
397 content,
398 r#"maintainer = "Alice <alice@example.com>"
399
400[source]
401vcs_git = "https://github.com/example/repo.git"
402vcs_browser = "https://github.com/example/repo"
403extra_lines = ["Vcs-Svn: https://svn.example.com/repo"]
404"#
405 );
406 }
407
408 #[test]
409 fn test_abstract_source_get_vcs_url_plain() {
410 let td = tempfile::tempdir().unwrap();
411 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
412 tree.mkdir(Path::new("debian")).unwrap();
414 tree.put_file_bytes_non_atomic(
415 Path::new("debian/control"),
416 br#"Source: example
417Maintainer: Alice <alice@example.com>
418Vcs-Git: https://github.com/example/repo.git
419Vcs-Browser: https://github.com/example/repo
420Vcs-Svn: https://svn.example.com/repo
421
422Package: example
423Architecture: any
424Description: Example package
425"#,
426 )
427 .unwrap();
428 tree.add(&[Path::new("debian/control")]).unwrap();
429
430 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
431 let source = editor.source().unwrap();
432
433 assert_eq!(
435 source.get_vcs_url("Git"),
436 Some("https://github.com/example/repo.git".to_string())
437 );
438 assert_eq!(
439 source.get_vcs_url("Browser"),
440 Some("https://github.com/example/repo".to_string())
441 );
442 assert_eq!(
443 source.get_vcs_url("Svn"),
444 Some("https://svn.example.com/repo".to_string())
445 );
446 assert_eq!(source.get_vcs_url("Bzr"), None);
447 }
448
449 #[test]
450 fn test_abstract_source_get_vcs_url_debcargo() {
451 let td = tempfile::tempdir().unwrap();
452 let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
453 tree.mkdir(Path::new("debian")).unwrap();
455 std::fs::write(
456 td.path().join("debian/debcargo.toml"),
457 br#"maintainer = "Alice <alice@example.com>"
458
459[source]
460vcs_git = "https://github.com/example/repo.git"
461vcs_browser = "https://github.com/example/repo"
462extra_lines = ["Vcs-Svn: https://svn.example.com/repo", "Vcs-Bzr: https://bzr.example.com/repo"]
463"#,
464 )
465 .unwrap();
466
467 std::fs::write(
468 td.path().join("Cargo.toml"),
469 br#"[package]
470name = "example"
471version = "0.1.0"
472"#,
473 )
474 .unwrap();
475
476 tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
477 .unwrap();
478
479 let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
480 let source = editor.source().unwrap();
481
482 assert_eq!(
484 source.get_vcs_url("Git"),
485 Some("https://github.com/example/repo.git".to_string())
486 );
487 assert_eq!(
488 source.get_vcs_url("Browser"),
489 Some("https://github.com/example/repo".to_string())
490 );
491
492 assert_eq!(
494 source.get_vcs_url("Svn"),
495 Some("https://svn.example.com/repo".to_string())
496 );
497 assert_eq!(
498 source.get_vcs_url("Bzr"),
499 Some("https://bzr.example.com/repo".to_string())
500 );
501
502 assert_eq!(source.get_vcs_url("Hg"), None);
504 }
505}