1use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph, BINARY_FIELD_ORDER, SOURCE_FIELD_ORDER};
38use rowan::ast::AstNode;
39
40fn format_field(name: &str, value: &str) -> String {
41 match name {
42 "Uploaders" => value
43 .split(',')
44 .map(|s| s.trim().to_string())
45 .collect::<Vec<_>>()
46 .join(",\n"),
47 "Build-Depends"
48 | "Build-Depends-Indep"
49 | "Build-Depends-Arch"
50 | "Build-Conflicts"
51 | "Build-Conflicts-Indep"
52 | "Build-Conflics-Arch"
53 | "Depends"
54 | "Recommends"
55 | "Suggests"
56 | "Enhances"
57 | "Pre-Depends"
58 | "Breaks" => {
59 let relations: Relations = value.parse().unwrap();
60 let relations = relations.wrap_and_sort();
61 relations.to_string()
62 }
63 _ => value.to_string(),
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Control(Deb822);
70
71impl Control {
72 pub fn new() -> Self {
74 Control(Deb822::new())
75 }
76
77 pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
79 &mut self.0
80 }
81
82 pub fn as_deb822(&self) -> &Deb822 {
84 &self.0
85 }
86
87 pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
89 let deb822_parse = Deb822::parse(text);
90 let green = deb822_parse.green().clone();
92 let errors = deb822_parse.errors().to_vec();
93 let positioned_errors = deb822_parse.positioned_errors().to_vec();
94 deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
95 }
96
97 pub fn source(&self) -> Option<Source> {
99 self.0
100 .paragraphs()
101 .find(|p| p.get("Source").is_some())
102 .map(Source)
103 }
104
105 pub fn binaries(&self) -> impl Iterator<Item = Binary> {
107 self.0
108 .paragraphs()
109 .filter(|p| p.get("Package").is_some())
110 .map(Binary)
111 }
112
113 pub fn add_source(&mut self, name: &str) -> Source {
129 let mut p = self.0.add_paragraph();
130 p.set("Source", name);
131 self.source().unwrap()
132 }
133
134 pub fn add_binary(&mut self, name: &str) -> Binary {
150 let mut p = self.0.add_paragraph();
151 p.set("Package", name);
152 Binary(p)
153 }
154
155 pub fn remove_binary(&mut self, name: &str) -> bool {
173 let index = self
174 .0
175 .paragraphs()
176 .position(|p| p.get("Package").as_deref() == Some(name));
177
178 if let Some(index) = index {
179 self.0.remove_paragraph(index);
180 true
181 } else {
182 false
183 }
184 }
185
186 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
188 Ok(Control(Deb822::from_file(path)?))
189 }
190
191 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
193 path: P,
194 ) -> Result<(Self, Vec<String>), std::io::Error> {
195 let (control, errors) = Deb822::from_file_relaxed(path)?;
196 Ok((Control(control), errors))
197 }
198
199 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
201 Ok(Control(Deb822::read(&mut r)?))
202 }
203
204 pub fn read_relaxed<R: std::io::Read>(
206 mut r: R,
207 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
208 let (control, errors) = Deb822::read_relaxed(&mut r)?;
209 Ok((Self(control), errors))
210 }
211
212 pub fn wrap_and_sort(
219 &mut self,
220 indentation: deb822_lossless::Indentation,
221 immediate_empty_line: bool,
222 max_line_length_one_liner: Option<usize>,
223 ) {
224 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
225 let a_is_source = a.get("Source").is_some();
227 let b_is_source = b.get("Source").is_some();
228
229 if a_is_source && !b_is_source {
230 return std::cmp::Ordering::Less;
231 } else if !a_is_source && b_is_source {
232 return std::cmp::Ordering::Greater;
233 } else if a_is_source && b_is_source {
234 return a.get("Source").cmp(&b.get("Source"));
235 }
236
237 a.get("Package").cmp(&b.get("Package"))
238 };
239
240 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
241 p.wrap_and_sort(
244 indentation,
245 immediate_empty_line,
246 max_line_length_one_liner,
247 None,
248 Some(&format_field),
249 )
250 };
251
252 self.0 = self
253 .0
254 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
255 }
256
257 pub fn sort_binaries(&mut self, keep_first: bool) {
288 let mut paragraphs: Vec<_> = self.0.paragraphs().collect();
289
290 if paragraphs.len() <= 1 {
291 return; }
293
294 let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
296 let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
297
298 let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
300 binary_start + 1
301 } else {
302 binary_start
303 };
304
305 if sort_start >= paragraphs.len() {
306 return; }
308
309 paragraphs[sort_start..].sort_by(|a, b| {
311 let a_name = a.get("Package");
312 let b_name = b.get("Package");
313 a_name.cmp(&b_name)
314 });
315
316 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
318 let a_pos = paragraphs.iter().position(|p| p == a);
319 let b_pos = paragraphs.iter().position(|p| p == b);
320 a_pos.cmp(&b_pos)
321 };
322
323 self.0 = self.0.wrap_and_sort(Some(&sort_paragraphs), None);
324 }
325
326 pub fn fields_in_range(
355 &self,
356 range: rowan::TextRange,
357 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
358 self.0
359 .paragraphs()
360 .flat_map(move |p| p.entries().collect::<Vec<_>>())
361 .filter(move |entry| {
362 let entry_range = entry.syntax().text_range();
363 entry_range.start() < range.end() && range.start() < entry_range.end()
365 })
366 }
367}
368
369impl From<Control> for Deb822 {
370 fn from(c: Control) -> Self {
371 c.0
372 }
373}
374
375impl From<Deb822> for Control {
376 fn from(d: Deb822) -> Self {
377 Control(d)
378 }
379}
380
381impl Default for Control {
382 fn default() -> Self {
383 Self::new()
384 }
385}
386
387impl std::str::FromStr for Control {
388 type Err = deb822_lossless::ParseError;
389
390 fn from_str(s: &str) -> Result<Self, Self::Err> {
391 Control::parse(s).to_result()
392 }
393}
394
395#[derive(Debug, Clone, PartialEq, Eq)]
397pub struct Source(Paragraph);
398
399impl From<Source> for Paragraph {
400 fn from(s: Source) -> Self {
401 s.0
402 }
403}
404
405impl From<Paragraph> for Source {
406 fn from(p: Paragraph) -> Self {
407 Source(p)
408 }
409}
410
411impl std::fmt::Display for Source {
412 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
413 self.0.fmt(f)
414 }
415}
416
417impl Source {
418 pub fn name(&self) -> Option<String> {
420 self.0.get("Source")
421 }
422
423 pub fn wrap_and_sort(
425 &mut self,
426 indentation: deb822_lossless::Indentation,
427 immediate_empty_line: bool,
428 max_line_length_one_liner: Option<usize>,
429 ) {
430 self.0 = self.0.wrap_and_sort(
431 indentation,
432 immediate_empty_line,
433 max_line_length_one_liner,
434 None,
435 Some(&format_field),
436 );
437 }
438
439 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
441 &mut self.0
442 }
443
444 pub fn as_deb822(&self) -> &Paragraph {
446 &self.0
447 }
448
449 pub fn set_name(&mut self, name: &str) {
451 self.set("Source", name);
452 }
453
454 pub fn section(&self) -> Option<String> {
456 self.0.get("Section")
457 }
458
459 pub fn set_section(&mut self, section: Option<&str>) {
461 if let Some(section) = section {
462 self.set("Section", section);
463 } else {
464 self.0.remove("Section");
465 }
466 }
467
468 pub fn priority(&self) -> Option<Priority> {
470 self.0.get("Priority").and_then(|v| v.parse().ok())
471 }
472
473 pub fn set_priority(&mut self, priority: Option<Priority>) {
475 if let Some(priority) = priority {
476 self.set("Priority", priority.to_string().as_str());
477 } else {
478 self.0.remove("Priority");
479 }
480 }
481
482 pub fn maintainer(&self) -> Option<String> {
484 self.0.get("Maintainer")
485 }
486
487 pub fn set_maintainer(&mut self, maintainer: &str) {
489 self.set("Maintainer", maintainer);
490 }
491
492 pub fn build_depends(&self) -> Option<Relations> {
494 self.0.get("Build-Depends").map(|s| s.parse().unwrap())
495 }
496
497 pub fn set_build_depends(&mut self, relations: &Relations) {
499 self.set("Build-Depends", relations.to_string().as_str());
500 }
501
502 pub fn build_depends_indep(&self) -> Option<Relations> {
504 self.0
505 .get("Build-Depends-Indep")
506 .map(|s| s.parse().unwrap())
507 }
508
509 pub fn build_depends_arch(&self) -> Option<Relations> {
511 self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
512 }
513
514 pub fn build_conflicts(&self) -> Option<Relations> {
516 self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
517 }
518
519 pub fn build_conflicts_indep(&self) -> Option<Relations> {
521 self.0
522 .get("Build-Conflicts-Indep")
523 .map(|s| s.parse().unwrap())
524 }
525
526 pub fn build_conflicts_arch(&self) -> Option<Relations> {
528 self.0
529 .get("Build-Conflicts-Arch")
530 .map(|s| s.parse().unwrap())
531 }
532
533 pub fn standards_version(&self) -> Option<String> {
535 self.0.get("Standards-Version")
536 }
537
538 pub fn set_standards_version(&mut self, version: &str) {
540 self.set("Standards-Version", version);
541 }
542
543 pub fn homepage(&self) -> Option<url::Url> {
545 self.0.get("Homepage").and_then(|s| s.parse().ok())
546 }
547
548 pub fn set_homepage(&mut self, homepage: &url::Url) {
550 self.set("Homepage", homepage.to_string().as_str());
551 }
552
553 pub fn vcs_git(&self) -> Option<String> {
555 self.0.get("Vcs-Git")
556 }
557
558 pub fn set_vcs_git(&mut self, url: &str) {
560 self.set("Vcs-Git", url);
561 }
562
563 pub fn vcs_svn(&self) -> Option<String> {
565 self.0.get("Vcs-Svn").map(|s| s.to_string())
566 }
567
568 pub fn set_vcs_svn(&mut self, url: &str) {
570 self.set("Vcs-Svn", url);
571 }
572
573 pub fn vcs_bzr(&self) -> Option<String> {
575 self.0.get("Vcs-Bzr").map(|s| s.to_string())
576 }
577
578 pub fn set_vcs_bzr(&mut self, url: &str) {
580 self.set("Vcs-Bzr", url);
581 }
582
583 pub fn vcs_arch(&self) -> Option<String> {
585 self.0.get("Vcs-Arch").map(|s| s.to_string())
586 }
587
588 pub fn set_vcs_arch(&mut self, url: &str) {
590 self.set("Vcs-Arch", url);
591 }
592
593 pub fn vcs_svk(&self) -> Option<String> {
595 self.0.get("Vcs-Svk").map(|s| s.to_string())
596 }
597
598 pub fn set_vcs_svk(&mut self, url: &str) {
600 self.set("Vcs-Svk", url);
601 }
602
603 pub fn vcs_darcs(&self) -> Option<String> {
605 self.0.get("Vcs-Darcs").map(|s| s.to_string())
606 }
607
608 pub fn set_vcs_darcs(&mut self, url: &str) {
610 self.set("Vcs-Darcs", url);
611 }
612
613 pub fn vcs_mtn(&self) -> Option<String> {
615 self.0.get("Vcs-Mtn").map(|s| s.to_string())
616 }
617
618 pub fn set_vcs_mtn(&mut self, url: &str) {
620 self.set("Vcs-Mtn", url);
621 }
622
623 pub fn vcs_cvs(&self) -> Option<String> {
625 self.0.get("Vcs-Cvs").map(|s| s.to_string())
626 }
627
628 pub fn set_vcs_cvs(&mut self, url: &str) {
630 self.set("Vcs-Cvs", url);
631 }
632
633 pub fn vcs_hg(&self) -> Option<String> {
635 self.0.get("Vcs-Hg").map(|s| s.to_string())
636 }
637
638 pub fn set_vcs_hg(&mut self, url: &str) {
640 self.set("Vcs-Hg", url);
641 }
642
643 pub fn set(&mut self, key: &str, value: &str) {
645 self.0.set_with_field_order(key, value, SOURCE_FIELD_ORDER);
646 }
647
648 pub fn vcs_browser(&self) -> Option<String> {
650 self.0.get("Vcs-Browser")
651 }
652
653 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
655 for (name, value) in self.0.items() {
656 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
657 return crate::vcs::Vcs::from_field(&name, &value).ok();
658 }
659 }
660 None
661 }
662
663 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
665 if let Some(url) = url {
666 self.set("Vcs-Browser", url);
667 } else {
668 self.0.remove("Vcs-Browser");
669 }
670 }
671
672 pub fn uploaders(&self) -> Option<Vec<String>> {
674 self.0
675 .get("Uploaders")
676 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
677 }
678
679 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
681 self.set(
682 "Uploaders",
683 uploaders
684 .iter()
685 .map(|s| s.to_string())
686 .collect::<Vec<_>>()
687 .join(", ")
688 .as_str(),
689 );
690 }
691
692 pub fn architecture(&self) -> Option<String> {
694 self.0.get("Architecture")
695 }
696
697 pub fn set_architecture(&mut self, arch: Option<&str>) {
699 if let Some(arch) = arch {
700 self.set("Architecture", arch);
701 } else {
702 self.0.remove("Architecture");
703 }
704 }
705
706 pub fn rules_requires_root(&self) -> Option<bool> {
708 self.0
709 .get("Rules-Requires-Root")
710 .map(|s| match s.to_lowercase().as_str() {
711 "yes" => true,
712 "no" => false,
713 _ => panic!("invalid Rules-Requires-Root value"),
714 })
715 }
716
717 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
719 self.set(
720 "Rules-Requires-Root",
721 if requires_root { "yes" } else { "no" },
722 );
723 }
724
725 pub fn testsuite(&self) -> Option<String> {
727 self.0.get("Testsuite")
728 }
729
730 pub fn set_testsuite(&mut self, testsuite: &str) {
732 self.set("Testsuite", testsuite);
733 }
734
735 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
743 let para_range = self.0.syntax().text_range();
744 para_range.start() < range.end() && range.start() < para_range.end()
745 }
746
747 pub fn fields_in_range(
755 &self,
756 range: rowan::TextRange,
757 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
758 self.0.entries().filter(move |entry| {
759 let entry_range = entry.syntax().text_range();
760 entry_range.start() < range.end() && range.start() < entry_range.end()
761 })
762 }
763}
764
765#[cfg(feature = "python-debian")]
766impl<'py> pyo3::IntoPyObject<'py> for Source {
767 type Target = pyo3::PyAny;
768 type Output = pyo3::Bound<'py, Self::Target>;
769 type Error = pyo3::PyErr;
770
771 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
772 self.0.into_pyobject(py)
773 }
774}
775
776#[cfg(feature = "python-debian")]
777impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
778 type Target = pyo3::PyAny;
779 type Output = pyo3::Bound<'py, Self::Target>;
780 type Error = pyo3::PyErr;
781
782 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
783 (&self.0).into_pyobject(py)
784 }
785}
786
787#[cfg(feature = "python-debian")]
788impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
789 type Error = pyo3::PyErr;
790
791 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
792 Ok(Source(ob.extract()?))
793 }
794}
795
796impl std::fmt::Display for Control {
797 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
798 self.0.fmt(f)
799 }
800}
801
802impl AstNode for Control {
803 type Language = deb822_lossless::Lang;
804
805 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
806 Deb822::can_cast(kind)
807 }
808
809 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
810 Deb822::cast(syntax).map(Control)
811 }
812
813 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
814 self.0.syntax()
815 }
816}
817
818#[derive(Debug, Clone, PartialEq, Eq)]
820pub struct Binary(Paragraph);
821
822impl From<Binary> for Paragraph {
823 fn from(b: Binary) -> Self {
824 b.0
825 }
826}
827
828impl From<Paragraph> for Binary {
829 fn from(p: Paragraph) -> Self {
830 Binary(p)
831 }
832}
833
834#[cfg(feature = "python-debian")]
835impl<'py> pyo3::IntoPyObject<'py> for Binary {
836 type Target = pyo3::PyAny;
837 type Output = pyo3::Bound<'py, Self::Target>;
838 type Error = pyo3::PyErr;
839
840 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
841 self.0.into_pyobject(py)
842 }
843}
844
845#[cfg(feature = "python-debian")]
846impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
847 type Target = pyo3::PyAny;
848 type Output = pyo3::Bound<'py, Self::Target>;
849 type Error = pyo3::PyErr;
850
851 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
852 (&self.0).into_pyobject(py)
853 }
854}
855
856#[cfg(feature = "python-debian")]
857impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
858 type Error = pyo3::PyErr;
859
860 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
861 Ok(Binary(ob.extract()?))
862 }
863}
864
865impl Default for Binary {
866 fn default() -> Self {
867 Self::new()
868 }
869}
870
871impl std::fmt::Display for Binary {
872 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
873 self.0.fmt(f)
874 }
875}
876
877impl Binary {
878 pub fn new() -> Self {
880 Binary(Paragraph::new())
881 }
882
883 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
885 &mut self.0
886 }
887
888 pub fn as_deb822(&self) -> &Paragraph {
890 &self.0
891 }
892
893 pub fn wrap_and_sort(
895 &mut self,
896 indentation: deb822_lossless::Indentation,
897 immediate_empty_line: bool,
898 max_line_length_one_liner: Option<usize>,
899 ) {
900 self.0 = self.0.wrap_and_sort(
901 indentation,
902 immediate_empty_line,
903 max_line_length_one_liner,
904 None,
905 Some(&format_field),
906 );
907 }
908
909 pub fn name(&self) -> Option<String> {
911 self.0.get("Package")
912 }
913
914 pub fn set_name(&mut self, name: &str) {
916 self.set("Package", name);
917 }
918
919 pub fn section(&self) -> Option<String> {
921 self.0.get("Section")
922 }
923
924 pub fn set_section(&mut self, section: Option<&str>) {
926 if let Some(section) = section {
927 self.set("Section", section);
928 } else {
929 self.0.remove("Section");
930 }
931 }
932
933 pub fn priority(&self) -> Option<Priority> {
935 self.0.get("Priority").and_then(|v| v.parse().ok())
936 }
937
938 pub fn set_priority(&mut self, priority: Option<Priority>) {
940 if let Some(priority) = priority {
941 self.set("Priority", priority.to_string().as_str());
942 } else {
943 self.0.remove("Priority");
944 }
945 }
946
947 pub fn architecture(&self) -> Option<String> {
949 self.0.get("Architecture")
950 }
951
952 pub fn set_architecture(&mut self, arch: Option<&str>) {
954 if let Some(arch) = arch {
955 self.set("Architecture", arch);
956 } else {
957 self.0.remove("Architecture");
958 }
959 }
960
961 pub fn depends(&self) -> Option<Relations> {
963 self.0.get("Depends").map(|s| s.parse().unwrap())
964 }
965
966 pub fn set_depends(&mut self, depends: Option<&Relations>) {
968 if let Some(depends) = depends {
969 self.set("Depends", depends.to_string().as_str());
970 } else {
971 self.0.remove("Depends");
972 }
973 }
974
975 pub fn recommends(&self) -> Option<Relations> {
977 self.0.get("Recommends").map(|s| s.parse().unwrap())
978 }
979
980 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
982 if let Some(recommends) = recommends {
983 self.set("Recommends", recommends.to_string().as_str());
984 } else {
985 self.0.remove("Recommends");
986 }
987 }
988
989 pub fn suggests(&self) -> Option<Relations> {
991 self.0.get("Suggests").map(|s| s.parse().unwrap())
992 }
993
994 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
996 if let Some(suggests) = suggests {
997 self.set("Suggests", suggests.to_string().as_str());
998 } else {
999 self.0.remove("Suggests");
1000 }
1001 }
1002
1003 pub fn enhances(&self) -> Option<Relations> {
1005 self.0.get("Enhances").map(|s| s.parse().unwrap())
1006 }
1007
1008 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1010 if let Some(enhances) = enhances {
1011 self.set("Enhances", enhances.to_string().as_str());
1012 } else {
1013 self.0.remove("Enhances");
1014 }
1015 }
1016
1017 pub fn pre_depends(&self) -> Option<Relations> {
1019 self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
1020 }
1021
1022 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1024 if let Some(pre_depends) = pre_depends {
1025 self.set("Pre-Depends", pre_depends.to_string().as_str());
1026 } else {
1027 self.0.remove("Pre-Depends");
1028 }
1029 }
1030
1031 pub fn breaks(&self) -> Option<Relations> {
1033 self.0.get("Breaks").map(|s| s.parse().unwrap())
1034 }
1035
1036 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1038 if let Some(breaks) = breaks {
1039 self.set("Breaks", breaks.to_string().as_str());
1040 } else {
1041 self.0.remove("Breaks");
1042 }
1043 }
1044
1045 pub fn conflicts(&self) -> Option<Relations> {
1047 self.0.get("Conflicts").map(|s| s.parse().unwrap())
1048 }
1049
1050 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1052 if let Some(conflicts) = conflicts {
1053 self.set("Conflicts", conflicts.to_string().as_str());
1054 } else {
1055 self.0.remove("Conflicts");
1056 }
1057 }
1058
1059 pub fn replaces(&self) -> Option<Relations> {
1061 self.0.get("Replaces").map(|s| s.parse().unwrap())
1062 }
1063
1064 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1066 if let Some(replaces) = replaces {
1067 self.set("Replaces", replaces.to_string().as_str());
1068 } else {
1069 self.0.remove("Replaces");
1070 }
1071 }
1072
1073 pub fn provides(&self) -> Option<Relations> {
1075 self.0.get("Provides").map(|s| s.parse().unwrap())
1076 }
1077
1078 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1080 if let Some(provides) = provides {
1081 self.set("Provides", provides.to_string().as_str());
1082 } else {
1083 self.0.remove("Provides");
1084 }
1085 }
1086
1087 pub fn built_using(&self) -> Option<Relations> {
1089 self.0.get("Built-Using").map(|s| s.parse().unwrap())
1090 }
1091
1092 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1094 if let Some(built_using) = built_using {
1095 self.set("Built-Using", built_using.to_string().as_str());
1096 } else {
1097 self.0.remove("Built-Using");
1098 }
1099 }
1100
1101 pub fn multi_arch(&self) -> Option<MultiArch> {
1103 self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
1104 }
1105
1106 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1108 if let Some(multi_arch) = multi_arch {
1109 self.set("Multi-Arch", multi_arch.to_string().as_str());
1110 } else {
1111 self.0.remove("Multi-Arch");
1112 }
1113 }
1114
1115 pub fn essential(&self) -> bool {
1117 self.0.get("Essential").map(|s| s == "yes").unwrap_or(false)
1118 }
1119
1120 pub fn set_essential(&mut self, essential: bool) {
1122 if essential {
1123 self.set("Essential", "yes");
1124 } else {
1125 self.0.remove("Essential");
1126 }
1127 }
1128
1129 pub fn description(&self) -> Option<String> {
1131 self.0.get("Description")
1132 }
1133
1134 pub fn set_description(&mut self, description: Option<&str>) {
1136 if let Some(description) = description {
1137 self.set("Description", description);
1138 } else {
1139 self.0.remove("Description");
1140 }
1141 }
1142
1143 pub fn homepage(&self) -> Option<url::Url> {
1145 self.0.get("Homepage").and_then(|s| s.parse().ok())
1146 }
1147
1148 pub fn set_homepage(&mut self, url: &url::Url) {
1150 self.set("Homepage", url.as_str());
1151 }
1152
1153 pub fn set(&mut self, key: &str, value: &str) {
1155 self.0.set_with_field_order(key, value, BINARY_FIELD_ORDER);
1156 }
1157
1158 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1166 let para_range = self.0.syntax().text_range();
1167 para_range.start() < range.end() && range.start() < para_range.end()
1168 }
1169
1170 pub fn fields_in_range(
1178 &self,
1179 range: rowan::TextRange,
1180 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1181 self.0.entries().filter(move |entry| {
1182 let entry_range = entry.syntax().text_range();
1183 entry_range.start() < range.end() && range.start() < entry_range.end()
1184 })
1185 }
1186}
1187
1188#[cfg(test)]
1189mod tests {
1190 use super::*;
1191 use crate::relations::VersionConstraint;
1192
1193 #[test]
1194 fn test_source_set_field_ordering() {
1195 let mut control = Control::new();
1196 let mut source = control.add_source("mypackage");
1197
1198 source.set("Homepage", "https://example.com");
1200 source.set("Build-Depends", "debhelper");
1201 source.set("Standards-Version", "4.5.0");
1202 source.set("Maintainer", "Test <test@example.com>");
1203
1204 let output = source.to_string();
1206 let lines: Vec<&str> = output.lines().collect();
1207
1208 assert!(lines[0].starts_with("Source:"));
1210
1211 let maintainer_pos = lines
1213 .iter()
1214 .position(|l| l.starts_with("Maintainer:"))
1215 .unwrap();
1216 let build_depends_pos = lines
1217 .iter()
1218 .position(|l| l.starts_with("Build-Depends:"))
1219 .unwrap();
1220 let standards_pos = lines
1221 .iter()
1222 .position(|l| l.starts_with("Standards-Version:"))
1223 .unwrap();
1224 let homepage_pos = lines
1225 .iter()
1226 .position(|l| l.starts_with("Homepage:"))
1227 .unwrap();
1228
1229 assert!(maintainer_pos < build_depends_pos);
1231 assert!(build_depends_pos < standards_pos);
1232 assert!(standards_pos < homepage_pos);
1233 }
1234
1235 #[test]
1236 fn test_binary_set_field_ordering() {
1237 let mut control = Control::new();
1238 let mut binary = control.add_binary("mypackage");
1239
1240 binary.set("Description", "A test package");
1242 binary.set("Architecture", "amd64");
1243 binary.set("Depends", "libc6");
1244 binary.set("Section", "utils");
1245
1246 let output = binary.to_string();
1248 let lines: Vec<&str> = output.lines().collect();
1249
1250 assert!(lines[0].starts_with("Package:"));
1252
1253 let arch_pos = lines
1255 .iter()
1256 .position(|l| l.starts_with("Architecture:"))
1257 .unwrap();
1258 let section_pos = lines
1259 .iter()
1260 .position(|l| l.starts_with("Section:"))
1261 .unwrap();
1262 let depends_pos = lines
1263 .iter()
1264 .position(|l| l.starts_with("Depends:"))
1265 .unwrap();
1266 let desc_pos = lines
1267 .iter()
1268 .position(|l| l.starts_with("Description:"))
1269 .unwrap();
1270
1271 assert!(arch_pos < section_pos);
1273 assert!(section_pos < depends_pos);
1274 assert!(depends_pos < desc_pos);
1275 }
1276
1277 #[test]
1278 fn test_source_specific_set_methods_use_field_ordering() {
1279 let mut control = Control::new();
1280 let mut source = control.add_source("mypackage");
1281
1282 source.set_homepage(&"https://example.com".parse().unwrap());
1284 source.set_maintainer("Test <test@example.com>");
1285 source.set_standards_version("4.5.0");
1286 source.set_vcs_git("https://github.com/example/repo");
1287
1288 let output = source.to_string();
1290 let lines: Vec<&str> = output.lines().collect();
1291
1292 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1294 let maintainer_pos = lines
1295 .iter()
1296 .position(|l| l.starts_with("Maintainer:"))
1297 .unwrap();
1298 let standards_pos = lines
1299 .iter()
1300 .position(|l| l.starts_with("Standards-Version:"))
1301 .unwrap();
1302 let vcs_git_pos = lines
1303 .iter()
1304 .position(|l| l.starts_with("Vcs-Git:"))
1305 .unwrap();
1306 let homepage_pos = lines
1307 .iter()
1308 .position(|l| l.starts_with("Homepage:"))
1309 .unwrap();
1310
1311 assert!(source_pos < maintainer_pos);
1313 assert!(maintainer_pos < standards_pos);
1314 assert!(standards_pos < vcs_git_pos);
1315 assert!(vcs_git_pos < homepage_pos);
1316 }
1317
1318 #[test]
1319 fn test_binary_specific_set_methods_use_field_ordering() {
1320 let mut control = Control::new();
1321 let mut binary = control.add_binary("mypackage");
1322
1323 binary.set_description(Some("A test package"));
1325 binary.set_architecture(Some("amd64"));
1326 let depends = "libc6".parse().unwrap();
1327 binary.set_depends(Some(&depends));
1328 binary.set_section(Some("utils"));
1329 binary.set_priority(Some(Priority::Optional));
1330
1331 let output = binary.to_string();
1333 let lines: Vec<&str> = output.lines().collect();
1334
1335 let package_pos = lines
1337 .iter()
1338 .position(|l| l.starts_with("Package:"))
1339 .unwrap();
1340 let arch_pos = lines
1341 .iter()
1342 .position(|l| l.starts_with("Architecture:"))
1343 .unwrap();
1344 let section_pos = lines
1345 .iter()
1346 .position(|l| l.starts_with("Section:"))
1347 .unwrap();
1348 let priority_pos = lines
1349 .iter()
1350 .position(|l| l.starts_with("Priority:"))
1351 .unwrap();
1352 let depends_pos = lines
1353 .iter()
1354 .position(|l| l.starts_with("Depends:"))
1355 .unwrap();
1356 let desc_pos = lines
1357 .iter()
1358 .position(|l| l.starts_with("Description:"))
1359 .unwrap();
1360
1361 assert!(package_pos < arch_pos);
1363 assert!(arch_pos < section_pos);
1364 assert!(section_pos < priority_pos);
1365 assert!(priority_pos < depends_pos);
1366 assert!(depends_pos < desc_pos);
1367 }
1368
1369 #[test]
1370 fn test_parse() {
1371 let control: Control = r#"Source: foo
1372Section: libs
1373Priority: optional
1374Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1375Homepage: https://example.com
1376
1377"#
1378 .parse()
1379 .unwrap();
1380 let source = control.source().unwrap();
1381
1382 assert_eq!(source.name(), Some("foo".to_owned()));
1383 assert_eq!(source.section(), Some("libs".to_owned()));
1384 assert_eq!(source.priority(), Some(super::Priority::Optional));
1385 assert_eq!(
1386 source.homepage(),
1387 Some("https://example.com".parse().unwrap())
1388 );
1389 let bd = source.build_depends().unwrap();
1390 let entries = bd.entries().collect::<Vec<_>>();
1391 assert_eq!(entries.len(), 2);
1392 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1393 assert_eq!(rel.name(), "bar");
1394 assert_eq!(
1395 rel.version(),
1396 Some((
1397 VersionConstraint::GreaterThanEqual,
1398 "1.0.0".parse().unwrap()
1399 ))
1400 );
1401 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1402 assert_eq!(rel.name(), "baz");
1403 assert_eq!(
1404 rel.version(),
1405 Some((
1406 VersionConstraint::GreaterThanEqual,
1407 "1.0.0".parse().unwrap()
1408 ))
1409 );
1410 }
1411
1412 #[test]
1413 fn test_description() {
1414 let control: Control = r#"Source: foo
1415
1416Package: foo
1417Description: this is the short description
1418 And the longer one
1419 .
1420 is on the next lines
1421"#
1422 .parse()
1423 .unwrap();
1424 let binary = control.binaries().next().unwrap();
1425 assert_eq!(
1426 binary.description(),
1427 Some(
1428 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1429 .to_owned()
1430 )
1431 );
1432 }
1433
1434 #[test]
1435 fn test_as_mut_deb822() {
1436 let mut control = Control::new();
1437 let deb822 = control.as_mut_deb822();
1438 let mut p = deb822.add_paragraph();
1439 p.set("Source", "foo");
1440 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1441 }
1442
1443 #[test]
1444 fn test_as_deb822() {
1445 let control = Control::new();
1446 let _deb822: &Deb822 = control.as_deb822();
1447 }
1448
1449 #[test]
1450 fn test_set_depends() {
1451 let mut control = Control::new();
1452 let mut binary = control.add_binary("foo");
1453 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1454 binary.set_depends(Some(&relations));
1455 }
1456
1457 #[test]
1458 fn test_wrap_and_sort() {
1459 let mut control: Control = r#"Package: blah
1460Section: libs
1461
1462
1463
1464Package: foo
1465Description: this is a
1466 bar
1467 blah
1468"#
1469 .parse()
1470 .unwrap();
1471 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1472 let expected = r#"Package: blah
1473Section: libs
1474
1475Package: foo
1476Description: this is a
1477 bar
1478 blah
1479"#
1480 .to_owned();
1481 assert_eq!(control.to_string(), expected);
1482 }
1483
1484 #[test]
1485 fn test_wrap_and_sort_source() {
1486 let mut control: Control = r#"Source: blah
1487Depends: foo, bar (<= 1.0.0)
1488
1489"#
1490 .parse()
1491 .unwrap();
1492 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1493 let expected = r#"Source: blah
1494Depends: bar (<= 1.0.0), foo
1495"#
1496 .to_owned();
1497 assert_eq!(control.to_string(), expected);
1498 }
1499
1500 #[test]
1501 fn test_source_wrap_and_sort() {
1502 let control: Control = r#"Source: blah
1503Build-Depends: foo, bar (>= 1.0.0)
1504
1505"#
1506 .parse()
1507 .unwrap();
1508 let mut source = control.source().unwrap();
1509 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1510 assert!(source.build_depends().is_some());
1514 }
1515
1516 #[test]
1517 fn test_binary_set_breaks() {
1518 let mut control = Control::new();
1519 let mut binary = control.add_binary("foo");
1520 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1521 binary.set_breaks(Some(&relations));
1522 assert!(binary.breaks().is_some());
1523 }
1524
1525 #[test]
1526 fn test_binary_set_pre_depends() {
1527 let mut control = Control::new();
1528 let mut binary = control.add_binary("foo");
1529 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1530 binary.set_pre_depends(Some(&relations));
1531 assert!(binary.pre_depends().is_some());
1532 }
1533
1534 #[test]
1535 fn test_binary_set_provides() {
1536 let mut control = Control::new();
1537 let mut binary = control.add_binary("foo");
1538 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1539 binary.set_provides(Some(&relations));
1540 assert!(binary.provides().is_some());
1541 }
1542
1543 #[test]
1544 fn test_source_build_conflicts() {
1545 let control: Control = r#"Source: blah
1546Build-Conflicts: foo, bar (>= 1.0.0)
1547
1548"#
1549 .parse()
1550 .unwrap();
1551 let source = control.source().unwrap();
1552 let conflicts = source.build_conflicts();
1553 assert!(conflicts.is_some());
1554 }
1555
1556 #[test]
1557 fn test_source_vcs_svn() {
1558 let control: Control = r#"Source: blah
1559Vcs-Svn: https://example.com/svn/repo
1560
1561"#
1562 .parse()
1563 .unwrap();
1564 let source = control.source().unwrap();
1565 assert_eq!(
1566 source.vcs_svn(),
1567 Some("https://example.com/svn/repo".to_string())
1568 );
1569 }
1570
1571 #[test]
1572 fn test_control_from_conversion() {
1573 let deb822_data = r#"Source: test
1574Section: libs
1575
1576"#;
1577 let deb822: Deb822 = deb822_data.parse().unwrap();
1578 let control = Control::from(deb822);
1579 assert!(control.source().is_some());
1580 }
1581
1582 #[test]
1583 fn test_fields_in_range() {
1584 let control_text = r#"Source: test-package
1585Maintainer: Test User <test@example.com>
1586Build-Depends: debhelper (>= 12)
1587
1588Package: test-binary
1589Architecture: any
1590Depends: ${shlibs:Depends}
1591Description: Test package
1592 This is a test package
1593"#;
1594 let control: Control = control_text.parse().unwrap();
1595
1596 let source_start = 0;
1598 let source_end = "Source: test-package".len();
1599 let source_range =
1600 rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1601
1602 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1603 assert_eq!(fields.len(), 1);
1604 assert_eq!(fields[0].key(), Some("Source".to_string()));
1605
1606 let maintainer_start = control_text.find("Maintainer:").unwrap();
1608 let build_depends_end = control_text
1609 .find("Build-Depends: debhelper (>= 12)")
1610 .unwrap()
1611 + "Build-Depends: debhelper (>= 12)".len();
1612 let multi_range = rowan::TextRange::new(
1613 (maintainer_start as u32).into(),
1614 (build_depends_end as u32).into(),
1615 );
1616
1617 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1618 assert_eq!(fields.len(), 2);
1619 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1620 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1621
1622 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1624 let cross_para_end =
1625 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1626 let cross_range = rowan::TextRange::new(
1627 (cross_para_start as u32).into(),
1628 (cross_para_end as u32).into(),
1629 );
1630
1631 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1632 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1634 assert_eq!(fields[1].key(), Some("Package".to_string()));
1635 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1636
1637 let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1639 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1640 assert_eq!(fields.len(), 0);
1641 }
1642
1643 #[test]
1644 fn test_source_overlaps_range() {
1645 let control_text = r#"Source: test-package
1646Maintainer: Test User <test@example.com>
1647
1648Package: test-binary
1649Architecture: any
1650"#;
1651 let control: Control = control_text.parse().unwrap();
1652 let source = control.source().unwrap();
1653
1654 let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1656 assert!(source.overlaps_range(overlap_range));
1657
1658 let binary_start = control_text.find("Package:").unwrap();
1660 let no_overlap_range = rowan::TextRange::new(
1661 (binary_start as u32).into(),
1662 ((binary_start + 20) as u32).into(),
1663 );
1664 assert!(!source.overlaps_range(no_overlap_range));
1665
1666 let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1668 assert!(source.overlaps_range(partial_overlap));
1669 }
1670
1671 #[test]
1672 fn test_source_fields_in_range() {
1673 let control_text = r#"Source: test-package
1674Maintainer: Test User <test@example.com>
1675Build-Depends: debhelper (>= 12)
1676
1677Package: test-binary
1678"#;
1679 let control: Control = control_text.parse().unwrap();
1680 let source = control.source().unwrap();
1681
1682 let maintainer_start = control_text.find("Maintainer:").unwrap();
1684 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1685 let maintainer_range = rowan::TextRange::new(
1686 (maintainer_start as u32).into(),
1687 (maintainer_end as u32).into(),
1688 );
1689
1690 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1691 assert_eq!(fields.len(), 1);
1692 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1693
1694 let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1696 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1697 assert_eq!(fields.len(), 3); }
1699
1700 #[test]
1701 fn test_binary_overlaps_range() {
1702 let control_text = r#"Source: test-package
1703
1704Package: test-binary
1705Architecture: any
1706Depends: ${shlibs:Depends}
1707"#;
1708 let control: Control = control_text.parse().unwrap();
1709 let binary = control.binaries().next().unwrap();
1710
1711 let package_start = control_text.find("Package:").unwrap();
1713 let overlap_range = rowan::TextRange::new(
1714 (package_start as u32).into(),
1715 ((package_start + 30) as u32).into(),
1716 );
1717 assert!(binary.overlaps_range(overlap_range));
1718
1719 let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1721 assert!(!binary.overlaps_range(no_overlap_range));
1722 }
1723
1724 #[test]
1725 fn test_binary_fields_in_range() {
1726 let control_text = r#"Source: test-package
1727
1728Package: test-binary
1729Architecture: any
1730Depends: ${shlibs:Depends}
1731Description: Test binary
1732 This is a test binary package
1733"#;
1734 let control: Control = control_text.parse().unwrap();
1735 let binary = control.binaries().next().unwrap();
1736
1737 let arch_start = control_text.find("Architecture:").unwrap();
1739 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1740 + "Depends: ${shlibs:Depends}".len();
1741 let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1742
1743 let fields: Vec<_> = binary.fields_in_range(range).collect();
1744 assert_eq!(fields.len(), 2);
1745 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1746 assert_eq!(fields[1].key(), Some("Depends".to_string()));
1747
1748 let desc_start = control_text.find("Description:").unwrap();
1750 let partial_range = rowan::TextRange::new(
1751 ((desc_start + 5) as u32).into(),
1752 ((desc_start + 15) as u32).into(),
1753 );
1754 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1755 assert_eq!(fields.len(), 1);
1756 assert_eq!(fields[0].key(), Some("Description".to_string()));
1757 }
1758
1759 #[test]
1760 fn test_incremental_parsing_use_case() {
1761 let control_text = r#"Source: example
1763Maintainer: John Doe <john@example.com>
1764Standards-Version: 4.6.0
1765Build-Depends: debhelper-compat (= 13)
1766
1767Package: example-bin
1768Architecture: all
1769Depends: ${misc:Depends}
1770Description: Example package
1771 This is an example.
1772"#;
1773 let control: Control = control_text.parse().unwrap();
1774
1775 let change_start = control_text.find("Standards-Version:").unwrap();
1777 let change_end = change_start + "Standards-Version: 4.6.0".len();
1778 let change_range =
1779 rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
1780
1781 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
1783 assert_eq!(affected_fields.len(), 1);
1784 assert_eq!(
1785 affected_fields[0].key(),
1786 Some("Standards-Version".to_string())
1787 );
1788
1789 for entry in &affected_fields {
1791 let key = entry.key().unwrap();
1792 assert_ne!(key, "Maintainer");
1793 assert_ne!(key, "Build-Depends");
1794 assert_ne!(key, "Architecture");
1795 }
1796 }
1797
1798 #[test]
1799 fn test_positioned_parse_errors() {
1800 let input = "Invalid: field\nBroken field without colon";
1802 let parsed = Control::parse(input);
1803
1804 let positioned_errors = parsed.positioned_errors();
1806 assert!(
1807 !positioned_errors.is_empty(),
1808 "Should have positioned errors"
1809 );
1810
1811 for error in positioned_errors {
1813 let start_offset: u32 = error.range.start().into();
1814 let end_offset: u32 = error.range.end().into();
1815
1816 assert!(!error.message.is_empty());
1818
1819 assert!(start_offset <= end_offset);
1821 assert!(end_offset <= input.len() as u32);
1822
1823 assert!(error.code.is_some());
1825
1826 println!(
1827 "Error at {:?}: {} (code: {:?})",
1828 error.range, error.message, error.code
1829 );
1830 }
1831
1832 let string_errors = parsed.errors();
1834 assert!(!string_errors.is_empty());
1835 assert_eq!(string_errors.len(), positioned_errors.len());
1836 }
1837
1838 #[test]
1839 fn test_sort_binaries_basic() {
1840 let input = r#"Source: foo
1841
1842Package: libfoo
1843Architecture: all
1844
1845Package: libbar
1846Architecture: all
1847"#;
1848
1849 let mut control: Control = input.parse().unwrap();
1850 control.sort_binaries(false);
1851
1852 let binaries: Vec<_> = control.binaries().collect();
1853 assert_eq!(binaries.len(), 2);
1854 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
1855 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
1856 }
1857
1858 #[test]
1859 fn test_sort_binaries_keep_first() {
1860 let input = r#"Source: foo
1861
1862Package: zzz-first
1863Architecture: all
1864
1865Package: libbar
1866Architecture: all
1867
1868Package: libaaa
1869Architecture: all
1870"#;
1871
1872 let mut control: Control = input.parse().unwrap();
1873 control.sort_binaries(true);
1874
1875 let binaries: Vec<_> = control.binaries().collect();
1876 assert_eq!(binaries.len(), 3);
1877 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
1879 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
1881 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
1882 }
1883
1884 #[test]
1885 fn test_sort_binaries_already_sorted() {
1886 let input = r#"Source: foo
1887
1888Package: aaa
1889Architecture: all
1890
1891Package: bbb
1892Architecture: all
1893
1894Package: ccc
1895Architecture: all
1896"#;
1897
1898 let mut control: Control = input.parse().unwrap();
1899 control.sort_binaries(false);
1900
1901 let binaries: Vec<_> = control.binaries().collect();
1902 assert_eq!(binaries.len(), 3);
1903 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1904 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
1905 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
1906 }
1907
1908 #[test]
1909 fn test_sort_binaries_no_binaries() {
1910 let input = r#"Source: foo
1911Maintainer: test@example.com
1912"#;
1913
1914 let mut control: Control = input.parse().unwrap();
1915 control.sort_binaries(false);
1916
1917 assert_eq!(control.binaries().count(), 0);
1919 }
1920
1921 #[test]
1922 fn test_sort_binaries_one_binary() {
1923 let input = r#"Source: foo
1924
1925Package: bar
1926Architecture: all
1927"#;
1928
1929 let mut control: Control = input.parse().unwrap();
1930 control.sort_binaries(false);
1931
1932 let binaries: Vec<_> = control.binaries().collect();
1933 assert_eq!(binaries.len(), 1);
1934 assert_eq!(binaries[0].name(), Some("bar".to_string()));
1935 }
1936
1937 #[test]
1938 fn test_sort_binaries_preserves_fields() {
1939 let input = r#"Source: foo
1940
1941Package: zzz
1942Architecture: any
1943Depends: libc6
1944Description: ZZZ package
1945
1946Package: aaa
1947Architecture: all
1948Depends: ${misc:Depends}
1949Description: AAA package
1950"#;
1951
1952 let mut control: Control = input.parse().unwrap();
1953 control.sort_binaries(false);
1954
1955 let binaries: Vec<_> = control.binaries().collect();
1956 assert_eq!(binaries.len(), 2);
1957
1958 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1960 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
1961 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
1962
1963 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
1965 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
1966 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
1967 }
1968
1969 #[test]
1970 fn test_remove_binary_basic() {
1971 let mut control = Control::new();
1972 control.add_binary("foo");
1973 assert_eq!(control.binaries().count(), 1);
1974 assert!(control.remove_binary("foo"));
1975 assert_eq!(control.binaries().count(), 0);
1976 }
1977
1978 #[test]
1979 fn test_remove_binary_nonexistent() {
1980 let mut control = Control::new();
1981 control.add_binary("foo");
1982 assert!(!control.remove_binary("bar"));
1983 assert_eq!(control.binaries().count(), 1);
1984 }
1985
1986 #[test]
1987 fn test_remove_binary_multiple() {
1988 let mut control = Control::new();
1989 control.add_binary("foo");
1990 control.add_binary("bar");
1991 control.add_binary("baz");
1992 assert_eq!(control.binaries().count(), 3);
1993
1994 assert!(control.remove_binary("bar"));
1995 assert_eq!(control.binaries().count(), 2);
1996
1997 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
1998 assert_eq!(names, vec!["foo", "baz"]);
1999 }
2000
2001 #[test]
2002 fn test_remove_binary_preserves_source() {
2003 let input = r#"Source: mypackage
2004
2005Package: foo
2006Architecture: all
2007
2008Package: bar
2009Architecture: all
2010"#;
2011 let mut control: Control = input.parse().unwrap();
2012 assert!(control.source().is_some());
2013 assert_eq!(control.binaries().count(), 2);
2014
2015 assert!(control.remove_binary("foo"));
2016
2017 assert!(control.source().is_some());
2019 assert_eq!(
2020 control.source().unwrap().name(),
2021 Some("mypackage".to_string())
2022 );
2023
2024 assert_eq!(control.binaries().count(), 1);
2026 assert_eq!(
2027 control.binaries().next().unwrap().name(),
2028 Some("bar".to_string())
2029 );
2030 }
2031
2032 #[test]
2033 fn test_remove_binary_from_parsed() {
2034 let input = r#"Source: test
2035
2036Package: test-bin
2037Architecture: any
2038Depends: libc6
2039Description: Test binary
2040
2041Package: test-lib
2042Architecture: all
2043Description: Test library
2044"#;
2045 let mut control: Control = input.parse().unwrap();
2046 assert_eq!(control.binaries().count(), 2);
2047
2048 assert!(control.remove_binary("test-bin"));
2049
2050 let output = control.to_string();
2051 assert!(!output.contains("test-bin"));
2052 assert!(output.contains("test-lib"));
2053 assert!(output.contains("Source: test"));
2054 }
2055}