1use crate::editor::{Editor, EditorError, FsEditor, GeneratedFile};
3use crate::relations::{ensure_relation, is_relation_implied};
4use deb822_lossless::Paragraph;
5use debian_control::lossless::relations::Relations;
6use std::ops::{Deref, DerefMut};
7use std::path::{Path, PathBuf};
8
9pub fn format_description(summary: &str, long_description: Vec<&str>) -> String {
11 let mut ret = summary.to_string() + "\n";
12 for line in long_description {
13 ret.push(' ');
14 ret.push_str(line);
15 ret.push('\n');
16 }
17 ret
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Copy)]
21pub enum TemplateType {
23 Rules,
25
26 Gnome,
28
29 Postgresql,
31
32 Directory,
34
35 Cdbs,
37
38 Debcargo,
40}
41
42#[derive(Debug)]
43enum TemplateExpansionError {
44 Failed(String),
45 ExpandCommandMissing(String),
46 UnknownTemplating(PathBuf, Option<PathBuf>),
47 Conflict(ChangeConflict),
48}
49
50impl From<ChangeConflict> for TemplateExpansionError {
51 fn from(e: ChangeConflict) -> Self {
52 TemplateExpansionError::Conflict(e)
53 }
54}
55
56impl std::fmt::Display for TemplateExpansionError {
57 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58 match self {
59 TemplateExpansionError::Failed(s) => write!(f, "Failed: {}", s),
60 TemplateExpansionError::ExpandCommandMissing(s) => {
61 write!(f, "Command not found: {}", s)
62 }
63 TemplateExpansionError::UnknownTemplating(p1, p2) => {
64 if let Some(p2) = p2 {
65 write!(
66 f,
67 "Unknown templating: {} -> {}",
68 p1.display(),
69 p2.display()
70 )
71 } else {
72 write!(f, "Unknown templating: {}", p1.display())
73 }
74 }
75 TemplateExpansionError::Conflict(c) => write!(f, "Conflict: {}", c),
76 }
77 }
78}
79
80impl std::error::Error for TemplateExpansionError {}
81
82fn dh_gnome_clean(path: &std::path::Path) -> Result<(), TemplateExpansionError> {
90 for entry in std::fs::read_dir(path.join("debian")).unwrap().flatten() {
91 if entry
92 .file_name()
93 .to_string_lossy()
94 .ends_with(".debhelper.log")
95 {
96 return Err(TemplateExpansionError::Failed(
97 "pre-existing .debhelper.log files".to_string(),
98 ));
99 }
100 }
101
102 if !path.join("debian/changelog").exists() {
103 return Err(TemplateExpansionError::Failed(
104 "no changelog file".to_string(),
105 ));
106 }
107
108 let result = std::process::Command::new("dh_gnome_clean")
109 .current_dir(path)
110 .output();
111
112 match result {
113 Ok(output) => {
114 if !output.status.success() {
115 let stderr = String::from_utf8_lossy(&output.stderr);
116 return Err(TemplateExpansionError::Failed(stderr.to_string()));
117 }
118 }
119 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
120 return Err(TemplateExpansionError::ExpandCommandMissing(
121 "dh_gnome_clean".to_string(),
122 ));
123 }
124 Err(e) => {
125 return Err(TemplateExpansionError::Failed(e.to_string()));
126 }
127 }
128
129 for entry in std::fs::read_dir(path.join("debian")).unwrap().flatten() {
130 if entry
131 .file_name()
132 .to_string_lossy()
133 .ends_with(".debhelper.log")
134 {
135 std::fs::remove_file(entry.path()).unwrap();
136 }
137 }
138
139 Ok(())
140}
141
142fn pg_buildext_updatecontrol(path: &std::path::Path) -> Result<(), TemplateExpansionError> {
147 let result = std::process::Command::new("pg_buildext")
148 .arg("updatecontrol")
149 .current_dir(path)
150 .output();
151
152 match result {
153 Ok(output) => {
154 if !output.status.success() {
155 let stderr = String::from_utf8_lossy(&output.stderr);
156 return Err(TemplateExpansionError::Failed(stderr.to_string()));
157 }
158 }
159 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
160 return Err(TemplateExpansionError::ExpandCommandMissing(
161 "pg_buildext".to_string(),
162 ));
163 }
164 Err(e) => {
165 return Err(TemplateExpansionError::Failed(e.to_string()));
166 }
167 }
168 Ok(())
169}
170
171fn expand_control_template(
181 template_path: &std::path::Path,
182 path: &std::path::Path,
183 template_type: TemplateType,
184) -> Result<(), TemplateExpansionError> {
185 let package_root = path.parent().unwrap().parent().unwrap();
186 match template_type {
187 TemplateType::Rules => {
188 let path_time = match std::fs::metadata(path) {
189 Ok(metadata) => Some(metadata.modified().unwrap()),
190 Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
191 Err(e) => panic!("Failed to get mtime of {}: {}", path.display(), e),
192 };
193 while let Ok(metadata) = std::fs::metadata(template_path) {
194 if Some(metadata.modified().unwrap()) == path_time {
195 filetime::set_file_mtime(template_path, filetime::FileTime::now()).unwrap();
197 } else {
198 break;
199 }
200 }
201 let result = std::process::Command::new("./debian/rules")
202 .arg("debian/control")
203 .current_dir(package_root)
204 .output();
205
206 match result {
207 Ok(output) => {
208 if !output.status.success() {
209 let stderr = String::from_utf8_lossy(&output.stderr);
210 Err(TemplateExpansionError::Failed(format!(
211 "Exit code {} running ./debian/rules debian/control: {}",
212 output.status, stderr
213 )))
214 } else {
215 Ok(())
216 }
217 }
218 Err(e) => Err(TemplateExpansionError::Failed(format!(
219 "Failed to run ./debian/rules debian/control: {}",
220 e
221 ))),
222 }
223 }
224 TemplateType::Gnome => dh_gnome_clean(package_root),
225 TemplateType::Postgresql => pg_buildext_updatecontrol(package_root),
226 TemplateType::Cdbs => unreachable!(),
227 TemplateType::Debcargo => unreachable!(),
228 TemplateType::Directory => Err(TemplateExpansionError::UnknownTemplating(
229 path.to_path_buf(),
230 Some(template_path.to_path_buf()),
231 )),
232 }
233}
234
235#[derive(Debug, Clone)]
236struct Deb822Changes(
237 std::collections::HashMap<(String, String), Vec<(String, Option<String>, Option<String>)>>,
238);
239
240impl Deb822Changes {
241 fn new() -> Self {
242 Self(std::collections::HashMap::new())
243 }
244
245 fn insert(
246 &mut self,
247 para_key: (String, String),
248 field: String,
249 old_value: Option<String>,
250 new_value: Option<String>,
251 ) {
252 self.0
253 .entry(para_key)
254 .or_default()
255 .push((field, old_value, new_value));
256 }
257
258 #[allow(dead_code)]
259 fn normalized(&self) -> Vec<((&str, &str), Vec<(&str, Option<&str>, Option<&str>)>)> {
260 let mut ret: Vec<_> = self
261 .0
262 .iter()
263 .map(|(k, v)| {
264 ((k.0.as_str(), k.1.as_str()), {
265 let mut v: Vec<_> = v
266 .iter()
267 .map(|(f, o, n)| (f.as_str(), o.as_deref(), n.as_deref()))
268 .collect();
269 v.sort();
270 v
271 })
272 })
273 .collect();
274 ret.sort_by_key(|(k, _)| *k);
275 ret
276 }
277}
278
279fn update_control_template(
290 template_path: &std::path::Path,
291 template_type: TemplateType,
292 path: &std::path::Path,
293 changes: Deb822Changes,
294 expand_template: bool,
295) -> Result<bool, TemplateExpansionError> {
296 if template_type == TemplateType::Directory {
297 return Err(TemplateExpansionError::UnknownTemplating(
299 path.to_path_buf(),
300 Some(template_path.to_path_buf()),
301 ));
302 }
303
304 let mut template_editor =
305 FsEditor::<deb822_lossless::Deb822>::new(template_path, true, false).unwrap();
306
307 let resolve_conflict = match template_type {
308 TemplateType::Cdbs => Some(resolve_cdbs_template as ResolveDeb822Conflict),
309 _ => None,
310 };
311
312 apply_changes(&mut template_editor, changes.clone(), resolve_conflict)?;
313
314 if !template_editor.has_changed() {
315 return Ok(false);
317 }
318
319 match template_editor.commit() {
320 Ok(_) => {}
321 Err(e) => return Err(TemplateExpansionError::Failed(e.to_string())),
322 }
323
324 if expand_template {
325 match template_type {
326 TemplateType::Cdbs => {
327 let mut editor =
328 FsEditor::<deb822_lossless::Deb822>::new(path, true, false).unwrap();
329 apply_changes(&mut editor, changes, None)?;
330 match editor.commit() {
331 Ok(_) => {}
332 Err(e) => return Err(TemplateExpansionError::Failed(e.to_string())),
333 }
334 }
335 _ => {
336 expand_control_template(template_path, path, template_type)?;
337 }
338 }
339 }
340
341 Ok(true)
342}
343
344#[derive(Debug, PartialEq, Eq)]
345pub struct ChangeConflict {
347 pub para_key: (String, String),
349 pub field: String,
351 pub actual_old_value: Option<String>,
353 pub template_old_value: Option<String>,
355 pub actual_new_value: Option<String>,
357}
358
359impl std::fmt::Display for ChangeConflict {
360 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
361 write!(
362 f,
363 "{}/{}: {} -> {} (template: {})",
364 self.para_key.0,
365 self.para_key.1,
366 self.actual_old_value.as_deref().unwrap_or(""),
367 self.actual_new_value.as_deref().unwrap_or(""),
368 self.template_old_value.as_deref().unwrap_or("")
369 )
370 }
371}
372
373impl std::error::Error for ChangeConflict {}
374
375type ResolveDeb822Conflict = fn(
376 para_key: (&str, &str),
377 field: &str,
378 actual_old_value: Option<&str>,
379 template_old_value: Option<&str>,
380 actual_new_value: Option<&str>,
381) -> Result<Option<String>, ChangeConflict>;
382
383fn resolve_cdbs_template(
384 para_key: (&str, &str),
385 field: &str,
386 actual_old_value: Option<&str>,
387 template_old_value: Option<&str>,
388 actual_new_value: Option<&str>,
389) -> Result<Option<String>, ChangeConflict> {
390 if para_key.0 == "Source"
391 && field == "Build-Depends"
392 && template_old_value.is_some()
393 && actual_old_value.is_some()
394 && actual_new_value.is_some()
395 {
396 if actual_new_value
397 .unwrap()
398 .contains(actual_old_value.unwrap())
399 {
400 return Ok(Some(
402 actual_new_value
403 .unwrap()
404 .replace(actual_old_value.unwrap(), template_old_value.unwrap()),
405 ));
406 } else {
407 let old_rels: Relations = actual_old_value.unwrap().parse().unwrap();
408 let new_rels: Relations = actual_new_value.unwrap().parse().unwrap();
409 let template_old_value = template_old_value.unwrap();
410 let (mut ret, errors) = Relations::parse_relaxed(template_old_value, true);
411 if !errors.is_empty() {
412 log::debug!("Errors parsing template value: {:?}", errors);
413 }
414 for v in new_rels.entries() {
415 if old_rels.entries().any(|r| is_relation_implied(&v, &r)) {
416 continue;
417 }
418 ensure_relation(&mut ret, v);
419 }
420 return Ok(Some(ret.to_string()));
421 }
422 }
423 Err(ChangeConflict {
424 para_key: (para_key.0.to_string(), para_key.1.to_string()),
425 field: field.to_string(),
426 actual_old_value: actual_old_value.map(|v| v.to_string()),
427 template_old_value: template_old_value.map(|v| v.to_string()),
428 actual_new_value: actual_new_value.map(|s| s.to_string()),
429 })
430}
431
432pub fn guess_template_type(
441 template_path: &std::path::Path,
442 debian_path: Option<&std::path::Path>,
443) -> Option<TemplateType> {
444 if let Some(debian_path) = debian_path {
446 match std::fs::read(debian_path.join("rules")) {
447 Ok(file) => {
448 for line in file.split(|&c| c == b'\n') {
449 if line.starts_with(b"debian/control:") {
450 return Some(TemplateType::Rules);
451 }
452 if line.starts_with(b"debian/%: debian/%.in") {
453 return Some(TemplateType::Rules);
454 }
455 if line.starts_with(b"include /usr/share/blends-dev/rules") {
456 return Some(TemplateType::Rules);
457 }
458 }
459 }
460 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
461 Err(e) => panic!(
462 "Failed to read {}: {}",
463 debian_path.join("rules").display(),
464 e
465 ),
466 }
467 }
468 match std::fs::read(template_path) {
469 Ok(template) => {
470 let template_str = std::str::from_utf8(&template).unwrap();
471 if template_str.contains("@GNOME_TEAM@") {
472 return Some(TemplateType::Gnome);
473 }
474 if template_str.contains("PGVERSION") {
475 return Some(TemplateType::Postgresql);
476 }
477 if template_str.contains("@cdbs@") {
478 return Some(TemplateType::Cdbs);
479 }
480
481 let control = debian_control::Control::read_relaxed(std::io::Cursor::new(&template))
482 .unwrap()
483 .0;
484
485 let build_depends = control.source().and_then(|s| s.build_depends());
486
487 if build_depends.iter().any(|d| {
488 d.entries()
489 .any(|e| e.relations().any(|r| r.name() == "gnome-pkg-tools"))
490 }) {
491 return Some(TemplateType::Gnome);
492 }
493
494 if build_depends.iter().any(|d| {
495 d.entries()
496 .any(|e| e.relations().any(|r| r.name() == "cdbs"))
497 }) {
498 return Some(TemplateType::Cdbs);
499 }
500 }
501 Err(_) if template_path.is_dir() => {
502 return Some(TemplateType::Directory);
503 }
504 Err(e) => panic!("Failed to read {}: {}", template_path.display(), e),
505 }
506 if let Some(debian_path) = debian_path {
507 if debian_path.join("debcargo.toml").exists() {
508 return Some(TemplateType::Debcargo);
509 }
510 }
511 None
512}
513
514fn apply_changes(
520 deb822: &mut deb822_lossless::Deb822,
521 mut changes: Deb822Changes,
522 resolve_conflict: Option<ResolveDeb822Conflict>,
523) -> Result<(), ChangeConflict> {
524 fn default_resolve_conflict(
525 para_key: (&str, &str),
526 field: &str,
527 actual_old_value: Option<&str>,
528 template_old_value: Option<&str>,
529 actual_new_value: Option<&str>,
530 ) -> Result<Option<String>, ChangeConflict> {
531 Err(ChangeConflict {
532 para_key: (para_key.0.to_string(), para_key.1.to_string()),
533 field: field.to_string(),
534 actual_old_value: actual_old_value.map(|v| v.to_string()),
535 template_old_value: template_old_value.map(|v| v.to_string()),
536 actual_new_value: actual_new_value.map(|s| s.to_string()),
537 })
538 }
539
540 let resolve_conflict = resolve_conflict.unwrap_or(default_resolve_conflict);
541
542 for mut paragraph in deb822.paragraphs() {
543 let items: Vec<_> = paragraph.items().collect();
544 for item in items {
545 for (key, old_value, mut new_value) in changes.0.remove(&item).unwrap_or_default() {
546 if paragraph.get(&key) != old_value {
547 new_value = resolve_conflict(
548 (&item.0, &item.1),
549 &key,
550 old_value.as_deref(),
551 paragraph.get(&key).as_deref(),
552 new_value.as_deref(),
553 )?;
554 }
555 if let Some(new_value) = new_value.as_ref() {
556 paragraph.set(&key, new_value);
557 } else {
558 paragraph.remove(&key);
559 }
560 }
561 }
562 }
563 for (key, p) in changes.0.drain() {
565 let mut paragraph = deb822.add_paragraph();
566 for (field, old_value, mut new_value) in p {
567 if old_value.is_some() {
568 new_value = resolve_conflict(
569 (&key.0, &key.1),
570 &field,
571 old_value.as_deref(),
572 paragraph.get(&field).as_deref(),
573 new_value.as_deref(),
574 )?;
575 }
576 if let Some(new_value) = new_value {
577 paragraph.set(&field, &new_value);
578 }
579 }
580 }
581 Ok(())
582}
583
584fn find_template_path(path: &Path) -> Option<PathBuf> {
585 for ext in &["in", "m4"] {
586 let template_path = path.with_extension(ext);
587 if template_path.exists() {
588 return Some(template_path);
589 }
590 }
591 None
592}
593
594pub struct TemplatedControlEditor {
610 primary: FsEditor<debian_control::Control>,
612 template: Option<Template>,
614 path: PathBuf,
616 template_only: bool,
618}
619
620impl Deref for TemplatedControlEditor {
621 type Target = debian_control::Control;
622
623 fn deref(&self) -> &Self::Target {
624 &self.primary
625 }
626}
627
628impl DerefMut for TemplatedControlEditor {
629 fn deref_mut(&mut self) -> &mut Self::Target {
630 &mut self.primary
631 }
632}
633
634impl TemplatedControlEditor {
635 pub fn create<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
637 if control_path.as_ref().exists() {
638 return Err(EditorError::IoError(std::io::Error::new(
639 std::io::ErrorKind::AlreadyExists,
640 "Control file already exists",
641 )));
642 }
643 Self::new(control_path, true)
644 }
645
646 pub fn template_type(&self) -> Option<TemplateType> {
648 self.template.as_ref().map(|t| t.template_type)
649 }
650
651 pub fn open<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
653 Self::new(control_path, false)
654 }
655
656 pub fn new<P: AsRef<Path>>(control_path: P, allow_missing: bool) -> Result<Self, EditorError> {
658 let path = control_path.as_ref();
659 let (template, template_only) = if !path.exists() {
660 if let Some(template) = Template::find(path) {
661 match template.expand() {
662 Ok(_) => {}
663 Err(e) => {
664 return Err(EditorError::TemplateError(
665 template.template_path,
666 e.to_string(),
667 ))
668 }
669 }
670 (Some(template), true)
671 } else if !allow_missing {
672 return Err(EditorError::IoError(std::io::Error::new(
673 std::io::ErrorKind::NotFound,
674 "No control file or template found",
675 )));
676 } else {
677 (None, false)
678 }
679 } else {
680 (Template::find(path), false)
681 };
682 let primary = FsEditor::<debian_control::Control>::new(path, false, false)?;
683 Ok(Self {
684 path: path.to_path_buf(),
685 primary,
686 template_only,
687 template,
688 })
689 }
690
691 fn changes(&self) -> Deb822Changes {
696 let orig = deb822_lossless::Deb822::read_relaxed(self.primary.orig_content().unwrap())
697 .unwrap()
698 .0;
699 let mut changes = Deb822Changes::new();
700
701 fn by_key(
702 ps: impl Iterator<Item = Paragraph>,
703 ) -> std::collections::HashMap<(String, String), Paragraph> {
704 let mut ret = std::collections::HashMap::new();
705 for p in ps {
706 if let Some(s) = p.get("Source") {
707 ret.insert(("Source".to_string(), s), p);
708 } else if let Some(s) = p.get("Package") {
709 ret.insert(("Package".to_string(), s), p);
710 } else {
711 let k = p.items().next().unwrap();
712 ret.insert(k, p);
713 }
714 }
715 ret
716 }
717
718 let orig_by_key = by_key(orig.paragraphs());
719 let new_by_key = by_key(self.as_deb822().paragraphs());
720 let keys = orig_by_key
721 .keys()
722 .chain(new_by_key.keys())
723 .collect::<std::collections::HashSet<_>>();
724 for key in keys {
725 let old = orig_by_key.get(key);
726 let new = new_by_key.get(key);
727 if old == new {
728 continue;
729 }
730 let fields = std::collections::HashSet::<String>::from_iter(
731 old.iter()
732 .flat_map(|p| p.keys())
733 .chain(new.iter().flat_map(|p| p.keys())),
734 );
735 for field in &fields {
736 let old_val = old.and_then(|x| x.get(field));
737 let new_val = new.and_then(|x| x.get(field));
738 if old_val != new_val {
739 changes.insert(key.clone(), field.to_string(), old_val, new_val);
740 }
741 }
742 }
743 changes
744 }
745
746 pub fn commit(&self) -> Result<Vec<PathBuf>, EditorError> {
748 let mut changed_files: Vec<PathBuf> = vec![];
749 if self.template_only {
750 match std::fs::remove_file(self.path.as_path()) {
752 Ok(_) => {}
753 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
754 Err(e) => return Err(EditorError::IoError(e)),
755 }
756
757 changed_files.push(self.path.clone());
758
759 let template = self
760 .template
761 .as_ref()
762 .expect("template_only implies template");
763
764 let changed = match template.update(self.changes(), false) {
766 Ok(changed) => changed,
767 Err(e) => {
768 return Err(EditorError::TemplateError(
769 template.template_path.clone(),
770 e.to_string(),
771 ))
772 }
773 };
774 if changed {
775 changed_files.push(template.template_path.clone());
776 }
777 } else {
778 match self.primary.commit() {
779 Ok(files) => {
780 changed_files.extend(files.iter().map(|p| p.to_path_buf()));
781 }
782 Err(EditorError::GeneratedFile(
783 p,
784 GeneratedFile {
785 template_path: tp,
786 template_type: tt,
787 },
788 )) => {
789 if tp.is_none() {
790 return Err(EditorError::GeneratedFile(
791 p,
792 GeneratedFile {
793 template_path: tp,
794 template_type: tt,
795 },
796 ));
797 }
798 let template = if let Some(template) = self.template.as_ref() {
799 template
800 } else {
801 return Err(EditorError::IoError(std::io::Error::new(
802 std::io::ErrorKind::NotFound,
803 "No control file or template found",
804 )));
805 };
806 let changes = self.changes();
807 let changed = match template.update(changes, true) {
808 Ok(changed) => changed,
809 Err(e) => {
810 return Err(EditorError::TemplateError(tp.unwrap(), e.to_string()))
811 }
812 };
813 changed_files = if changed {
814 vec![tp.as_ref().unwrap().to_path_buf(), p]
815 } else {
816 vec![]
817 };
818 }
819 Err(EditorError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => {
820 let template = if let Some(p) = self.template.as_ref() {
821 p
822 } else {
823 return Err(EditorError::IoError(std::io::Error::new(
824 std::io::ErrorKind::NotFound,
825 "No control file or template found",
826 )));
827 };
828 let changed = match template.update(self.changes(), !self.template_only) {
829 Ok(changed) => changed,
830 Err(e) => {
831 return Err(EditorError::TemplateError(
832 template.template_path.clone(),
833 e.to_string(),
834 ))
835 }
836 };
837 if changed {
838 changed_files.push(template.template_path.clone());
839 changed_files.push(self.path.clone());
840 }
841 }
842 Err(e) => return Err(e),
843 }
844 }
845
846 Ok(changed_files)
847 }
848}
849
850struct Template {
851 path: PathBuf,
852 template_path: PathBuf,
853 template_type: TemplateType,
854}
855
856impl Template {
857 fn find(path: &Path) -> Option<Self> {
858 let template_path = find_template_path(path)?;
859 let template_type = guess_template_type(&template_path, Some(path.parent().unwrap()))?;
860 Some(Self {
861 path: path.to_path_buf(),
862 template_path,
863 template_type,
864 })
865 }
866
867 fn expand(&self) -> Result<(), TemplateExpansionError> {
868 expand_control_template(&self.template_path, &self.path, self.template_type)
869 }
870
871 fn update(&self, changes: Deb822Changes, expand: bool) -> Result<bool, TemplateExpansionError> {
872 update_control_template(
873 &self.template_path,
874 self.template_type,
875 &self.path,
876 changes,
877 expand,
878 )
879 }
880}
881
882impl Editor<debian_control::Control> for TemplatedControlEditor {
883 fn orig_content(&self) -> Option<&[u8]> {
884 self.primary.orig_content()
885 }
886
887 fn updated_content(&self) -> Option<Vec<u8>> {
888 self.primary.updated_content()
889 }
890
891 fn rewritten_content(&self) -> Option<&[u8]> {
892 self.primary.rewritten_content()
893 }
894
895 fn is_generated(&self) -> bool {
896 self.primary.is_generated()
897 }
898
899 fn commit(&self) -> Result<Vec<std::path::PathBuf>, EditorError> {
900 TemplatedControlEditor::commit(self)
901 }
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907
908 #[test]
909 fn test_format_description() {
910 let summary = "Summary";
911 let long_description = vec!["Long", "Description"];
912 let expected = "Summary\n Long\n Description\n";
913 assert_eq!(format_description(summary, long_description), expected);
914 }
915
916 #[test]
917 fn test_resolve_cdbs_conflicts() {
918 let val = resolve_cdbs_template(
919 ("Source", "libnetsds-perl"),
920 "Build-Depends",
921 Some("debhelper (>= 6), foo"),
922 Some("@cdbs@, debhelper (>= 9)"),
923 Some("debhelper (>= 10), foo"),
924 )
925 .unwrap();
926
927 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
928
929 let val = resolve_cdbs_template(
930 ("Source", "libnetsds-perl"),
931 "Build-Depends",
932 Some("debhelper (>= 6), foo"),
933 Some("@cdbs@, foo"),
934 Some("debhelper (>= 10), foo"),
935 )
936 .unwrap();
937 assert_eq!(val, Some("@cdbs@, foo, debhelper (>= 10)".to_string()));
938 let val = resolve_cdbs_template(
939 ("Source", "libnetsds-perl"),
940 "Build-Depends",
941 Some("debhelper (>= 6), foo"),
942 Some("@cdbs@, debhelper (>= 9)"),
943 Some("debhelper (>= 10), foo"),
944 )
945 .unwrap();
946 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
947 }
948
949 mod guess_template_type {
950
951 #[test]
952 fn test_rules_generates_control() {
953 let td = tempfile::tempdir().unwrap();
954 std::fs::create_dir(td.path().join("debian")).unwrap();
955 std::fs::write(
956 td.path().join("debian/rules"),
957 r#"%:
958 dh $@
959
960debian/control: debian/control.in
961 cp $@ $<
962"#,
963 )
964 .unwrap();
965 assert_eq!(
966 super::guess_template_type(
967 &td.path().join("debian/control.in"),
968 Some(&td.path().join("debian"))
969 ),
970 Some(super::TemplateType::Rules)
971 );
972 }
973
974 #[test]
975 fn test_rules_generates_control_percent() {
976 let td = tempfile::tempdir().unwrap();
977 std::fs::create_dir(td.path().join("debian")).unwrap();
978 std::fs::write(
979 td.path().join("debian/rules"),
980 r#"%:
981 dh $@
982
983debian/%: debian/%.in
984 cp $@ $<
985"#,
986 )
987 .unwrap();
988 assert_eq!(
989 super::guess_template_type(
990 &td.path().join("debian/control.in"),
991 Some(&td.path().join("debian"))
992 ),
993 Some(super::TemplateType::Rules)
994 );
995 }
996
997 #[test]
998 fn test_rules_generates_control_blends() {
999 let td = tempfile::tempdir().unwrap();
1000 std::fs::create_dir(td.path().join("debian")).unwrap();
1001 std::fs::write(
1002 td.path().join("debian/rules"),
1003 r#"%:
1004 dh $@
1005
1006include /usr/share/blends-dev/rules
1007"#,
1008 )
1009 .unwrap();
1010 assert_eq!(
1011 super::guess_template_type(
1012 &td.path().join("debian/control.stub"),
1013 Some(&td.path().join("debian"))
1014 ),
1015 Some(super::TemplateType::Rules)
1016 );
1017 }
1018
1019 #[test]
1020 fn test_empty_template() {
1021 let td = tempfile::tempdir().unwrap();
1022 std::fs::create_dir(td.path().join("debian")).unwrap();
1023 std::fs::write(td.path().join("debian/control.in"), "").unwrap();
1025
1026 assert_eq!(
1027 None,
1028 super::guess_template_type(
1029 &td.path().join("debian/control.in"),
1030 Some(&td.path().join("debian"))
1031 )
1032 );
1033 }
1034
1035 #[test]
1036 fn test_build_depends_cdbs() {
1037 let td = tempfile::tempdir().unwrap();
1038 std::fs::create_dir(td.path().join("debian")).unwrap();
1039 std::fs::write(
1040 td.path().join("debian/control.in"),
1041 r#"Source: blah
1042Build-Depends: cdbs
1043Vcs-Git: file://
1044
1045Package: bar
1046"#,
1047 )
1048 .unwrap();
1049 assert_eq!(
1050 Some(super::TemplateType::Cdbs),
1051 super::guess_template_type(
1052 &td.path().join("debian/control.in"),
1053 Some(&td.path().join("debian"))
1054 )
1055 );
1056 }
1057
1058 #[test]
1059 fn test_no_build_depends() {
1060 let td = tempfile::tempdir().unwrap();
1061 std::fs::create_dir(td.path().join("debian")).unwrap();
1062 std::fs::write(
1063 td.path().join("debian/control.in"),
1064 r#"Source: blah
1065Vcs-Git: file://
1066
1067Package: bar
1068"#,
1069 )
1070 .unwrap();
1071 assert_eq!(
1072 None,
1073 super::guess_template_type(
1074 &td.path().join("debian/control.in"),
1075 Some(&td.path().join("debian"))
1076 )
1077 );
1078 }
1079
1080 #[test]
1081 fn test_gnome() {
1082 let td = tempfile::tempdir().unwrap();
1083 std::fs::create_dir(td.path().join("debian")).unwrap();
1084 std::fs::write(
1085 td.path().join("debian/control.in"),
1086 r#"Foo @GNOME_TEAM@
1087"#,
1088 )
1089 .unwrap();
1090 assert_eq!(
1091 Some(super::TemplateType::Gnome),
1092 super::guess_template_type(
1093 &td.path().join("debian/control.in"),
1094 Some(&td.path().join("debian"))
1095 )
1096 );
1097 }
1098
1099 #[test]
1100 fn test_gnome_build_depends() {
1101 let td = tempfile::tempdir().unwrap();
1102 std::fs::create_dir(td.path().join("debian")).unwrap();
1103 std::fs::write(
1104 td.path().join("debian/control.in"),
1105 r#"Source: blah
1106Build-Depends: gnome-pkg-tools, libc6-dev
1107"#,
1108 )
1109 .unwrap();
1110 assert_eq!(
1111 Some(super::TemplateType::Gnome),
1112 super::guess_template_type(
1113 &td.path().join("debian/control.in"),
1114 Some(&td.path().join("debian"))
1115 )
1116 );
1117 }
1118
1119 #[test]
1120 fn test_cdbs() {
1121 let td = tempfile::tempdir().unwrap();
1122 std::fs::create_dir(td.path().join("debian")).unwrap();
1123 std::fs::write(
1124 td.path().join("debian/control.in"),
1125 r#"Source: blah
1126Build-Depends: debhelper, cdbs
1127"#,
1128 )
1129 .unwrap();
1130 assert_eq!(
1131 Some(super::TemplateType::Cdbs),
1132 super::guess_template_type(
1133 &td.path().join("debian/control.in"),
1134 Some(&td.path().join("debian"))
1135 )
1136 );
1137 }
1138
1139 #[test]
1140 fn test_multiple_paragraphs() {
1141 let td = tempfile::tempdir().unwrap();
1142 std::fs::create_dir(td.path().join("debian")).unwrap();
1143 std::fs::write(
1144 td.path().join("debian/control.in"),
1145 r#"Source: blah
1146Build-Depends: debhelper, cdbs
1147
1148Package: foo
1149"#,
1150 )
1151 .unwrap();
1152 assert_eq!(
1153 Some(super::TemplateType::Cdbs),
1154 super::guess_template_type(
1155 &td.path().join("debian/control.in"),
1156 Some(&td.path().join("debian"))
1157 )
1158 );
1159 }
1160
1161 #[test]
1162 fn test_directory() {
1163 let td = tempfile::tempdir().unwrap();
1164 std::fs::create_dir(td.path().join("debian")).unwrap();
1165 std::fs::create_dir(td.path().join("debian/control.in")).unwrap();
1166 assert_eq!(
1167 Some(super::TemplateType::Directory),
1168 super::guess_template_type(
1169 &td.path().join("debian/control.in"),
1170 Some(&td.path().join("debian"))
1171 )
1172 );
1173 }
1174
1175 #[test]
1176 fn test_debcargo() {
1177 let td = tempfile::tempdir().unwrap();
1178 std::fs::create_dir(td.path().join("debian")).unwrap();
1179 std::fs::write(
1180 td.path().join("debian/control.in"),
1181 r#"Source: blah
1182Build-Depends: bar
1183"#,
1184 )
1185 .unwrap();
1186 std::fs::write(
1187 td.path().join("debian/debcargo.toml"),
1188 r#"maintainer = Joe Example <joe@example.com>
1189"#,
1190 )
1191 .unwrap();
1192 assert_eq!(
1193 Some(super::TemplateType::Debcargo),
1194 super::guess_template_type(
1195 &td.path().join("debian/control.in"),
1196 Some(&td.path().join("debian"))
1197 )
1198 );
1199 }
1200 }
1201
1202 #[test]
1203 fn test_postgresql() {
1204 let td = tempfile::tempdir().unwrap();
1205 std::fs::create_dir(td.path().join("debian")).unwrap();
1206 std::fs::write(
1207 td.path().join("debian/control.in"),
1208 r#"Source: blah
1209Build-Depends: bar, postgresql
1210
1211Package: foo-PGVERSION
1212"#,
1213 )
1214 .unwrap();
1215 assert_eq!(
1216 Some(super::TemplateType::Postgresql),
1217 super::guess_template_type(
1218 &td.path().join("debian/control.in"),
1219 Some(&td.path().join("debian"))
1220 )
1221 );
1222 }
1223
1224 #[test]
1225 fn test_apply_changes() {
1226 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1227Build-Depends: debhelper (>= 6), foo
1228
1229Package: bar
1230"#
1231 .parse()
1232 .unwrap();
1233
1234 let mut changes = Deb822Changes(std::collections::HashMap::new());
1235 changes.0.insert(
1236 ("Source".to_string(), "blah".to_string()),
1237 vec![(
1238 "Build-Depends".to_string(),
1239 Some("debhelper (>= 6), foo".to_string()),
1240 Some("debhelper (>= 10), foo".to_string()),
1241 )],
1242 );
1243
1244 super::apply_changes(&mut deb822, changes, None).unwrap();
1245
1246 assert_eq!(
1247 deb822.to_string(),
1248 r#"Source: blah
1249Build-Depends: debhelper (>= 10), foo
1250
1251Package: bar
1252"#
1253 );
1254 }
1255
1256 #[test]
1257 fn test_apply_changes_new_paragraph() {
1258 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1259Build-Depends: debhelper (>= 6), foo
1260
1261Package: bar
1262"#
1263 .parse()
1264 .unwrap();
1265
1266 let mut changes = Deb822Changes(std::collections::HashMap::new());
1267 changes.0.insert(
1268 ("Source".to_string(), "blah".to_string()),
1269 vec![(
1270 "Build-Depends".to_string(),
1271 Some("debhelper (>= 6), foo".to_string()),
1272 Some("debhelper (>= 10), foo".to_string()),
1273 )],
1274 );
1275 changes.0.insert(
1276 ("Package".to_string(), "blah2".to_string()),
1277 vec![
1278 ("Package".to_string(), None, Some("blah2".to_string())),
1279 (
1280 "Description".to_string(),
1281 None,
1282 Some("Some package".to_string()),
1283 ),
1284 ],
1285 );
1286
1287 super::apply_changes(&mut deb822, changes, None).unwrap();
1288
1289 assert_eq!(
1290 deb822.to_string(),
1291 r#"Source: blah
1292Build-Depends: debhelper (>= 10), foo
1293
1294Package: bar
1295
1296Package: blah2
1297Description: Some package
1298"#
1299 );
1300 }
1301
1302 #[test]
1303 fn test_apply_changes_conflict() {
1304 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1305Build-Depends: debhelper (>= 6), foo
1306
1307Package: bar
1308"#
1309 .parse()
1310 .unwrap();
1311
1312 let mut changes = Deb822Changes(std::collections::HashMap::new());
1313 changes.0.insert(
1314 ("Source".to_string(), "blah".to_string()),
1315 vec![(
1316 "Build-Depends".to_string(),
1317 Some("debhelper (>= 7), foo".to_string()),
1318 Some("debhelper (>= 10), foo".to_string()),
1319 )],
1320 );
1321
1322 let result = super::apply_changes(&mut deb822, changes, None);
1323 assert!(result.is_err());
1324 let err = result.unwrap_err();
1325 assert_eq!(
1326 err,
1327 ChangeConflict {
1328 para_key: ("Source".to_string(), "blah".to_string()),
1329 field: "Build-Depends".to_string(),
1330 actual_old_value: Some("debhelper (>= 7), foo".to_string()),
1331 template_old_value: Some("debhelper (>= 6), foo".to_string()),
1332 actual_new_value: Some("debhelper (>= 10), foo".to_string()),
1333 }
1334 );
1335 }
1336
1337 #[test]
1338 fn test_apply_changes_resolve_conflict() {
1339 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1340Build-Depends: debhelper (>= 6), foo
1341
1342Package: bar
1343"#
1344 .parse()
1345 .unwrap();
1346
1347 let mut changes = Deb822Changes(std::collections::HashMap::new());
1348 changes.0.insert(
1349 ("Source".to_string(), "blah".to_string()),
1350 vec![(
1351 "Build-Depends".to_string(),
1352 Some("debhelper (>= 7), foo".to_string()),
1353 Some("debhelper (>= 10), foo".to_string()),
1354 )],
1355 );
1356
1357 let result = super::apply_changes(&mut deb822, changes, Some(|_, _, _, _, _| Ok(None)));
1358 assert!(result.is_ok());
1359 assert_eq!(
1360 deb822.to_string(),
1361 r#"Source: blah
1362
1363Package: bar
1364"#
1365 );
1366 }
1367
1368 mod control_editor {
1369 #[test]
1370 fn test_do_not_edit() {
1371 let td = tempfile::tempdir().unwrap();
1372 std::fs::create_dir(td.path().join("debian")).unwrap();
1373 std::fs::write(
1374 td.path().join("debian/control"),
1375 r#"# DO NOT EDIT
1376# This file was generated by blah
1377
1378Source: blah
1379Testsuite: autopkgtest
1380
1381"#,
1382 )
1383 .unwrap();
1384 let editor =
1385 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1386 editor.source().unwrap().set_name("foo");
1387 let changes = editor.changes();
1388 assert_eq!(
1389 changes.normalized(),
1390 vec![
1391 (
1392 ("Source", "blah"),
1393 vec![
1394 ("Source", Some("blah"), None),
1395 ("Testsuite", Some("autopkgtest"), None),
1396 ]
1397 ),
1398 (
1399 ("Source", "foo"),
1400 vec![
1401 ("Source", None, Some("foo")),
1402 ("Testsuite", None, Some("autopkgtest"))
1403 ]
1404 )
1405 ]
1406 );
1407 assert!(matches!(
1408 editor.commit().unwrap_err(),
1409 super::EditorError::GeneratedFile(_, _)
1410 ));
1411 }
1412
1413 #[test]
1414 fn test_add_binary() {
1415 let td = tempfile::tempdir().unwrap();
1416 std::fs::create_dir(td.path().join("debian")).unwrap();
1417 std::fs::write(
1418 td.path().join("debian/control"),
1419 r#"Source: blah
1420Testsuite: autopkgtest
1421
1422Package: blah
1423Description: Some description
1424 And there are more lines
1425 And more lines
1426"#,
1427 )
1428 .unwrap();
1429 let mut editor =
1430 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1431 let mut binary = editor.add_binary("foo");
1432 binary.set_description(Some("A new package foo"));
1433 let paths = editor.commit().unwrap();
1434 assert_eq!(paths.len(), 1);
1435 }
1436
1437 #[test]
1438 fn test_list_binaries() {
1439 let td = tempfile::tempdir().unwrap();
1440 std::fs::create_dir(td.path().join("debian")).unwrap();
1441 std::fs::write(
1442 td.path().join("debian/control"),
1443 r#"Source: blah
1444Testsuite: autopkgtest
1445
1446Package: blah
1447Description: Some description
1448 And there are more lines
1449 And more lines
1450"#,
1451 )
1452 .unwrap();
1453 let editor =
1454 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1455 let binaries = editor.binaries().collect::<Vec<_>>();
1456 assert_eq!(binaries.len(), 1);
1457 assert_eq!(binaries[0].name().as_deref(), Some("blah"));
1458 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1459 }
1460
1461 #[test]
1462 fn test_no_source() {
1463 let td = tempfile::tempdir().unwrap();
1464 std::fs::create_dir(td.path().join("debian")).unwrap();
1465 std::fs::write(
1466 td.path().join("debian/control"),
1467 r#"Package: blah
1468Testsuite: autopkgtest
1469
1470Package: bar
1471"#,
1472 )
1473 .unwrap();
1474 let editor =
1475 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1476 assert!(editor.source().is_none());
1477 }
1478
1479 #[test]
1480 fn test_create() {
1481 let td = tempfile::tempdir().unwrap();
1482 std::fs::create_dir(td.path().join("debian")).unwrap();
1483 let mut editor =
1484 super::TemplatedControlEditor::create(td.path().join("debian/control")).unwrap();
1485 editor.add_source("foo");
1486 assert_eq!(
1487 r#"Source: foo
1488"#,
1489 editor.as_deb822().to_string()
1490 );
1491 }
1492
1493 #[test]
1494 fn test_do_not_edit_no_change() {
1495 let td = tempfile::tempdir().unwrap();
1496 std::fs::create_dir(td.path().join("debian")).unwrap();
1497 std::fs::write(
1498 td.path().join("debian/control"),
1499 r#"# DO NOT EDIT
1500# This file was generated by blah
1501
1502Source: blah
1503Testsuite: autopkgtest
1504
1505"#,
1506 )
1507 .unwrap();
1508 let editor =
1509 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1510 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1511 }
1512
1513 #[test]
1514 fn test_unpreservable() {
1515 let td = tempfile::tempdir().unwrap();
1516 std::fs::create_dir(td.path().join("debian")).unwrap();
1517 std::fs::write(
1518 td.path().join("debian/control"),
1519 r#"Source: blah
1520# A comment
1521Testsuite: autopkgtest
1522
1523"#,
1524 )
1525 .unwrap();
1526 let editor =
1527 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1528 editor
1529 .source()
1530 .unwrap()
1531 .as_mut_deb822()
1532 .set("NewField", "New Field");
1533
1534 editor.commit().unwrap();
1535
1536 assert_eq!(
1537 r#"Source: blah
1538# A comment
1539Testsuite: autopkgtest
1540NewField: New Field
1541
1542"#,
1543 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1544 );
1545 }
1546
1547 #[test]
1548 fn test_modify_source() {
1549 let td = tempfile::tempdir().unwrap();
1550 std::fs::create_dir(td.path().join("debian")).unwrap();
1551 std::fs::write(
1552 td.path().join("debian/control"),
1553 r#"Source: blah
1554Testsuite: autopkgtest
1555"#,
1556 )
1557 .unwrap();
1558
1559 let editor =
1560 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1561 editor
1562 .source()
1563 .unwrap()
1564 .as_mut_deb822()
1565 .set("XS-Vcs-Git", "git://github.com/example/example");
1566
1567 editor.commit().unwrap();
1568
1569 assert_eq!(
1570 r#"Source: blah
1571Testsuite: autopkgtest
1572XS-Vcs-Git: git://github.com/example/example
1573"#,
1574 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1575 );
1576 }
1577
1578 #[test]
1659 fn test_modify_binary() {
1660 let td = tempfile::tempdir().unwrap();
1661 std::fs::create_dir(td.path().join("debian")).unwrap();
1662 std::fs::write(
1663 td.path().join("debian/control"),
1664 r#"Source: blah
1665Testsuite: autopkgtest
1666
1667Package: libblah
1668Section: extra
1669"#,
1670 )
1671 .unwrap();
1672
1673 let editor =
1674 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1675 let mut binary = editor
1676 .binaries()
1677 .find(|b| b.name().as_deref() == Some("libblah"))
1678 .unwrap();
1679 binary.set_architecture(Some("all"));
1680
1681 editor.commit().unwrap();
1682
1683 assert_eq!(
1684 r#"Source: blah
1685Testsuite: autopkgtest
1686
1687Package: libblah
1688Section: extra
1689Architecture: all
1690"#,
1691 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1692 );
1693 }
1694
1695 #[test]
1696 fn test_doesnt_strip_whitespace() {
1697 let td = tempfile::tempdir().unwrap();
1698 std::fs::create_dir(td.path().join("debian")).unwrap();
1699 std::fs::write(
1700 td.path().join("debian/control"),
1701 r#"Source: blah
1702Testsuite: autopkgtest
1703
1704"#,
1705 )
1706 .unwrap();
1707 let editor =
1708 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1709 editor.commit().unwrap();
1710
1711 assert_eq!(
1712 r#"Source: blah
1713Testsuite: autopkgtest
1714
1715"#,
1716 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1717 );
1718 }
1719
1720 #[cfg(unix)]
1721 #[test]
1722 fn test_update_template() {
1723 use std::os::unix::fs::PermissionsExt;
1724 let td = tempfile::tempdir().unwrap();
1725 std::fs::create_dir(td.path().join("debian")).unwrap();
1726 std::fs::write(
1727 td.path().join("debian/control"),
1728 r#"# DO NOT EDIT
1729# This file was generated by blah
1730
1731Source: blah
1732Testsuite: autopkgtest
1733Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1734
1735"#,
1736 )
1737 .unwrap();
1738 std::fs::write(
1739 td.path().join("debian/control.in"),
1740 r#"Source: blah
1741Testsuite: autopkgtest
1742Uploaders: @lintian-brush-test@
1743
1744"#,
1745 )
1746 .unwrap();
1747 std::fs::write(
1748 td.path().join("debian/rules"),
1749 r#"#!/usr/bin/make -f
1750
1751debian/control: debian/control.in
1752 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1753"#,
1754 )
1755 .unwrap();
1756 std::fs::set_permissions(
1758 td.path().join("debian/rules"),
1759 std::fs::Permissions::from_mode(0o755),
1760 )
1761 .unwrap();
1762
1763 let editor =
1764 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1765 editor
1766 .source()
1767 .unwrap()
1768 .as_mut_deb822()
1769 .set("Testsuite", "autopkgtest8");
1770
1771 assert_eq!(
1772 editor.commit().unwrap(),
1773 vec![
1774 td.path().join("debian/control.in"),
1775 td.path().join("debian/control")
1776 ]
1777 );
1778
1779 assert_eq!(
1780 r#"Source: blah
1781Testsuite: autopkgtest8
1782Uploaders: @lintian-brush-test@
1783
1784"#,
1785 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1786 );
1787
1788 assert_eq!(
1789 r#"Source: blah
1790Testsuite: autopkgtest8
1791Uploaders: testvalue
1792
1793"#,
1794 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1795 );
1796 }
1797
1798 #[cfg(unix)]
1799 #[test]
1800 fn test_update_template_only() {
1801 use std::os::unix::fs::PermissionsExt;
1802 let td = tempfile::tempdir().unwrap();
1803 std::fs::create_dir(td.path().join("debian")).unwrap();
1804 std::fs::write(
1805 td.path().join("debian/control.in"),
1806 r#"Source: blah
1807Testsuite: autopkgtest
1808Uploaders: @lintian-brush-test@
1809
1810"#,
1811 )
1812 .unwrap();
1813 std::fs::write(
1814 td.path().join("debian/rules"),
1815 r#"#!/usr/bin/make -f
1816
1817debian/control: debian/control.in
1818 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1819"#,
1820 )
1821 .unwrap();
1822
1823 std::fs::set_permissions(
1824 td.path().join("debian/rules"),
1825 std::fs::Permissions::from_mode(0o755),
1826 )
1827 .unwrap();
1828
1829 let editor =
1830 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1831 editor
1832 .source()
1833 .unwrap()
1834 .as_mut_deb822()
1835 .set("Testsuite", "autopkgtest8");
1836
1837 editor.commit().unwrap();
1838
1839 assert_eq!(
1840 r#"Source: blah
1841Testsuite: autopkgtest8
1842Uploaders: @lintian-brush-test@
1843
1844"#,
1845 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1846 );
1847
1848 assert!(!td.path().join("debian/control").exists());
1849 }
1850
1851 #[cfg(unix)]
1852 #[test]
1853 fn test_update_template_invalid_tokens() {
1854 use std::os::unix::fs::PermissionsExt;
1855 let td = tempfile::tempdir().unwrap();
1856 std::fs::create_dir(td.path().join("debian")).unwrap();
1857 std::fs::write(
1858 td.path().join("debian/control"),
1859 r#"# DO NOT EDIT
1860# This file was generated by blah
1861
1862Source: blah
1863Testsuite: autopkgtest
1864Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1865"#,
1866 )
1867 .unwrap();
1868 std::fs::write(
1869 td.path().join("debian/control.in"),
1870 r#"Source: blah
1871Testsuite: autopkgtest
1872@OTHERSTUFF@
1873"#,
1874 )
1875 .unwrap();
1876
1877 std::fs::write(
1878 td.path().join("debian/rules"),
1879 r#"#!/usr/bin/make -f
1880
1881debian/control: debian/control.in
1882 sed -e 's/@OTHERSTUFF@/Vcs-Git: example.com/' < $< > $@
1883"#,
1884 )
1885 .unwrap();
1886
1887 std::fs::set_permissions(
1888 td.path().join("debian/rules"),
1889 std::fs::Permissions::from_mode(0o755),
1890 )
1891 .unwrap();
1892
1893 let editor =
1894 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1895 editor
1896 .source()
1897 .unwrap()
1898 .as_mut_deb822()
1899 .set("Testsuite", "autopkgtest8");
1900 editor.commit().unwrap();
1901
1902 assert_eq!(
1903 r#"Source: blah
1904Testsuite: autopkgtest8
1905@OTHERSTUFF@
1906"#,
1907 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1908 );
1909
1910 assert_eq!(
1911 r#"Source: blah
1912Testsuite: autopkgtest8
1913Vcs-Git: example.com
1914"#,
1915 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1916 );
1917 }
1918
1919 #[test]
1920 fn test_update_cdbs_template() {
1921 let td = tempfile::tempdir().unwrap();
1922 std::fs::create_dir(td.path().join("debian")).unwrap();
1923
1924 std::fs::write(
1925 td.path().join("debian/control"),
1926 r#"Source: blah
1927Testsuite: autopkgtest
1928Build-Depends: some-foo, libc6
1929
1930"#,
1931 )
1932 .unwrap();
1933
1934 std::fs::write(
1935 td.path().join("debian/control.in"),
1936 r#"Source: blah
1937Testsuite: autopkgtest
1938Build-Depends: @cdbs@, libc6
1939
1940"#,
1941 )
1942 .unwrap();
1943
1944 let editor =
1945 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1946
1947 editor
1948 .source()
1949 .unwrap()
1950 .as_mut_deb822()
1951 .set("Build-Depends", "some-foo, libc6, some-bar");
1952
1953 assert_eq!(
1954 editor
1955 .source()
1956 .unwrap()
1957 .build_depends()
1958 .unwrap()
1959 .to_string(),
1960 "some-foo, libc6, some-bar".to_string()
1961 );
1962
1963 assert_eq!(Some(super::TemplateType::Cdbs), editor.template_type());
1964
1965 assert_eq!(
1966 editor.commit().unwrap(),
1967 vec![
1968 td.path().join("debian/control.in"),
1969 td.path().join("debian/control")
1970 ]
1971 );
1972
1973 assert_eq!(
1974 r#"Source: blah
1975Testsuite: autopkgtest
1976Build-Depends: @cdbs@, libc6, some-bar
1977
1978"#,
1979 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1980 );
1981
1982 assert_eq!(
1983 r#"Source: blah
1984Testsuite: autopkgtest
1985Build-Depends: some-foo, libc6, some-bar
1986
1987"#,
1988 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1989 );
1990 }
1991
1992 #[test]
1993 #[ignore = "Not implemented yet"]
1994 fn test_description_stays_last() {
1995 let td = tempfile::tempdir().unwrap();
1996 std::fs::create_dir(td.path().join("debian")).unwrap();
1997 std::fs::write(
1998 td.path().join("debian/control"),
1999 r#"Source: blah
2000Testsuite: autopkgtest
2001
2002Package: libblah
2003Section: extra
2004Description: foo
2005 bar
2006
2007"#,
2008 )
2009 .unwrap();
2010
2011 let editor =
2012 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2013 editor
2014 .binaries()
2015 .find(|b| b.name().as_deref() == Some("libblah"))
2016 .unwrap()
2017 .set_architecture(Some("all"));
2018
2019 editor.commit().unwrap();
2020
2021 assert_eq!(
2022 r#"Source: blah
2023Testsuite: autopkgtest
2024
2025Package: libblah
2026Section: extra
2027Architecture: all
2028Description: foo
2029 bar
2030"#,
2031 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2032 );
2033 }
2034
2035 #[test]
2036 fn test_no_new_heading_whitespace() {
2037 let td = tempfile::tempdir().unwrap();
2038 std::fs::create_dir(td.path().join("debian")).unwrap();
2039 std::fs::write(
2040 td.path().join("debian/control"),
2041 r#"Source: blah
2042Build-Depends:
2043 debhelper-compat (= 11),
2044 uuid-dev
2045
2046"#,
2047 )
2048 .unwrap();
2049
2050 let editor =
2051 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2052 editor
2053 .source()
2054 .unwrap()
2055 .as_mut_deb822()
2056 .set("Build-Depends", "\ndebhelper-compat (= 12),\nuuid-dev");
2057
2058 editor.commit().unwrap();
2059
2060 assert_eq!(
2061 r#"Source: blah
2062Build-Depends:
2063 debhelper-compat (= 12),
2064 uuid-dev
2065
2066"#,
2067 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2068 );
2069 }
2070 }
2071}