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().any(|e| {
506 e.relations()
507 .any(|r| r.try_name().as_deref() == Some("gnome-pkg-tools"))
508 })
509 }) {
510 return Some(TemplateType::Gnome);
511 }
512
513 if build_depends.iter().any(|d| {
514 d.entries().any(|e| {
515 e.relations()
516 .any(|r| r.try_name().as_deref() == Some("cdbs"))
517 })
518 }) {
519 return Some(TemplateType::Cdbs);
520 }
521 }
522 Err(_) if template_path.is_dir() => {
523 return Some(TemplateType::Directory);
524 }
525 Err(e) => panic!("Failed to read {}: {}", template_path.display(), e),
526 }
527 if let Some(debian_path) = debian_path {
528 if debian_path.join("debcargo.toml").exists() {
529 return Some(TemplateType::Debcargo);
530 }
531 }
532 None
533}
534
535fn apply_changes(
541 deb822: &mut deb822_lossless::Deb822,
542 mut changes: Deb822Changes,
543 resolve_conflict: Option<ResolveDeb822Conflict>,
544) -> Result<(), ChangeConflict> {
545 fn default_resolve_conflict(
546 para_key: (&str, &str),
547 field: &str,
548 actual_old_value: Option<&str>,
549 template_old_value: Option<&str>,
550 actual_new_value: Option<&str>,
551 ) -> Result<Option<String>, ChangeConflict> {
552 Err(ChangeConflict {
553 para_key: (para_key.0.to_string(), para_key.1.to_string()),
554 field: field.to_string(),
555 actual_old_value: actual_old_value.map(|v| v.to_string()),
556 template_old_value: template_old_value.map(|v| v.to_string()),
557 actual_new_value: actual_new_value.map(|s| s.to_string()),
558 })
559 }
560
561 let resolve_conflict = resolve_conflict.unwrap_or(default_resolve_conflict);
562
563 for mut paragraph in deb822.paragraphs() {
564 let items: Vec<_> = paragraph.items().collect();
565 for item in items {
566 for (key, old_value, mut new_value) in changes.0.remove(&item).unwrap_or_default() {
567 if paragraph.get(&key) != old_value {
568 new_value = resolve_conflict(
569 (&item.0, &item.1),
570 &key,
571 old_value.as_deref(),
572 paragraph.get(&key).as_deref(),
573 new_value.as_deref(),
574 )?;
575 }
576 if let Some(new_value) = new_value.as_ref() {
577 paragraph.set(&key, new_value);
578 } else {
579 paragraph.remove(&key);
580 }
581 }
582 }
583 }
584 for (key, p) in changes.0.drain() {
586 let mut paragraph = deb822.add_paragraph();
587 for (field, old_value, mut new_value) in p {
588 if old_value.is_some() {
589 new_value = resolve_conflict(
590 (&key.0, &key.1),
591 &field,
592 old_value.as_deref(),
593 paragraph.get(&field).as_deref(),
594 new_value.as_deref(),
595 )?;
596 }
597 if let Some(new_value) = new_value {
598 paragraph.set(&field, &new_value);
599 }
600 }
601 }
602 Ok(())
603}
604
605fn find_template_path(path: &Path) -> Option<PathBuf> {
606 for ext in &["in", "m4"] {
607 let template_path = path.with_extension(ext);
608 if template_path.exists() {
609 return Some(template_path);
610 }
611 }
612 None
613}
614
615pub struct TemplatedControlEditor {
631 primary: FsEditor<debian_control::Control>,
633 template: Option<Template>,
635 path: PathBuf,
637 template_only: bool,
639}
640
641impl Deref for TemplatedControlEditor {
642 type Target = debian_control::Control;
643
644 fn deref(&self) -> &Self::Target {
645 &self.primary
646 }
647}
648
649impl DerefMut for TemplatedControlEditor {
650 fn deref_mut(&mut self) -> &mut Self::Target {
651 &mut self.primary
652 }
653}
654
655impl TemplatedControlEditor {
656 pub fn create<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
658 if control_path.as_ref().exists() {
659 return Err(EditorError::IoError(std::io::Error::new(
660 std::io::ErrorKind::AlreadyExists,
661 "Control file already exists",
662 )));
663 }
664 Self::new(control_path, true)
665 }
666
667 pub fn template_type(&self) -> Option<TemplateType> {
669 self.template.as_ref().map(|t| t.template_type)
670 }
671
672 pub fn normalize_field_spacing(&mut self) -> Result<(), EditorError> {
684 let Some(template) = &self.template else {
685 self.primary.as_mut_deb822().normalize_field_spacing();
687 return Ok(());
688 };
689
690 let is_deb822_template = matches!(
692 template.template_type,
693 TemplateType::Cdbs | TemplateType::Directory
694 );
695
696 if !is_deb822_template {
697 return Err(EditorError::GeneratedFile(
699 self.path.clone(),
700 GeneratedFile {
701 template_path: Some(template.template_path.clone()),
702 template_type: None,
703 },
704 ));
705 }
706
707 let mut template_editor =
709 FsEditor::<deb822_lossless::Deb822>::new(&template.template_path, true, false)?;
710 template_editor.normalize_field_spacing();
711 template_editor.commit()?;
712
713 self.primary.as_mut_deb822().normalize_field_spacing();
715 Ok(())
716 }
717
718 pub fn open<P: AsRef<Path>>(control_path: P) -> Result<Self, EditorError> {
720 Self::new(control_path, false)
721 }
722
723 pub fn new<P: AsRef<Path>>(control_path: P, allow_missing: bool) -> Result<Self, EditorError> {
725 let path = control_path.as_ref();
726 let (template, template_only) = if !path.exists() {
727 if let Some(template) = Template::find(path) {
728 match template.expand() {
729 Ok(_) => {}
730 Err(e) => {
731 return Err(EditorError::TemplateError(
732 template.template_path,
733 e.to_string(),
734 ))
735 }
736 }
737 (Some(template), true)
738 } else if !allow_missing {
739 return Err(EditorError::IoError(std::io::Error::new(
740 std::io::ErrorKind::NotFound,
741 "No control file or template found",
742 )));
743 } else {
744 (None, false)
745 }
746 } else {
747 (Template::find(path), false)
748 };
749 let primary = FsEditor::<debian_control::Control>::new(path, false, false)?;
750 Ok(Self {
751 path: path.to_path_buf(),
752 primary,
753 template_only,
754 template,
755 })
756 }
757
758 fn changes(&self) -> Deb822Changes {
763 let orig = deb822_lossless::Deb822::read_relaxed(self.primary.orig_content().unwrap())
764 .unwrap()
765 .0;
766 let mut changes = Deb822Changes::new();
767
768 fn by_key(
769 ps: impl Iterator<Item = Paragraph>,
770 ) -> std::collections::HashMap<(String, String), Paragraph> {
771 let mut ret = std::collections::HashMap::new();
772 for p in ps {
773 if let Some(s) = p.get("Source") {
774 ret.insert(("Source".to_string(), s), p);
775 } else if let Some(s) = p.get("Package") {
776 ret.insert(("Package".to_string(), s), p);
777 } else {
778 let k = p.items().next().unwrap();
779 ret.insert(k, p);
780 }
781 }
782 ret
783 }
784
785 let orig_by_key = by_key(orig.paragraphs());
786 let new_by_key = by_key(self.as_deb822().paragraphs());
787 let keys = orig_by_key
788 .keys()
789 .chain(new_by_key.keys())
790 .collect::<std::collections::HashSet<_>>();
791 for key in keys {
792 let old = orig_by_key.get(key);
793 let new = new_by_key.get(key);
794 if old == new {
795 continue;
796 }
797 let fields = std::collections::HashSet::<String>::from_iter(
798 old.iter()
799 .flat_map(|p| p.keys())
800 .chain(new.iter().flat_map(|p| p.keys())),
801 );
802 for field in &fields {
803 let old_val = old.and_then(|x| x.get(field));
804 let new_val = new.and_then(|x| x.get(field));
805 if old_val != new_val {
806 changes.insert(key.clone(), field.to_string(), old_val, new_val);
807 }
808 }
809 }
810 changes
811 }
812
813 pub fn commit(&self) -> Result<Vec<PathBuf>, EditorError> {
815 let mut changed_files: Vec<PathBuf> = vec![];
816 if self.template_only {
817 match std::fs::remove_file(self.path.as_path()) {
819 Ok(_) => {}
820 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
821 Err(e) => return Err(EditorError::IoError(e)),
822 }
823
824 changed_files.push(self.path.clone());
825
826 let template = self
827 .template
828 .as_ref()
829 .expect("template_only implies template");
830
831 let changed = match template.update(self.changes(), false) {
833 Ok(changed) => changed,
834 Err(e) => {
835 return Err(EditorError::TemplateError(
836 template.template_path.clone(),
837 e.to_string(),
838 ))
839 }
840 };
841 if changed {
842 changed_files.push(template.template_path.clone());
843 }
844 } else {
845 match self.primary.commit() {
846 Ok(files) => {
847 changed_files.extend(files.iter().map(|p| p.to_path_buf()));
848 }
849 Err(EditorError::GeneratedFile(
850 p,
851 GeneratedFile {
852 template_path: tp,
853 template_type: tt,
854 },
855 )) => {
856 if tp.is_none() {
857 return Err(EditorError::GeneratedFile(
858 p,
859 GeneratedFile {
860 template_path: tp,
861 template_type: tt,
862 },
863 ));
864 }
865 let template = if let Some(template) = self.template.as_ref() {
866 template
867 } else {
868 return Err(EditorError::IoError(std::io::Error::new(
869 std::io::ErrorKind::NotFound,
870 "No control file or template found",
871 )));
872 };
873 let changes = self.changes();
874 let changed = match template.update(changes, true) {
875 Ok(changed) => changed,
876 Err(e) => {
877 return Err(EditorError::TemplateError(tp.unwrap(), e.to_string()))
878 }
879 };
880 changed_files = if changed {
881 vec![tp.as_ref().unwrap().to_path_buf(), p]
882 } else {
883 vec![]
884 };
885 }
886 Err(EditorError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => {
887 let template = if let Some(p) = self.template.as_ref() {
888 p
889 } else {
890 return Err(EditorError::IoError(std::io::Error::new(
891 std::io::ErrorKind::NotFound,
892 "No control file or template found",
893 )));
894 };
895 let changed = match template.update(self.changes(), !self.template_only) {
896 Ok(changed) => changed,
897 Err(e) => {
898 return Err(EditorError::TemplateError(
899 template.template_path.clone(),
900 e.to_string(),
901 ))
902 }
903 };
904 if changed {
905 changed_files.push(template.template_path.clone());
906 changed_files.push(self.path.clone());
907 }
908 }
909 Err(e) => return Err(e),
910 }
911 }
912
913 Ok(changed_files)
914 }
915}
916
917struct Template {
918 path: PathBuf,
919 template_path: PathBuf,
920 template_type: TemplateType,
921}
922
923impl Template {
924 fn find(path: &Path) -> Option<Self> {
925 let template_path = find_template_path(path)?;
926 let template_type = guess_template_type(&template_path, Some(path.parent().unwrap()))?;
927 Some(Self {
928 path: path.to_path_buf(),
929 template_path,
930 template_type,
931 })
932 }
933
934 fn expand(&self) -> Result<(), TemplateExpansionError> {
935 expand_control_template(&self.template_path, &self.path, self.template_type)
936 }
937
938 fn update(&self, changes: Deb822Changes, expand: bool) -> Result<bool, TemplateExpansionError> {
939 update_control_template(
940 &self.template_path,
941 self.template_type,
942 &self.path,
943 changes,
944 expand,
945 )
946 }
947}
948
949impl Editor<debian_control::Control> for TemplatedControlEditor {
950 fn orig_content(&self) -> Option<&[u8]> {
951 self.primary.orig_content()
952 }
953
954 fn updated_content(&self) -> Option<Vec<u8>> {
955 self.primary.updated_content()
956 }
957
958 fn rewritten_content(&self) -> Option<&[u8]> {
959 self.primary.rewritten_content()
960 }
961
962 fn is_generated(&self) -> bool {
963 self.primary.is_generated()
964 }
965
966 fn commit(&self) -> Result<Vec<std::path::PathBuf>, EditorError> {
967 TemplatedControlEditor::commit(self)
968 }
969}
970
971#[cfg(test)]
972mod tests {
973 use super::*;
974
975 #[test]
976 fn test_format_description() {
977 let summary = "Summary";
978 let long_description = vec!["Long", "Description"];
979 let expected = "Summary\n Long\n Description\n";
980 assert_eq!(format_description(summary, long_description), expected);
981 }
982
983 #[test]
984 fn test_resolve_cdbs_conflicts() {
985 let val = resolve_cdbs_template(
986 ("Source", "libnetsds-perl"),
987 "Build-Depends",
988 Some("debhelper (>= 6), foo"),
989 Some("@cdbs@, debhelper (>= 9)"),
990 Some("debhelper (>= 10), foo"),
991 )
992 .unwrap();
993
994 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
995
996 let val = resolve_cdbs_template(
997 ("Source", "libnetsds-perl"),
998 "Build-Depends",
999 Some("debhelper (>= 6), foo"),
1000 Some("@cdbs@, foo"),
1001 Some("debhelper (>= 10), foo"),
1002 )
1003 .unwrap();
1004 assert_eq!(val, Some("@cdbs@, debhelper (>= 10), foo".to_string()));
1005 let val = resolve_cdbs_template(
1006 ("Source", "libnetsds-perl"),
1007 "Build-Depends",
1008 Some("debhelper (>= 6), foo"),
1009 Some("@cdbs@, debhelper (>= 9)"),
1010 Some("debhelper (>= 10), foo"),
1011 )
1012 .unwrap();
1013 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
1014 }
1015
1016 #[test]
1017 fn test_normalize_field_spacing_without_template() {
1018 let td = tempfile::tempdir().unwrap();
1020 let control_path = td.path().join("debian").join("control");
1021 std::fs::create_dir_all(control_path.parent().unwrap()).unwrap();
1022 std::fs::write(
1023 &control_path,
1024 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1025 )
1026 .unwrap();
1027
1028 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1029
1030 let original = editor.as_deb822().to_string();
1032 assert_eq!(
1033 original,
1034 "Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1035 );
1036
1037 editor.normalize_field_spacing().unwrap();
1039
1040 let normalized = editor.as_deb822().to_string();
1042 assert_eq!(
1043 normalized,
1044 "Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1045 );
1046 }
1047
1048 #[test]
1049 fn test_normalize_field_spacing_with_non_deb822_template() {
1050 let td = tempfile::tempdir().unwrap();
1052 let debian_path = td.path().join("debian");
1053 std::fs::create_dir_all(&debian_path).unwrap();
1054
1055 let control_in_path = debian_path.join("control.in");
1056 let control_path = debian_path.join("control");
1057 let rules_path = debian_path.join("rules");
1058
1059 std::fs::write(
1061 &control_in_path,
1062 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1063 )
1064 .unwrap();
1065
1066 std::fs::write(
1068 &rules_path,
1069 b"#!/usr/bin/make -f\n\ndebian/control: debian/control.in\n\tcp $< $@\n",
1070 )
1071 .unwrap();
1072
1073 std::fs::write(
1075 &control_path,
1076 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1077 )
1078 .unwrap();
1079
1080 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1081 assert_eq!(editor.template_type(), Some(TemplateType::Rules));
1082
1083 let result = editor.normalize_field_spacing();
1085 assert!(result.is_err());
1086
1087 match result {
1089 Err(EditorError::GeneratedFile(_, _)) => {
1090 }
1092 _ => panic!("Expected GeneratedFile error"),
1093 }
1094 }
1095
1096 #[test]
1097 fn test_normalize_field_spacing_with_cdbs_template() {
1098 let td = tempfile::tempdir().unwrap();
1102 let debian_path = td.path().join("debian");
1103 std::fs::create_dir_all(&debian_path).unwrap();
1104
1105 let control_in_path = debian_path.join("control.in");
1106 let control_path = debian_path.join("control");
1107
1108 std::fs::write(
1110 &control_in_path,
1111 b"Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n",
1112 )
1113 .unwrap();
1114
1115 std::fs::write(
1117 &control_path,
1118 b"Source: test\nBuild-Depends: debhelper\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1119 )
1120 .unwrap();
1121
1122 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1123 assert_eq!(editor.template_type(), Some(TemplateType::Cdbs));
1124
1125 let original_template = std::fs::read_to_string(&control_in_path).unwrap();
1127 assert_eq!(
1128 original_template,
1129 "Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n"
1130 );
1131
1132 editor.normalize_field_spacing().unwrap();
1134
1135 let template_content = std::fs::read_to_string(&control_in_path).unwrap();
1137 assert_eq!(
1138 template_content,
1139 "Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n"
1140 );
1141
1142 let control_content = editor.as_deb822().to_string();
1144 assert_eq!(
1145 control_content,
1146 "Source: test\nBuild-Depends: debhelper\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1147 );
1148 }
1149
1150 mod guess_template_type {
1151
1152 #[test]
1153 fn test_rules_generates_control() {
1154 let td = tempfile::tempdir().unwrap();
1155 std::fs::create_dir(td.path().join("debian")).unwrap();
1156 std::fs::write(
1157 td.path().join("debian/rules"),
1158 r#"%:
1159 dh $@
1160
1161debian/control: debian/control.in
1162 cp $@ $<
1163"#,
1164 )
1165 .unwrap();
1166 assert_eq!(
1167 super::guess_template_type(
1168 &td.path().join("debian/control.in"),
1169 Some(&td.path().join("debian"))
1170 ),
1171 Some(super::TemplateType::Rules)
1172 );
1173 }
1174
1175 #[test]
1176 fn test_rules_generates_control_percent() {
1177 let td = tempfile::tempdir().unwrap();
1178 std::fs::create_dir(td.path().join("debian")).unwrap();
1179 std::fs::write(
1180 td.path().join("debian/rules"),
1181 r#"%:
1182 dh $@
1183
1184debian/%: debian/%.in
1185 cp $@ $<
1186"#,
1187 )
1188 .unwrap();
1189 assert_eq!(
1190 super::guess_template_type(
1191 &td.path().join("debian/control.in"),
1192 Some(&td.path().join("debian"))
1193 ),
1194 Some(super::TemplateType::Rules)
1195 );
1196 }
1197
1198 #[test]
1199 fn test_rules_generates_control_blends() {
1200 let td = tempfile::tempdir().unwrap();
1201 std::fs::create_dir(td.path().join("debian")).unwrap();
1202 std::fs::write(
1203 td.path().join("debian/rules"),
1204 r#"%:
1205 dh $@
1206
1207include /usr/share/blends-dev/rules
1208"#,
1209 )
1210 .unwrap();
1211 assert_eq!(
1212 super::guess_template_type(
1213 &td.path().join("debian/control.stub"),
1214 Some(&td.path().join("debian"))
1215 ),
1216 Some(super::TemplateType::Rules)
1217 );
1218 }
1219
1220 #[test]
1221 fn test_empty_template() {
1222 let td = tempfile::tempdir().unwrap();
1223 std::fs::create_dir(td.path().join("debian")).unwrap();
1224 std::fs::write(td.path().join("debian/control.in"), "").unwrap();
1226
1227 assert_eq!(
1228 None,
1229 super::guess_template_type(
1230 &td.path().join("debian/control.in"),
1231 Some(&td.path().join("debian"))
1232 )
1233 );
1234 }
1235
1236 #[test]
1237 fn test_build_depends_cdbs() {
1238 let td = tempfile::tempdir().unwrap();
1239 std::fs::create_dir(td.path().join("debian")).unwrap();
1240 std::fs::write(
1241 td.path().join("debian/control.in"),
1242 r#"Source: blah
1243Build-Depends: cdbs
1244Vcs-Git: file://
1245
1246Package: bar
1247"#,
1248 )
1249 .unwrap();
1250 assert_eq!(
1251 Some(super::TemplateType::Cdbs),
1252 super::guess_template_type(
1253 &td.path().join("debian/control.in"),
1254 Some(&td.path().join("debian"))
1255 )
1256 );
1257 }
1258
1259 #[test]
1260 fn test_no_build_depends() {
1261 let td = tempfile::tempdir().unwrap();
1262 std::fs::create_dir(td.path().join("debian")).unwrap();
1263 std::fs::write(
1264 td.path().join("debian/control.in"),
1265 r#"Source: blah
1266Vcs-Git: file://
1267
1268Package: bar
1269"#,
1270 )
1271 .unwrap();
1272 assert_eq!(
1273 None,
1274 super::guess_template_type(
1275 &td.path().join("debian/control.in"),
1276 Some(&td.path().join("debian"))
1277 )
1278 );
1279 }
1280
1281 #[test]
1282 fn test_gnome() {
1283 let td = tempfile::tempdir().unwrap();
1284 std::fs::create_dir(td.path().join("debian")).unwrap();
1285 std::fs::write(
1286 td.path().join("debian/control.in"),
1287 r#"Foo @GNOME_TEAM@
1288"#,
1289 )
1290 .unwrap();
1291 assert_eq!(
1292 Some(super::TemplateType::Gnome),
1293 super::guess_template_type(
1294 &td.path().join("debian/control.in"),
1295 Some(&td.path().join("debian"))
1296 )
1297 );
1298 }
1299
1300 #[test]
1301 fn test_gnome_build_depends() {
1302 let td = tempfile::tempdir().unwrap();
1303 std::fs::create_dir(td.path().join("debian")).unwrap();
1304 std::fs::write(
1305 td.path().join("debian/control.in"),
1306 r#"Source: blah
1307Build-Depends: gnome-pkg-tools, libc6-dev
1308"#,
1309 )
1310 .unwrap();
1311 assert_eq!(
1312 Some(super::TemplateType::Gnome),
1313 super::guess_template_type(
1314 &td.path().join("debian/control.in"),
1315 Some(&td.path().join("debian"))
1316 )
1317 );
1318 }
1319
1320 #[test]
1321 fn test_cdbs() {
1322 let td = tempfile::tempdir().unwrap();
1323 std::fs::create_dir(td.path().join("debian")).unwrap();
1324 std::fs::write(
1325 td.path().join("debian/control.in"),
1326 r#"Source: blah
1327Build-Depends: debhelper, cdbs
1328"#,
1329 )
1330 .unwrap();
1331 assert_eq!(
1332 Some(super::TemplateType::Cdbs),
1333 super::guess_template_type(
1334 &td.path().join("debian/control.in"),
1335 Some(&td.path().join("debian"))
1336 )
1337 );
1338 }
1339
1340 #[test]
1341 fn test_multiple_paragraphs() {
1342 let td = tempfile::tempdir().unwrap();
1343 std::fs::create_dir(td.path().join("debian")).unwrap();
1344 std::fs::write(
1345 td.path().join("debian/control.in"),
1346 r#"Source: blah
1347Build-Depends: debhelper, cdbs
1348
1349Package: foo
1350"#,
1351 )
1352 .unwrap();
1353 assert_eq!(
1354 Some(super::TemplateType::Cdbs),
1355 super::guess_template_type(
1356 &td.path().join("debian/control.in"),
1357 Some(&td.path().join("debian"))
1358 )
1359 );
1360 }
1361
1362 #[test]
1363 fn test_directory() {
1364 let td = tempfile::tempdir().unwrap();
1365 std::fs::create_dir(td.path().join("debian")).unwrap();
1366 std::fs::create_dir(td.path().join("debian/control.in")).unwrap();
1367 assert_eq!(
1368 Some(super::TemplateType::Directory),
1369 super::guess_template_type(
1370 &td.path().join("debian/control.in"),
1371 Some(&td.path().join("debian"))
1372 )
1373 );
1374 }
1375
1376 #[test]
1377 fn test_debcargo() {
1378 let td = tempfile::tempdir().unwrap();
1379 std::fs::create_dir(td.path().join("debian")).unwrap();
1380 std::fs::write(
1381 td.path().join("debian/control.in"),
1382 r#"Source: blah
1383Build-Depends: bar
1384"#,
1385 )
1386 .unwrap();
1387 std::fs::write(
1388 td.path().join("debian/debcargo.toml"),
1389 r#"maintainer = Joe Example <joe@example.com>
1390"#,
1391 )
1392 .unwrap();
1393 assert_eq!(
1394 Some(super::TemplateType::Debcargo),
1395 super::guess_template_type(
1396 &td.path().join("debian/control.in"),
1397 Some(&td.path().join("debian"))
1398 )
1399 );
1400 }
1401 }
1402
1403 #[test]
1404 fn test_postgresql() {
1405 let td = tempfile::tempdir().unwrap();
1406 std::fs::create_dir(td.path().join("debian")).unwrap();
1407 std::fs::write(
1408 td.path().join("debian/control.in"),
1409 r#"Source: blah
1410Build-Depends: bar, postgresql
1411
1412Package: foo-PGVERSION
1413"#,
1414 )
1415 .unwrap();
1416 assert_eq!(
1417 Some(super::TemplateType::Postgresql),
1418 super::guess_template_type(
1419 &td.path().join("debian/control.in"),
1420 Some(&td.path().join("debian"))
1421 )
1422 );
1423 }
1424
1425 #[test]
1426 fn test_apply_changes() {
1427 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1428Build-Depends: debhelper (>= 6), foo
1429
1430Package: bar
1431"#
1432 .parse()
1433 .unwrap();
1434
1435 let mut changes = Deb822Changes(std::collections::HashMap::new());
1436 changes.0.insert(
1437 ("Source".to_string(), "blah".to_string()),
1438 vec![(
1439 "Build-Depends".to_string(),
1440 Some("debhelper (>= 6), foo".to_string()),
1441 Some("debhelper (>= 10), foo".to_string()),
1442 )],
1443 );
1444
1445 super::apply_changes(&mut deb822, changes, None).unwrap();
1446
1447 assert_eq!(
1448 deb822.to_string(),
1449 r#"Source: blah
1450Build-Depends: debhelper (>= 10), foo
1451
1452Package: bar
1453"#
1454 );
1455 }
1456
1457 #[test]
1458 fn test_apply_changes_new_paragraph() {
1459 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1460Build-Depends: debhelper (>= 6), foo
1461
1462Package: bar
1463"#
1464 .parse()
1465 .unwrap();
1466
1467 let mut changes = Deb822Changes(std::collections::HashMap::new());
1468 changes.0.insert(
1469 ("Source".to_string(), "blah".to_string()),
1470 vec![(
1471 "Build-Depends".to_string(),
1472 Some("debhelper (>= 6), foo".to_string()),
1473 Some("debhelper (>= 10), foo".to_string()),
1474 )],
1475 );
1476 changes.0.insert(
1477 ("Package".to_string(), "blah2".to_string()),
1478 vec![
1479 ("Package".to_string(), None, Some("blah2".to_string())),
1480 (
1481 "Description".to_string(),
1482 None,
1483 Some("Some package".to_string()),
1484 ),
1485 ],
1486 );
1487
1488 super::apply_changes(&mut deb822, changes, None).unwrap();
1489
1490 assert_eq!(
1491 deb822.to_string(),
1492 r#"Source: blah
1493Build-Depends: debhelper (>= 10), foo
1494
1495Package: bar
1496
1497Package: blah2
1498Description: Some package
1499"#
1500 );
1501 }
1502
1503 #[test]
1504 fn test_apply_changes_conflict() {
1505 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1506Build-Depends: debhelper (>= 6), foo
1507
1508Package: bar
1509"#
1510 .parse()
1511 .unwrap();
1512
1513 let mut changes = Deb822Changes(std::collections::HashMap::new());
1514 changes.0.insert(
1515 ("Source".to_string(), "blah".to_string()),
1516 vec![(
1517 "Build-Depends".to_string(),
1518 Some("debhelper (>= 7), foo".to_string()),
1519 Some("debhelper (>= 10), foo".to_string()),
1520 )],
1521 );
1522
1523 let result = super::apply_changes(&mut deb822, changes, None);
1524 assert!(result.is_err());
1525 let err = result.unwrap_err();
1526 assert_eq!(
1527 err,
1528 ChangeConflict {
1529 para_key: ("Source".to_string(), "blah".to_string()),
1530 field: "Build-Depends".to_string(),
1531 actual_old_value: Some("debhelper (>= 7), foo".to_string()),
1532 template_old_value: Some("debhelper (>= 6), foo".to_string()),
1533 actual_new_value: Some("debhelper (>= 10), foo".to_string()),
1534 }
1535 );
1536 }
1537
1538 #[test]
1539 fn test_apply_changes_resolve_conflict() {
1540 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1541Build-Depends: debhelper (>= 6), foo
1542
1543Package: bar
1544"#
1545 .parse()
1546 .unwrap();
1547
1548 let mut changes = Deb822Changes(std::collections::HashMap::new());
1549 changes.0.insert(
1550 ("Source".to_string(), "blah".to_string()),
1551 vec![(
1552 "Build-Depends".to_string(),
1553 Some("debhelper (>= 7), foo".to_string()),
1554 Some("debhelper (>= 10), foo".to_string()),
1555 )],
1556 );
1557
1558 let result = super::apply_changes(&mut deb822, changes, Some(|_, _, _, _, _| Ok(None)));
1559 assert!(result.is_ok());
1560 assert_eq!(
1561 deb822.to_string(),
1562 r#"Source: blah
1563
1564Package: bar
1565"#
1566 );
1567 }
1568
1569 mod control_editor {
1570 #[test]
1571 fn test_do_not_edit() {
1572 let td = tempfile::tempdir().unwrap();
1573 std::fs::create_dir(td.path().join("debian")).unwrap();
1574 std::fs::write(
1575 td.path().join("debian/control"),
1576 r#"# DO NOT EDIT
1577# This file was generated by blah
1578
1579Source: blah
1580Testsuite: autopkgtest
1581
1582"#,
1583 )
1584 .unwrap();
1585 let editor =
1586 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1587 editor.source().unwrap().set_name("foo");
1588 let changes = editor.changes();
1589 assert_eq!(
1590 changes.normalized(),
1591 vec![
1592 (
1593 ("Source", "blah"),
1594 vec![
1595 ("Source", Some("blah"), None),
1596 ("Testsuite", Some("autopkgtest"), None),
1597 ]
1598 ),
1599 (
1600 ("Source", "foo"),
1601 vec![
1602 ("Source", None, Some("foo")),
1603 ("Testsuite", None, Some("autopkgtest"))
1604 ]
1605 )
1606 ]
1607 );
1608 assert!(matches!(
1609 editor.commit().unwrap_err(),
1610 super::EditorError::GeneratedFile(_, _)
1611 ));
1612 }
1613
1614 #[test]
1615 fn test_add_binary() {
1616 let td = tempfile::tempdir().unwrap();
1617 std::fs::create_dir(td.path().join("debian")).unwrap();
1618 std::fs::write(
1619 td.path().join("debian/control"),
1620 r#"Source: blah
1621Testsuite: autopkgtest
1622
1623Package: blah
1624Description: Some description
1625 And there are more lines
1626 And more lines
1627"#,
1628 )
1629 .unwrap();
1630 let mut editor =
1631 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1632 let mut binary = editor.add_binary("foo");
1633 binary.set_description(Some("A new package foo"));
1634 let paths = editor.commit().unwrap();
1635 assert_eq!(paths.len(), 1);
1636 }
1637
1638 #[test]
1639 fn test_list_binaries() {
1640 let td = tempfile::tempdir().unwrap();
1641 std::fs::create_dir(td.path().join("debian")).unwrap();
1642 std::fs::write(
1643 td.path().join("debian/control"),
1644 r#"Source: blah
1645Testsuite: autopkgtest
1646
1647Package: blah
1648Description: Some description
1649 And there are more lines
1650 And more lines
1651"#,
1652 )
1653 .unwrap();
1654 let editor =
1655 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1656 let binaries = editor.binaries().collect::<Vec<_>>();
1657 assert_eq!(binaries.len(), 1);
1658 assert_eq!(binaries[0].name().as_deref(), Some("blah"));
1659 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1660 }
1661
1662 #[test]
1663 fn test_no_source() {
1664 let td = tempfile::tempdir().unwrap();
1665 std::fs::create_dir(td.path().join("debian")).unwrap();
1666 std::fs::write(
1667 td.path().join("debian/control"),
1668 r#"Package: blah
1669Testsuite: autopkgtest
1670
1671Package: bar
1672"#,
1673 )
1674 .unwrap();
1675 let editor =
1676 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1677 assert!(editor.source().is_none());
1678 }
1679
1680 #[test]
1681 fn test_create() {
1682 let td = tempfile::tempdir().unwrap();
1683 std::fs::create_dir(td.path().join("debian")).unwrap();
1684 let mut editor =
1685 super::TemplatedControlEditor::create(td.path().join("debian/control")).unwrap();
1686 editor.add_source("foo");
1687 assert_eq!(
1688 r#"Source: foo
1689"#,
1690 editor.as_deb822().to_string()
1691 );
1692 }
1693
1694 #[test]
1695 fn test_do_not_edit_no_change() {
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#"# DO NOT EDIT
1701# This file was generated by blah
1702
1703Source: blah
1704Testsuite: autopkgtest
1705
1706"#,
1707 )
1708 .unwrap();
1709 let editor =
1710 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1711 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1712 }
1713
1714 #[test]
1715 fn test_unpreservable() {
1716 let td = tempfile::tempdir().unwrap();
1717 std::fs::create_dir(td.path().join("debian")).unwrap();
1718 std::fs::write(
1719 td.path().join("debian/control"),
1720 r#"Source: blah
1721# A comment
1722Testsuite: autopkgtest
1723
1724"#,
1725 )
1726 .unwrap();
1727 let editor =
1728 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1729 editor
1730 .source()
1731 .unwrap()
1732 .as_mut_deb822()
1733 .set("NewField", "New Field");
1734
1735 editor.commit().unwrap();
1736
1737 assert_eq!(
1738 r#"Source: blah
1739# A comment
1740Testsuite: autopkgtest
1741NewField: New Field
1742
1743"#,
1744 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1745 );
1746 }
1747
1748 #[test]
1749 fn test_modify_source() {
1750 let td = tempfile::tempdir().unwrap();
1751 std::fs::create_dir(td.path().join("debian")).unwrap();
1752 std::fs::write(
1753 td.path().join("debian/control"),
1754 r#"Source: blah
1755Testsuite: autopkgtest
1756"#,
1757 )
1758 .unwrap();
1759
1760 let editor =
1761 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1762 editor
1763 .source()
1764 .unwrap()
1765 .as_mut_deb822()
1766 .set("XS-Vcs-Git", "git://github.com/example/example");
1767
1768 editor.commit().unwrap();
1769
1770 assert_eq!(
1771 r#"Source: blah
1772Testsuite: autopkgtest
1773XS-Vcs-Git: git://github.com/example/example
1774"#,
1775 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1776 );
1777 }
1778
1779 #[test]
1860 fn test_modify_binary() {
1861 let td = tempfile::tempdir().unwrap();
1862 std::fs::create_dir(td.path().join("debian")).unwrap();
1863 std::fs::write(
1864 td.path().join("debian/control"),
1865 r#"Source: blah
1866Testsuite: autopkgtest
1867
1868Package: libblah
1869Section: extra
1870"#,
1871 )
1872 .unwrap();
1873
1874 let editor =
1875 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1876 let mut binary = editor
1877 .binaries()
1878 .find(|b| b.name().as_deref() == Some("libblah"))
1879 .unwrap();
1880 binary.set_architecture(Some("all"));
1881
1882 editor.commit().unwrap();
1883
1884 assert_eq!(
1885 r#"Source: blah
1886Testsuite: autopkgtest
1887
1888Package: libblah
1889Architecture: all
1890Section: extra
1891"#,
1892 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1893 );
1894 }
1895
1896 #[test]
1897 fn test_doesnt_strip_whitespace() {
1898 let td = tempfile::tempdir().unwrap();
1899 std::fs::create_dir(td.path().join("debian")).unwrap();
1900 std::fs::write(
1901 td.path().join("debian/control"),
1902 r#"Source: blah
1903Testsuite: autopkgtest
1904
1905"#,
1906 )
1907 .unwrap();
1908 let editor =
1909 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1910 editor.commit().unwrap();
1911
1912 assert_eq!(
1913 r#"Source: blah
1914Testsuite: autopkgtest
1915
1916"#,
1917 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1918 );
1919 }
1920
1921 #[cfg(unix)]
1922 #[test]
1923 fn test_update_template() {
1924 use std::os::unix::fs::PermissionsExt;
1925 let td = tempfile::tempdir().unwrap();
1926 std::fs::create_dir(td.path().join("debian")).unwrap();
1927 std::fs::write(
1928 td.path().join("debian/control"),
1929 r#"# DO NOT EDIT
1930# This file was generated by blah
1931
1932Source: blah
1933Testsuite: autopkgtest
1934Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1935
1936"#,
1937 )
1938 .unwrap();
1939 std::fs::write(
1940 td.path().join("debian/control.in"),
1941 r#"Source: blah
1942Testsuite: autopkgtest
1943Uploaders: @lintian-brush-test@
1944
1945"#,
1946 )
1947 .unwrap();
1948 std::fs::write(
1949 td.path().join("debian/rules"),
1950 r#"#!/usr/bin/make -f
1951
1952debian/control: debian/control.in
1953 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1954"#,
1955 )
1956 .unwrap();
1957 std::fs::set_permissions(
1959 td.path().join("debian/rules"),
1960 std::fs::Permissions::from_mode(0o755),
1961 )
1962 .unwrap();
1963
1964 let editor =
1965 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1966 editor
1967 .source()
1968 .unwrap()
1969 .as_mut_deb822()
1970 .set("Testsuite", "autopkgtest8");
1971
1972 assert_eq!(
1973 editor.commit().unwrap(),
1974 vec![
1975 td.path().join("debian/control.in"),
1976 td.path().join("debian/control")
1977 ]
1978 );
1979
1980 assert_eq!(
1981 r#"Source: blah
1982Testsuite: autopkgtest8
1983Uploaders: @lintian-brush-test@
1984
1985"#,
1986 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1987 );
1988
1989 assert_eq!(
1990 r#"Source: blah
1991Testsuite: autopkgtest8
1992Uploaders: testvalue
1993
1994"#,
1995 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1996 );
1997 }
1998
1999 #[cfg(unix)]
2000 #[test]
2001 fn test_update_template_only() {
2002 use std::os::unix::fs::PermissionsExt;
2003 let td = tempfile::tempdir().unwrap();
2004 std::fs::create_dir(td.path().join("debian")).unwrap();
2005 std::fs::write(
2006 td.path().join("debian/control.in"),
2007 r#"Source: blah
2008Testsuite: autopkgtest
2009Uploaders: @lintian-brush-test@
2010
2011"#,
2012 )
2013 .unwrap();
2014 std::fs::write(
2015 td.path().join("debian/rules"),
2016 r#"#!/usr/bin/make -f
2017
2018debian/control: debian/control.in
2019 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
2020"#,
2021 )
2022 .unwrap();
2023
2024 std::fs::set_permissions(
2025 td.path().join("debian/rules"),
2026 std::fs::Permissions::from_mode(0o755),
2027 )
2028 .unwrap();
2029
2030 let editor =
2031 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2032 editor
2033 .source()
2034 .unwrap()
2035 .as_mut_deb822()
2036 .set("Testsuite", "autopkgtest8");
2037
2038 editor.commit().unwrap();
2039
2040 assert_eq!(
2041 r#"Source: blah
2042Testsuite: autopkgtest8
2043Uploaders: @lintian-brush-test@
2044
2045"#,
2046 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2047 );
2048
2049 assert!(!td.path().join("debian/control").exists());
2050 }
2051
2052 #[cfg(unix)]
2053 #[test]
2054 fn test_update_template_invalid_tokens() {
2055 use std::os::unix::fs::PermissionsExt;
2056 let td = tempfile::tempdir().unwrap();
2057 std::fs::create_dir(td.path().join("debian")).unwrap();
2058 std::fs::write(
2059 td.path().join("debian/control"),
2060 r#"# DO NOT EDIT
2061# This file was generated by blah
2062
2063Source: blah
2064Testsuite: autopkgtest
2065Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
2066"#,
2067 )
2068 .unwrap();
2069 std::fs::write(
2070 td.path().join("debian/control.in"),
2071 r#"Source: blah
2072Testsuite: autopkgtest
2073@OTHERSTUFF@
2074"#,
2075 )
2076 .unwrap();
2077
2078 std::fs::write(
2079 td.path().join("debian/rules"),
2080 r#"#!/usr/bin/make -f
2081
2082debian/control: debian/control.in
2083 sed -e 's/@OTHERSTUFF@/Vcs-Git: example.com/' < $< > $@
2084"#,
2085 )
2086 .unwrap();
2087
2088 std::fs::set_permissions(
2089 td.path().join("debian/rules"),
2090 std::fs::Permissions::from_mode(0o755),
2091 )
2092 .unwrap();
2093
2094 let editor =
2095 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2096 editor
2097 .source()
2098 .unwrap()
2099 .as_mut_deb822()
2100 .set("Testsuite", "autopkgtest8");
2101 editor.commit().unwrap();
2102
2103 assert_eq!(
2104 r#"Source: blah
2105Testsuite: autopkgtest8
2106@OTHERSTUFF@
2107"#,
2108 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2109 );
2110
2111 assert_eq!(
2112 r#"Source: blah
2113Testsuite: autopkgtest8
2114Vcs-Git: example.com
2115"#,
2116 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2117 );
2118 }
2119
2120 #[test]
2121 fn test_update_cdbs_template() {
2122 let td = tempfile::tempdir().unwrap();
2123 std::fs::create_dir(td.path().join("debian")).unwrap();
2124
2125 std::fs::write(
2126 td.path().join("debian/control"),
2127 r#"Source: blah
2128Testsuite: autopkgtest
2129Build-Depends: some-foo, libc6
2130
2131"#,
2132 )
2133 .unwrap();
2134
2135 std::fs::write(
2136 td.path().join("debian/control.in"),
2137 r#"Source: blah
2138Testsuite: autopkgtest
2139Build-Depends: @cdbs@, libc6
2140
2141"#,
2142 )
2143 .unwrap();
2144
2145 let editor =
2146 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2147
2148 editor
2149 .source()
2150 .unwrap()
2151 .as_mut_deb822()
2152 .set("Build-Depends", "some-foo, libc6, some-bar");
2153
2154 assert_eq!(
2155 editor
2156 .source()
2157 .unwrap()
2158 .build_depends()
2159 .unwrap()
2160 .to_string(),
2161 "some-foo, libc6, some-bar".to_string()
2162 );
2163
2164 assert_eq!(Some(super::TemplateType::Cdbs), editor.template_type());
2165
2166 assert_eq!(
2167 editor.commit().unwrap(),
2168 vec![
2169 td.path().join("debian/control.in"),
2170 td.path().join("debian/control")
2171 ]
2172 );
2173
2174 assert_eq!(
2175 r#"Source: blah
2176Testsuite: autopkgtest
2177Build-Depends: @cdbs@, libc6, some-bar
2178
2179"#,
2180 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2181 );
2182
2183 assert_eq!(
2184 r#"Source: blah
2185Testsuite: autopkgtest
2186Build-Depends: some-foo, libc6, some-bar
2187
2188"#,
2189 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2190 );
2191 }
2192
2193 #[test]
2194 #[ignore = "Not implemented yet"]
2195 fn test_description_stays_last() {
2196 let td = tempfile::tempdir().unwrap();
2197 std::fs::create_dir(td.path().join("debian")).unwrap();
2198 std::fs::write(
2199 td.path().join("debian/control"),
2200 r#"Source: blah
2201Testsuite: autopkgtest
2202
2203Package: libblah
2204Section: extra
2205Description: foo
2206 bar
2207
2208"#,
2209 )
2210 .unwrap();
2211
2212 let editor =
2213 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2214 editor
2215 .binaries()
2216 .find(|b| b.name().as_deref() == Some("libblah"))
2217 .unwrap()
2218 .set_architecture(Some("all"));
2219
2220 editor.commit().unwrap();
2221
2222 assert_eq!(
2223 r#"Source: blah
2224Testsuite: autopkgtest
2225
2226Package: libblah
2227Section: extra
2228Architecture: all
2229Description: foo
2230 bar
2231"#,
2232 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2233 );
2234 }
2235
2236 #[test]
2237 fn test_no_new_heading_whitespace() {
2238 let td = tempfile::tempdir().unwrap();
2239 std::fs::create_dir(td.path().join("debian")).unwrap();
2240 std::fs::write(
2241 td.path().join("debian/control"),
2242 r#"Source: blah
2243Build-Depends:
2244 debhelper-compat (= 11),
2245 uuid-dev
2246
2247"#,
2248 )
2249 .unwrap();
2250
2251 let editor =
2252 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2253 editor
2254 .source()
2255 .unwrap()
2256 .as_mut_deb822()
2257 .set("Build-Depends", "debhelper-compat (= 12),\nuuid-dev");
2258
2259 editor.commit().unwrap();
2260
2261 assert_eq!(
2262 r#"Source: blah
2263Build-Depends:
2264 debhelper-compat (= 12),
2265 uuid-dev
2266
2267"#,
2268 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2269 );
2270 }
2271 }
2272}