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 for item in paragraph.items().collect::<Vec<_>>() {
544 for (key, old_value, mut new_value) in changes.0.remove(&item).unwrap_or_default() {
545 if paragraph.get(&key) != old_value {
546 new_value = resolve_conflict(
547 (&item.0, &item.1),
548 &key,
549 old_value.as_deref(),
550 paragraph.get(&key).as_deref(),
551 new_value.as_deref(),
552 )?;
553 }
554 if let Some(new_value) = new_value.as_ref() {
555 paragraph.set(&key, new_value);
556 } else {
557 paragraph.remove(&key);
558 }
559 }
560 }
561 }
562 for (key, p) in changes.0.drain() {
564 let mut paragraph = deb822.add_paragraph();
565 for (field, old_value, mut new_value) in p {
566 if old_value.is_some() {
567 new_value = resolve_conflict(
568 (&key.0, &key.1),
569 &field,
570 old_value.as_deref(),
571 paragraph.get(&field).as_deref(),
572 new_value.as_deref(),
573 )?;
574 }
575 if let Some(new_value) = new_value {
576 paragraph.set(&field, &new_value);
577 }
578 }
579 }
580 Ok(())
581}
582
583fn find_template_path(path: &Path) -> Option<PathBuf> {
584 for ext in &["in", "m4"] {
585 let template_path = path.with_extension(ext);
586 if template_path.exists() {
587 return Some(template_path);
588 }
589 }
590 None
591}
592
593pub struct TemplatedControlEditor {
609 primary: FsEditor<debian_control::Control>,
611 template: Option<Template>,
613 path: PathBuf,
615 template_only: bool,
617}
618
619impl Deref for TemplatedControlEditor {
620 type Target = debian_control::Control;
621
622 fn deref(&self) -> &Self::Target {
623 &self.primary
624 }
625}
626
627impl DerefMut for TemplatedControlEditor {
628 fn deref_mut(&mut self) -> &mut Self::Target {
629 &mut self.primary
630 }
631}
632
633impl TemplatedControlEditor {
634 pub fn create<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
636 if control_path.as_ref().exists() {
637 return Err(EditorError::IoError(std::io::Error::new(
638 std::io::ErrorKind::AlreadyExists,
639 "Control file already exists",
640 )));
641 }
642 Self::new(control_path, true)
643 }
644
645 pub fn template_type(&self) -> Option<TemplateType> {
647 self.template.as_ref().map(|t| t.template_type)
648 }
649
650 pub fn open<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
652 Self::new(control_path, false)
653 }
654
655 pub fn new<P: AsRef<Path>>(control_path: P, allow_missing: bool) -> Result<Self, EditorError> {
657 let path = control_path.as_ref();
658 let (template, template_only) = if !path.exists() {
659 if let Some(template) = Template::find(path) {
660 match template.expand() {
661 Ok(_) => {}
662 Err(e) => {
663 return Err(EditorError::TemplateError(
664 template.template_path,
665 e.to_string(),
666 ))
667 }
668 }
669 (Some(template), true)
670 } else if !allow_missing {
671 return Err(EditorError::IoError(std::io::Error::new(
672 std::io::ErrorKind::NotFound,
673 "No control file or template found",
674 )));
675 } else {
676 (None, false)
677 }
678 } else {
679 (Template::find(path), false)
680 };
681 let primary = FsEditor::<debian_control::Control>::new(path, false, false)?;
682 Ok(Self {
683 path: path.to_path_buf(),
684 primary,
685 template_only,
686 template,
687 })
688 }
689
690 fn changes(&self) -> Deb822Changes {
695 let orig = deb822_lossless::Deb822::read_relaxed(self.primary.orig_content().unwrap())
696 .unwrap()
697 .0;
698 let mut changes = Deb822Changes::new();
699
700 fn by_key(
701 ps: impl Iterator<Item = Paragraph>,
702 ) -> std::collections::HashMap<(String, String), Paragraph> {
703 let mut ret = std::collections::HashMap::new();
704 for p in ps {
705 if let Some(s) = p.get("Source") {
706 ret.insert(("Source".to_string(), s), p);
707 } else if let Some(s) = p.get("Package") {
708 ret.insert(("Package".to_string(), s), p);
709 } else {
710 let k = p.items().next().unwrap().clone();
711 ret.insert(k, p);
712 }
713 }
714 ret
715 }
716
717 let orig_by_key = by_key(orig.paragraphs());
718 let new_by_key = by_key(self.as_deb822().paragraphs());
719 let keys = orig_by_key
720 .keys()
721 .chain(new_by_key.keys())
722 .collect::<std::collections::HashSet<_>>();
723 for key in keys {
724 let old = orig_by_key.get(key);
725 let new = new_by_key.get(key);
726 if old == new {
727 continue;
728 }
729 let fields = std::collections::HashSet::<String>::from_iter(
730 old.iter()
731 .flat_map(|p| p.keys())
732 .chain(new.iter().flat_map(|p| p.keys())),
733 );
734 for field in &fields {
735 let old_val = old.and_then(|x| x.get(field));
736 let new_val = new.and_then(|x| x.get(field));
737 if old_val != new_val {
738 changes.insert(key.clone(), field.to_string(), old_val, new_val);
739 }
740 }
741 }
742 changes
743 }
744
745 pub fn commit(&self) -> Result<Vec<PathBuf>, EditorError> {
747 let mut changed_files: Vec<PathBuf> = vec![];
748 if self.template_only {
749 match std::fs::remove_file(self.path.as_path()) {
751 Ok(_) => {}
752 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
753 Err(e) => return Err(EditorError::IoError(e)),
754 }
755
756 changed_files.push(self.path.clone());
757
758 let template = self
759 .template
760 .as_ref()
761 .expect("template_only implies template");
762
763 let changed = match template.update(self.changes(), false) {
765 Ok(changed) => changed,
766 Err(e) => {
767 return Err(EditorError::TemplateError(
768 template.template_path.clone(),
769 e.to_string(),
770 ))
771 }
772 };
773 if changed {
774 changed_files.push(template.template_path.clone());
775 }
776 } else {
777 match self.primary.commit() {
778 Ok(files) => {
779 changed_files.extend(files.iter().map(|p| p.to_path_buf()));
780 }
781 Err(EditorError::GeneratedFile(
782 p,
783 GeneratedFile {
784 template_path: tp,
785 template_type: tt,
786 },
787 )) => {
788 if tp.is_none() {
789 return Err(EditorError::GeneratedFile(
790 p,
791 GeneratedFile {
792 template_path: tp,
793 template_type: tt,
794 },
795 ));
796 }
797 let template = if let Some(template) = self.template.as_ref() {
798 template
799 } else {
800 return Err(EditorError::IoError(std::io::Error::new(
801 std::io::ErrorKind::NotFound,
802 "No control file or template found",
803 )));
804 };
805 let changes = self.changes();
806 let changed = match template.update(changes, true) {
807 Ok(changed) => changed,
808 Err(e) => {
809 return Err(EditorError::TemplateError(tp.unwrap(), e.to_string()))
810 }
811 };
812 changed_files = if changed {
813 vec![tp.as_ref().unwrap().to_path_buf(), p]
814 } else {
815 vec![]
816 };
817 }
818 Err(EditorError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => {
819 let template = if let Some(p) = self.template.as_ref() {
820 p
821 } else {
822 return Err(EditorError::IoError(std::io::Error::new(
823 std::io::ErrorKind::NotFound,
824 "No control file or template found",
825 )));
826 };
827 let changed = match template.update(self.changes(), !self.template_only) {
828 Ok(changed) => changed,
829 Err(e) => {
830 return Err(EditorError::TemplateError(
831 template.template_path.clone(),
832 e.to_string(),
833 ))
834 }
835 };
836 if changed {
837 changed_files.push(template.template_path.clone());
838 changed_files.push(self.path.clone());
839 }
840 }
841 Err(e) => return Err(e),
842 }
843 }
844
845 Ok(changed_files)
846 }
847}
848
849struct Template {
850 path: PathBuf,
851 template_path: PathBuf,
852 template_type: TemplateType,
853}
854
855impl Template {
856 fn find(path: &Path) -> Option<Self> {
857 let template_path = find_template_path(path)?;
858 let template_type = guess_template_type(&template_path, Some(path.parent().unwrap()))?;
859 Some(Self {
860 path: path.to_path_buf(),
861 template_path,
862 template_type,
863 })
864 }
865
866 fn expand(&self) -> Result<(), TemplateExpansionError> {
867 expand_control_template(&self.template_path, &self.path, self.template_type)
868 }
869
870 fn update(&self, changes: Deb822Changes, expand: bool) -> Result<bool, TemplateExpansionError> {
871 update_control_template(
872 &self.template_path,
873 self.template_type,
874 &self.path,
875 changes,
876 expand,
877 )
878 }
879}
880
881impl Editor<debian_control::Control> for TemplatedControlEditor {
882 fn orig_content(&self) -> Option<&[u8]> {
883 self.primary.orig_content()
884 }
885
886 fn updated_content(&self) -> Option<Vec<u8>> {
887 self.primary.updated_content()
888 }
889
890 fn rewritten_content(&self) -> Option<&[u8]> {
891 self.primary.rewritten_content()
892 }
893
894 fn is_generated(&self) -> bool {
895 self.primary.is_generated()
896 }
897
898 fn commit(&self) -> Result<Vec<std::path::PathBuf>, EditorError> {
899 TemplatedControlEditor::commit(self)
900 }
901}
902
903#[cfg(test)]
904mod tests {
905 use super::*;
906
907 #[test]
908 fn test_format_description() {
909 let summary = "Summary";
910 let long_description = vec!["Long", "Description"];
911 let expected = "Summary\n Long\n Description\n";
912 assert_eq!(format_description(summary, long_description), expected);
913 }
914
915 #[test]
916 fn test_resolve_cdbs_conflicts() {
917 let val = resolve_cdbs_template(
918 ("Source", "libnetsds-perl"),
919 "Build-Depends",
920 Some("debhelper (>= 6), foo"),
921 Some("@cdbs@, debhelper (>= 9)"),
922 Some("debhelper (>= 10), foo"),
923 )
924 .unwrap();
925
926 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
927
928 let val = resolve_cdbs_template(
929 ("Source", "libnetsds-perl"),
930 "Build-Depends",
931 Some("debhelper (>= 6), foo"),
932 Some("@cdbs@, foo"),
933 Some("debhelper (>= 10), foo"),
934 )
935 .unwrap();
936 assert_eq!(val, Some("@cdbs@, foo, debhelper (>= 10)".to_string()));
937 let val = resolve_cdbs_template(
938 ("Source", "libnetsds-perl"),
939 "Build-Depends",
940 Some("debhelper (>= 6), foo"),
941 Some("@cdbs@, debhelper (>= 9)"),
942 Some("debhelper (>= 10), foo"),
943 )
944 .unwrap();
945 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
946 }
947
948 mod guess_template_type {
949
950 #[test]
951 fn test_rules_generates_control() {
952 let td = tempfile::tempdir().unwrap();
953 std::fs::create_dir(td.path().join("debian")).unwrap();
954 std::fs::write(
955 td.path().join("debian/rules"),
956 r#"%:
957 dh $@
958
959debian/control: debian/control.in
960 cp $@ $<
961"#,
962 )
963 .unwrap();
964 assert_eq!(
965 super::guess_template_type(
966 &td.path().join("debian/control.in"),
967 Some(&td.path().join("debian"))
968 ),
969 Some(super::TemplateType::Rules)
970 );
971 }
972
973 #[test]
974 fn test_rules_generates_control_percent() {
975 let td = tempfile::tempdir().unwrap();
976 std::fs::create_dir(td.path().join("debian")).unwrap();
977 std::fs::write(
978 td.path().join("debian/rules"),
979 r#"%:
980 dh $@
981
982debian/%: debian/%.in
983 cp $@ $<
984"#,
985 )
986 .unwrap();
987 assert_eq!(
988 super::guess_template_type(
989 &td.path().join("debian/control.in"),
990 Some(&td.path().join("debian"))
991 ),
992 Some(super::TemplateType::Rules)
993 );
994 }
995
996 #[test]
997 fn test_rules_generates_control_blends() {
998 let td = tempfile::tempdir().unwrap();
999 std::fs::create_dir(td.path().join("debian")).unwrap();
1000 std::fs::write(
1001 td.path().join("debian/rules"),
1002 r#"%:
1003 dh $@
1004
1005include /usr/share/blends-dev/rules
1006"#,
1007 )
1008 .unwrap();
1009 assert_eq!(
1010 super::guess_template_type(
1011 &td.path().join("debian/control.stub"),
1012 Some(&td.path().join("debian"))
1013 ),
1014 Some(super::TemplateType::Rules)
1015 );
1016 }
1017
1018 #[test]
1019 fn test_empty_template() {
1020 let td = tempfile::tempdir().unwrap();
1021 std::fs::create_dir(td.path().join("debian")).unwrap();
1022 std::fs::write(td.path().join("debian/control.in"), "").unwrap();
1024
1025 assert_eq!(
1026 None,
1027 super::guess_template_type(
1028 &td.path().join("debian/control.in"),
1029 Some(&td.path().join("debian"))
1030 )
1031 );
1032 }
1033
1034 #[test]
1035 fn test_build_depends_cdbs() {
1036 let td = tempfile::tempdir().unwrap();
1037 std::fs::create_dir(td.path().join("debian")).unwrap();
1038 std::fs::write(
1039 td.path().join("debian/control.in"),
1040 r#"Source: blah
1041Build-Depends: cdbs
1042Vcs-Git: file://
1043
1044Package: bar
1045"#,
1046 )
1047 .unwrap();
1048 assert_eq!(
1049 Some(super::TemplateType::Cdbs),
1050 super::guess_template_type(
1051 &td.path().join("debian/control.in"),
1052 Some(&td.path().join("debian"))
1053 )
1054 );
1055 }
1056
1057 #[test]
1058 fn test_no_build_depends() {
1059 let td = tempfile::tempdir().unwrap();
1060 std::fs::create_dir(td.path().join("debian")).unwrap();
1061 std::fs::write(
1062 td.path().join("debian/control.in"),
1063 r#"Source: blah
1064Vcs-Git: file://
1065
1066Package: bar
1067"#,
1068 )
1069 .unwrap();
1070 assert_eq!(
1071 None,
1072 super::guess_template_type(
1073 &td.path().join("debian/control.in"),
1074 Some(&td.path().join("debian"))
1075 )
1076 );
1077 }
1078
1079 #[test]
1080 fn test_gnome() {
1081 let td = tempfile::tempdir().unwrap();
1082 std::fs::create_dir(td.path().join("debian")).unwrap();
1083 std::fs::write(
1084 td.path().join("debian/control.in"),
1085 r#"Foo @GNOME_TEAM@
1086"#,
1087 )
1088 .unwrap();
1089 assert_eq!(
1090 Some(super::TemplateType::Gnome),
1091 super::guess_template_type(
1092 &td.path().join("debian/control.in"),
1093 Some(&td.path().join("debian"))
1094 )
1095 );
1096 }
1097
1098 #[test]
1099 fn test_gnome_build_depends() {
1100 let td = tempfile::tempdir().unwrap();
1101 std::fs::create_dir(td.path().join("debian")).unwrap();
1102 std::fs::write(
1103 td.path().join("debian/control.in"),
1104 r#"Source: blah
1105Build-Depends: gnome-pkg-tools, libc6-dev
1106"#,
1107 )
1108 .unwrap();
1109 assert_eq!(
1110 Some(super::TemplateType::Gnome),
1111 super::guess_template_type(
1112 &td.path().join("debian/control.in"),
1113 Some(&td.path().join("debian"))
1114 )
1115 );
1116 }
1117
1118 #[test]
1119 fn test_cdbs() {
1120 let td = tempfile::tempdir().unwrap();
1121 std::fs::create_dir(td.path().join("debian")).unwrap();
1122 std::fs::write(
1123 td.path().join("debian/control.in"),
1124 r#"Source: blah
1125Build-Depends: debhelper, cdbs
1126"#,
1127 )
1128 .unwrap();
1129 assert_eq!(
1130 Some(super::TemplateType::Cdbs),
1131 super::guess_template_type(
1132 &td.path().join("debian/control.in"),
1133 Some(&td.path().join("debian"))
1134 )
1135 );
1136 }
1137
1138 #[test]
1139 fn test_multiple_paragraphs() {
1140 let td = tempfile::tempdir().unwrap();
1141 std::fs::create_dir(td.path().join("debian")).unwrap();
1142 std::fs::write(
1143 td.path().join("debian/control.in"),
1144 r#"Source: blah
1145Build-Depends: debhelper, cdbs
1146
1147Package: foo
1148"#,
1149 )
1150 .unwrap();
1151 assert_eq!(
1152 Some(super::TemplateType::Cdbs),
1153 super::guess_template_type(
1154 &td.path().join("debian/control.in"),
1155 Some(&td.path().join("debian"))
1156 )
1157 );
1158 }
1159
1160 #[test]
1161 fn test_directory() {
1162 let td = tempfile::tempdir().unwrap();
1163 std::fs::create_dir(td.path().join("debian")).unwrap();
1164 std::fs::create_dir(td.path().join("debian/control.in")).unwrap();
1165 assert_eq!(
1166 Some(super::TemplateType::Directory),
1167 super::guess_template_type(
1168 &td.path().join("debian/control.in"),
1169 Some(&td.path().join("debian"))
1170 )
1171 );
1172 }
1173
1174 #[test]
1175 fn test_debcargo() {
1176 let td = tempfile::tempdir().unwrap();
1177 std::fs::create_dir(td.path().join("debian")).unwrap();
1178 std::fs::write(
1179 td.path().join("debian/control.in"),
1180 r#"Source: blah
1181Build-Depends: bar
1182"#,
1183 )
1184 .unwrap();
1185 std::fs::write(
1186 td.path().join("debian/debcargo.toml"),
1187 r#"maintainer = Joe Example <joe@example.com>
1188"#,
1189 )
1190 .unwrap();
1191 assert_eq!(
1192 Some(super::TemplateType::Debcargo),
1193 super::guess_template_type(
1194 &td.path().join("debian/control.in"),
1195 Some(&td.path().join("debian"))
1196 )
1197 );
1198 }
1199 }
1200
1201 #[test]
1202 fn test_postgresql() {
1203 let td = tempfile::tempdir().unwrap();
1204 std::fs::create_dir(td.path().join("debian")).unwrap();
1205 std::fs::write(
1206 td.path().join("debian/control.in"),
1207 r#"Source: blah
1208Build-Depends: bar, postgresql
1209
1210Package: foo-PGVERSION
1211"#,
1212 )
1213 .unwrap();
1214 assert_eq!(
1215 Some(super::TemplateType::Postgresql),
1216 super::guess_template_type(
1217 &td.path().join("debian/control.in"),
1218 Some(&td.path().join("debian"))
1219 )
1220 );
1221 }
1222
1223 #[test]
1224 fn test_apply_changes() {
1225 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1226Build-Depends: debhelper (>= 6), foo
1227
1228Package: bar
1229"#
1230 .parse()
1231 .unwrap();
1232
1233 let mut changes = Deb822Changes(std::collections::HashMap::new());
1234 changes.0.insert(
1235 ("Source".to_string(), "blah".to_string()),
1236 vec![(
1237 "Build-Depends".to_string(),
1238 Some("debhelper (>= 6), foo".to_string()),
1239 Some("debhelper (>= 10), foo".to_string()),
1240 )],
1241 );
1242
1243 super::apply_changes(&mut deb822, changes, None).unwrap();
1244
1245 assert_eq!(
1246 deb822.to_string(),
1247 r#"Source: blah
1248Build-Depends: debhelper (>= 10), foo
1249
1250Package: bar
1251"#
1252 );
1253 }
1254
1255 #[test]
1256 fn test_apply_changes_new_paragraph() {
1257 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1258Build-Depends: debhelper (>= 6), foo
1259
1260Package: bar
1261"#
1262 .parse()
1263 .unwrap();
1264
1265 let mut changes = Deb822Changes(std::collections::HashMap::new());
1266 changes.0.insert(
1267 ("Source".to_string(), "blah".to_string()),
1268 vec![(
1269 "Build-Depends".to_string(),
1270 Some("debhelper (>= 6), foo".to_string()),
1271 Some("debhelper (>= 10), foo".to_string()),
1272 )],
1273 );
1274 changes.0.insert(
1275 ("Package".to_string(), "blah2".to_string()),
1276 vec![
1277 ("Package".to_string(), None, Some("blah2".to_string())),
1278 (
1279 "Description".to_string(),
1280 None,
1281 Some("Some package".to_string()),
1282 ),
1283 ],
1284 );
1285
1286 super::apply_changes(&mut deb822, changes, None).unwrap();
1287
1288 assert_eq!(
1289 deb822.to_string(),
1290 r#"Source: blah
1291Build-Depends: debhelper (>= 10), foo
1292
1293Package: bar
1294
1295Package: blah2
1296Description: Some package
1297"#
1298 );
1299 }
1300
1301 #[test]
1302 fn test_apply_changes_conflict() {
1303 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1304Build-Depends: debhelper (>= 6), foo
1305
1306Package: bar
1307"#
1308 .parse()
1309 .unwrap();
1310
1311 let mut changes = Deb822Changes(std::collections::HashMap::new());
1312 changes.0.insert(
1313 ("Source".to_string(), "blah".to_string()),
1314 vec![(
1315 "Build-Depends".to_string(),
1316 Some("debhelper (>= 7), foo".to_string()),
1317 Some("debhelper (>= 10), foo".to_string()),
1318 )],
1319 );
1320
1321 let result = super::apply_changes(&mut deb822, changes, None);
1322 assert!(result.is_err());
1323 let err = result.unwrap_err();
1324 assert_eq!(
1325 err,
1326 ChangeConflict {
1327 para_key: ("Source".to_string(), "blah".to_string()),
1328 field: "Build-Depends".to_string(),
1329 actual_old_value: Some("debhelper (>= 7), foo".to_string()),
1330 template_old_value: Some("debhelper (>= 6), foo".to_string()),
1331 actual_new_value: Some("debhelper (>= 10), foo".to_string()),
1332 }
1333 );
1334 }
1335
1336 #[test]
1337 fn test_apply_changes_resolve_conflict() {
1338 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1339Build-Depends: debhelper (>= 6), foo
1340
1341Package: bar
1342"#
1343 .parse()
1344 .unwrap();
1345
1346 let mut changes = Deb822Changes(std::collections::HashMap::new());
1347 changes.0.insert(
1348 ("Source".to_string(), "blah".to_string()),
1349 vec![(
1350 "Build-Depends".to_string(),
1351 Some("debhelper (>= 7), foo".to_string()),
1352 Some("debhelper (>= 10), foo".to_string()),
1353 )],
1354 );
1355
1356 let result = super::apply_changes(&mut deb822, changes, Some(|_, _, _, _, _| Ok(None)));
1357 assert!(result.is_ok());
1358 assert_eq!(
1359 deb822.to_string(),
1360 r#"Source: blah
1361
1362Package: bar
1363"#
1364 );
1365 }
1366
1367 mod control_editor {
1368 #[test]
1369 fn test_do_not_edit() {
1370 let td = tempfile::tempdir().unwrap();
1371 std::fs::create_dir(td.path().join("debian")).unwrap();
1372 std::fs::write(
1373 td.path().join("debian/control"),
1374 r#"# DO NOT EDIT
1375# This file was generated by blah
1376
1377Source: blah
1378Testsuite: autopkgtest
1379
1380"#,
1381 )
1382 .unwrap();
1383 let editor =
1384 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1385 editor.source().unwrap().set_name("foo");
1386 let changes = editor.changes();
1387 assert_eq!(
1388 changes.normalized(),
1389 vec![
1390 (
1391 ("Source", "blah"),
1392 vec![
1393 ("Source", Some("blah"), None),
1394 ("Testsuite", Some("autopkgtest"), None),
1395 ]
1396 ),
1397 (
1398 ("Source", "foo"),
1399 vec![
1400 ("Source", None, Some("foo")),
1401 ("Testsuite", None, Some("autopkgtest"))
1402 ]
1403 )
1404 ]
1405 );
1406 assert!(matches!(
1407 editor.commit().unwrap_err(),
1408 super::EditorError::GeneratedFile(_, _)
1409 ));
1410 }
1411
1412 #[test]
1413 fn test_add_binary() {
1414 let td = tempfile::tempdir().unwrap();
1415 std::fs::create_dir(td.path().join("debian")).unwrap();
1416 std::fs::write(
1417 td.path().join("debian/control"),
1418 r#"Source: blah
1419Testsuite: autopkgtest
1420
1421Package: blah
1422Description: Some description
1423 And there are more lines
1424 And more lines
1425"#,
1426 )
1427 .unwrap();
1428 let mut editor =
1429 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1430 let mut binary = editor.add_binary("foo");
1431 binary.set_description(Some("A new package foo"));
1432 let paths = editor.commit().unwrap();
1433 assert_eq!(paths.len(), 1);
1434 }
1435
1436 #[test]
1437 fn test_list_binaries() {
1438 let td = tempfile::tempdir().unwrap();
1439 std::fs::create_dir(td.path().join("debian")).unwrap();
1440 std::fs::write(
1441 td.path().join("debian/control"),
1442 r#"Source: blah
1443Testsuite: autopkgtest
1444
1445Package: blah
1446Description: Some description
1447 And there are more lines
1448 And more lines
1449"#,
1450 )
1451 .unwrap();
1452 let editor =
1453 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1454 let binaries = editor.binaries().collect::<Vec<_>>();
1455 assert_eq!(binaries.len(), 1);
1456 assert_eq!(binaries[0].name().as_deref(), Some("blah"));
1457 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1458 }
1459
1460 #[test]
1461 fn test_no_source() {
1462 let td = tempfile::tempdir().unwrap();
1463 std::fs::create_dir(td.path().join("debian")).unwrap();
1464 std::fs::write(
1465 td.path().join("debian/control"),
1466 r#"Package: blah
1467Testsuite: autopkgtest
1468
1469Package: bar
1470"#,
1471 )
1472 .unwrap();
1473 let editor =
1474 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1475 assert!(editor.source().is_none());
1476 }
1477
1478 #[test]
1479 fn test_create() {
1480 let td = tempfile::tempdir().unwrap();
1481 std::fs::create_dir(td.path().join("debian")).unwrap();
1482 let mut editor =
1483 super::TemplatedControlEditor::create(td.path().join("debian/control")).unwrap();
1484 editor.add_source("foo");
1485 assert_eq!(
1486 r#"Source: foo
1487"#,
1488 editor.as_deb822().to_string()
1489 );
1490 }
1491
1492 #[test]
1493 fn test_do_not_edit_no_change() {
1494 let td = tempfile::tempdir().unwrap();
1495 std::fs::create_dir(td.path().join("debian")).unwrap();
1496 std::fs::write(
1497 td.path().join("debian/control"),
1498 r#"# DO NOT EDIT
1499# This file was generated by blah
1500
1501Source: blah
1502Testsuite: autopkgtest
1503
1504"#,
1505 )
1506 .unwrap();
1507 let editor =
1508 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1509 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1510 }
1511
1512 #[test]
1513 fn test_unpreservable() {
1514 let td = tempfile::tempdir().unwrap();
1515 std::fs::create_dir(td.path().join("debian")).unwrap();
1516 std::fs::write(
1517 td.path().join("debian/control"),
1518 r#"Source: blah
1519# A comment
1520Testsuite: autopkgtest
1521
1522"#,
1523 )
1524 .unwrap();
1525 let editor =
1526 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1527 editor
1528 .source()
1529 .unwrap()
1530 .as_mut_deb822()
1531 .set("NewField", "New Field");
1532
1533 editor.commit().unwrap();
1534
1535 assert_eq!(
1536 r#"Source: blah
1537# A comment
1538Testsuite: autopkgtest
1539NewField: New Field
1540
1541"#,
1542 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1543 );
1544 }
1545
1546 #[test]
1547 fn test_modify_source() {
1548 let td = tempfile::tempdir().unwrap();
1549 std::fs::create_dir(td.path().join("debian")).unwrap();
1550 std::fs::write(
1551 td.path().join("debian/control"),
1552 r#"Source: blah
1553Testsuite: autopkgtest
1554"#,
1555 )
1556 .unwrap();
1557
1558 let editor =
1559 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1560 editor
1561 .source()
1562 .unwrap()
1563 .as_mut_deb822()
1564 .set("XS-Vcs-Git", "git://github.com/example/example");
1565
1566 editor.commit().unwrap();
1567
1568 assert_eq!(
1569 r#"Source: blah
1570Testsuite: autopkgtest
1571XS-Vcs-Git: git://github.com/example/example
1572"#,
1573 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1574 );
1575 }
1576
1577 #[test]
1658 fn test_modify_binary() {
1659 let td = tempfile::tempdir().unwrap();
1660 std::fs::create_dir(td.path().join("debian")).unwrap();
1661 std::fs::write(
1662 td.path().join("debian/control"),
1663 r#"Source: blah
1664Testsuite: autopkgtest
1665
1666Package: libblah
1667Section: extra
1668"#,
1669 )
1670 .unwrap();
1671
1672 let editor =
1673 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1674 let mut binary = editor
1675 .binaries()
1676 .find(|b| b.name().as_deref() == Some("libblah"))
1677 .unwrap();
1678 binary.set_architecture(Some("all"));
1679
1680 editor.commit().unwrap();
1681
1682 assert_eq!(
1683 r#"Source: blah
1684Testsuite: autopkgtest
1685
1686Package: libblah
1687Section: extra
1688Architecture: all
1689"#,
1690 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1691 );
1692 }
1693
1694 #[test]
1695 fn test_doesnt_strip_whitespace() {
1696 let td = tempfile::tempdir().unwrap();
1697 std::fs::create_dir(td.path().join("debian")).unwrap();
1698 std::fs::write(
1699 td.path().join("debian/control"),
1700 r#"Source: blah
1701Testsuite: autopkgtest
1702
1703"#,
1704 )
1705 .unwrap();
1706 let editor =
1707 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1708 editor.commit().unwrap();
1709
1710 assert_eq!(
1711 r#"Source: blah
1712Testsuite: autopkgtest
1713
1714"#,
1715 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1716 );
1717 }
1718
1719 #[cfg(unix)]
1720 #[test]
1721 fn test_update_template() {
1722 use std::os::unix::fs::PermissionsExt;
1723 let td = tempfile::tempdir().unwrap();
1724 std::fs::create_dir(td.path().join("debian")).unwrap();
1725 std::fs::write(
1726 td.path().join("debian/control"),
1727 r#"# DO NOT EDIT
1728# This file was generated by blah
1729
1730Source: blah
1731Testsuite: autopkgtest
1732Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1733
1734"#,
1735 )
1736 .unwrap();
1737 std::fs::write(
1738 td.path().join("debian/control.in"),
1739 r#"Source: blah
1740Testsuite: autopkgtest
1741Uploaders: @lintian-brush-test@
1742
1743"#,
1744 )
1745 .unwrap();
1746 std::fs::write(
1747 td.path().join("debian/rules"),
1748 r#"#!/usr/bin/make -f
1749
1750debian/control: debian/control.in
1751 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1752"#,
1753 )
1754 .unwrap();
1755 std::fs::set_permissions(
1757 td.path().join("debian/rules"),
1758 std::fs::Permissions::from_mode(0o755),
1759 )
1760 .unwrap();
1761
1762 let editor =
1763 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1764 editor
1765 .source()
1766 .unwrap()
1767 .as_mut_deb822()
1768 .set("Testsuite", "autopkgtest8");
1769
1770 assert_eq!(
1771 editor.commit().unwrap(),
1772 vec![
1773 td.path().join("debian/control.in"),
1774 td.path().join("debian/control")
1775 ]
1776 );
1777
1778 assert_eq!(
1779 r#"Source: blah
1780Testsuite: autopkgtest8
1781Uploaders: @lintian-brush-test@
1782
1783"#,
1784 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1785 );
1786
1787 assert_eq!(
1788 r#"Source: blah
1789Testsuite: autopkgtest8
1790Uploaders: testvalue
1791
1792"#,
1793 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1794 );
1795 }
1796
1797 #[cfg(unix)]
1798 #[test]
1799 fn test_update_template_only() {
1800 use std::os::unix::fs::PermissionsExt;
1801 let td = tempfile::tempdir().unwrap();
1802 std::fs::create_dir(td.path().join("debian")).unwrap();
1803 std::fs::write(
1804 td.path().join("debian/control.in"),
1805 r#"Source: blah
1806Testsuite: autopkgtest
1807Uploaders: @lintian-brush-test@
1808
1809"#,
1810 )
1811 .unwrap();
1812 std::fs::write(
1813 td.path().join("debian/rules"),
1814 r#"#!/usr/bin/make -f
1815
1816debian/control: debian/control.in
1817 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1818"#,
1819 )
1820 .unwrap();
1821
1822 std::fs::set_permissions(
1823 td.path().join("debian/rules"),
1824 std::fs::Permissions::from_mode(0o755),
1825 )
1826 .unwrap();
1827
1828 let editor =
1829 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1830 editor
1831 .source()
1832 .unwrap()
1833 .as_mut_deb822()
1834 .set("Testsuite", "autopkgtest8");
1835
1836 editor.commit().unwrap();
1837
1838 assert_eq!(
1839 r#"Source: blah
1840Testsuite: autopkgtest8
1841Uploaders: @lintian-brush-test@
1842
1843"#,
1844 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1845 );
1846
1847 assert!(!td.path().join("debian/control").exists());
1848 }
1849
1850 #[cfg(unix)]
1851 #[test]
1852 fn test_update_template_invalid_tokens() {
1853 use std::os::unix::fs::PermissionsExt;
1854 let td = tempfile::tempdir().unwrap();
1855 std::fs::create_dir(td.path().join("debian")).unwrap();
1856 std::fs::write(
1857 td.path().join("debian/control"),
1858 r#"# DO NOT EDIT
1859# This file was generated by blah
1860
1861Source: blah
1862Testsuite: autopkgtest
1863Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1864"#,
1865 )
1866 .unwrap();
1867 std::fs::write(
1868 td.path().join("debian/control.in"),
1869 r#"Source: blah
1870Testsuite: autopkgtest
1871@OTHERSTUFF@
1872"#,
1873 )
1874 .unwrap();
1875
1876 std::fs::write(
1877 td.path().join("debian/rules"),
1878 r#"#!/usr/bin/make -f
1879
1880debian/control: debian/control.in
1881 sed -e 's/@OTHERSTUFF@/Vcs-Git: example.com/' < $< > $@
1882"#,
1883 )
1884 .unwrap();
1885
1886 std::fs::set_permissions(
1887 td.path().join("debian/rules"),
1888 std::fs::Permissions::from_mode(0o755),
1889 )
1890 .unwrap();
1891
1892 let editor =
1893 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1894 editor
1895 .source()
1896 .unwrap()
1897 .as_mut_deb822()
1898 .set("Testsuite", "autopkgtest8");
1899 editor.commit().unwrap();
1900
1901 assert_eq!(
1902 r#"Source: blah
1903Testsuite: autopkgtest8
1904@OTHERSTUFF@
1905"#,
1906 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1907 );
1908
1909 assert_eq!(
1910 r#"Source: blah
1911Testsuite: autopkgtest8
1912Vcs-Git: example.com
1913"#,
1914 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1915 );
1916 }
1917
1918 #[test]
1919 fn test_update_cdbs_template() {
1920 let td = tempfile::tempdir().unwrap();
1921 std::fs::create_dir(td.path().join("debian")).unwrap();
1922
1923 std::fs::write(
1924 td.path().join("debian/control"),
1925 r#"Source: blah
1926Testsuite: autopkgtest
1927Build-Depends: some-foo, libc6
1928
1929"#,
1930 )
1931 .unwrap();
1932
1933 std::fs::write(
1934 td.path().join("debian/control.in"),
1935 r#"Source: blah
1936Testsuite: autopkgtest
1937Build-Depends: @cdbs@, libc6
1938
1939"#,
1940 )
1941 .unwrap();
1942
1943 let editor =
1944 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1945
1946 editor
1947 .source()
1948 .unwrap()
1949 .as_mut_deb822()
1950 .set("Build-Depends", "some-foo, libc6, some-bar");
1951
1952 assert_eq!(
1953 editor
1954 .source()
1955 .unwrap()
1956 .build_depends()
1957 .unwrap()
1958 .to_string(),
1959 "some-foo, libc6, some-bar".to_string()
1960 );
1961
1962 assert_eq!(Some(super::TemplateType::Cdbs), editor.template_type());
1963
1964 assert_eq!(
1965 editor.commit().unwrap(),
1966 vec![
1967 td.path().join("debian/control.in"),
1968 td.path().join("debian/control")
1969 ]
1970 );
1971
1972 assert_eq!(
1973 r#"Source: blah
1974Testsuite: autopkgtest
1975Build-Depends: @cdbs@, libc6, some-bar
1976
1977"#,
1978 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1979 );
1980
1981 assert_eq!(
1982 r#"Source: blah
1983Testsuite: autopkgtest
1984Build-Depends: some-foo, libc6, some-bar
1985
1986"#,
1987 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1988 );
1989 }
1990
1991 #[test]
1992 #[ignore = "Not implemented yet"]
1993 fn test_description_stays_last() {
1994 let td = tempfile::tempdir().unwrap();
1995 std::fs::create_dir(td.path().join("debian")).unwrap();
1996 std::fs::write(
1997 td.path().join("debian/control"),
1998 r#"Source: blah
1999Testsuite: autopkgtest
2000
2001Package: libblah
2002Section: extra
2003Description: foo
2004 bar
2005
2006"#,
2007 )
2008 .unwrap();
2009
2010 let editor =
2011 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2012 editor
2013 .binaries()
2014 .find(|b| b.name().as_deref() == Some("libblah"))
2015 .unwrap()
2016 .set_architecture(Some("all"));
2017
2018 editor.commit().unwrap();
2019
2020 assert_eq!(
2021 r#"Source: blah
2022Testsuite: autopkgtest
2023
2024Package: libblah
2025Section: extra
2026Architecture: all
2027Description: foo
2028 bar
2029"#,
2030 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2031 );
2032 }
2033
2034 #[test]
2035 fn test_no_new_heading_whitespace() {
2036 let td = tempfile::tempdir().unwrap();
2037 std::fs::create_dir(td.path().join("debian")).unwrap();
2038 std::fs::write(
2039 td.path().join("debian/control"),
2040 r#"Source: blah
2041Build-Depends:
2042 debhelper-compat (= 11),
2043 uuid-dev
2044
2045"#,
2046 )
2047 .unwrap();
2048
2049 let editor =
2050 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2051 editor
2052 .source()
2053 .unwrap()
2054 .as_mut_deb822()
2055 .set("Build-Depends", "\ndebhelper-compat (= 12),\nuuid-dev");
2056
2057 editor.commit().unwrap();
2058
2059 assert_eq!(
2060 r#"Source: blah
2061Build-Depends:
2062 debhelper-compat (= 12),
2063 uuid-dev
2064
2065"#,
2066 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2067 );
2068 }
2069 }
2070}