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)]
44pub enum TemplateExpansionError {
45 Failed(String),
47 ExpandCommandMissing(String),
49 UnknownTemplating(PathBuf, Option<PathBuf>),
51 Conflict(ChangeConflict),
53}
54
55impl From<ChangeConflict> for TemplateExpansionError {
56 fn from(e: ChangeConflict) -> Self {
57 TemplateExpansionError::Conflict(e)
58 }
59}
60
61impl std::fmt::Display for TemplateExpansionError {
62 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
63 match self {
64 TemplateExpansionError::Failed(s) => write!(f, "Failed: {}", s),
65 TemplateExpansionError::ExpandCommandMissing(s) => {
66 write!(f, "Command not found: {}", s)
67 }
68 TemplateExpansionError::UnknownTemplating(p1, p2) => {
69 if let Some(p2) = p2 {
70 write!(
71 f,
72 "Unknown templating: {} -> {}",
73 p1.display(),
74 p2.display()
75 )
76 } else {
77 write!(f, "Unknown templating: {}", p1.display())
78 }
79 }
80 TemplateExpansionError::Conflict(c) => write!(f, "Conflict: {}", c),
81 }
82 }
83}
84
85impl std::error::Error for TemplateExpansionError {}
86
87pub fn dh_gnome_clean(path: &std::path::Path) -> Result<(), TemplateExpansionError> {
102 for entry in std::fs::read_dir(path.join("debian")).unwrap().flatten() {
103 if entry
104 .file_name()
105 .to_string_lossy()
106 .ends_with(".debhelper.log")
107 {
108 return Err(TemplateExpansionError::Failed(
109 "pre-existing .debhelper.log files".to_string(),
110 ));
111 }
112 }
113
114 if !path.join("debian/changelog").exists() {
115 return Err(TemplateExpansionError::Failed(
116 "no changelog file".to_string(),
117 ));
118 }
119
120 let result = std::process::Command::new("dh_gnome_clean")
121 .current_dir(path)
122 .output();
123
124 match result {
125 Ok(output) => {
126 if !output.status.success() {
127 let stderr = String::from_utf8_lossy(&output.stderr);
128 return Err(TemplateExpansionError::Failed(stderr.to_string()));
129 }
130 }
131 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
132 return Err(TemplateExpansionError::ExpandCommandMissing(
133 "dh_gnome_clean".to_string(),
134 ));
135 }
136 Err(e) => {
137 return Err(TemplateExpansionError::Failed(e.to_string()));
138 }
139 }
140
141 for entry in std::fs::read_dir(path.join("debian")).unwrap().flatten() {
142 if entry
143 .file_name()
144 .to_string_lossy()
145 .ends_with(".debhelper.log")
146 {
147 std::fs::remove_file(entry.path()).unwrap();
148 }
149 }
150
151 Ok(())
152}
153
154pub fn pg_buildext_updatecontrol(path: &std::path::Path) -> Result<(), TemplateExpansionError> {
164 let result = std::process::Command::new("pg_buildext")
165 .arg("updatecontrol")
166 .current_dir(path)
167 .output();
168
169 match result {
170 Ok(output) => {
171 if !output.status.success() {
172 let stderr = String::from_utf8_lossy(&output.stderr);
173 return Err(TemplateExpansionError::Failed(stderr.to_string()));
174 }
175 }
176 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
177 return Err(TemplateExpansionError::ExpandCommandMissing(
178 "pg_buildext".to_string(),
179 ));
180 }
181 Err(e) => {
182 return Err(TemplateExpansionError::Failed(e.to_string()));
183 }
184 }
185 Ok(())
186}
187
188fn expand_control_template(
198 template_path: &std::path::Path,
199 path: &std::path::Path,
200 template_type: TemplateType,
201) -> Result<(), TemplateExpansionError> {
202 let package_root = path.parent().unwrap().parent().unwrap();
203 match template_type {
204 TemplateType::Rules => {
205 let path_time = match std::fs::metadata(path) {
206 Ok(metadata) => Some(metadata.modified().unwrap()),
207 Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
208 Err(e) => panic!("Failed to get mtime of {}: {}", path.display(), e),
209 };
210 while let Ok(metadata) = std::fs::metadata(template_path) {
211 if Some(metadata.modified().unwrap()) == path_time {
212 filetime::set_file_mtime(template_path, filetime::FileTime::now()).unwrap();
214 } else {
215 break;
216 }
217 }
218 let result = std::process::Command::new("./debian/rules")
219 .arg("debian/control")
220 .current_dir(package_root)
221 .output();
222
223 match result {
224 Ok(output) => {
225 if !output.status.success() {
226 let stderr = String::from_utf8_lossy(&output.stderr);
227 Err(TemplateExpansionError::Failed(format!(
228 "Exit code {} running ./debian/rules debian/control: {}",
229 output.status, stderr
230 )))
231 } else {
232 Ok(())
233 }
234 }
235 Err(e) => Err(TemplateExpansionError::Failed(format!(
236 "Failed to run ./debian/rules debian/control: {}",
237 e
238 ))),
239 }
240 }
241 TemplateType::Gnome => dh_gnome_clean(package_root),
242 TemplateType::Postgresql => pg_buildext_updatecontrol(package_root),
243 TemplateType::Cdbs => unreachable!(),
244 TemplateType::Debcargo => unreachable!(),
245 TemplateType::Directory => Err(TemplateExpansionError::UnknownTemplating(
246 path.to_path_buf(),
247 Some(template_path.to_path_buf()),
248 )),
249 }
250}
251
252#[derive(Debug, Clone)]
253struct Deb822Changes(
254 std::collections::HashMap<(String, String), Vec<(String, Option<String>, Option<String>)>>,
255);
256
257impl Deb822Changes {
258 fn new() -> Self {
259 Self(std::collections::HashMap::new())
260 }
261
262 fn insert(
263 &mut self,
264 para_key: (String, String),
265 field: String,
266 old_value: Option<String>,
267 new_value: Option<String>,
268 ) {
269 self.0
270 .entry(para_key)
271 .or_default()
272 .push((field, old_value, new_value));
273 }
274
275 #[allow(dead_code)]
276 fn normalized(&self) -> Vec<((&str, &str), Vec<(&str, Option<&str>, Option<&str>)>)> {
277 let mut ret: Vec<_> = self
278 .0
279 .iter()
280 .map(|(k, v)| {
281 ((k.0.as_str(), k.1.as_str()), {
282 let mut v: Vec<_> = v
283 .iter()
284 .map(|(f, o, n)| (f.as_str(), o.as_deref(), n.as_deref()))
285 .collect();
286 v.sort();
287 v
288 })
289 })
290 .collect();
291 ret.sort_by_key(|(k, _)| *k);
292 ret
293 }
294}
295
296fn update_control_template(
307 template_path: &std::path::Path,
308 template_type: TemplateType,
309 path: &std::path::Path,
310 changes: Deb822Changes,
311 expand_template: bool,
312) -> Result<bool, TemplateExpansionError> {
313 if template_type == TemplateType::Directory {
314 return Err(TemplateExpansionError::UnknownTemplating(
316 path.to_path_buf(),
317 Some(template_path.to_path_buf()),
318 ));
319 }
320
321 let mut template_editor =
322 FsEditor::<deb822_lossless::Deb822>::new(template_path, true, false).unwrap();
323
324 let resolve_conflict = match template_type {
325 TemplateType::Cdbs => Some(resolve_cdbs_template as ResolveDeb822Conflict),
326 _ => None,
327 };
328
329 apply_changes(&mut template_editor, changes.clone(), resolve_conflict)?;
330
331 if !template_editor.has_changed() {
332 return Ok(false);
334 }
335
336 match template_editor.commit() {
337 Ok(_) => {}
338 Err(e) => return Err(TemplateExpansionError::Failed(e.to_string())),
339 }
340
341 if expand_template {
342 match template_type {
343 TemplateType::Cdbs => {
344 let mut editor =
345 FsEditor::<deb822_lossless::Deb822>::new(path, true, false).unwrap();
346 apply_changes(&mut editor, changes, None)?;
347 match editor.commit() {
348 Ok(_) => {}
349 Err(e) => return Err(TemplateExpansionError::Failed(e.to_string())),
350 }
351 }
352 _ => {
353 expand_control_template(template_path, path, template_type)?;
354 }
355 }
356 }
357
358 Ok(true)
359}
360
361#[derive(Debug, PartialEq, Eq)]
362pub struct ChangeConflict {
364 pub para_key: (String, String),
366 pub field: String,
368 pub actual_old_value: Option<String>,
370 pub template_old_value: Option<String>,
372 pub actual_new_value: Option<String>,
374}
375
376impl std::fmt::Display for ChangeConflict {
377 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
378 write!(
379 f,
380 "{}/{}: {} -> {} (template: {})",
381 self.para_key.0,
382 self.para_key.1,
383 self.actual_old_value.as_deref().unwrap_or(""),
384 self.actual_new_value.as_deref().unwrap_or(""),
385 self.template_old_value.as_deref().unwrap_or("")
386 )
387 }
388}
389
390impl std::error::Error for ChangeConflict {}
391
392type ResolveDeb822Conflict = fn(
393 para_key: (&str, &str),
394 field: &str,
395 actual_old_value: Option<&str>,
396 template_old_value: Option<&str>,
397 actual_new_value: Option<&str>,
398) -> Result<Option<String>, ChangeConflict>;
399
400fn resolve_cdbs_template(
401 para_key: (&str, &str),
402 field: &str,
403 actual_old_value: Option<&str>,
404 template_old_value: Option<&str>,
405 actual_new_value: Option<&str>,
406) -> Result<Option<String>, ChangeConflict> {
407 if para_key.0 == "Source"
408 && field == "Build-Depends"
409 && template_old_value.is_some()
410 && actual_old_value.is_some()
411 && actual_new_value.is_some()
412 {
413 if actual_new_value
414 .unwrap()
415 .contains(actual_old_value.unwrap())
416 {
417 return Ok(Some(
419 actual_new_value
420 .unwrap()
421 .replace(actual_old_value.unwrap(), template_old_value.unwrap()),
422 ));
423 } else {
424 let old_rels: Relations = actual_old_value.unwrap().parse().unwrap();
425 let new_rels: Relations = actual_new_value.unwrap().parse().unwrap();
426 let template_old_value = template_old_value.unwrap();
427 let (mut ret, errors) = Relations::parse_relaxed(template_old_value, true);
428 if !errors.is_empty() {
429 log::debug!("Errors parsing template value: {:?}", errors);
430 }
431 for v in new_rels.entries() {
432 if old_rels.entries().any(|r| is_relation_implied(&v, &r)) {
433 continue;
434 }
435 ensure_relation(&mut ret, v);
436 }
437 return Ok(Some(ret.to_string()));
438 }
439 }
440 Err(ChangeConflict {
441 para_key: (para_key.0.to_string(), para_key.1.to_string()),
442 field: field.to_string(),
443 actual_old_value: actual_old_value.map(|v| v.to_string()),
444 template_old_value: template_old_value.map(|v| v.to_string()),
445 actual_new_value: actual_new_value.map(|s| s.to_string()),
446 })
447}
448
449pub fn guess_template_type(
458 template_path: &std::path::Path,
459 debian_path: Option<&std::path::Path>,
460) -> Option<TemplateType> {
461 if let Some(debian_path) = debian_path {
463 match std::fs::read(debian_path.join("rules")) {
464 Ok(file) => {
465 for line in file.split(|&c| c == b'\n') {
466 if line.starts_with(b"debian/control:") {
467 return Some(TemplateType::Rules);
468 }
469 if line.starts_with(b"debian/%: debian/%.in") {
470 return Some(TemplateType::Rules);
471 }
472 if line.starts_with(b"include /usr/share/blends-dev/rules") {
473 return Some(TemplateType::Rules);
474 }
475 }
476 }
477 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
478 Err(e) => panic!(
479 "Failed to read {}: {}",
480 debian_path.join("rules").display(),
481 e
482 ),
483 }
484 }
485 match std::fs::read(template_path) {
486 Ok(template) => {
487 let template_str = std::str::from_utf8(&template).unwrap();
488 if template_str.contains("@GNOME_TEAM@") {
489 return Some(TemplateType::Gnome);
490 }
491 if template_str.contains("PGVERSION") {
492 return Some(TemplateType::Postgresql);
493 }
494 if template_str.contains("@cdbs@") {
495 return Some(TemplateType::Cdbs);
496 }
497
498 let control = debian_control::Control::read_relaxed(std::io::Cursor::new(&template))
499 .unwrap()
500 .0;
501
502 let build_depends = control.source().and_then(|s| s.build_depends());
503
504 if build_depends.iter().any(|d| {
505 d.entries()
506 .any(|e| e.relations().any(|r| r.name() == "gnome-pkg-tools"))
507 }) {
508 return Some(TemplateType::Gnome);
509 }
510
511 if build_depends.iter().any(|d| {
512 d.entries()
513 .any(|e| e.relations().any(|r| r.name() == "cdbs"))
514 }) {
515 return Some(TemplateType::Cdbs);
516 }
517 }
518 Err(_) if template_path.is_dir() => {
519 return Some(TemplateType::Directory);
520 }
521 Err(e) => panic!("Failed to read {}: {}", template_path.display(), e),
522 }
523 if let Some(debian_path) = debian_path {
524 if debian_path.join("debcargo.toml").exists() {
525 return Some(TemplateType::Debcargo);
526 }
527 }
528 None
529}
530
531fn apply_changes(
537 deb822: &mut deb822_lossless::Deb822,
538 mut changes: Deb822Changes,
539 resolve_conflict: Option<ResolveDeb822Conflict>,
540) -> Result<(), ChangeConflict> {
541 fn default_resolve_conflict(
542 para_key: (&str, &str),
543 field: &str,
544 actual_old_value: Option<&str>,
545 template_old_value: Option<&str>,
546 actual_new_value: Option<&str>,
547 ) -> Result<Option<String>, ChangeConflict> {
548 Err(ChangeConflict {
549 para_key: (para_key.0.to_string(), para_key.1.to_string()),
550 field: field.to_string(),
551 actual_old_value: actual_old_value.map(|v| v.to_string()),
552 template_old_value: template_old_value.map(|v| v.to_string()),
553 actual_new_value: actual_new_value.map(|s| s.to_string()),
554 })
555 }
556
557 let resolve_conflict = resolve_conflict.unwrap_or(default_resolve_conflict);
558
559 for mut paragraph in deb822.paragraphs() {
560 let items: Vec<_> = paragraph.items().collect();
561 for item in items {
562 for (key, old_value, mut new_value) in changes.0.remove(&item).unwrap_or_default() {
563 if paragraph.get(&key) != old_value {
564 new_value = resolve_conflict(
565 (&item.0, &item.1),
566 &key,
567 old_value.as_deref(),
568 paragraph.get(&key).as_deref(),
569 new_value.as_deref(),
570 )?;
571 }
572 if let Some(new_value) = new_value.as_ref() {
573 paragraph.set(&key, new_value);
574 } else {
575 paragraph.remove(&key);
576 }
577 }
578 }
579 }
580 for (key, p) in changes.0.drain() {
582 let mut paragraph = deb822.add_paragraph();
583 for (field, old_value, mut new_value) in p {
584 if old_value.is_some() {
585 new_value = resolve_conflict(
586 (&key.0, &key.1),
587 &field,
588 old_value.as_deref(),
589 paragraph.get(&field).as_deref(),
590 new_value.as_deref(),
591 )?;
592 }
593 if let Some(new_value) = new_value {
594 paragraph.set(&field, &new_value);
595 }
596 }
597 }
598 Ok(())
599}
600
601fn find_template_path(path: &Path) -> Option<PathBuf> {
602 for ext in &["in", "m4"] {
603 let template_path = path.with_extension(ext);
604 if template_path.exists() {
605 return Some(template_path);
606 }
607 }
608 None
609}
610
611pub struct TemplatedControlEditor {
627 primary: FsEditor<debian_control::Control>,
629 template: Option<Template>,
631 path: PathBuf,
633 template_only: bool,
635}
636
637impl Deref for TemplatedControlEditor {
638 type Target = debian_control::Control;
639
640 fn deref(&self) -> &Self::Target {
641 &self.primary
642 }
643}
644
645impl DerefMut for TemplatedControlEditor {
646 fn deref_mut(&mut self) -> &mut Self::Target {
647 &mut self.primary
648 }
649}
650
651impl TemplatedControlEditor {
652 pub fn create<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
654 if control_path.as_ref().exists() {
655 return Err(EditorError::IoError(std::io::Error::new(
656 std::io::ErrorKind::AlreadyExists,
657 "Control file already exists",
658 )));
659 }
660 Self::new(control_path, true)
661 }
662
663 pub fn template_type(&self) -> Option<TemplateType> {
665 self.template.as_ref().map(|t| t.template_type)
666 }
667
668 pub fn normalize_field_spacing(&mut self) -> Result<(), EditorError> {
680 let Some(template) = &self.template else {
681 self.primary.as_mut_deb822().normalize_field_spacing();
683 return Ok(());
684 };
685
686 let is_deb822_template = matches!(
688 template.template_type,
689 TemplateType::Cdbs | TemplateType::Directory
690 );
691
692 if !is_deb822_template {
693 return Err(EditorError::GeneratedFile(
695 self.path.clone(),
696 GeneratedFile {
697 template_path: Some(template.template_path.clone()),
698 template_type: None,
699 },
700 ));
701 }
702
703 let mut template_editor =
705 FsEditor::<deb822_lossless::Deb822>::new(&template.template_path, true, false)?;
706 template_editor.normalize_field_spacing();
707 template_editor.commit()?;
708
709 self.primary.as_mut_deb822().normalize_field_spacing();
711 Ok(())
712 }
713
714 pub fn open<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
716 Self::new(control_path, false)
717 }
718
719 pub fn new<P: AsRef<Path>>(control_path: P, allow_missing: bool) -> Result<Self, EditorError> {
721 let path = control_path.as_ref();
722 let (template, template_only) = if !path.exists() {
723 if let Some(template) = Template::find(path) {
724 match template.expand() {
725 Ok(_) => {}
726 Err(e) => {
727 return Err(EditorError::TemplateError(
728 template.template_path,
729 e.to_string(),
730 ))
731 }
732 }
733 (Some(template), true)
734 } else if !allow_missing {
735 return Err(EditorError::IoError(std::io::Error::new(
736 std::io::ErrorKind::NotFound,
737 "No control file or template found",
738 )));
739 } else {
740 (None, false)
741 }
742 } else {
743 (Template::find(path), false)
744 };
745 let primary = FsEditor::<debian_control::Control>::new(path, false, false)?;
746 Ok(Self {
747 path: path.to_path_buf(),
748 primary,
749 template_only,
750 template,
751 })
752 }
753
754 fn changes(&self) -> Deb822Changes {
759 let orig = deb822_lossless::Deb822::read_relaxed(self.primary.orig_content().unwrap())
760 .unwrap()
761 .0;
762 let mut changes = Deb822Changes::new();
763
764 fn by_key(
765 ps: impl Iterator<Item = Paragraph>,
766 ) -> std::collections::HashMap<(String, String), Paragraph> {
767 let mut ret = std::collections::HashMap::new();
768 for p in ps {
769 if let Some(s) = p.get("Source") {
770 ret.insert(("Source".to_string(), s), p);
771 } else if let Some(s) = p.get("Package") {
772 ret.insert(("Package".to_string(), s), p);
773 } else {
774 let k = p.items().next().unwrap();
775 ret.insert(k, p);
776 }
777 }
778 ret
779 }
780
781 let orig_by_key = by_key(orig.paragraphs());
782 let new_by_key = by_key(self.as_deb822().paragraphs());
783 let keys = orig_by_key
784 .keys()
785 .chain(new_by_key.keys())
786 .collect::<std::collections::HashSet<_>>();
787 for key in keys {
788 let old = orig_by_key.get(key);
789 let new = new_by_key.get(key);
790 if old == new {
791 continue;
792 }
793 let fields = std::collections::HashSet::<String>::from_iter(
794 old.iter()
795 .flat_map(|p| p.keys())
796 .chain(new.iter().flat_map(|p| p.keys())),
797 );
798 for field in &fields {
799 let old_val = old.and_then(|x| x.get(field));
800 let new_val = new.and_then(|x| x.get(field));
801 if old_val != new_val {
802 changes.insert(key.clone(), field.to_string(), old_val, new_val);
803 }
804 }
805 }
806 changes
807 }
808
809 pub fn commit(&self) -> Result<Vec<PathBuf>, EditorError> {
811 let mut changed_files: Vec<PathBuf> = vec![];
812 if self.template_only {
813 match std::fs::remove_file(self.path.as_path()) {
815 Ok(_) => {}
816 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
817 Err(e) => return Err(EditorError::IoError(e)),
818 }
819
820 changed_files.push(self.path.clone());
821
822 let template = self
823 .template
824 .as_ref()
825 .expect("template_only implies template");
826
827 let changed = match template.update(self.changes(), false) {
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 }
840 } else {
841 match self.primary.commit() {
842 Ok(files) => {
843 changed_files.extend(files.iter().map(|p| p.to_path_buf()));
844 }
845 Err(EditorError::GeneratedFile(
846 p,
847 GeneratedFile {
848 template_path: tp,
849 template_type: tt,
850 },
851 )) => {
852 if tp.is_none() {
853 return Err(EditorError::GeneratedFile(
854 p,
855 GeneratedFile {
856 template_path: tp,
857 template_type: tt,
858 },
859 ));
860 }
861 let template = if let Some(template) = self.template.as_ref() {
862 template
863 } else {
864 return Err(EditorError::IoError(std::io::Error::new(
865 std::io::ErrorKind::NotFound,
866 "No control file or template found",
867 )));
868 };
869 let changes = self.changes();
870 let changed = match template.update(changes, true) {
871 Ok(changed) => changed,
872 Err(e) => {
873 return Err(EditorError::TemplateError(tp.unwrap(), e.to_string()))
874 }
875 };
876 changed_files = if changed {
877 vec![tp.as_ref().unwrap().to_path_buf(), p]
878 } else {
879 vec![]
880 };
881 }
882 Err(EditorError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => {
883 let template = if let Some(p) = self.template.as_ref() {
884 p
885 } else {
886 return Err(EditorError::IoError(std::io::Error::new(
887 std::io::ErrorKind::NotFound,
888 "No control file or template found",
889 )));
890 };
891 let changed = match template.update(self.changes(), !self.template_only) {
892 Ok(changed) => changed,
893 Err(e) => {
894 return Err(EditorError::TemplateError(
895 template.template_path.clone(),
896 e.to_string(),
897 ))
898 }
899 };
900 if changed {
901 changed_files.push(template.template_path.clone());
902 changed_files.push(self.path.clone());
903 }
904 }
905 Err(e) => return Err(e),
906 }
907 }
908
909 Ok(changed_files)
910 }
911}
912
913struct Template {
914 path: PathBuf,
915 template_path: PathBuf,
916 template_type: TemplateType,
917}
918
919impl Template {
920 fn find(path: &Path) -> Option<Self> {
921 let template_path = find_template_path(path)?;
922 let template_type = guess_template_type(&template_path, Some(path.parent().unwrap()))?;
923 Some(Self {
924 path: path.to_path_buf(),
925 template_path,
926 template_type,
927 })
928 }
929
930 fn expand(&self) -> Result<(), TemplateExpansionError> {
931 expand_control_template(&self.template_path, &self.path, self.template_type)
932 }
933
934 fn update(&self, changes: Deb822Changes, expand: bool) -> Result<bool, TemplateExpansionError> {
935 update_control_template(
936 &self.template_path,
937 self.template_type,
938 &self.path,
939 changes,
940 expand,
941 )
942 }
943}
944
945impl Editor<debian_control::Control> for TemplatedControlEditor {
946 fn orig_content(&self) -> Option<&[u8]> {
947 self.primary.orig_content()
948 }
949
950 fn updated_content(&self) -> Option<Vec<u8>> {
951 self.primary.updated_content()
952 }
953
954 fn rewritten_content(&self) -> Option<&[u8]> {
955 self.primary.rewritten_content()
956 }
957
958 fn is_generated(&self) -> bool {
959 self.primary.is_generated()
960 }
961
962 fn commit(&self) -> Result<Vec<std::path::PathBuf>, EditorError> {
963 TemplatedControlEditor::commit(self)
964 }
965}
966
967#[cfg(test)]
968mod tests {
969 use super::*;
970
971 #[test]
972 fn test_format_description() {
973 let summary = "Summary";
974 let long_description = vec!["Long", "Description"];
975 let expected = "Summary\n Long\n Description\n";
976 assert_eq!(format_description(summary, long_description), expected);
977 }
978
979 #[test]
980 fn test_resolve_cdbs_conflicts() {
981 let val = resolve_cdbs_template(
982 ("Source", "libnetsds-perl"),
983 "Build-Depends",
984 Some("debhelper (>= 6), foo"),
985 Some("@cdbs@, debhelper (>= 9)"),
986 Some("debhelper (>= 10), foo"),
987 )
988 .unwrap();
989
990 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
991
992 let val = resolve_cdbs_template(
993 ("Source", "libnetsds-perl"),
994 "Build-Depends",
995 Some("debhelper (>= 6), foo"),
996 Some("@cdbs@, foo"),
997 Some("debhelper (>= 10), foo"),
998 )
999 .unwrap();
1000 assert_eq!(val, Some("@cdbs@, debhelper (>= 10), foo".to_string()));
1001 let val = resolve_cdbs_template(
1002 ("Source", "libnetsds-perl"),
1003 "Build-Depends",
1004 Some("debhelper (>= 6), foo"),
1005 Some("@cdbs@, debhelper (>= 9)"),
1006 Some("debhelper (>= 10), foo"),
1007 )
1008 .unwrap();
1009 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
1010 }
1011
1012 #[test]
1013 fn test_normalize_field_spacing_without_template() {
1014 let td = tempfile::tempdir().unwrap();
1016 let control_path = td.path().join("debian").join("control");
1017 std::fs::create_dir_all(control_path.parent().unwrap()).unwrap();
1018 std::fs::write(
1019 &control_path,
1020 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1021 )
1022 .unwrap();
1023
1024 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1025
1026 let original = editor.as_deb822().to_string();
1028 assert_eq!(
1029 original,
1030 "Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1031 );
1032
1033 editor.normalize_field_spacing().unwrap();
1035
1036 let normalized = editor.as_deb822().to_string();
1038 assert_eq!(
1039 normalized,
1040 "Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1041 );
1042 }
1043
1044 #[test]
1045 fn test_normalize_field_spacing_with_non_deb822_template() {
1046 let td = tempfile::tempdir().unwrap();
1048 let debian_path = td.path().join("debian");
1049 std::fs::create_dir_all(&debian_path).unwrap();
1050
1051 let control_in_path = debian_path.join("control.in");
1052 let control_path = debian_path.join("control");
1053 let rules_path = debian_path.join("rules");
1054
1055 std::fs::write(
1057 &control_in_path,
1058 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1059 )
1060 .unwrap();
1061
1062 std::fs::write(
1064 &rules_path,
1065 b"#!/usr/bin/make -f\n\ndebian/control: debian/control.in\n\tcp $< $@\n",
1066 )
1067 .unwrap();
1068
1069 std::fs::write(
1071 &control_path,
1072 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1073 )
1074 .unwrap();
1075
1076 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1077 assert_eq!(editor.template_type(), Some(TemplateType::Rules));
1078
1079 let result = editor.normalize_field_spacing();
1081 assert!(result.is_err());
1082
1083 match result {
1085 Err(EditorError::GeneratedFile(_, _)) => {
1086 }
1088 _ => panic!("Expected GeneratedFile error"),
1089 }
1090 }
1091
1092 #[test]
1093 fn test_normalize_field_spacing_with_cdbs_template() {
1094 let td = tempfile::tempdir().unwrap();
1098 let debian_path = td.path().join("debian");
1099 std::fs::create_dir_all(&debian_path).unwrap();
1100
1101 let control_in_path = debian_path.join("control.in");
1102 let control_path = debian_path.join("control");
1103
1104 std::fs::write(
1106 &control_in_path,
1107 b"Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n",
1108 )
1109 .unwrap();
1110
1111 std::fs::write(
1113 &control_path,
1114 b"Source: test\nBuild-Depends: debhelper\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1115 )
1116 .unwrap();
1117
1118 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1119 assert_eq!(editor.template_type(), Some(TemplateType::Cdbs));
1120
1121 let original_template = std::fs::read_to_string(&control_in_path).unwrap();
1123 assert_eq!(
1124 original_template,
1125 "Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n"
1126 );
1127
1128 editor.normalize_field_spacing().unwrap();
1130
1131 let template_content = std::fs::read_to_string(&control_in_path).unwrap();
1133 assert_eq!(
1134 template_content,
1135 "Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n"
1136 );
1137
1138 let control_content = editor.as_deb822().to_string();
1140 assert_eq!(
1141 control_content,
1142 "Source: test\nBuild-Depends: debhelper\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1143 );
1144 }
1145
1146 mod guess_template_type {
1147
1148 #[test]
1149 fn test_rules_generates_control() {
1150 let td = tempfile::tempdir().unwrap();
1151 std::fs::create_dir(td.path().join("debian")).unwrap();
1152 std::fs::write(
1153 td.path().join("debian/rules"),
1154 r#"%:
1155 dh $@
1156
1157debian/control: debian/control.in
1158 cp $@ $<
1159"#,
1160 )
1161 .unwrap();
1162 assert_eq!(
1163 super::guess_template_type(
1164 &td.path().join("debian/control.in"),
1165 Some(&td.path().join("debian"))
1166 ),
1167 Some(super::TemplateType::Rules)
1168 );
1169 }
1170
1171 #[test]
1172 fn test_rules_generates_control_percent() {
1173 let td = tempfile::tempdir().unwrap();
1174 std::fs::create_dir(td.path().join("debian")).unwrap();
1175 std::fs::write(
1176 td.path().join("debian/rules"),
1177 r#"%:
1178 dh $@
1179
1180debian/%: debian/%.in
1181 cp $@ $<
1182"#,
1183 )
1184 .unwrap();
1185 assert_eq!(
1186 super::guess_template_type(
1187 &td.path().join("debian/control.in"),
1188 Some(&td.path().join("debian"))
1189 ),
1190 Some(super::TemplateType::Rules)
1191 );
1192 }
1193
1194 #[test]
1195 fn test_rules_generates_control_blends() {
1196 let td = tempfile::tempdir().unwrap();
1197 std::fs::create_dir(td.path().join("debian")).unwrap();
1198 std::fs::write(
1199 td.path().join("debian/rules"),
1200 r#"%:
1201 dh $@
1202
1203include /usr/share/blends-dev/rules
1204"#,
1205 )
1206 .unwrap();
1207 assert_eq!(
1208 super::guess_template_type(
1209 &td.path().join("debian/control.stub"),
1210 Some(&td.path().join("debian"))
1211 ),
1212 Some(super::TemplateType::Rules)
1213 );
1214 }
1215
1216 #[test]
1217 fn test_empty_template() {
1218 let td = tempfile::tempdir().unwrap();
1219 std::fs::create_dir(td.path().join("debian")).unwrap();
1220 std::fs::write(td.path().join("debian/control.in"), "").unwrap();
1222
1223 assert_eq!(
1224 None,
1225 super::guess_template_type(
1226 &td.path().join("debian/control.in"),
1227 Some(&td.path().join("debian"))
1228 )
1229 );
1230 }
1231
1232 #[test]
1233 fn test_build_depends_cdbs() {
1234 let td = tempfile::tempdir().unwrap();
1235 std::fs::create_dir(td.path().join("debian")).unwrap();
1236 std::fs::write(
1237 td.path().join("debian/control.in"),
1238 r#"Source: blah
1239Build-Depends: cdbs
1240Vcs-Git: file://
1241
1242Package: bar
1243"#,
1244 )
1245 .unwrap();
1246 assert_eq!(
1247 Some(super::TemplateType::Cdbs),
1248 super::guess_template_type(
1249 &td.path().join("debian/control.in"),
1250 Some(&td.path().join("debian"))
1251 )
1252 );
1253 }
1254
1255 #[test]
1256 fn test_no_build_depends() {
1257 let td = tempfile::tempdir().unwrap();
1258 std::fs::create_dir(td.path().join("debian")).unwrap();
1259 std::fs::write(
1260 td.path().join("debian/control.in"),
1261 r#"Source: blah
1262Vcs-Git: file://
1263
1264Package: bar
1265"#,
1266 )
1267 .unwrap();
1268 assert_eq!(
1269 None,
1270 super::guess_template_type(
1271 &td.path().join("debian/control.in"),
1272 Some(&td.path().join("debian"))
1273 )
1274 );
1275 }
1276
1277 #[test]
1278 fn test_gnome() {
1279 let td = tempfile::tempdir().unwrap();
1280 std::fs::create_dir(td.path().join("debian")).unwrap();
1281 std::fs::write(
1282 td.path().join("debian/control.in"),
1283 r#"Foo @GNOME_TEAM@
1284"#,
1285 )
1286 .unwrap();
1287 assert_eq!(
1288 Some(super::TemplateType::Gnome),
1289 super::guess_template_type(
1290 &td.path().join("debian/control.in"),
1291 Some(&td.path().join("debian"))
1292 )
1293 );
1294 }
1295
1296 #[test]
1297 fn test_gnome_build_depends() {
1298 let td = tempfile::tempdir().unwrap();
1299 std::fs::create_dir(td.path().join("debian")).unwrap();
1300 std::fs::write(
1301 td.path().join("debian/control.in"),
1302 r#"Source: blah
1303Build-Depends: gnome-pkg-tools, libc6-dev
1304"#,
1305 )
1306 .unwrap();
1307 assert_eq!(
1308 Some(super::TemplateType::Gnome),
1309 super::guess_template_type(
1310 &td.path().join("debian/control.in"),
1311 Some(&td.path().join("debian"))
1312 )
1313 );
1314 }
1315
1316 #[test]
1317 fn test_cdbs() {
1318 let td = tempfile::tempdir().unwrap();
1319 std::fs::create_dir(td.path().join("debian")).unwrap();
1320 std::fs::write(
1321 td.path().join("debian/control.in"),
1322 r#"Source: blah
1323Build-Depends: debhelper, cdbs
1324"#,
1325 )
1326 .unwrap();
1327 assert_eq!(
1328 Some(super::TemplateType::Cdbs),
1329 super::guess_template_type(
1330 &td.path().join("debian/control.in"),
1331 Some(&td.path().join("debian"))
1332 )
1333 );
1334 }
1335
1336 #[test]
1337 fn test_multiple_paragraphs() {
1338 let td = tempfile::tempdir().unwrap();
1339 std::fs::create_dir(td.path().join("debian")).unwrap();
1340 std::fs::write(
1341 td.path().join("debian/control.in"),
1342 r#"Source: blah
1343Build-Depends: debhelper, cdbs
1344
1345Package: foo
1346"#,
1347 )
1348 .unwrap();
1349 assert_eq!(
1350 Some(super::TemplateType::Cdbs),
1351 super::guess_template_type(
1352 &td.path().join("debian/control.in"),
1353 Some(&td.path().join("debian"))
1354 )
1355 );
1356 }
1357
1358 #[test]
1359 fn test_directory() {
1360 let td = tempfile::tempdir().unwrap();
1361 std::fs::create_dir(td.path().join("debian")).unwrap();
1362 std::fs::create_dir(td.path().join("debian/control.in")).unwrap();
1363 assert_eq!(
1364 Some(super::TemplateType::Directory),
1365 super::guess_template_type(
1366 &td.path().join("debian/control.in"),
1367 Some(&td.path().join("debian"))
1368 )
1369 );
1370 }
1371
1372 #[test]
1373 fn test_debcargo() {
1374 let td = tempfile::tempdir().unwrap();
1375 std::fs::create_dir(td.path().join("debian")).unwrap();
1376 std::fs::write(
1377 td.path().join("debian/control.in"),
1378 r#"Source: blah
1379Build-Depends: bar
1380"#,
1381 )
1382 .unwrap();
1383 std::fs::write(
1384 td.path().join("debian/debcargo.toml"),
1385 r#"maintainer = Joe Example <joe@example.com>
1386"#,
1387 )
1388 .unwrap();
1389 assert_eq!(
1390 Some(super::TemplateType::Debcargo),
1391 super::guess_template_type(
1392 &td.path().join("debian/control.in"),
1393 Some(&td.path().join("debian"))
1394 )
1395 );
1396 }
1397 }
1398
1399 #[test]
1400 fn test_postgresql() {
1401 let td = tempfile::tempdir().unwrap();
1402 std::fs::create_dir(td.path().join("debian")).unwrap();
1403 std::fs::write(
1404 td.path().join("debian/control.in"),
1405 r#"Source: blah
1406Build-Depends: bar, postgresql
1407
1408Package: foo-PGVERSION
1409"#,
1410 )
1411 .unwrap();
1412 assert_eq!(
1413 Some(super::TemplateType::Postgresql),
1414 super::guess_template_type(
1415 &td.path().join("debian/control.in"),
1416 Some(&td.path().join("debian"))
1417 )
1418 );
1419 }
1420
1421 #[test]
1422 fn test_apply_changes() {
1423 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1424Build-Depends: debhelper (>= 6), foo
1425
1426Package: bar
1427"#
1428 .parse()
1429 .unwrap();
1430
1431 let mut changes = Deb822Changes(std::collections::HashMap::new());
1432 changes.0.insert(
1433 ("Source".to_string(), "blah".to_string()),
1434 vec![(
1435 "Build-Depends".to_string(),
1436 Some("debhelper (>= 6), foo".to_string()),
1437 Some("debhelper (>= 10), foo".to_string()),
1438 )],
1439 );
1440
1441 super::apply_changes(&mut deb822, changes, None).unwrap();
1442
1443 assert_eq!(
1444 deb822.to_string(),
1445 r#"Source: blah
1446Build-Depends: debhelper (>= 10), foo
1447
1448Package: bar
1449"#
1450 );
1451 }
1452
1453 #[test]
1454 fn test_apply_changes_new_paragraph() {
1455 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1456Build-Depends: debhelper (>= 6), foo
1457
1458Package: bar
1459"#
1460 .parse()
1461 .unwrap();
1462
1463 let mut changes = Deb822Changes(std::collections::HashMap::new());
1464 changes.0.insert(
1465 ("Source".to_string(), "blah".to_string()),
1466 vec![(
1467 "Build-Depends".to_string(),
1468 Some("debhelper (>= 6), foo".to_string()),
1469 Some("debhelper (>= 10), foo".to_string()),
1470 )],
1471 );
1472 changes.0.insert(
1473 ("Package".to_string(), "blah2".to_string()),
1474 vec![
1475 ("Package".to_string(), None, Some("blah2".to_string())),
1476 (
1477 "Description".to_string(),
1478 None,
1479 Some("Some package".to_string()),
1480 ),
1481 ],
1482 );
1483
1484 super::apply_changes(&mut deb822, changes, None).unwrap();
1485
1486 assert_eq!(
1487 deb822.to_string(),
1488 r#"Source: blah
1489Build-Depends: debhelper (>= 10), foo
1490
1491Package: bar
1492
1493Package: blah2
1494Description: Some package
1495"#
1496 );
1497 }
1498
1499 #[test]
1500 fn test_apply_changes_conflict() {
1501 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1502Build-Depends: debhelper (>= 6), foo
1503
1504Package: bar
1505"#
1506 .parse()
1507 .unwrap();
1508
1509 let mut changes = Deb822Changes(std::collections::HashMap::new());
1510 changes.0.insert(
1511 ("Source".to_string(), "blah".to_string()),
1512 vec![(
1513 "Build-Depends".to_string(),
1514 Some("debhelper (>= 7), foo".to_string()),
1515 Some("debhelper (>= 10), foo".to_string()),
1516 )],
1517 );
1518
1519 let result = super::apply_changes(&mut deb822, changes, None);
1520 assert!(result.is_err());
1521 let err = result.unwrap_err();
1522 assert_eq!(
1523 err,
1524 ChangeConflict {
1525 para_key: ("Source".to_string(), "blah".to_string()),
1526 field: "Build-Depends".to_string(),
1527 actual_old_value: Some("debhelper (>= 7), foo".to_string()),
1528 template_old_value: Some("debhelper (>= 6), foo".to_string()),
1529 actual_new_value: Some("debhelper (>= 10), foo".to_string()),
1530 }
1531 );
1532 }
1533
1534 #[test]
1535 fn test_apply_changes_resolve_conflict() {
1536 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1537Build-Depends: debhelper (>= 6), foo
1538
1539Package: bar
1540"#
1541 .parse()
1542 .unwrap();
1543
1544 let mut changes = Deb822Changes(std::collections::HashMap::new());
1545 changes.0.insert(
1546 ("Source".to_string(), "blah".to_string()),
1547 vec![(
1548 "Build-Depends".to_string(),
1549 Some("debhelper (>= 7), foo".to_string()),
1550 Some("debhelper (>= 10), foo".to_string()),
1551 )],
1552 );
1553
1554 let result = super::apply_changes(&mut deb822, changes, Some(|_, _, _, _, _| Ok(None)));
1555 assert!(result.is_ok());
1556 assert_eq!(
1557 deb822.to_string(),
1558 r#"Source: blah
1559
1560Package: bar
1561"#
1562 );
1563 }
1564
1565 mod control_editor {
1566 #[test]
1567 fn test_do_not_edit() {
1568 let td = tempfile::tempdir().unwrap();
1569 std::fs::create_dir(td.path().join("debian")).unwrap();
1570 std::fs::write(
1571 td.path().join("debian/control"),
1572 r#"# DO NOT EDIT
1573# This file was generated by blah
1574
1575Source: blah
1576Testsuite: autopkgtest
1577
1578"#,
1579 )
1580 .unwrap();
1581 let editor =
1582 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1583 editor.source().unwrap().set_name("foo");
1584 let changes = editor.changes();
1585 assert_eq!(
1586 changes.normalized(),
1587 vec![
1588 (
1589 ("Source", "blah"),
1590 vec![
1591 ("Source", Some("blah"), None),
1592 ("Testsuite", Some("autopkgtest"), None),
1593 ]
1594 ),
1595 (
1596 ("Source", "foo"),
1597 vec![
1598 ("Source", None, Some("foo")),
1599 ("Testsuite", None, Some("autopkgtest"))
1600 ]
1601 )
1602 ]
1603 );
1604 assert!(matches!(
1605 editor.commit().unwrap_err(),
1606 super::EditorError::GeneratedFile(_, _)
1607 ));
1608 }
1609
1610 #[test]
1611 fn test_add_binary() {
1612 let td = tempfile::tempdir().unwrap();
1613 std::fs::create_dir(td.path().join("debian")).unwrap();
1614 std::fs::write(
1615 td.path().join("debian/control"),
1616 r#"Source: blah
1617Testsuite: autopkgtest
1618
1619Package: blah
1620Description: Some description
1621 And there are more lines
1622 And more lines
1623"#,
1624 )
1625 .unwrap();
1626 let mut editor =
1627 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1628 let mut binary = editor.add_binary("foo");
1629 binary.set_description(Some("A new package foo"));
1630 let paths = editor.commit().unwrap();
1631 assert_eq!(paths.len(), 1);
1632 }
1633
1634 #[test]
1635 fn test_list_binaries() {
1636 let td = tempfile::tempdir().unwrap();
1637 std::fs::create_dir(td.path().join("debian")).unwrap();
1638 std::fs::write(
1639 td.path().join("debian/control"),
1640 r#"Source: blah
1641Testsuite: autopkgtest
1642
1643Package: blah
1644Description: Some description
1645 And there are more lines
1646 And more lines
1647"#,
1648 )
1649 .unwrap();
1650 let editor =
1651 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1652 let binaries = editor.binaries().collect::<Vec<_>>();
1653 assert_eq!(binaries.len(), 1);
1654 assert_eq!(binaries[0].name().as_deref(), Some("blah"));
1655 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1656 }
1657
1658 #[test]
1659 fn test_no_source() {
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#"Package: blah
1665Testsuite: autopkgtest
1666
1667Package: bar
1668"#,
1669 )
1670 .unwrap();
1671 let editor =
1672 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1673 assert!(editor.source().is_none());
1674 }
1675
1676 #[test]
1677 fn test_create() {
1678 let td = tempfile::tempdir().unwrap();
1679 std::fs::create_dir(td.path().join("debian")).unwrap();
1680 let mut editor =
1681 super::TemplatedControlEditor::create(td.path().join("debian/control")).unwrap();
1682 editor.add_source("foo");
1683 assert_eq!(
1684 r#"Source: foo
1685"#,
1686 editor.as_deb822().to_string()
1687 );
1688 }
1689
1690 #[test]
1691 fn test_do_not_edit_no_change() {
1692 let td = tempfile::tempdir().unwrap();
1693 std::fs::create_dir(td.path().join("debian")).unwrap();
1694 std::fs::write(
1695 td.path().join("debian/control"),
1696 r#"# DO NOT EDIT
1697# This file was generated by blah
1698
1699Source: blah
1700Testsuite: autopkgtest
1701
1702"#,
1703 )
1704 .unwrap();
1705 let editor =
1706 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1707 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1708 }
1709
1710 #[test]
1711 fn test_unpreservable() {
1712 let td = tempfile::tempdir().unwrap();
1713 std::fs::create_dir(td.path().join("debian")).unwrap();
1714 std::fs::write(
1715 td.path().join("debian/control"),
1716 r#"Source: blah
1717# A comment
1718Testsuite: autopkgtest
1719
1720"#,
1721 )
1722 .unwrap();
1723 let editor =
1724 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1725 editor
1726 .source()
1727 .unwrap()
1728 .as_mut_deb822()
1729 .set("NewField", "New Field");
1730
1731 editor.commit().unwrap();
1732
1733 assert_eq!(
1734 r#"Source: blah
1735# A comment
1736Testsuite: autopkgtest
1737NewField: New Field
1738
1739"#,
1740 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1741 );
1742 }
1743
1744 #[test]
1745 fn test_modify_source() {
1746 let td = tempfile::tempdir().unwrap();
1747 std::fs::create_dir(td.path().join("debian")).unwrap();
1748 std::fs::write(
1749 td.path().join("debian/control"),
1750 r#"Source: blah
1751Testsuite: autopkgtest
1752"#,
1753 )
1754 .unwrap();
1755
1756 let editor =
1757 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1758 editor
1759 .source()
1760 .unwrap()
1761 .as_mut_deb822()
1762 .set("XS-Vcs-Git", "git://github.com/example/example");
1763
1764 editor.commit().unwrap();
1765
1766 assert_eq!(
1767 r#"Source: blah
1768Testsuite: autopkgtest
1769XS-Vcs-Git: git://github.com/example/example
1770"#,
1771 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1772 );
1773 }
1774
1775 #[test]
1856 fn test_modify_binary() {
1857 let td = tempfile::tempdir().unwrap();
1858 std::fs::create_dir(td.path().join("debian")).unwrap();
1859 std::fs::write(
1860 td.path().join("debian/control"),
1861 r#"Source: blah
1862Testsuite: autopkgtest
1863
1864Package: libblah
1865Section: extra
1866"#,
1867 )
1868 .unwrap();
1869
1870 let editor =
1871 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1872 let mut binary = editor
1873 .binaries()
1874 .find(|b| b.name().as_deref() == Some("libblah"))
1875 .unwrap();
1876 binary.set_architecture(Some("all"));
1877
1878 editor.commit().unwrap();
1879
1880 assert_eq!(
1881 r#"Source: blah
1882Testsuite: autopkgtest
1883
1884Package: libblah
1885Architecture: all
1886Section: extra
1887"#,
1888 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1889 );
1890 }
1891
1892 #[test]
1893 fn test_doesnt_strip_whitespace() {
1894 let td = tempfile::tempdir().unwrap();
1895 std::fs::create_dir(td.path().join("debian")).unwrap();
1896 std::fs::write(
1897 td.path().join("debian/control"),
1898 r#"Source: blah
1899Testsuite: autopkgtest
1900
1901"#,
1902 )
1903 .unwrap();
1904 let editor =
1905 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1906 editor.commit().unwrap();
1907
1908 assert_eq!(
1909 r#"Source: blah
1910Testsuite: autopkgtest
1911
1912"#,
1913 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1914 );
1915 }
1916
1917 #[cfg(unix)]
1918 #[test]
1919 fn test_update_template() {
1920 use std::os::unix::fs::PermissionsExt;
1921 let td = tempfile::tempdir().unwrap();
1922 std::fs::create_dir(td.path().join("debian")).unwrap();
1923 std::fs::write(
1924 td.path().join("debian/control"),
1925 r#"# DO NOT EDIT
1926# This file was generated by blah
1927
1928Source: blah
1929Testsuite: autopkgtest
1930Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1931
1932"#,
1933 )
1934 .unwrap();
1935 std::fs::write(
1936 td.path().join("debian/control.in"),
1937 r#"Source: blah
1938Testsuite: autopkgtest
1939Uploaders: @lintian-brush-test@
1940
1941"#,
1942 )
1943 .unwrap();
1944 std::fs::write(
1945 td.path().join("debian/rules"),
1946 r#"#!/usr/bin/make -f
1947
1948debian/control: debian/control.in
1949 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1950"#,
1951 )
1952 .unwrap();
1953 std::fs::set_permissions(
1955 td.path().join("debian/rules"),
1956 std::fs::Permissions::from_mode(0o755),
1957 )
1958 .unwrap();
1959
1960 let editor =
1961 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1962 editor
1963 .source()
1964 .unwrap()
1965 .as_mut_deb822()
1966 .set("Testsuite", "autopkgtest8");
1967
1968 assert_eq!(
1969 editor.commit().unwrap(),
1970 vec![
1971 td.path().join("debian/control.in"),
1972 td.path().join("debian/control")
1973 ]
1974 );
1975
1976 assert_eq!(
1977 r#"Source: blah
1978Testsuite: autopkgtest8
1979Uploaders: @lintian-brush-test@
1980
1981"#,
1982 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1983 );
1984
1985 assert_eq!(
1986 r#"Source: blah
1987Testsuite: autopkgtest8
1988Uploaders: testvalue
1989
1990"#,
1991 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1992 );
1993 }
1994
1995 #[cfg(unix)]
1996 #[test]
1997 fn test_update_template_only() {
1998 use std::os::unix::fs::PermissionsExt;
1999 let td = tempfile::tempdir().unwrap();
2000 std::fs::create_dir(td.path().join("debian")).unwrap();
2001 std::fs::write(
2002 td.path().join("debian/control.in"),
2003 r#"Source: blah
2004Testsuite: autopkgtest
2005Uploaders: @lintian-brush-test@
2006
2007"#,
2008 )
2009 .unwrap();
2010 std::fs::write(
2011 td.path().join("debian/rules"),
2012 r#"#!/usr/bin/make -f
2013
2014debian/control: debian/control.in
2015 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
2016"#,
2017 )
2018 .unwrap();
2019
2020 std::fs::set_permissions(
2021 td.path().join("debian/rules"),
2022 std::fs::Permissions::from_mode(0o755),
2023 )
2024 .unwrap();
2025
2026 let editor =
2027 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2028 editor
2029 .source()
2030 .unwrap()
2031 .as_mut_deb822()
2032 .set("Testsuite", "autopkgtest8");
2033
2034 editor.commit().unwrap();
2035
2036 assert_eq!(
2037 r#"Source: blah
2038Testsuite: autopkgtest8
2039Uploaders: @lintian-brush-test@
2040
2041"#,
2042 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2043 );
2044
2045 assert!(!td.path().join("debian/control").exists());
2046 }
2047
2048 #[cfg(unix)]
2049 #[test]
2050 fn test_update_template_invalid_tokens() {
2051 use std::os::unix::fs::PermissionsExt;
2052 let td = tempfile::tempdir().unwrap();
2053 std::fs::create_dir(td.path().join("debian")).unwrap();
2054 std::fs::write(
2055 td.path().join("debian/control"),
2056 r#"# DO NOT EDIT
2057# This file was generated by blah
2058
2059Source: blah
2060Testsuite: autopkgtest
2061Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
2062"#,
2063 )
2064 .unwrap();
2065 std::fs::write(
2066 td.path().join("debian/control.in"),
2067 r#"Source: blah
2068Testsuite: autopkgtest
2069@OTHERSTUFF@
2070"#,
2071 )
2072 .unwrap();
2073
2074 std::fs::write(
2075 td.path().join("debian/rules"),
2076 r#"#!/usr/bin/make -f
2077
2078debian/control: debian/control.in
2079 sed -e 's/@OTHERSTUFF@/Vcs-Git: example.com/' < $< > $@
2080"#,
2081 )
2082 .unwrap();
2083
2084 std::fs::set_permissions(
2085 td.path().join("debian/rules"),
2086 std::fs::Permissions::from_mode(0o755),
2087 )
2088 .unwrap();
2089
2090 let editor =
2091 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2092 editor
2093 .source()
2094 .unwrap()
2095 .as_mut_deb822()
2096 .set("Testsuite", "autopkgtest8");
2097 editor.commit().unwrap();
2098
2099 assert_eq!(
2100 r#"Source: blah
2101Testsuite: autopkgtest8
2102@OTHERSTUFF@
2103"#,
2104 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2105 );
2106
2107 assert_eq!(
2108 r#"Source: blah
2109Testsuite: autopkgtest8
2110Vcs-Git: example.com
2111"#,
2112 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2113 );
2114 }
2115
2116 #[test]
2117 fn test_update_cdbs_template() {
2118 let td = tempfile::tempdir().unwrap();
2119 std::fs::create_dir(td.path().join("debian")).unwrap();
2120
2121 std::fs::write(
2122 td.path().join("debian/control"),
2123 r#"Source: blah
2124Testsuite: autopkgtest
2125Build-Depends: some-foo, libc6
2126
2127"#,
2128 )
2129 .unwrap();
2130
2131 std::fs::write(
2132 td.path().join("debian/control.in"),
2133 r#"Source: blah
2134Testsuite: autopkgtest
2135Build-Depends: @cdbs@, libc6
2136
2137"#,
2138 )
2139 .unwrap();
2140
2141 let editor =
2142 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2143
2144 editor
2145 .source()
2146 .unwrap()
2147 .as_mut_deb822()
2148 .set("Build-Depends", "some-foo, libc6, some-bar");
2149
2150 assert_eq!(
2151 editor
2152 .source()
2153 .unwrap()
2154 .build_depends()
2155 .unwrap()
2156 .to_string(),
2157 "some-foo, libc6, some-bar".to_string()
2158 );
2159
2160 assert_eq!(Some(super::TemplateType::Cdbs), editor.template_type());
2161
2162 assert_eq!(
2163 editor.commit().unwrap(),
2164 vec![
2165 td.path().join("debian/control.in"),
2166 td.path().join("debian/control")
2167 ]
2168 );
2169
2170 assert_eq!(
2171 r#"Source: blah
2172Testsuite: autopkgtest
2173Build-Depends: @cdbs@, libc6, some-bar
2174
2175"#,
2176 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2177 );
2178
2179 assert_eq!(
2180 r#"Source: blah
2181Testsuite: autopkgtest
2182Build-Depends: some-foo, libc6, some-bar
2183
2184"#,
2185 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2186 );
2187 }
2188
2189 #[test]
2190 #[ignore = "Not implemented yet"]
2191 fn test_description_stays_last() {
2192 let td = tempfile::tempdir().unwrap();
2193 std::fs::create_dir(td.path().join("debian")).unwrap();
2194 std::fs::write(
2195 td.path().join("debian/control"),
2196 r#"Source: blah
2197Testsuite: autopkgtest
2198
2199Package: libblah
2200Section: extra
2201Description: foo
2202 bar
2203
2204"#,
2205 )
2206 .unwrap();
2207
2208 let editor =
2209 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2210 editor
2211 .binaries()
2212 .find(|b| b.name().as_deref() == Some("libblah"))
2213 .unwrap()
2214 .set_architecture(Some("all"));
2215
2216 editor.commit().unwrap();
2217
2218 assert_eq!(
2219 r#"Source: blah
2220Testsuite: autopkgtest
2221
2222Package: libblah
2223Section: extra
2224Architecture: all
2225Description: foo
2226 bar
2227"#,
2228 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2229 );
2230 }
2231
2232 #[test]
2233 fn test_no_new_heading_whitespace() {
2234 let td = tempfile::tempdir().unwrap();
2235 std::fs::create_dir(td.path().join("debian")).unwrap();
2236 std::fs::write(
2237 td.path().join("debian/control"),
2238 r#"Source: blah
2239Build-Depends:
2240 debhelper-compat (= 11),
2241 uuid-dev
2242
2243"#,
2244 )
2245 .unwrap();
2246
2247 let editor =
2248 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2249 editor
2250 .source()
2251 .unwrap()
2252 .as_mut_deb822()
2253 .set("Build-Depends", "debhelper-compat (= 12),\nuuid-dev");
2254
2255 editor.commit().unwrap();
2256
2257 assert_eq!(
2258 r#"Source: blah
2259Build-Depends:
2260 debhelper-compat (= 12),
2261 uuid-dev
2262
2263"#,
2264 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2265 );
2266 }
2267 }
2268}