1use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph, TextRange};
38use rowan::ast::AstNode;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ParseMode {
43 Strict,
45 Relaxed,
47 Substvar,
49}
50
51pub const SOURCE_FIELD_ORDER: &[&str] = &[
53 "Source",
54 "Section",
55 "Priority",
56 "Maintainer",
57 "Uploaders",
58 "Build-Depends",
59 "Build-Depends-Indep",
60 "Build-Depends-Arch",
61 "Build-Conflicts",
62 "Build-Conflicts-Indep",
63 "Build-Conflicts-Arch",
64 "Standards-Version",
65 "Vcs-Browser",
66 "Vcs-Git",
67 "Vcs-Svn",
68 "Vcs-Bzr",
69 "Vcs-Hg",
70 "Vcs-Darcs",
71 "Vcs-Cvs",
72 "Vcs-Arch",
73 "Vcs-Mtn",
74 "Homepage",
75 "Rules-Requires-Root",
76 "Testsuite",
77 "Testsuite-Triggers",
78];
79
80pub const BINARY_FIELD_ORDER: &[&str] = &[
82 "Package",
83 "Architecture",
84 "Section",
85 "Priority",
86 "Multi-Arch",
87 "Essential",
88 "Build-Profiles",
89 "Built-Using",
90 "Static-Built-Using",
91 "Pre-Depends",
92 "Depends",
93 "Recommends",
94 "Suggests",
95 "Enhances",
96 "Conflicts",
97 "Breaks",
98 "Replaces",
99 "Provides",
100 "Description",
101];
102
103fn format_field(name: &str, value: &str) -> String {
104 match name {
105 "Uploaders" => value
106 .split(',')
107 .map(|s| s.trim().to_string())
108 .collect::<Vec<_>>()
109 .join(",\n"),
110 "Build-Depends"
111 | "Build-Depends-Indep"
112 | "Build-Depends-Arch"
113 | "Build-Conflicts"
114 | "Build-Conflicts-Indep"
115 | "Build-Conflics-Arch"
116 | "Depends"
117 | "Recommends"
118 | "Suggests"
119 | "Enhances"
120 | "Pre-Depends"
121 | "Breaks" => {
122 match value.parse::<Relations>() {
125 Ok(relations) => {
126 let relations = relations.wrap_and_sort();
127 relations.to_string()
128 }
129 Err(_) => value.to_string(),
130 }
131 }
132 _ => value.to_string(),
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct Control {
139 deb822: Deb822,
140 parse_mode: ParseMode,
141}
142
143impl Control {
144 pub fn new() -> Self {
146 Control {
147 deb822: Deb822::new(),
148 parse_mode: ParseMode::Strict,
149 }
150 }
151
152 pub fn new_with_mode(parse_mode: ParseMode) -> Self {
154 Control {
155 deb822: Deb822::new(),
156 parse_mode,
157 }
158 }
159
160 pub fn parse_mode(&self) -> ParseMode {
162 self.parse_mode
163 }
164
165 pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
167 &mut self.deb822
168 }
169
170 pub fn as_deb822(&self) -> &Deb822 {
172 &self.deb822
173 }
174
175 pub fn snapshot(&self) -> Self {
184 Control {
185 deb822: self.deb822.snapshot(),
186 parse_mode: self.parse_mode,
187 }
188 }
189
190 pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
192 let deb822_parse = Deb822::parse(text);
193 let green = deb822_parse.green().clone();
195 let errors = deb822_parse.errors().to_vec();
196 let positioned_errors = deb822_parse.positioned_errors().to_vec();
197 deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
198 }
199
200 pub fn source(&self) -> Option<Source> {
202 let parse_mode = self.parse_mode;
203 self.deb822
204 .paragraphs()
205 .find(|p| p.get("Source").is_some())
206 .map(|paragraph| Source {
207 paragraph,
208 parse_mode,
209 })
210 }
211
212 pub fn binaries(&self) -> impl Iterator<Item = Binary> + '_ {
214 let parse_mode = self.parse_mode;
215 self.deb822
216 .paragraphs()
217 .filter(|p| p.get("Package").is_some())
218 .map(move |paragraph| Binary {
219 paragraph,
220 parse_mode,
221 })
222 }
223
224 pub fn source_in_range(&self, range: TextRange) -> Option<Source> {
232 self.source().filter(|s| {
233 let para_range = s.as_deb822().text_range();
234 para_range.start() < range.end() && para_range.end() > range.start()
235 })
236 }
237
238 pub fn binaries_in_range(&self, range: TextRange) -> impl Iterator<Item = Binary> + '_ {
246 self.binaries().filter(move |b| {
247 let para_range = b.as_deb822().text_range();
248 para_range.start() < range.end() && para_range.end() > range.start()
249 })
250 }
251
252 pub fn add_source(&mut self, name: &str) -> Source {
268 let mut p = self.deb822.add_paragraph();
269 p.set("Source", name);
270 self.source().unwrap()
271 }
272
273 pub fn add_binary(&mut self, name: &str) -> Binary {
289 let mut p = self.deb822.add_paragraph();
290 p.set("Package", name);
291 Binary {
292 paragraph: p,
293 parse_mode: ParseMode::Strict,
294 }
295 }
296
297 pub fn remove_binary(&mut self, name: &str) -> bool {
315 let index = self
316 .deb822
317 .paragraphs()
318 .position(|p| p.get("Package").as_deref() == Some(name));
319
320 if let Some(index) = index {
321 self.deb822.remove_paragraph(index);
322 true
323 } else {
324 false
325 }
326 }
327
328 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
330 Ok(Control {
331 deb822: Deb822::from_file(path)?,
332 parse_mode: ParseMode::Strict,
333 })
334 }
335
336 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
338 path: P,
339 ) -> Result<(Self, Vec<String>), std::io::Error> {
340 let (deb822, errors) = Deb822::from_file_relaxed(path)?;
341 Ok((
342 Control {
343 deb822,
344 parse_mode: ParseMode::Relaxed,
345 },
346 errors,
347 ))
348 }
349
350 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
352 Ok(Control {
353 deb822: Deb822::read(&mut r)?,
354 parse_mode: ParseMode::Strict,
355 })
356 }
357
358 pub fn read_relaxed<R: std::io::Read>(
360 mut r: R,
361 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
362 let (deb822, errors) = Deb822::read_relaxed(&mut r)?;
363 Ok((
364 Control {
365 deb822,
366 parse_mode: ParseMode::Relaxed,
367 },
368 errors,
369 ))
370 }
371
372 pub fn wrap_and_sort(
379 &mut self,
380 indentation: deb822_lossless::Indentation,
381 immediate_empty_line: bool,
382 max_line_length_one_liner: Option<usize>,
383 ) {
384 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
385 let a_is_source = a.get("Source").is_some();
387 let b_is_source = b.get("Source").is_some();
388
389 if a_is_source && !b_is_source {
390 return std::cmp::Ordering::Less;
391 } else if !a_is_source && b_is_source {
392 return std::cmp::Ordering::Greater;
393 } else if a_is_source && b_is_source {
394 return a.get("Source").cmp(&b.get("Source"));
395 }
396
397 a.get("Package").cmp(&b.get("Package"))
398 };
399
400 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
401 p.wrap_and_sort(
404 indentation,
405 immediate_empty_line,
406 max_line_length_one_liner,
407 None,
408 Some(&format_field),
409 )
410 };
411
412 self.deb822 = self
413 .deb822
414 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
415 }
416
417 pub fn sort_binaries(&mut self, keep_first: bool) {
448 let mut paragraphs: Vec<_> = self.deb822.paragraphs().collect();
449
450 if paragraphs.len() <= 1 {
451 return; }
453
454 let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
456 let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
457
458 let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
460 binary_start + 1
461 } else {
462 binary_start
463 };
464
465 if sort_start >= paragraphs.len() {
466 return; }
468
469 paragraphs[sort_start..].sort_by(|a, b| {
471 let a_name = a.get("Package");
472 let b_name = b.get("Package");
473 a_name.cmp(&b_name)
474 });
475
476 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
478 let a_pos = paragraphs.iter().position(|p| p == a);
479 let b_pos = paragraphs.iter().position(|p| p == b);
480 a_pos.cmp(&b_pos)
481 };
482
483 self.deb822 = self.deb822.wrap_and_sort(Some(&sort_paragraphs), None);
484 }
485
486 pub fn fields_in_range(
515 &self,
516 range: TextRange,
517 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
518 self.deb822
519 .paragraphs()
520 .flat_map(move |p| p.entries().collect::<Vec<_>>())
521 .filter(move |entry| {
522 let entry_range = entry.syntax().text_range();
523 entry_range.start() < range.end() && range.start() < entry_range.end()
525 })
526 }
527}
528
529impl From<Control> for Deb822 {
530 fn from(c: Control) -> Self {
531 c.deb822
532 }
533}
534
535impl From<Deb822> for Control {
536 fn from(d: Deb822) -> Self {
537 Control {
538 deb822: d,
539 parse_mode: ParseMode::Strict,
540 }
541 }
542}
543
544impl Default for Control {
545 fn default() -> Self {
546 Self::new()
547 }
548}
549
550impl std::str::FromStr for Control {
551 type Err = deb822_lossless::ParseError;
552
553 fn from_str(s: &str) -> Result<Self, Self::Err> {
554 Control::parse(s).to_result()
555 }
556}
557
558#[derive(Debug, Clone, PartialEq, Eq)]
560pub struct Source {
561 paragraph: Paragraph,
562 parse_mode: ParseMode,
563}
564
565impl From<Source> for Paragraph {
566 fn from(s: Source) -> Self {
567 s.paragraph
568 }
569}
570
571impl From<Paragraph> for Source {
572 fn from(p: Paragraph) -> Self {
573 Source {
574 paragraph: p,
575 parse_mode: ParseMode::Strict,
576 }
577 }
578}
579
580impl std::fmt::Display for Source {
581 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
582 self.paragraph.fmt(f)
583 }
584}
585
586impl Source {
587 fn parse_relations(&self, s: &str) -> Relations {
589 match self.parse_mode {
590 ParseMode::Strict => s.parse().unwrap(),
591 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
592 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
593 }
594 }
595
596 pub fn name(&self) -> Option<String> {
598 self.paragraph.get("Source")
599 }
600
601 pub fn wrap_and_sort(
603 &mut self,
604 indentation: deb822_lossless::Indentation,
605 immediate_empty_line: bool,
606 max_line_length_one_liner: Option<usize>,
607 ) {
608 self.paragraph = self.paragraph.wrap_and_sort(
609 indentation,
610 immediate_empty_line,
611 max_line_length_one_liner,
612 None,
613 Some(&format_field),
614 );
615 }
616
617 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
619 &mut self.paragraph
620 }
621
622 pub fn as_deb822(&self) -> &Paragraph {
624 &self.paragraph
625 }
626
627 pub fn set_name(&mut self, name: &str) {
629 self.set("Source", name);
630 }
631
632 pub fn section(&self) -> Option<String> {
634 self.paragraph.get("Section")
635 }
636
637 pub fn set_section(&mut self, section: Option<&str>) {
639 if let Some(section) = section {
640 self.set("Section", section);
641 } else {
642 self.paragraph.remove("Section");
643 }
644 }
645
646 pub fn priority(&self) -> Option<Priority> {
648 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
649 }
650
651 pub fn set_priority(&mut self, priority: Option<Priority>) {
653 if let Some(priority) = priority {
654 self.set("Priority", priority.to_string().as_str());
655 } else {
656 self.paragraph.remove("Priority");
657 }
658 }
659
660 pub fn maintainer(&self) -> Option<String> {
662 self.paragraph.get("Maintainer")
663 }
664
665 pub fn set_maintainer(&mut self, maintainer: &str) {
667 self.set("Maintainer", maintainer);
668 }
669
670 pub fn build_depends(&self) -> Option<Relations> {
672 self.paragraph
673 .get("Build-Depends")
674 .map(|s| self.parse_relations(&s))
675 }
676
677 pub fn set_build_depends(&mut self, relations: &Relations) {
679 self.set("Build-Depends", relations.to_string().as_str());
680 }
681
682 pub fn build_depends_indep(&self) -> Option<Relations> {
684 self.paragraph
685 .get("Build-Depends-Indep")
686 .map(|s| self.parse_relations(&s))
687 }
688
689 pub fn set_build_depends_indep(&mut self, relations: &Relations) {
691 self.set("Build-Depends-Indep", relations.to_string().as_str());
692 }
693
694 pub fn build_depends_arch(&self) -> Option<Relations> {
696 self.paragraph
697 .get("Build-Depends-Arch")
698 .map(|s| self.parse_relations(&s))
699 }
700
701 pub fn set_build_depends_arch(&mut self, relations: &Relations) {
703 self.set("Build-Depends-Arch", relations.to_string().as_str());
704 }
705
706 pub fn build_conflicts(&self) -> Option<Relations> {
708 self.paragraph
709 .get("Build-Conflicts")
710 .map(|s| self.parse_relations(&s))
711 }
712
713 pub fn set_build_conflicts(&mut self, relations: &Relations) {
715 self.set("Build-Conflicts", relations.to_string().as_str());
716 }
717
718 pub fn build_conflicts_indep(&self) -> Option<Relations> {
720 self.paragraph
721 .get("Build-Conflicts-Indep")
722 .map(|s| self.parse_relations(&s))
723 }
724
725 pub fn set_build_conflicts_indep(&mut self, relations: &Relations) {
727 self.set("Build-Conflicts-Indep", relations.to_string().as_str());
728 }
729
730 pub fn build_conflicts_arch(&self) -> Option<Relations> {
732 self.paragraph
733 .get("Build-Conflicts-Arch")
734 .map(|s| self.parse_relations(&s))
735 }
736
737 pub fn standards_version(&self) -> Option<String> {
739 self.paragraph.get("Standards-Version")
740 }
741
742 pub fn set_standards_version(&mut self, version: &str) {
744 self.set("Standards-Version", version);
745 }
746
747 pub fn homepage(&self) -> Option<url::Url> {
749 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
750 }
751
752 pub fn set_homepage(&mut self, homepage: &url::Url) {
754 self.set("Homepage", homepage.to_string().as_str());
755 }
756
757 pub fn vcs_git(&self) -> Option<String> {
759 self.paragraph.get("Vcs-Git")
760 }
761
762 pub fn set_vcs_git(&mut self, url: &str) {
764 self.set("Vcs-Git", url);
765 }
766
767 pub fn vcs_svn(&self) -> Option<String> {
769 self.paragraph.get("Vcs-Svn").map(|s| s.to_string())
770 }
771
772 pub fn set_vcs_svn(&mut self, url: &str) {
774 self.set("Vcs-Svn", url);
775 }
776
777 pub fn vcs_bzr(&self) -> Option<String> {
779 self.paragraph.get("Vcs-Bzr").map(|s| s.to_string())
780 }
781
782 pub fn set_vcs_bzr(&mut self, url: &str) {
784 self.set("Vcs-Bzr", url);
785 }
786
787 pub fn vcs_arch(&self) -> Option<String> {
789 self.paragraph.get("Vcs-Arch").map(|s| s.to_string())
790 }
791
792 pub fn set_vcs_arch(&mut self, url: &str) {
794 self.set("Vcs-Arch", url);
795 }
796
797 pub fn vcs_svk(&self) -> Option<String> {
799 self.paragraph.get("Vcs-Svk").map(|s| s.to_string())
800 }
801
802 pub fn set_vcs_svk(&mut self, url: &str) {
804 self.set("Vcs-Svk", url);
805 }
806
807 pub fn vcs_darcs(&self) -> Option<String> {
809 self.paragraph.get("Vcs-Darcs").map(|s| s.to_string())
810 }
811
812 pub fn set_vcs_darcs(&mut self, url: &str) {
814 self.set("Vcs-Darcs", url);
815 }
816
817 pub fn vcs_mtn(&self) -> Option<String> {
819 self.paragraph.get("Vcs-Mtn").map(|s| s.to_string())
820 }
821
822 pub fn set_vcs_mtn(&mut self, url: &str) {
824 self.set("Vcs-Mtn", url);
825 }
826
827 pub fn vcs_cvs(&self) -> Option<String> {
829 self.paragraph.get("Vcs-Cvs").map(|s| s.to_string())
830 }
831
832 pub fn set_vcs_cvs(&mut self, url: &str) {
834 self.set("Vcs-Cvs", url);
835 }
836
837 pub fn vcs_hg(&self) -> Option<String> {
839 self.paragraph.get("Vcs-Hg").map(|s| s.to_string())
840 }
841
842 pub fn set_vcs_hg(&mut self, url: &str) {
844 self.set("Vcs-Hg", url);
845 }
846
847 pub fn set(&mut self, key: &str, value: &str) {
849 self.paragraph
850 .set_with_field_order(key, value, SOURCE_FIELD_ORDER);
851 }
852
853 pub fn get(&self, key: &str) -> Option<String> {
855 self.paragraph.get(key)
856 }
857
858 pub fn vcs_browser(&self) -> Option<String> {
860 self.paragraph.get("Vcs-Browser")
861 }
862
863 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
865 for (name, value) in self.paragraph.items() {
866 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
867 return crate::vcs::Vcs::from_field(&name, &value).ok();
868 }
869 }
870 None
871 }
872
873 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
875 if let Some(url) = url {
876 self.set("Vcs-Browser", url);
877 } else {
878 self.paragraph.remove("Vcs-Browser");
879 }
880 }
881
882 pub fn uploaders(&self) -> Option<Vec<String>> {
884 self.paragraph
885 .get("Uploaders")
886 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
887 }
888
889 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
891 self.set(
892 "Uploaders",
893 uploaders
894 .iter()
895 .map(|s| s.to_string())
896 .collect::<Vec<_>>()
897 .join(", ")
898 .as_str(),
899 );
900 }
901
902 pub fn architecture(&self) -> Option<String> {
904 self.paragraph.get("Architecture")
905 }
906
907 pub fn set_architecture(&mut self, arch: Option<&str>) {
909 if let Some(arch) = arch {
910 self.set("Architecture", arch);
911 } else {
912 self.paragraph.remove("Architecture");
913 }
914 }
915
916 pub fn rules_requires_root(&self) -> Option<bool> {
918 self.paragraph
919 .get("Rules-Requires-Root")
920 .map(|s| match s.to_lowercase().as_str() {
921 "yes" => true,
922 "no" => false,
923 _ => panic!("invalid Rules-Requires-Root value"),
924 })
925 }
926
927 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
929 self.set(
930 "Rules-Requires-Root",
931 if requires_root { "yes" } else { "no" },
932 );
933 }
934
935 pub fn testsuite(&self) -> Option<String> {
937 self.paragraph.get("Testsuite")
938 }
939
940 pub fn set_testsuite(&mut self, testsuite: &str) {
942 self.set("Testsuite", testsuite);
943 }
944
945 pub fn overlaps_range(&self, range: TextRange) -> bool {
953 let para_range = self.paragraph.syntax().text_range();
954 para_range.start() < range.end() && range.start() < para_range.end()
955 }
956
957 pub fn fields_in_range(
965 &self,
966 range: TextRange,
967 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
968 self.paragraph.entries().filter(move |entry| {
969 let entry_range = entry.syntax().text_range();
970 entry_range.start() < range.end() && range.start() < entry_range.end()
971 })
972 }
973}
974
975#[cfg(feature = "python-debian")]
976impl<'py> pyo3::IntoPyObject<'py> for Source {
977 type Target = pyo3::PyAny;
978 type Output = pyo3::Bound<'py, Self::Target>;
979 type Error = pyo3::PyErr;
980
981 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
982 self.paragraph.into_pyobject(py)
983 }
984}
985
986#[cfg(feature = "python-debian")]
987impl<'py> pyo3::IntoPyObject<'py> for &Source {
988 type Target = pyo3::PyAny;
989 type Output = pyo3::Bound<'py, Self::Target>;
990 type Error = pyo3::PyErr;
991
992 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
993 (&self.paragraph).into_pyobject(py)
994 }
995}
996
997#[cfg(feature = "python-debian")]
998impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
999 type Error = pyo3::PyErr;
1000
1001 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1002 Ok(Source {
1003 paragraph: ob.extract()?,
1004 parse_mode: ParseMode::Strict,
1005 })
1006 }
1007}
1008
1009impl std::fmt::Display for Control {
1010 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1011 self.deb822.fmt(f)
1012 }
1013}
1014
1015impl AstNode for Control {
1016 type Language = deb822_lossless::Lang;
1017
1018 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
1019 Deb822::can_cast(kind)
1020 }
1021
1022 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
1023 Deb822::cast(syntax).map(|deb822| Control {
1024 deb822,
1025 parse_mode: ParseMode::Strict,
1026 })
1027 }
1028
1029 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
1030 self.deb822.syntax()
1031 }
1032}
1033
1034#[derive(Debug, Clone, PartialEq, Eq)]
1036pub struct Binary {
1037 paragraph: Paragraph,
1038 parse_mode: ParseMode,
1039}
1040
1041impl From<Binary> for Paragraph {
1042 fn from(b: Binary) -> Self {
1043 b.paragraph
1044 }
1045}
1046
1047impl From<Paragraph> for Binary {
1048 fn from(p: Paragraph) -> Self {
1049 Binary {
1050 paragraph: p,
1051 parse_mode: ParseMode::Strict,
1052 }
1053 }
1054}
1055
1056#[cfg(feature = "python-debian")]
1057impl<'py> pyo3::IntoPyObject<'py> for Binary {
1058 type Target = pyo3::PyAny;
1059 type Output = pyo3::Bound<'py, Self::Target>;
1060 type Error = pyo3::PyErr;
1061
1062 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1063 self.paragraph.into_pyobject(py)
1064 }
1065}
1066
1067#[cfg(feature = "python-debian")]
1068impl<'py> pyo3::IntoPyObject<'py> for &Binary {
1069 type Target = pyo3::PyAny;
1070 type Output = pyo3::Bound<'py, Self::Target>;
1071 type Error = pyo3::PyErr;
1072
1073 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1074 (&self.paragraph).into_pyobject(py)
1075 }
1076}
1077
1078#[cfg(feature = "python-debian")]
1079impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
1080 type Error = pyo3::PyErr;
1081
1082 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1083 Ok(Binary {
1084 paragraph: ob.extract()?,
1085 parse_mode: ParseMode::Strict,
1086 })
1087 }
1088}
1089
1090impl Default for Binary {
1091 fn default() -> Self {
1092 Self::new()
1093 }
1094}
1095
1096impl std::fmt::Display for Binary {
1097 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1098 self.paragraph.fmt(f)
1099 }
1100}
1101
1102impl Binary {
1103 fn parse_relations(&self, s: &str) -> Relations {
1105 match self.parse_mode {
1106 ParseMode::Strict => s.parse().unwrap(),
1107 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
1108 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
1109 }
1110 }
1111
1112 pub fn new() -> Self {
1114 Binary {
1115 paragraph: Paragraph::new(),
1116 parse_mode: ParseMode::Strict,
1117 }
1118 }
1119
1120 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
1122 &mut self.paragraph
1123 }
1124
1125 pub fn as_deb822(&self) -> &Paragraph {
1127 &self.paragraph
1128 }
1129
1130 pub fn wrap_and_sort(
1132 &mut self,
1133 indentation: deb822_lossless::Indentation,
1134 immediate_empty_line: bool,
1135 max_line_length_one_liner: Option<usize>,
1136 ) {
1137 self.paragraph = self.paragraph.wrap_and_sort(
1138 indentation,
1139 immediate_empty_line,
1140 max_line_length_one_liner,
1141 None,
1142 Some(&format_field),
1143 );
1144 }
1145
1146 pub fn name(&self) -> Option<String> {
1148 self.paragraph.get("Package")
1149 }
1150
1151 pub fn set_name(&mut self, name: &str) {
1153 self.set("Package", name);
1154 }
1155
1156 pub fn section(&self) -> Option<String> {
1158 self.paragraph.get("Section")
1159 }
1160
1161 pub fn set_section(&mut self, section: Option<&str>) {
1163 if let Some(section) = section {
1164 self.set("Section", section);
1165 } else {
1166 self.paragraph.remove("Section");
1167 }
1168 }
1169
1170 pub fn priority(&self) -> Option<Priority> {
1172 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
1173 }
1174
1175 pub fn set_priority(&mut self, priority: Option<Priority>) {
1177 if let Some(priority) = priority {
1178 self.set("Priority", priority.to_string().as_str());
1179 } else {
1180 self.paragraph.remove("Priority");
1181 }
1182 }
1183
1184 pub fn architecture(&self) -> Option<String> {
1186 self.paragraph.get("Architecture")
1187 }
1188
1189 pub fn set_architecture(&mut self, arch: Option<&str>) {
1191 if let Some(arch) = arch {
1192 self.set("Architecture", arch);
1193 } else {
1194 self.paragraph.remove("Architecture");
1195 }
1196 }
1197
1198 pub fn depends(&self) -> Option<Relations> {
1200 self.paragraph
1201 .get("Depends")
1202 .map(|s| self.parse_relations(&s))
1203 }
1204
1205 pub fn set_depends(&mut self, depends: Option<&Relations>) {
1207 if let Some(depends) = depends {
1208 self.set("Depends", depends.to_string().as_str());
1209 } else {
1210 self.paragraph.remove("Depends");
1211 }
1212 }
1213
1214 pub fn recommends(&self) -> Option<Relations> {
1216 self.paragraph
1217 .get("Recommends")
1218 .map(|s| self.parse_relations(&s))
1219 }
1220
1221 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
1223 if let Some(recommends) = recommends {
1224 self.set("Recommends", recommends.to_string().as_str());
1225 } else {
1226 self.paragraph.remove("Recommends");
1227 }
1228 }
1229
1230 pub fn suggests(&self) -> Option<Relations> {
1232 self.paragraph
1233 .get("Suggests")
1234 .map(|s| self.parse_relations(&s))
1235 }
1236
1237 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
1239 if let Some(suggests) = suggests {
1240 self.set("Suggests", suggests.to_string().as_str());
1241 } else {
1242 self.paragraph.remove("Suggests");
1243 }
1244 }
1245
1246 pub fn enhances(&self) -> Option<Relations> {
1248 self.paragraph
1249 .get("Enhances")
1250 .map(|s| self.parse_relations(&s))
1251 }
1252
1253 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1255 if let Some(enhances) = enhances {
1256 self.set("Enhances", enhances.to_string().as_str());
1257 } else {
1258 self.paragraph.remove("Enhances");
1259 }
1260 }
1261
1262 pub fn pre_depends(&self) -> Option<Relations> {
1264 self.paragraph
1265 .get("Pre-Depends")
1266 .map(|s| self.parse_relations(&s))
1267 }
1268
1269 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1271 if let Some(pre_depends) = pre_depends {
1272 self.set("Pre-Depends", pre_depends.to_string().as_str());
1273 } else {
1274 self.paragraph.remove("Pre-Depends");
1275 }
1276 }
1277
1278 pub fn breaks(&self) -> Option<Relations> {
1280 self.paragraph
1281 .get("Breaks")
1282 .map(|s| self.parse_relations(&s))
1283 }
1284
1285 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1287 if let Some(breaks) = breaks {
1288 self.set("Breaks", breaks.to_string().as_str());
1289 } else {
1290 self.paragraph.remove("Breaks");
1291 }
1292 }
1293
1294 pub fn conflicts(&self) -> Option<Relations> {
1296 self.paragraph
1297 .get("Conflicts")
1298 .map(|s| self.parse_relations(&s))
1299 }
1300
1301 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1303 if let Some(conflicts) = conflicts {
1304 self.set("Conflicts", conflicts.to_string().as_str());
1305 } else {
1306 self.paragraph.remove("Conflicts");
1307 }
1308 }
1309
1310 pub fn replaces(&self) -> Option<Relations> {
1312 self.paragraph
1313 .get("Replaces")
1314 .map(|s| self.parse_relations(&s))
1315 }
1316
1317 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1319 if let Some(replaces) = replaces {
1320 self.set("Replaces", replaces.to_string().as_str());
1321 } else {
1322 self.paragraph.remove("Replaces");
1323 }
1324 }
1325
1326 pub fn provides(&self) -> Option<Relations> {
1328 self.paragraph
1329 .get("Provides")
1330 .map(|s| self.parse_relations(&s))
1331 }
1332
1333 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1335 if let Some(provides) = provides {
1336 self.set("Provides", provides.to_string().as_str());
1337 } else {
1338 self.paragraph.remove("Provides");
1339 }
1340 }
1341
1342 pub fn built_using(&self) -> Option<Relations> {
1344 self.paragraph
1345 .get("Built-Using")
1346 .map(|s| self.parse_relations(&s))
1347 }
1348
1349 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1351 if let Some(built_using) = built_using {
1352 self.set("Built-Using", built_using.to_string().as_str());
1353 } else {
1354 self.paragraph.remove("Built-Using");
1355 }
1356 }
1357
1358 pub fn static_built_using(&self) -> Option<Relations> {
1360 self.paragraph
1361 .get("Static-Built-Using")
1362 .map(|s| self.parse_relations(&s))
1363 }
1364
1365 pub fn set_static_built_using(&mut self, static_built_using: Option<&Relations>) {
1367 if let Some(static_built_using) = static_built_using {
1368 self.set(
1369 "Static-Built-Using",
1370 static_built_using.to_string().as_str(),
1371 );
1372 } else {
1373 self.paragraph.remove("Static-Built-Using");
1374 }
1375 }
1376
1377 pub fn multi_arch(&self) -> Option<MultiArch> {
1379 self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap())
1380 }
1381
1382 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1384 if let Some(multi_arch) = multi_arch {
1385 self.set("Multi-Arch", multi_arch.to_string().as_str());
1386 } else {
1387 self.paragraph.remove("Multi-Arch");
1388 }
1389 }
1390
1391 pub fn essential(&self) -> bool {
1393 self.paragraph
1394 .get("Essential")
1395 .map(|s| s == "yes")
1396 .unwrap_or(false)
1397 }
1398
1399 pub fn set_essential(&mut self, essential: bool) {
1401 if essential {
1402 self.set("Essential", "yes");
1403 } else {
1404 self.paragraph.remove("Essential");
1405 }
1406 }
1407
1408 pub fn description(&self) -> Option<String> {
1410 self.paragraph.get_multiline("Description")
1411 }
1412
1413 pub fn set_description(&mut self, description: Option<&str>) {
1415 if let Some(description) = description {
1416 self.paragraph.set_with_indent_pattern(
1417 "Description",
1418 description,
1419 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1420 Some(BINARY_FIELD_ORDER),
1421 );
1422 } else {
1423 self.paragraph.remove("Description");
1424 }
1425 }
1426
1427 pub fn homepage(&self) -> Option<url::Url> {
1429 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
1430 }
1431
1432 pub fn set_homepage(&mut self, url: &url::Url) {
1434 self.set("Homepage", url.as_str());
1435 }
1436
1437 pub fn set(&mut self, key: &str, value: &str) {
1439 self.paragraph
1440 .set_with_field_order(key, value, BINARY_FIELD_ORDER);
1441 }
1442
1443 pub fn get(&self, key: &str) -> Option<String> {
1445 self.paragraph.get(key)
1446 }
1447
1448 pub fn overlaps_range(&self, range: TextRange) -> bool {
1456 let para_range = self.paragraph.syntax().text_range();
1457 para_range.start() < range.end() && range.start() < para_range.end()
1458 }
1459
1460 pub fn fields_in_range(
1468 &self,
1469 range: TextRange,
1470 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1471 self.paragraph.entries().filter(move |entry| {
1472 let entry_range = entry.syntax().text_range();
1473 entry_range.start() < range.end() && range.start() < entry_range.end()
1474 })
1475 }
1476}
1477
1478#[cfg(test)]
1479mod tests {
1480 use super::*;
1481 use crate::relations::VersionConstraint;
1482
1483 #[test]
1484 fn test_source_set_field_ordering() {
1485 let mut control = Control::new();
1486 let mut source = control.add_source("mypackage");
1487
1488 source.set("Homepage", "https://example.com");
1490 source.set("Build-Depends", "debhelper");
1491 source.set("Standards-Version", "4.5.0");
1492 source.set("Maintainer", "Test <test@example.com>");
1493
1494 let output = source.to_string();
1496 let lines: Vec<&str> = output.lines().collect();
1497
1498 assert!(lines[0].starts_with("Source:"));
1500
1501 let maintainer_pos = lines
1503 .iter()
1504 .position(|l| l.starts_with("Maintainer:"))
1505 .unwrap();
1506 let build_depends_pos = lines
1507 .iter()
1508 .position(|l| l.starts_with("Build-Depends:"))
1509 .unwrap();
1510 let standards_pos = lines
1511 .iter()
1512 .position(|l| l.starts_with("Standards-Version:"))
1513 .unwrap();
1514 let homepage_pos = lines
1515 .iter()
1516 .position(|l| l.starts_with("Homepage:"))
1517 .unwrap();
1518
1519 assert!(maintainer_pos < build_depends_pos);
1521 assert!(build_depends_pos < standards_pos);
1522 assert!(standards_pos < homepage_pos);
1523 }
1524
1525 #[test]
1526 fn test_binary_set_field_ordering() {
1527 let mut control = Control::new();
1528 let mut binary = control.add_binary("mypackage");
1529
1530 binary.set("Description", "A test package");
1532 binary.set("Architecture", "amd64");
1533 binary.set("Depends", "libc6");
1534 binary.set("Section", "utils");
1535
1536 let output = binary.to_string();
1538 let lines: Vec<&str> = output.lines().collect();
1539
1540 assert!(lines[0].starts_with("Package:"));
1542
1543 let arch_pos = lines
1545 .iter()
1546 .position(|l| l.starts_with("Architecture:"))
1547 .unwrap();
1548 let section_pos = lines
1549 .iter()
1550 .position(|l| l.starts_with("Section:"))
1551 .unwrap();
1552 let depends_pos = lines
1553 .iter()
1554 .position(|l| l.starts_with("Depends:"))
1555 .unwrap();
1556 let desc_pos = lines
1557 .iter()
1558 .position(|l| l.starts_with("Description:"))
1559 .unwrap();
1560
1561 assert!(arch_pos < section_pos);
1563 assert!(section_pos < depends_pos);
1564 assert!(depends_pos < desc_pos);
1565 }
1566
1567 #[test]
1568 fn test_source_specific_set_methods_use_field_ordering() {
1569 let mut control = Control::new();
1570 let mut source = control.add_source("mypackage");
1571
1572 source.set_homepage(&"https://example.com".parse().unwrap());
1574 source.set_maintainer("Test <test@example.com>");
1575 source.set_standards_version("4.5.0");
1576 source.set_vcs_git("https://github.com/example/repo");
1577
1578 let output = source.to_string();
1580 let lines: Vec<&str> = output.lines().collect();
1581
1582 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1584 let maintainer_pos = lines
1585 .iter()
1586 .position(|l| l.starts_with("Maintainer:"))
1587 .unwrap();
1588 let standards_pos = lines
1589 .iter()
1590 .position(|l| l.starts_with("Standards-Version:"))
1591 .unwrap();
1592 let vcs_git_pos = lines
1593 .iter()
1594 .position(|l| l.starts_with("Vcs-Git:"))
1595 .unwrap();
1596 let homepage_pos = lines
1597 .iter()
1598 .position(|l| l.starts_with("Homepage:"))
1599 .unwrap();
1600
1601 assert!(source_pos < maintainer_pos);
1603 assert!(maintainer_pos < standards_pos);
1604 assert!(standards_pos < vcs_git_pos);
1605 assert!(vcs_git_pos < homepage_pos);
1606 }
1607
1608 #[test]
1609 fn test_binary_specific_set_methods_use_field_ordering() {
1610 let mut control = Control::new();
1611 let mut binary = control.add_binary("mypackage");
1612
1613 binary.set_description(Some("A test package"));
1615 binary.set_architecture(Some("amd64"));
1616 let depends = "libc6".parse().unwrap();
1617 binary.set_depends(Some(&depends));
1618 binary.set_section(Some("utils"));
1619 binary.set_priority(Some(Priority::Optional));
1620
1621 let output = binary.to_string();
1623 let lines: Vec<&str> = output.lines().collect();
1624
1625 let package_pos = lines
1627 .iter()
1628 .position(|l| l.starts_with("Package:"))
1629 .unwrap();
1630 let arch_pos = lines
1631 .iter()
1632 .position(|l| l.starts_with("Architecture:"))
1633 .unwrap();
1634 let section_pos = lines
1635 .iter()
1636 .position(|l| l.starts_with("Section:"))
1637 .unwrap();
1638 let priority_pos = lines
1639 .iter()
1640 .position(|l| l.starts_with("Priority:"))
1641 .unwrap();
1642 let depends_pos = lines
1643 .iter()
1644 .position(|l| l.starts_with("Depends:"))
1645 .unwrap();
1646 let desc_pos = lines
1647 .iter()
1648 .position(|l| l.starts_with("Description:"))
1649 .unwrap();
1650
1651 assert!(package_pos < arch_pos);
1653 assert!(arch_pos < section_pos);
1654 assert!(section_pos < priority_pos);
1655 assert!(priority_pos < depends_pos);
1656 assert!(depends_pos < desc_pos);
1657 }
1658
1659 #[test]
1660 fn test_parse() {
1661 let control: Control = r#"Source: foo
1662Section: libs
1663Priority: optional
1664Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1665Homepage: https://example.com
1666
1667"#
1668 .parse()
1669 .unwrap();
1670 let source = control.source().unwrap();
1671
1672 assert_eq!(source.name(), Some("foo".to_owned()));
1673 assert_eq!(source.section(), Some("libs".to_owned()));
1674 assert_eq!(source.priority(), Some(super::Priority::Optional));
1675 assert_eq!(
1676 source.homepage(),
1677 Some("https://example.com".parse().unwrap())
1678 );
1679 let bd = source.build_depends().unwrap();
1680 let entries = bd.entries().collect::<Vec<_>>();
1681 assert_eq!(entries.len(), 2);
1682 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1683 assert_eq!(rel.name(), "bar");
1684 assert_eq!(
1685 rel.version(),
1686 Some((
1687 VersionConstraint::GreaterThanEqual,
1688 "1.0.0".parse().unwrap()
1689 ))
1690 );
1691 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1692 assert_eq!(rel.name(), "baz");
1693 assert_eq!(
1694 rel.version(),
1695 Some((
1696 VersionConstraint::GreaterThanEqual,
1697 "1.0.0".parse().unwrap()
1698 ))
1699 );
1700 }
1701
1702 #[test]
1703 fn test_description() {
1704 let control: Control = r#"Source: foo
1705
1706Package: foo
1707Description: this is the short description
1708 And the longer one
1709 .
1710 is on the next lines
1711"#
1712 .parse()
1713 .unwrap();
1714 let binary = control.binaries().next().unwrap();
1715 assert_eq!(
1716 binary.description(),
1717 Some(
1718 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1719 .to_owned()
1720 )
1721 );
1722 }
1723
1724 #[test]
1725 fn test_set_description_on_package_without_description() {
1726 let control: Control = r#"Source: foo
1727
1728Package: foo
1729Architecture: amd64
1730"#
1731 .parse()
1732 .unwrap();
1733 let mut binary = control.binaries().next().unwrap();
1734
1735 binary.set_description(Some(
1737 "Short description\nLonger description\n.\nAnother line",
1738 ));
1739
1740 let output = binary.to_string();
1741
1742 assert_eq!(
1744 binary.description(),
1745 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1746 );
1747
1748 assert_eq!(
1750 output,
1751 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1752 );
1753 }
1754
1755 #[test]
1756 fn test_as_mut_deb822() {
1757 let mut control = Control::new();
1758 let deb822 = control.as_mut_deb822();
1759 let mut p = deb822.add_paragraph();
1760 p.set("Source", "foo");
1761 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1762 }
1763
1764 #[test]
1765 fn test_as_deb822() {
1766 let control = Control::new();
1767 let _deb822: &Deb822 = control.as_deb822();
1768 }
1769
1770 #[test]
1771 fn test_set_depends() {
1772 let mut control = Control::new();
1773 let mut binary = control.add_binary("foo");
1774 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1775 binary.set_depends(Some(&relations));
1776 }
1777
1778 #[test]
1779 fn test_wrap_and_sort() {
1780 let mut control: Control = r#"Package: blah
1781Section: libs
1782
1783
1784
1785Package: foo
1786Description: this is a
1787 bar
1788 blah
1789"#
1790 .parse()
1791 .unwrap();
1792 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1793 let expected = r#"Package: blah
1794Section: libs
1795
1796Package: foo
1797Description: this is a
1798 bar
1799 blah
1800"#
1801 .to_owned();
1802 assert_eq!(control.to_string(), expected);
1803 }
1804
1805 #[test]
1806 fn test_wrap_and_sort_source() {
1807 let mut control: Control = r#"Source: blah
1808Depends: foo, bar (<= 1.0.0)
1809
1810"#
1811 .parse()
1812 .unwrap();
1813 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1814 let expected = r#"Source: blah
1815Depends: bar (<= 1.0.0), foo
1816"#
1817 .to_owned();
1818 assert_eq!(control.to_string(), expected);
1819 }
1820
1821 #[test]
1822 fn test_source_wrap_and_sort() {
1823 let control: Control = r#"Source: blah
1824Build-Depends: foo, bar (>= 1.0.0)
1825
1826"#
1827 .parse()
1828 .unwrap();
1829 let mut source = control.source().unwrap();
1830 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1831 assert!(source.build_depends().is_some());
1835 }
1836
1837 #[test]
1838 fn test_binary_set_breaks() {
1839 let mut control = Control::new();
1840 let mut binary = control.add_binary("foo");
1841 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1842 binary.set_breaks(Some(&relations));
1843 assert!(binary.breaks().is_some());
1844 }
1845
1846 #[test]
1847 fn test_binary_set_pre_depends() {
1848 let mut control = Control::new();
1849 let mut binary = control.add_binary("foo");
1850 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1851 binary.set_pre_depends(Some(&relations));
1852 assert!(binary.pre_depends().is_some());
1853 }
1854
1855 #[test]
1856 fn test_binary_set_provides() {
1857 let mut control = Control::new();
1858 let mut binary = control.add_binary("foo");
1859 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1860 binary.set_provides(Some(&relations));
1861 assert!(binary.provides().is_some());
1862 }
1863
1864 #[test]
1865 fn test_source_build_conflicts() {
1866 let control: Control = r#"Source: blah
1867Build-Conflicts: foo, bar (>= 1.0.0)
1868
1869"#
1870 .parse()
1871 .unwrap();
1872 let source = control.source().unwrap();
1873 let conflicts = source.build_conflicts();
1874 assert!(conflicts.is_some());
1875 }
1876
1877 #[test]
1878 fn test_source_vcs_svn() {
1879 let control: Control = r#"Source: blah
1880Vcs-Svn: https://example.com/svn/repo
1881
1882"#
1883 .parse()
1884 .unwrap();
1885 let source = control.source().unwrap();
1886 assert_eq!(
1887 source.vcs_svn(),
1888 Some("https://example.com/svn/repo".to_string())
1889 );
1890 }
1891
1892 #[test]
1893 fn test_control_from_conversion() {
1894 let deb822_data = r#"Source: test
1895Section: libs
1896
1897"#;
1898 let deb822: Deb822 = deb822_data.parse().unwrap();
1899 let control = Control::from(deb822);
1900 assert!(control.source().is_some());
1901 }
1902
1903 #[test]
1904 fn test_fields_in_range() {
1905 let control_text = r#"Source: test-package
1906Maintainer: Test User <test@example.com>
1907Build-Depends: debhelper (>= 12)
1908
1909Package: test-binary
1910Architecture: any
1911Depends: ${shlibs:Depends}
1912Description: Test package
1913 This is a test package
1914"#;
1915 let control: Control = control_text.parse().unwrap();
1916
1917 let source_start = 0;
1919 let source_end = "Source: test-package".len();
1920 let source_range = TextRange::new((source_start as u32).into(), (source_end as u32).into());
1921
1922 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1923 assert_eq!(fields.len(), 1);
1924 assert_eq!(fields[0].key(), Some("Source".to_string()));
1925
1926 let maintainer_start = control_text.find("Maintainer:").unwrap();
1928 let build_depends_end = control_text
1929 .find("Build-Depends: debhelper (>= 12)")
1930 .unwrap()
1931 + "Build-Depends: debhelper (>= 12)".len();
1932 let multi_range = TextRange::new(
1933 (maintainer_start as u32).into(),
1934 (build_depends_end as u32).into(),
1935 );
1936
1937 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1938 assert_eq!(fields.len(), 2);
1939 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1940 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1941
1942 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1944 let cross_para_end =
1945 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1946 let cross_range = TextRange::new(
1947 (cross_para_start as u32).into(),
1948 (cross_para_end as u32).into(),
1949 );
1950
1951 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1952 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1954 assert_eq!(fields[1].key(), Some("Package".to_string()));
1955 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1956
1957 let empty_range = TextRange::new(1000.into(), 1001.into());
1959 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1960 assert_eq!(fields.len(), 0);
1961 }
1962
1963 #[test]
1964 fn test_source_overlaps_range() {
1965 let control_text = r#"Source: test-package
1966Maintainer: Test User <test@example.com>
1967
1968Package: test-binary
1969Architecture: any
1970"#;
1971 let control: Control = control_text.parse().unwrap();
1972 let source = control.source().unwrap();
1973
1974 let overlap_range = TextRange::new(10.into(), 30.into());
1976 assert!(source.overlaps_range(overlap_range));
1977
1978 let binary_start = control_text.find("Package:").unwrap();
1980 let no_overlap_range = TextRange::new(
1981 (binary_start as u32).into(),
1982 ((binary_start + 20) as u32).into(),
1983 );
1984 assert!(!source.overlaps_range(no_overlap_range));
1985
1986 let partial_overlap = TextRange::new(0.into(), 15.into());
1988 assert!(source.overlaps_range(partial_overlap));
1989 }
1990
1991 #[test]
1992 fn test_source_fields_in_range() {
1993 let control_text = r#"Source: test-package
1994Maintainer: Test User <test@example.com>
1995Build-Depends: debhelper (>= 12)
1996
1997Package: test-binary
1998"#;
1999 let control: Control = control_text.parse().unwrap();
2000 let source = control.source().unwrap();
2001
2002 let maintainer_start = control_text.find("Maintainer:").unwrap();
2004 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
2005 let maintainer_range = TextRange::new(
2006 (maintainer_start as u32).into(),
2007 (maintainer_end as u32).into(),
2008 );
2009
2010 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
2011 assert_eq!(fields.len(), 1);
2012 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
2013
2014 let all_source_range = TextRange::new(0.into(), 100.into());
2016 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
2017 assert_eq!(fields.len(), 3); }
2019
2020 #[test]
2021 fn test_binary_overlaps_range() {
2022 let control_text = r#"Source: test-package
2023
2024Package: test-binary
2025Architecture: any
2026Depends: ${shlibs:Depends}
2027"#;
2028 let control: Control = control_text.parse().unwrap();
2029 let binary = control.binaries().next().unwrap();
2030
2031 let package_start = control_text.find("Package:").unwrap();
2033 let overlap_range = TextRange::new(
2034 (package_start as u32).into(),
2035 ((package_start + 30) as u32).into(),
2036 );
2037 assert!(binary.overlaps_range(overlap_range));
2038
2039 let no_overlap_range = TextRange::new(0.into(), 10.into());
2041 assert!(!binary.overlaps_range(no_overlap_range));
2042 }
2043
2044 #[test]
2045 fn test_binary_fields_in_range() {
2046 let control_text = r#"Source: test-package
2047
2048Package: test-binary
2049Architecture: any
2050Depends: ${shlibs:Depends}
2051Description: Test binary
2052 This is a test binary package
2053"#;
2054 let control: Control = control_text.parse().unwrap();
2055 let binary = control.binaries().next().unwrap();
2056
2057 let arch_start = control_text.find("Architecture:").unwrap();
2059 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
2060 + "Depends: ${shlibs:Depends}".len();
2061 let range = TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
2062
2063 let fields: Vec<_> = binary.fields_in_range(range).collect();
2064 assert_eq!(fields.len(), 2);
2065 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
2066 assert_eq!(fields[1].key(), Some("Depends".to_string()));
2067
2068 let desc_start = control_text.find("Description:").unwrap();
2070 let partial_range = TextRange::new(
2071 ((desc_start + 5) as u32).into(),
2072 ((desc_start + 15) as u32).into(),
2073 );
2074 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
2075 assert_eq!(fields.len(), 1);
2076 assert_eq!(fields[0].key(), Some("Description".to_string()));
2077 }
2078
2079 #[test]
2080 fn test_incremental_parsing_use_case() {
2081 let control_text = r#"Source: example
2083Maintainer: John Doe <john@example.com>
2084Standards-Version: 4.6.0
2085Build-Depends: debhelper-compat (= 13)
2086
2087Package: example-bin
2088Architecture: all
2089Depends: ${misc:Depends}
2090Description: Example package
2091 This is an example.
2092"#;
2093 let control: Control = control_text.parse().unwrap();
2094
2095 let change_start = control_text.find("Standards-Version:").unwrap();
2097 let change_end = change_start + "Standards-Version: 4.6.0".len();
2098 let change_range = TextRange::new((change_start as u32).into(), (change_end as u32).into());
2099
2100 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
2102 assert_eq!(affected_fields.len(), 1);
2103 assert_eq!(
2104 affected_fields[0].key(),
2105 Some("Standards-Version".to_string())
2106 );
2107
2108 for entry in &affected_fields {
2110 let key = entry.key().unwrap();
2111 assert_ne!(key, "Maintainer");
2112 assert_ne!(key, "Build-Depends");
2113 assert_ne!(key, "Architecture");
2114 }
2115 }
2116
2117 #[test]
2118 fn test_positioned_parse_errors() {
2119 let input = "Invalid: field\nBroken field without colon";
2121 let parsed = Control::parse(input);
2122
2123 let positioned_errors = parsed.positioned_errors();
2125 assert!(
2126 !positioned_errors.is_empty(),
2127 "Should have positioned errors"
2128 );
2129
2130 for error in positioned_errors {
2132 let start_offset: u32 = error.range.start().into();
2133 let end_offset: u32 = error.range.end().into();
2134
2135 assert!(!error.message.is_empty());
2137
2138 assert!(start_offset <= end_offset);
2140 assert!(end_offset <= input.len() as u32);
2141
2142 assert!(error.code.is_some());
2144
2145 println!(
2146 "Error at {:?}: {} (code: {:?})",
2147 error.range, error.message, error.code
2148 );
2149 }
2150
2151 let string_errors = parsed.errors();
2153 assert!(!string_errors.is_empty());
2154 assert_eq!(string_errors.len(), positioned_errors.len());
2155 }
2156
2157 #[test]
2158 fn test_sort_binaries_basic() {
2159 let input = r#"Source: foo
2160
2161Package: libfoo
2162Architecture: all
2163
2164Package: libbar
2165Architecture: all
2166"#;
2167
2168 let mut control: Control = input.parse().unwrap();
2169 control.sort_binaries(false);
2170
2171 let binaries: Vec<_> = control.binaries().collect();
2172 assert_eq!(binaries.len(), 2);
2173 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
2174 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
2175 }
2176
2177 #[test]
2178 fn test_sort_binaries_keep_first() {
2179 let input = r#"Source: foo
2180
2181Package: zzz-first
2182Architecture: all
2183
2184Package: libbar
2185Architecture: all
2186
2187Package: libaaa
2188Architecture: all
2189"#;
2190
2191 let mut control: Control = input.parse().unwrap();
2192 control.sort_binaries(true);
2193
2194 let binaries: Vec<_> = control.binaries().collect();
2195 assert_eq!(binaries.len(), 3);
2196 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
2198 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
2200 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
2201 }
2202
2203 #[test]
2204 fn test_sort_binaries_already_sorted() {
2205 let input = r#"Source: foo
2206
2207Package: aaa
2208Architecture: all
2209
2210Package: bbb
2211Architecture: all
2212
2213Package: ccc
2214Architecture: all
2215"#;
2216
2217 let mut control: Control = input.parse().unwrap();
2218 control.sort_binaries(false);
2219
2220 let binaries: Vec<_> = control.binaries().collect();
2221 assert_eq!(binaries.len(), 3);
2222 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2223 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
2224 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
2225 }
2226
2227 #[test]
2228 fn test_sort_binaries_no_binaries() {
2229 let input = r#"Source: foo
2230Maintainer: test@example.com
2231"#;
2232
2233 let mut control: Control = input.parse().unwrap();
2234 control.sort_binaries(false);
2235
2236 assert_eq!(control.binaries().count(), 0);
2238 }
2239
2240 #[test]
2241 fn test_sort_binaries_one_binary() {
2242 let input = r#"Source: foo
2243
2244Package: bar
2245Architecture: all
2246"#;
2247
2248 let mut control: Control = input.parse().unwrap();
2249 control.sort_binaries(false);
2250
2251 let binaries: Vec<_> = control.binaries().collect();
2252 assert_eq!(binaries.len(), 1);
2253 assert_eq!(binaries[0].name(), Some("bar".to_string()));
2254 }
2255
2256 #[test]
2257 fn test_sort_binaries_preserves_fields() {
2258 let input = r#"Source: foo
2259
2260Package: zzz
2261Architecture: any
2262Depends: libc6
2263Description: ZZZ package
2264
2265Package: aaa
2266Architecture: all
2267Depends: ${misc:Depends}
2268Description: AAA package
2269"#;
2270
2271 let mut control: Control = input.parse().unwrap();
2272 control.sort_binaries(false);
2273
2274 let binaries: Vec<_> = control.binaries().collect();
2275 assert_eq!(binaries.len(), 2);
2276
2277 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2279 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
2280 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
2281
2282 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
2284 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2285 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2286 }
2287
2288 #[test]
2289 fn test_remove_binary_basic() {
2290 let mut control = Control::new();
2291 control.add_binary("foo");
2292 assert_eq!(control.binaries().count(), 1);
2293 assert!(control.remove_binary("foo"));
2294 assert_eq!(control.binaries().count(), 0);
2295 }
2296
2297 #[test]
2298 fn test_remove_binary_nonexistent() {
2299 let mut control = Control::new();
2300 control.add_binary("foo");
2301 assert!(!control.remove_binary("bar"));
2302 assert_eq!(control.binaries().count(), 1);
2303 }
2304
2305 #[test]
2306 fn test_remove_binary_multiple() {
2307 let mut control = Control::new();
2308 control.add_binary("foo");
2309 control.add_binary("bar");
2310 control.add_binary("baz");
2311 assert_eq!(control.binaries().count(), 3);
2312
2313 assert!(control.remove_binary("bar"));
2314 assert_eq!(control.binaries().count(), 2);
2315
2316 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2317 assert_eq!(names, vec!["foo", "baz"]);
2318 }
2319
2320 #[test]
2321 fn test_remove_binary_preserves_source() {
2322 let input = r#"Source: mypackage
2323
2324Package: foo
2325Architecture: all
2326
2327Package: bar
2328Architecture: all
2329"#;
2330 let mut control: Control = input.parse().unwrap();
2331 assert!(control.source().is_some());
2332 assert_eq!(control.binaries().count(), 2);
2333
2334 assert!(control.remove_binary("foo"));
2335
2336 assert!(control.source().is_some());
2338 assert_eq!(
2339 control.source().unwrap().name(),
2340 Some("mypackage".to_string())
2341 );
2342
2343 assert_eq!(control.binaries().count(), 1);
2345 assert_eq!(
2346 control.binaries().next().unwrap().name(),
2347 Some("bar".to_string())
2348 );
2349 }
2350
2351 #[test]
2352 fn test_remove_binary_from_parsed() {
2353 let input = r#"Source: test
2354
2355Package: test-bin
2356Architecture: any
2357Depends: libc6
2358Description: Test binary
2359
2360Package: test-lib
2361Architecture: all
2362Description: Test library
2363"#;
2364 let mut control: Control = input.parse().unwrap();
2365 assert_eq!(control.binaries().count(), 2);
2366
2367 assert!(control.remove_binary("test-bin"));
2368
2369 let output = control.to_string();
2370 assert!(!output.contains("test-bin"));
2371 assert!(output.contains("test-lib"));
2372 assert!(output.contains("Source: test"));
2373 }
2374
2375 #[test]
2376 fn test_build_depends_preserves_indentation_after_removal() {
2377 let input = r#"Source: acpi-support
2378Section: admin
2379Priority: optional
2380Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2381Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2382 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2383"#;
2384 let control: Control = input.parse().unwrap();
2385 let mut source = control.source().unwrap();
2386
2387 let mut build_depends = source.build_depends().unwrap();
2389
2390 let mut to_remove = Vec::new();
2392 for (idx, entry) in build_depends.entries().enumerate() {
2393 for relation in entry.relations() {
2394 if relation.name() == "dh-systemd" {
2395 to_remove.push(idx);
2396 break;
2397 }
2398 }
2399 }
2400
2401 for idx in to_remove.into_iter().rev() {
2402 build_depends.remove_entry(idx);
2403 }
2404
2405 source.set_build_depends(&build_depends);
2407
2408 let output = source.to_string();
2409
2410 assert!(
2412 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2413 "Expected 4-space indentation to be preserved, but got:\n{}",
2414 output
2415 );
2416 }
2417
2418 #[test]
2419 fn test_build_depends_direct_string_set_loses_indentation() {
2420 let input = r#"Source: acpi-support
2421Section: admin
2422Priority: optional
2423Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2424Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2425 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2426"#;
2427 let control: Control = input.parse().unwrap();
2428 let mut source = control.source().unwrap();
2429
2430 let mut build_depends = source.build_depends().unwrap();
2432
2433 let mut to_remove = Vec::new();
2435 for (idx, entry) in build_depends.entries().enumerate() {
2436 for relation in entry.relations() {
2437 if relation.name() == "dh-systemd" {
2438 to_remove.push(idx);
2439 break;
2440 }
2441 }
2442 }
2443
2444 for idx in to_remove.into_iter().rev() {
2445 build_depends.remove_entry(idx);
2446 }
2447
2448 source.set("Build-Depends", &build_depends.to_string());
2450
2451 let output = source.to_string();
2452 println!("Output with string set:");
2453 println!("{}", output);
2454
2455 assert!(
2458 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2459 "Expected 4-space indentation to be preserved, but got:\n{}",
2460 output
2461 );
2462 }
2463
2464 #[test]
2465 fn test_parse_mode_strict_default() {
2466 let control = Control::new();
2467 assert_eq!(control.parse_mode(), ParseMode::Strict);
2468
2469 let control: Control = "Source: test\n".parse().unwrap();
2470 assert_eq!(control.parse_mode(), ParseMode::Strict);
2471 }
2472
2473 #[test]
2474 fn test_parse_mode_new_with_mode() {
2475 let control_relaxed = Control::new_with_mode(ParseMode::Relaxed);
2476 assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed);
2477
2478 let control_substvar = Control::new_with_mode(ParseMode::Substvar);
2479 assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar);
2480 }
2481
2482 #[test]
2483 fn test_relaxed_mode_handles_broken_relations() {
2484 let input = r#"Source: test-package
2485Build-Depends: debhelper, @@@broken@@@, python3
2486
2487Package: test-pkg
2488Depends: libfoo, %%%invalid%%%, libbar
2489"#;
2490
2491 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2492 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2493
2494 if let Some(source) = control.source() {
2496 let bd = source.build_depends();
2497 assert!(bd.is_some());
2498 let relations = bd.unwrap();
2499 assert!(relations.len() >= 2); }
2502
2503 for binary in control.binaries() {
2504 let deps = binary.depends();
2505 assert!(deps.is_some());
2506 let relations = deps.unwrap();
2507 assert!(relations.len() >= 2); }
2510 }
2511
2512 #[test]
2513 fn test_substvar_mode_via_parse() {
2514 let input = r#"Source: test-package
2518Build-Depends: debhelper, ${misc:Depends}
2519
2520Package: test-pkg
2521Depends: ${shlibs:Depends}, libfoo
2522"#;
2523
2524 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2526
2527 if let Some(source) = control.source() {
2528 let bd = source.build_depends();
2530 assert!(bd.is_some());
2531 }
2532
2533 for binary in control.binaries() {
2534 let deps = binary.depends();
2535 assert!(deps.is_some());
2536 }
2537 }
2538
2539 #[test]
2540 #[should_panic]
2541 fn test_strict_mode_panics_on_broken_syntax() {
2542 let input = r#"Source: test-package
2543Build-Depends: debhelper, @@@broken@@@
2544"#;
2545
2546 let control: Control = input.parse().unwrap();
2548
2549 if let Some(source) = control.source() {
2550 let _ = source.build_depends();
2552 }
2553 }
2554
2555 #[test]
2556 fn test_from_file_relaxed_sets_relaxed_mode() {
2557 let input = r#"Source: test-package
2558Maintainer: Test <test@example.com>
2559"#;
2560
2561 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2562 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2563 }
2564
2565 #[test]
2566 fn test_parse_mode_propagates_to_paragraphs() {
2567 let input = r#"Source: test-package
2568Build-Depends: debhelper, @@@invalid@@@, python3
2569
2570Package: test-pkg
2571Depends: libfoo, %%%bad%%%, libbar
2572"#;
2573
2574 let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap();
2576
2577 if let Some(source) = control.source() {
2580 assert!(source.build_depends().is_some());
2581 }
2582
2583 for binary in control.binaries() {
2584 assert!(binary.depends().is_some());
2585 }
2586 }
2587
2588 #[test]
2589 fn test_preserves_final_newline() {
2590 let input_with_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2592 let control: Control = input_with_newline.parse().unwrap();
2593 let output = control.to_string();
2594 assert_eq!(output, input_with_newline);
2595 }
2596
2597 #[test]
2598 fn test_preserves_no_final_newline() {
2599 let input_without_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any";
2601 let control: Control = input_without_newline.parse().unwrap();
2602 let output = control.to_string();
2603 assert_eq!(output, input_without_newline);
2604 }
2605
2606 #[test]
2607 fn test_final_newline_after_modifications() {
2608 let input = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2610 let control: Control = input.parse().unwrap();
2611
2612 let mut source = control.source().unwrap();
2614 source.set_section(Some("utils"));
2615
2616 let output = control.to_string();
2617 let expected = "Source: test-package\nSection: utils\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2618 assert_eq!(output, expected);
2619 }
2620
2621 #[test]
2622 fn test_source_in_range() {
2623 let input = r#"Source: test-package
2625Maintainer: Test <test@example.com>
2626Section: utils
2627
2628Package: test-pkg
2629Architecture: any
2630"#;
2631 let control: Control = input.parse().unwrap();
2632
2633 let source = control.source().unwrap();
2635 let source_range = source.as_deb822().text_range();
2636
2637 let result = control.source_in_range(source_range);
2639 assert!(result.is_some());
2640 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2641
2642 let overlap_range = TextRange::new(0.into(), 20.into());
2644 let result = control.source_in_range(overlap_range);
2645 assert!(result.is_some());
2646 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2647
2648 let no_overlap_range = TextRange::new(100.into(), 150.into());
2650 let result = control.source_in_range(no_overlap_range);
2651 assert!(result.is_none());
2652 }
2653
2654 #[test]
2655 fn test_binaries_in_range_single() {
2656 let input = r#"Source: test-package
2658Maintainer: Test <test@example.com>
2659
2660Package: test-pkg
2661Architecture: any
2662
2663Package: another-pkg
2664Architecture: all
2665"#;
2666 let control: Control = input.parse().unwrap();
2667
2668 let first_binary = control.binaries().next().unwrap();
2670 let binary_range = first_binary.as_deb822().text_range();
2671
2672 let binaries: Vec<_> = control.binaries_in_range(binary_range).collect();
2674 assert_eq!(binaries.len(), 1);
2675 assert_eq!(binaries[0].name(), Some("test-pkg".to_string()));
2676 }
2677
2678 #[test]
2679 fn test_binaries_in_range_multiple() {
2680 let input = r#"Source: test-package
2682Maintainer: Test <test@example.com>
2683
2684Package: test-pkg
2685Architecture: any
2686
2687Package: another-pkg
2688Architecture: all
2689
2690Package: third-pkg
2691Architecture: any
2692"#;
2693 let control: Control = input.parse().unwrap();
2694
2695 let range = TextRange::new(50.into(), 130.into());
2697
2698 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2700 assert!(binaries.len() >= 2);
2701 assert!(binaries
2702 .iter()
2703 .any(|b| b.name() == Some("test-pkg".to_string())));
2704 assert!(binaries
2705 .iter()
2706 .any(|b| b.name() == Some("another-pkg".to_string())));
2707 }
2708
2709 #[test]
2710 fn test_binaries_in_range_none() {
2711 let input = r#"Source: test-package
2713Maintainer: Test <test@example.com>
2714
2715Package: test-pkg
2716Architecture: any
2717"#;
2718 let control: Control = input.parse().unwrap();
2719
2720 let range = TextRange::new(1000.into(), 2000.into());
2722
2723 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2725 assert_eq!(binaries.len(), 0);
2726 }
2727
2728 #[test]
2729 fn test_binaries_in_range_all() {
2730 let input = r#"Source: test-package
2732Maintainer: Test <test@example.com>
2733
2734Package: test-pkg
2735Architecture: any
2736
2737Package: another-pkg
2738Architecture: all
2739"#;
2740 let control: Control = input.parse().unwrap();
2741
2742 let range = TextRange::new(0.into(), input.len().try_into().unwrap());
2744
2745 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2747 assert_eq!(binaries.len(), 2);
2748 }
2749
2750 #[test]
2751 fn test_source_in_range_partial_overlap() {
2752 let input = r#"Source: test-package
2754Maintainer: Test <test@example.com>
2755
2756Package: test-pkg
2757Architecture: any
2758"#;
2759 let control: Control = input.parse().unwrap();
2760
2761 let range = TextRange::new(10.into(), 30.into());
2763
2764 let result = control.source_in_range(range);
2766 assert!(result.is_some());
2767 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2768 }
2769
2770 #[test]
2771 fn test_wrap_and_sort_with_malformed_relations() {
2772 let input = r#"Source: test-package
2775Maintainer: Test <test@example.com>
2776Build-Depends: some invalid relation syntax here
2777
2778Package: test-pkg
2779Architecture: any
2780"#;
2781 let mut control: Control = input.parse().unwrap();
2782
2783 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
2785
2786 let output = control.to_string();
2788 let expected = r#"Source: test-package
2789Maintainer: Test <test@example.com>
2790Build-Depends: some invalid relation syntax here
2791
2792Package: test-pkg
2793Architecture: any
2794"#;
2795 assert_eq!(output, expected);
2796 }
2797}