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, max_line_length_one_liner: Option<usize>) -> 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 let relations = match value.parse::<Relations>() {
125 Ok(r) => r.wrap_and_sort(),
126 Err(_) => return value.to_string(),
127 };
128 let one_line = relations.to_string();
129
130 if let Some(mll) = max_line_length_one_liner {
135 if name.len() + 2 + one_line.len() > mll {
136 return relations
137 .entries()
138 .map(|e| e.to_string())
139 .collect::<Vec<_>>()
140 .join(",\n");
141 }
142 }
143 one_line
144 }
145 _ => value.to_string(),
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct Control {
152 deb822: Deb822,
153 parse_mode: ParseMode,
154}
155
156impl Control {
157 pub fn snapshot(&self) -> Self {
164 Control {
165 deb822: self.deb822.snapshot(),
166 parse_mode: self.parse_mode,
167 }
168 }
169
170 pub fn tree_eq(&self, other: &Self) -> bool {
174 self.deb822.tree_eq(&other.deb822)
175 }
176
177 pub fn new() -> Self {
179 Control {
180 deb822: Deb822::new(),
181 parse_mode: ParseMode::Strict,
182 }
183 }
184
185 pub fn new_with_mode(parse_mode: ParseMode) -> Self {
187 Control {
188 deb822: Deb822::new(),
189 parse_mode,
190 }
191 }
192
193 pub fn parse_mode(&self) -> ParseMode {
195 self.parse_mode
196 }
197
198 pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
200 &mut self.deb822
201 }
202
203 pub fn as_deb822(&self) -> &Deb822 {
205 &self.deb822
206 }
207
208 pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
210 let deb822_parse = Deb822::parse(text);
211 let green = deb822_parse.green().clone();
213 let errors = deb822_parse.errors().to_vec();
214 let positioned_errors = deb822_parse.positioned_errors().to_vec();
215 deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
216 }
217
218 pub fn source(&self) -> Option<Source> {
220 let parse_mode = self.parse_mode;
221 self.deb822
222 .paragraphs()
223 .find(|p| p.get("Source").is_some())
224 .map(|paragraph| Source {
225 paragraph,
226 parse_mode,
227 })
228 }
229
230 pub fn binaries(&self) -> impl Iterator<Item = Binary> + '_ {
232 let parse_mode = self.parse_mode;
233 self.deb822
234 .paragraphs()
235 .filter(|p| p.get("Package").is_some())
236 .map(move |paragraph| Binary {
237 paragraph,
238 parse_mode,
239 })
240 }
241
242 pub fn source_in_range(&self, range: TextRange) -> Option<Source> {
250 self.source().filter(|s| {
251 let para_range = s.as_deb822().text_range();
252 para_range.start() < range.end() && para_range.end() > range.start()
253 })
254 }
255
256 pub fn binaries_in_range(&self, range: TextRange) -> impl Iterator<Item = Binary> + '_ {
264 self.binaries().filter(move |b| {
265 let para_range = b.as_deb822().text_range();
266 para_range.start() < range.end() && para_range.end() > range.start()
267 })
268 }
269
270 pub fn add_source(&mut self, name: &str) -> Source {
286 let mut p = self.deb822.add_paragraph();
287 p.set("Source", name);
288 self.source().unwrap()
289 }
290
291 pub fn add_binary(&mut self, name: &str) -> Binary {
307 let mut p = self.deb822.add_paragraph();
308 p.set("Package", name);
309 Binary {
310 paragraph: p,
311 parse_mode: ParseMode::Strict,
312 }
313 }
314
315 pub fn remove_binary(&mut self, name: &str) -> bool {
333 let index = self
334 .deb822
335 .paragraphs()
336 .position(|p| p.get("Package").as_deref() == Some(name));
337
338 if let Some(index) = index {
339 self.deb822.remove_paragraph(index);
340 true
341 } else {
342 false
343 }
344 }
345
346 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
348 Ok(Control {
349 deb822: Deb822::from_file(path)?,
350 parse_mode: ParseMode::Strict,
351 })
352 }
353
354 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
356 path: P,
357 ) -> Result<(Self, Vec<String>), std::io::Error> {
358 let (deb822, errors) = Deb822::from_file_relaxed(path)?;
359 Ok((
360 Control {
361 deb822,
362 parse_mode: ParseMode::Relaxed,
363 },
364 errors,
365 ))
366 }
367
368 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
370 Ok(Control {
371 deb822: Deb822::read(&mut r)?,
372 parse_mode: ParseMode::Strict,
373 })
374 }
375
376 pub fn read_relaxed<R: std::io::Read>(
378 mut r: R,
379 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
380 let (deb822, errors) = Deb822::read_relaxed(&mut r)?;
381 Ok((
382 Control {
383 deb822,
384 parse_mode: ParseMode::Relaxed,
385 },
386 errors,
387 ))
388 }
389
390 pub fn wrap_and_sort(
397 &mut self,
398 indentation: deb822_lossless::Indentation,
399 immediate_empty_line: bool,
400 max_line_length_one_liner: Option<usize>,
401 ) {
402 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
403 let a_is_source = a.get("Source").is_some();
405 let b_is_source = b.get("Source").is_some();
406
407 if a_is_source && !b_is_source {
408 return std::cmp::Ordering::Less;
409 } else if !a_is_source && b_is_source {
410 return std::cmp::Ordering::Greater;
411 } else if a_is_source && b_is_source {
412 return a.get("Source").cmp(&b.get("Source"));
413 }
414
415 a.get("Package").cmp(&b.get("Package"))
416 };
417
418 let format = |name: &str, value: &str| -> String {
419 format_field(name, value, max_line_length_one_liner)
420 };
421 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
422 p.wrap_and_sort(
425 indentation,
426 immediate_empty_line,
427 max_line_length_one_liner,
428 None,
429 Some(&format),
430 )
431 };
432
433 self.deb822 = self
434 .deb822
435 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
436 }
437
438 pub fn sort_binaries(&mut self, keep_first: bool) {
469 let mut paragraphs: Vec<_> = self.deb822.paragraphs().collect();
470
471 if paragraphs.len() <= 1 {
472 return; }
474
475 let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
477 let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
478
479 let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
481 binary_start + 1
482 } else {
483 binary_start
484 };
485
486 if sort_start >= paragraphs.len() {
487 return; }
489
490 paragraphs[sort_start..].sort_by(|a, b| {
492 let a_name = a.get("Package");
493 let b_name = b.get("Package");
494 a_name.cmp(&b_name)
495 });
496
497 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
499 let a_pos = paragraphs.iter().position(|p| p == a);
500 let b_pos = paragraphs.iter().position(|p| p == b);
501 a_pos.cmp(&b_pos)
502 };
503
504 self.deb822 = self.deb822.wrap_and_sort(Some(&sort_paragraphs), None);
505 }
506
507 pub fn fields_in_range(
536 &self,
537 range: TextRange,
538 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
539 self.deb822
540 .paragraphs()
541 .flat_map(move |p| p.entries().collect::<Vec<_>>())
542 .filter(move |entry| {
543 let entry_range = entry.syntax().text_range();
544 entry_range.start() < range.end() && range.start() < entry_range.end()
546 })
547 }
548}
549
550impl From<Control> for Deb822 {
551 fn from(c: Control) -> Self {
552 c.deb822
553 }
554}
555
556impl From<Deb822> for Control {
557 fn from(d: Deb822) -> Self {
558 Control {
559 deb822: d,
560 parse_mode: ParseMode::Strict,
561 }
562 }
563}
564
565impl Default for Control {
566 fn default() -> Self {
567 Self::new()
568 }
569}
570
571impl std::str::FromStr for Control {
572 type Err = deb822_lossless::ParseError;
573
574 fn from_str(s: &str) -> Result<Self, Self::Err> {
575 Control::parse(s).to_result()
576 }
577}
578
579#[derive(Debug, Clone, PartialEq, Eq)]
581pub struct Source {
582 paragraph: Paragraph,
583 parse_mode: ParseMode,
584}
585
586impl From<Source> for Paragraph {
587 fn from(s: Source) -> Self {
588 s.paragraph
589 }
590}
591
592impl From<Paragraph> for Source {
593 fn from(p: Paragraph) -> Self {
594 Source {
595 paragraph: p,
596 parse_mode: ParseMode::Strict,
597 }
598 }
599}
600
601impl std::fmt::Display for Source {
602 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
603 self.paragraph.fmt(f)
604 }
605}
606
607impl Source {
608 fn parse_relations(&self, s: &str) -> Relations {
610 match self.parse_mode {
611 ParseMode::Strict => s.parse().unwrap(),
612 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
613 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
614 }
615 }
616
617 pub fn name(&self) -> Option<String> {
619 self.paragraph.get("Source")
620 }
621
622 pub fn wrap_and_sort(
624 &mut self,
625 indentation: deb822_lossless::Indentation,
626 immediate_empty_line: bool,
627 max_line_length_one_liner: Option<usize>,
628 ) {
629 let format = |name: &str, value: &str| -> String {
630 format_field(name, value, max_line_length_one_liner)
631 };
632 self.paragraph = self.paragraph.wrap_and_sort(
633 indentation,
634 immediate_empty_line,
635 max_line_length_one_liner,
636 None,
637 Some(&format),
638 );
639 }
640
641 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
643 &mut self.paragraph
644 }
645
646 pub fn as_deb822(&self) -> &Paragraph {
648 &self.paragraph
649 }
650
651 pub fn set_name(&mut self, name: &str) {
653 self.set("Source", name);
654 }
655
656 pub fn section(&self) -> Option<String> {
658 self.paragraph.get("Section")
659 }
660
661 pub fn set_section(&mut self, section: Option<&str>) {
663 if let Some(section) = section {
664 self.set("Section", section);
665 } else {
666 self.paragraph.remove("Section");
667 }
668 }
669
670 pub fn priority(&self) -> Option<Priority> {
672 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
673 }
674
675 pub fn set_priority(&mut self, priority: Option<Priority>) {
677 if let Some(priority) = priority {
678 self.set("Priority", priority.to_string().as_str());
679 } else {
680 self.paragraph.remove("Priority");
681 }
682 }
683
684 pub fn maintainer(&self) -> Option<String> {
686 self.paragraph.get("Maintainer")
687 }
688
689 pub fn set_maintainer(&mut self, maintainer: &str) {
691 self.set("Maintainer", maintainer);
692 }
693
694 pub fn is_qa_package(&self) -> bool {
699 self.maintainer()
700 .as_deref()
701 .and_then(|m| crate::parse_identity(m).ok())
702 .map(|(_, email)| email.eq_ignore_ascii_case("packages@qa.debian.org"))
703 .unwrap_or(false)
704 }
705
706 pub fn build_depends(&self) -> Option<Relations> {
708 self.paragraph
709 .get_with_comments("Build-Depends")
710 .map(|s| self.parse_relations(&s))
711 }
712
713 pub fn set_build_depends(&mut self, relations: &Relations) {
715 self.set("Build-Depends", relations.to_string().as_str());
716 }
717
718 pub fn build_depends_indep(&self) -> Option<Relations> {
720 self.paragraph
721 .get_with_comments("Build-Depends-Indep")
722 .map(|s| self.parse_relations(&s))
723 }
724
725 pub fn set_build_depends_indep(&mut self, relations: &Relations) {
727 self.set("Build-Depends-Indep", relations.to_string().as_str());
728 }
729
730 pub fn build_depends_arch(&self) -> Option<Relations> {
732 self.paragraph
733 .get_with_comments("Build-Depends-Arch")
734 .map(|s| self.parse_relations(&s))
735 }
736
737 pub fn set_build_depends_arch(&mut self, relations: &Relations) {
739 self.set("Build-Depends-Arch", relations.to_string().as_str());
740 }
741
742 pub fn build_conflicts(&self) -> Option<Relations> {
744 self.paragraph
745 .get_with_comments("Build-Conflicts")
746 .map(|s| self.parse_relations(&s))
747 }
748
749 pub fn set_build_conflicts(&mut self, relations: &Relations) {
751 self.set("Build-Conflicts", relations.to_string().as_str());
752 }
753
754 pub fn build_conflicts_indep(&self) -> Option<Relations> {
756 self.paragraph
757 .get_with_comments("Build-Conflicts-Indep")
758 .map(|s| self.parse_relations(&s))
759 }
760
761 pub fn set_build_conflicts_indep(&mut self, relations: &Relations) {
763 self.set("Build-Conflicts-Indep", relations.to_string().as_str());
764 }
765
766 pub fn build_conflicts_arch(&self) -> Option<Relations> {
768 self.paragraph
769 .get_with_comments("Build-Conflicts-Arch")
770 .map(|s| self.parse_relations(&s))
771 }
772
773 pub fn standards_version(&self) -> Option<String> {
775 self.paragraph.get("Standards-Version")
776 }
777
778 pub fn set_standards_version(&mut self, version: &str) {
780 self.set("Standards-Version", version);
781 }
782
783 pub fn homepage(&self) -> Option<url::Url> {
785 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
786 }
787
788 pub fn set_homepage(&mut self, homepage: &url::Url) {
790 self.set("Homepage", homepage.to_string().as_str());
791 }
792
793 pub fn vcs_git(&self) -> Option<String> {
795 self.paragraph.get("Vcs-Git")
796 }
797
798 pub fn set_vcs_git(&mut self, url: &str) {
800 self.set("Vcs-Git", url);
801 }
802
803 pub fn vcs_svn(&self) -> Option<String> {
805 self.paragraph.get("Vcs-Svn").map(|s| s.to_string())
806 }
807
808 pub fn set_vcs_svn(&mut self, url: &str) {
810 self.set("Vcs-Svn", url);
811 }
812
813 pub fn vcs_bzr(&self) -> Option<String> {
815 self.paragraph.get("Vcs-Bzr").map(|s| s.to_string())
816 }
817
818 pub fn set_vcs_bzr(&mut self, url: &str) {
820 self.set("Vcs-Bzr", url);
821 }
822
823 pub fn vcs_arch(&self) -> Option<String> {
825 self.paragraph.get("Vcs-Arch").map(|s| s.to_string())
826 }
827
828 pub fn set_vcs_arch(&mut self, url: &str) {
830 self.set("Vcs-Arch", url);
831 }
832
833 pub fn vcs_svk(&self) -> Option<String> {
835 self.paragraph.get("Vcs-Svk").map(|s| s.to_string())
836 }
837
838 pub fn set_vcs_svk(&mut self, url: &str) {
840 self.set("Vcs-Svk", url);
841 }
842
843 pub fn vcs_darcs(&self) -> Option<String> {
845 self.paragraph.get("Vcs-Darcs").map(|s| s.to_string())
846 }
847
848 pub fn set_vcs_darcs(&mut self, url: &str) {
850 self.set("Vcs-Darcs", url);
851 }
852
853 pub fn vcs_mtn(&self) -> Option<String> {
855 self.paragraph.get("Vcs-Mtn").map(|s| s.to_string())
856 }
857
858 pub fn set_vcs_mtn(&mut self, url: &str) {
860 self.set("Vcs-Mtn", url);
861 }
862
863 pub fn vcs_cvs(&self) -> Option<String> {
865 self.paragraph.get("Vcs-Cvs").map(|s| s.to_string())
866 }
867
868 pub fn set_vcs_cvs(&mut self, url: &str) {
870 self.set("Vcs-Cvs", url);
871 }
872
873 pub fn vcs_hg(&self) -> Option<String> {
875 self.paragraph.get("Vcs-Hg").map(|s| s.to_string())
876 }
877
878 pub fn set_vcs_hg(&mut self, url: &str) {
880 self.set("Vcs-Hg", url);
881 }
882
883 pub fn set(&mut self, key: &str, value: &str) {
885 self.paragraph
886 .set_with_field_order(key, value, SOURCE_FIELD_ORDER);
887 }
888
889 pub fn get(&self, key: &str) -> Option<String> {
891 self.paragraph.get(key)
892 }
893
894 pub fn vcs_browser(&self) -> Option<String> {
896 self.paragraph.get("Vcs-Browser")
897 }
898
899 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
901 for (name, value) in self.paragraph.items() {
902 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
903 return crate::vcs::Vcs::from_field(&name, &value).ok();
904 }
905 }
906 None
907 }
908
909 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
911 if let Some(url) = url {
912 self.set("Vcs-Browser", url);
913 } else {
914 self.paragraph.remove("Vcs-Browser");
915 }
916 }
917
918 pub fn uploaders(&self) -> Option<Vec<String>> {
920 self.paragraph
921 .get("Uploaders")
922 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
923 }
924
925 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
927 self.set(
928 "Uploaders",
929 uploaders
930 .iter()
931 .map(|s| s.to_string())
932 .collect::<Vec<_>>()
933 .join(", ")
934 .as_str(),
935 );
936 }
937
938 pub fn architecture(&self) -> Option<String> {
940 self.paragraph.get("Architecture")
941 }
942
943 pub fn set_architecture(&mut self, arch: Option<&str>) {
945 if let Some(arch) = arch {
946 self.set("Architecture", arch);
947 } else {
948 self.paragraph.remove("Architecture");
949 }
950 }
951
952 pub fn rules_requires_root(&self) -> Option<bool> {
954 self.paragraph
955 .get("Rules-Requires-Root")
956 .map(|s| match s.to_lowercase().as_str() {
957 "yes" => true,
958 "no" => false,
959 _ => panic!("invalid Rules-Requires-Root value"),
960 })
961 }
962
963 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
965 self.set(
966 "Rules-Requires-Root",
967 if requires_root { "yes" } else { "no" },
968 );
969 }
970
971 pub fn testsuite(&self) -> Option<String> {
973 self.paragraph.get("Testsuite")
974 }
975
976 pub fn set_testsuite(&mut self, testsuite: &str) {
978 self.set("Testsuite", testsuite);
979 }
980
981 pub fn overlaps_range(&self, range: TextRange) -> bool {
989 let para_range = self.paragraph.syntax().text_range();
990 para_range.start() < range.end() && range.start() < para_range.end()
991 }
992
993 pub fn fields_in_range(
1001 &self,
1002 range: TextRange,
1003 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1004 self.paragraph.entries().filter(move |entry| {
1005 let entry_range = entry.syntax().text_range();
1006 entry_range.start() < range.end() && range.start() < entry_range.end()
1007 })
1008 }
1009}
1010
1011#[cfg(feature = "python-debian")]
1012impl<'py> pyo3::IntoPyObject<'py> for Source {
1013 type Target = pyo3::PyAny;
1014 type Output = pyo3::Bound<'py, Self::Target>;
1015 type Error = pyo3::PyErr;
1016
1017 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1018 self.paragraph.into_pyobject(py)
1019 }
1020}
1021
1022#[cfg(feature = "python-debian")]
1023impl<'py> pyo3::IntoPyObject<'py> for &Source {
1024 type Target = pyo3::PyAny;
1025 type Output = pyo3::Bound<'py, Self::Target>;
1026 type Error = pyo3::PyErr;
1027
1028 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1029 (&self.paragraph).into_pyobject(py)
1030 }
1031}
1032
1033#[cfg(feature = "python-debian")]
1034impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
1035 type Error = pyo3::PyErr;
1036
1037 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1038 Ok(Source {
1039 paragraph: ob.extract()?,
1040 parse_mode: ParseMode::Strict,
1041 })
1042 }
1043}
1044
1045impl std::fmt::Display for Control {
1046 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1047 self.deb822.fmt(f)
1048 }
1049}
1050
1051impl AstNode for Control {
1052 type Language = deb822_lossless::Lang;
1053
1054 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
1055 Deb822::can_cast(kind)
1056 }
1057
1058 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
1059 Deb822::cast(syntax).map(|deb822| Control {
1060 deb822,
1061 parse_mode: ParseMode::Strict,
1062 })
1063 }
1064
1065 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
1066 self.deb822.syntax()
1067 }
1068}
1069
1070#[derive(Debug, Clone, PartialEq, Eq)]
1072pub struct Binary {
1073 paragraph: Paragraph,
1074 parse_mode: ParseMode,
1075}
1076
1077impl From<Binary> for Paragraph {
1078 fn from(b: Binary) -> Self {
1079 b.paragraph
1080 }
1081}
1082
1083impl From<Paragraph> for Binary {
1084 fn from(p: Paragraph) -> Self {
1085 Binary {
1086 paragraph: p,
1087 parse_mode: ParseMode::Strict,
1088 }
1089 }
1090}
1091
1092#[cfg(feature = "python-debian")]
1093impl<'py> pyo3::IntoPyObject<'py> for Binary {
1094 type Target = pyo3::PyAny;
1095 type Output = pyo3::Bound<'py, Self::Target>;
1096 type Error = pyo3::PyErr;
1097
1098 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1099 self.paragraph.into_pyobject(py)
1100 }
1101}
1102
1103#[cfg(feature = "python-debian")]
1104impl<'py> pyo3::IntoPyObject<'py> for &Binary {
1105 type Target = pyo3::PyAny;
1106 type Output = pyo3::Bound<'py, Self::Target>;
1107 type Error = pyo3::PyErr;
1108
1109 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1110 (&self.paragraph).into_pyobject(py)
1111 }
1112}
1113
1114#[cfg(feature = "python-debian")]
1115impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
1116 type Error = pyo3::PyErr;
1117
1118 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1119 Ok(Binary {
1120 paragraph: ob.extract()?,
1121 parse_mode: ParseMode::Strict,
1122 })
1123 }
1124}
1125
1126impl Default for Binary {
1127 fn default() -> Self {
1128 Self::new()
1129 }
1130}
1131
1132impl std::fmt::Display for Binary {
1133 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1134 self.paragraph.fmt(f)
1135 }
1136}
1137
1138impl Binary {
1139 fn parse_relations(&self, s: &str) -> Relations {
1141 match self.parse_mode {
1142 ParseMode::Strict => s.parse().unwrap(),
1143 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
1144 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
1145 }
1146 }
1147
1148 pub fn new() -> Self {
1150 Binary {
1151 paragraph: Paragraph::new(),
1152 parse_mode: ParseMode::Strict,
1153 }
1154 }
1155
1156 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
1158 &mut self.paragraph
1159 }
1160
1161 pub fn as_deb822(&self) -> &Paragraph {
1163 &self.paragraph
1164 }
1165
1166 pub fn wrap_and_sort(
1168 &mut self,
1169 indentation: deb822_lossless::Indentation,
1170 immediate_empty_line: bool,
1171 max_line_length_one_liner: Option<usize>,
1172 ) {
1173 let format = |name: &str, value: &str| -> String {
1174 format_field(name, value, max_line_length_one_liner)
1175 };
1176 self.paragraph = self.paragraph.wrap_and_sort(
1177 indentation,
1178 immediate_empty_line,
1179 max_line_length_one_liner,
1180 None,
1181 Some(&format),
1182 );
1183 }
1184
1185 pub fn name(&self) -> Option<String> {
1187 self.paragraph.get("Package")
1188 }
1189
1190 pub fn set_name(&mut self, name: &str) {
1192 self.set("Package", name);
1193 }
1194
1195 pub fn section(&self) -> Option<String> {
1197 self.paragraph.get("Section")
1198 }
1199
1200 pub fn set_section(&mut self, section: Option<&str>) {
1202 if let Some(section) = section {
1203 self.set("Section", section);
1204 } else {
1205 self.paragraph.remove("Section");
1206 }
1207 }
1208
1209 pub fn priority(&self) -> Option<Priority> {
1211 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
1212 }
1213
1214 pub fn set_priority(&mut self, priority: Option<Priority>) {
1216 if let Some(priority) = priority {
1217 self.set("Priority", priority.to_string().as_str());
1218 } else {
1219 self.paragraph.remove("Priority");
1220 }
1221 }
1222
1223 pub fn architecture(&self) -> Option<String> {
1225 self.paragraph.get("Architecture")
1226 }
1227
1228 pub fn set_architecture(&mut self, arch: Option<&str>) {
1230 if let Some(arch) = arch {
1231 self.set("Architecture", arch);
1232 } else {
1233 self.paragraph.remove("Architecture");
1234 }
1235 }
1236
1237 pub fn depends(&self) -> Option<Relations> {
1239 self.paragraph
1240 .get_with_comments("Depends")
1241 .map(|s| self.parse_relations(&s))
1242 }
1243
1244 pub fn set_depends(&mut self, depends: Option<&Relations>) {
1246 if let Some(depends) = depends {
1247 self.set("Depends", depends.to_string().as_str());
1248 } else {
1249 self.paragraph.remove("Depends");
1250 }
1251 }
1252
1253 pub fn recommends(&self) -> Option<Relations> {
1255 self.paragraph
1256 .get_with_comments("Recommends")
1257 .map(|s| self.parse_relations(&s))
1258 }
1259
1260 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
1262 if let Some(recommends) = recommends {
1263 self.set("Recommends", recommends.to_string().as_str());
1264 } else {
1265 self.paragraph.remove("Recommends");
1266 }
1267 }
1268
1269 pub fn suggests(&self) -> Option<Relations> {
1271 self.paragraph
1272 .get_with_comments("Suggests")
1273 .map(|s| self.parse_relations(&s))
1274 }
1275
1276 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
1278 if let Some(suggests) = suggests {
1279 self.set("Suggests", suggests.to_string().as_str());
1280 } else {
1281 self.paragraph.remove("Suggests");
1282 }
1283 }
1284
1285 pub fn enhances(&self) -> Option<Relations> {
1287 self.paragraph
1288 .get_with_comments("Enhances")
1289 .map(|s| self.parse_relations(&s))
1290 }
1291
1292 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1294 if let Some(enhances) = enhances {
1295 self.set("Enhances", enhances.to_string().as_str());
1296 } else {
1297 self.paragraph.remove("Enhances");
1298 }
1299 }
1300
1301 pub fn pre_depends(&self) -> Option<Relations> {
1303 self.paragraph
1304 .get_with_comments("Pre-Depends")
1305 .map(|s| self.parse_relations(&s))
1306 }
1307
1308 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1310 if let Some(pre_depends) = pre_depends {
1311 self.set("Pre-Depends", pre_depends.to_string().as_str());
1312 } else {
1313 self.paragraph.remove("Pre-Depends");
1314 }
1315 }
1316
1317 pub fn breaks(&self) -> Option<Relations> {
1319 self.paragraph
1320 .get_with_comments("Breaks")
1321 .map(|s| self.parse_relations(&s))
1322 }
1323
1324 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1326 if let Some(breaks) = breaks {
1327 self.set("Breaks", breaks.to_string().as_str());
1328 } else {
1329 self.paragraph.remove("Breaks");
1330 }
1331 }
1332
1333 pub fn conflicts(&self) -> Option<Relations> {
1335 self.paragraph
1336 .get_with_comments("Conflicts")
1337 .map(|s| self.parse_relations(&s))
1338 }
1339
1340 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1342 if let Some(conflicts) = conflicts {
1343 self.set("Conflicts", conflicts.to_string().as_str());
1344 } else {
1345 self.paragraph.remove("Conflicts");
1346 }
1347 }
1348
1349 pub fn replaces(&self) -> Option<Relations> {
1351 self.paragraph
1352 .get_with_comments("Replaces")
1353 .map(|s| self.parse_relations(&s))
1354 }
1355
1356 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1358 if let Some(replaces) = replaces {
1359 self.set("Replaces", replaces.to_string().as_str());
1360 } else {
1361 self.paragraph.remove("Replaces");
1362 }
1363 }
1364
1365 pub fn provides(&self) -> Option<Relations> {
1367 self.paragraph
1368 .get_with_comments("Provides")
1369 .map(|s| self.parse_relations(&s))
1370 }
1371
1372 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1374 if let Some(provides) = provides {
1375 self.set("Provides", provides.to_string().as_str());
1376 } else {
1377 self.paragraph.remove("Provides");
1378 }
1379 }
1380
1381 pub fn built_using(&self) -> Option<Relations> {
1383 self.paragraph
1384 .get_with_comments("Built-Using")
1385 .map(|s| self.parse_relations(&s))
1386 }
1387
1388 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1390 if let Some(built_using) = built_using {
1391 self.set("Built-Using", built_using.to_string().as_str());
1392 } else {
1393 self.paragraph.remove("Built-Using");
1394 }
1395 }
1396
1397 pub fn static_built_using(&self) -> Option<Relations> {
1399 self.paragraph
1400 .get_with_comments("Static-Built-Using")
1401 .map(|s| self.parse_relations(&s))
1402 }
1403
1404 pub fn set_static_built_using(&mut self, static_built_using: Option<&Relations>) {
1406 if let Some(static_built_using) = static_built_using {
1407 self.set(
1408 "Static-Built-Using",
1409 static_built_using.to_string().as_str(),
1410 );
1411 } else {
1412 self.paragraph.remove("Static-Built-Using");
1413 }
1414 }
1415
1416 pub fn multi_arch(&self) -> Option<MultiArch> {
1418 self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap())
1419 }
1420
1421 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1423 if let Some(multi_arch) = multi_arch {
1424 self.set("Multi-Arch", multi_arch.to_string().as_str());
1425 } else {
1426 self.paragraph.remove("Multi-Arch");
1427 }
1428 }
1429
1430 pub fn essential(&self) -> bool {
1432 self.paragraph
1433 .get("Essential")
1434 .map(|s| s == "yes")
1435 .unwrap_or(false)
1436 }
1437
1438 pub fn set_essential(&mut self, essential: bool) {
1440 if essential {
1441 self.set("Essential", "yes");
1442 } else {
1443 self.paragraph.remove("Essential");
1444 }
1445 }
1446
1447 pub fn description(&self) -> Option<String> {
1449 self.paragraph.get_multiline("Description")
1450 }
1451
1452 pub fn set_description(&mut self, description: Option<&str>) {
1454 if let Some(description) = description {
1455 self.paragraph.set_with_indent_pattern(
1456 "Description",
1457 description,
1458 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1459 Some(BINARY_FIELD_ORDER),
1460 );
1461 } else {
1462 self.paragraph.remove("Description");
1463 }
1464 }
1465
1466 pub fn homepage(&self) -> Option<url::Url> {
1468 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
1469 }
1470
1471 pub fn set_homepage(&mut self, url: &url::Url) {
1473 self.set("Homepage", url.as_str());
1474 }
1475
1476 pub fn set(&mut self, key: &str, value: &str) {
1478 self.paragraph
1479 .set_with_field_order(key, value, BINARY_FIELD_ORDER);
1480 }
1481
1482 pub fn get(&self, key: &str) -> Option<String> {
1484 self.paragraph.get(key)
1485 }
1486
1487 pub fn overlaps_range(&self, range: TextRange) -> bool {
1495 let para_range = self.paragraph.syntax().text_range();
1496 para_range.start() < range.end() && range.start() < para_range.end()
1497 }
1498
1499 pub fn fields_in_range(
1507 &self,
1508 range: TextRange,
1509 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1510 self.paragraph.entries().filter(move |entry| {
1511 let entry_range = entry.syntax().text_range();
1512 entry_range.start() < range.end() && range.start() < entry_range.end()
1513 })
1514 }
1515}
1516
1517#[cfg(test)]
1518mod tests {
1519 use super::*;
1520 use crate::relations::VersionConstraint;
1521
1522 #[test]
1523 fn test_source_set_field_ordering() {
1524 let mut control = Control::new();
1525 let mut source = control.add_source("mypackage");
1526
1527 source.set("Homepage", "https://example.com");
1529 source.set("Build-Depends", "debhelper");
1530 source.set("Standards-Version", "4.5.0");
1531 source.set("Maintainer", "Test <test@example.com>");
1532
1533 let output = source.to_string();
1535 let lines: Vec<&str> = output.lines().collect();
1536
1537 assert!(lines[0].starts_with("Source:"));
1539
1540 let maintainer_pos = lines
1542 .iter()
1543 .position(|l| l.starts_with("Maintainer:"))
1544 .unwrap();
1545 let build_depends_pos = lines
1546 .iter()
1547 .position(|l| l.starts_with("Build-Depends:"))
1548 .unwrap();
1549 let standards_pos = lines
1550 .iter()
1551 .position(|l| l.starts_with("Standards-Version:"))
1552 .unwrap();
1553 let homepage_pos = lines
1554 .iter()
1555 .position(|l| l.starts_with("Homepage:"))
1556 .unwrap();
1557
1558 assert!(maintainer_pos < build_depends_pos);
1560 assert!(build_depends_pos < standards_pos);
1561 assert!(standards_pos < homepage_pos);
1562 }
1563
1564 #[test]
1565 fn test_binary_set_field_ordering() {
1566 let mut control = Control::new();
1567 let mut binary = control.add_binary("mypackage");
1568
1569 binary.set("Description", "A test package");
1571 binary.set("Architecture", "amd64");
1572 binary.set("Depends", "libc6");
1573 binary.set("Section", "utils");
1574
1575 let output = binary.to_string();
1577 let lines: Vec<&str> = output.lines().collect();
1578
1579 assert!(lines[0].starts_with("Package:"));
1581
1582 let arch_pos = lines
1584 .iter()
1585 .position(|l| l.starts_with("Architecture:"))
1586 .unwrap();
1587 let section_pos = lines
1588 .iter()
1589 .position(|l| l.starts_with("Section:"))
1590 .unwrap();
1591 let depends_pos = lines
1592 .iter()
1593 .position(|l| l.starts_with("Depends:"))
1594 .unwrap();
1595 let desc_pos = lines
1596 .iter()
1597 .position(|l| l.starts_with("Description:"))
1598 .unwrap();
1599
1600 assert!(arch_pos < section_pos);
1602 assert!(section_pos < depends_pos);
1603 assert!(depends_pos < desc_pos);
1604 }
1605
1606 #[test]
1607 fn test_source_specific_set_methods_use_field_ordering() {
1608 let mut control = Control::new();
1609 let mut source = control.add_source("mypackage");
1610
1611 source.set_homepage(&"https://example.com".parse().unwrap());
1613 source.set_maintainer("Test <test@example.com>");
1614 source.set_standards_version("4.5.0");
1615 source.set_vcs_git("https://github.com/example/repo");
1616
1617 let output = source.to_string();
1619 let lines: Vec<&str> = output.lines().collect();
1620
1621 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1623 let maintainer_pos = lines
1624 .iter()
1625 .position(|l| l.starts_with("Maintainer:"))
1626 .unwrap();
1627 let standards_pos = lines
1628 .iter()
1629 .position(|l| l.starts_with("Standards-Version:"))
1630 .unwrap();
1631 let vcs_git_pos = lines
1632 .iter()
1633 .position(|l| l.starts_with("Vcs-Git:"))
1634 .unwrap();
1635 let homepage_pos = lines
1636 .iter()
1637 .position(|l| l.starts_with("Homepage:"))
1638 .unwrap();
1639
1640 assert!(source_pos < maintainer_pos);
1642 assert!(maintainer_pos < standards_pos);
1643 assert!(standards_pos < vcs_git_pos);
1644 assert!(vcs_git_pos < homepage_pos);
1645 }
1646
1647 #[test]
1648 fn test_binary_specific_set_methods_use_field_ordering() {
1649 let mut control = Control::new();
1650 let mut binary = control.add_binary("mypackage");
1651
1652 binary.set_description(Some("A test package"));
1654 binary.set_architecture(Some("amd64"));
1655 let depends = "libc6".parse().unwrap();
1656 binary.set_depends(Some(&depends));
1657 binary.set_section(Some("utils"));
1658 binary.set_priority(Some(Priority::Optional));
1659
1660 let output = binary.to_string();
1662 let lines: Vec<&str> = output.lines().collect();
1663
1664 let package_pos = lines
1666 .iter()
1667 .position(|l| l.starts_with("Package:"))
1668 .unwrap();
1669 let arch_pos = lines
1670 .iter()
1671 .position(|l| l.starts_with("Architecture:"))
1672 .unwrap();
1673 let section_pos = lines
1674 .iter()
1675 .position(|l| l.starts_with("Section:"))
1676 .unwrap();
1677 let priority_pos = lines
1678 .iter()
1679 .position(|l| l.starts_with("Priority:"))
1680 .unwrap();
1681 let depends_pos = lines
1682 .iter()
1683 .position(|l| l.starts_with("Depends:"))
1684 .unwrap();
1685 let desc_pos = lines
1686 .iter()
1687 .position(|l| l.starts_with("Description:"))
1688 .unwrap();
1689
1690 assert!(package_pos < arch_pos);
1692 assert!(arch_pos < section_pos);
1693 assert!(section_pos < priority_pos);
1694 assert!(priority_pos < depends_pos);
1695 assert!(depends_pos < desc_pos);
1696 }
1697
1698 #[test]
1699 fn test_parse() {
1700 let control: Control = r#"Source: foo
1701Section: libs
1702Priority: optional
1703Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1704Homepage: https://example.com
1705
1706"#
1707 .parse()
1708 .unwrap();
1709 let source = control.source().unwrap();
1710
1711 assert_eq!(source.name(), Some("foo".to_owned()));
1712 assert_eq!(source.section(), Some("libs".to_owned()));
1713 assert_eq!(source.priority(), Some(super::Priority::Optional));
1714 assert_eq!(
1715 source.homepage(),
1716 Some("https://example.com".parse().unwrap())
1717 );
1718 let bd = source.build_depends().unwrap();
1719 let entries = bd.entries().collect::<Vec<_>>();
1720 assert_eq!(entries.len(), 2);
1721 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1722 assert_eq!(rel.name(), "bar");
1723 assert_eq!(
1724 rel.version(),
1725 Some((
1726 VersionConstraint::GreaterThanEqual,
1727 "1.0.0".parse().unwrap()
1728 ))
1729 );
1730 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1731 assert_eq!(rel.name(), "baz");
1732 assert_eq!(
1733 rel.version(),
1734 Some((
1735 VersionConstraint::GreaterThanEqual,
1736 "1.0.0".parse().unwrap()
1737 ))
1738 );
1739 }
1740
1741 #[test]
1742 fn test_description() {
1743 let control: Control = r#"Source: foo
1744
1745Package: foo
1746Description: this is the short description
1747 And the longer one
1748 .
1749 is on the next lines
1750"#
1751 .parse()
1752 .unwrap();
1753 let binary = control.binaries().next().unwrap();
1754 assert_eq!(
1755 binary.description(),
1756 Some(
1757 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1758 .to_owned()
1759 )
1760 );
1761 }
1762
1763 #[test]
1764 fn test_set_description_on_package_without_description() {
1765 let control: Control = r#"Source: foo
1766
1767Package: foo
1768Architecture: amd64
1769"#
1770 .parse()
1771 .unwrap();
1772 let mut binary = control.binaries().next().unwrap();
1773
1774 binary.set_description(Some(
1776 "Short description\nLonger description\n.\nAnother line",
1777 ));
1778
1779 let output = binary.to_string();
1780
1781 assert_eq!(
1783 binary.description(),
1784 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1785 );
1786
1787 assert_eq!(
1789 output,
1790 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1791 );
1792 }
1793
1794 #[test]
1795 fn test_as_mut_deb822() {
1796 let mut control = Control::new();
1797 let deb822 = control.as_mut_deb822();
1798 let mut p = deb822.add_paragraph();
1799 p.set("Source", "foo");
1800 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1801 }
1802
1803 #[test]
1804 fn test_as_deb822() {
1805 let control = Control::new();
1806 let _deb822: &Deb822 = control.as_deb822();
1807 }
1808
1809 #[test]
1810 fn test_set_depends() {
1811 let mut control = Control::new();
1812 let mut binary = control.add_binary("foo");
1813 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1814 binary.set_depends(Some(&relations));
1815 }
1816
1817 #[test]
1818 fn test_wrap_and_sort() {
1819 let mut control: Control = r#"Package: blah
1820Section: libs
1821
1822
1823
1824Package: foo
1825Description: this is a
1826 bar
1827 blah
1828"#
1829 .parse()
1830 .unwrap();
1831 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1832 let expected = r#"Package: blah
1833Section: libs
1834
1835Package: foo
1836Description: this is a
1837 bar
1838 blah
1839"#
1840 .to_owned();
1841 assert_eq!(control.to_string(), expected);
1842 }
1843
1844 #[test]
1845 fn test_wrap_and_sort_source() {
1846 let mut control: Control = r#"Source: blah
1847Depends: foo, bar (<= 1.0.0)
1848
1849"#
1850 .parse()
1851 .unwrap();
1852 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1853 let expected = r#"Source: blah
1854Depends: bar (<= 1.0.0), foo
1855"#
1856 .to_owned();
1857 assert_eq!(control.to_string(), expected);
1858 }
1859
1860 #[test]
1861 fn test_source_wrap_and_sort() {
1862 let control: Control = r#"Source: blah
1863Build-Depends: foo, bar (>= 1.0.0)
1864
1865"#
1866 .parse()
1867 .unwrap();
1868 let mut source = control.source().unwrap();
1869 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1870 assert!(source.build_depends().is_some());
1874 }
1875
1876 #[test]
1877 fn test_binary_set_breaks() {
1878 let mut control = Control::new();
1879 let mut binary = control.add_binary("foo");
1880 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1881 binary.set_breaks(Some(&relations));
1882 assert!(binary.breaks().is_some());
1883 }
1884
1885 #[test]
1886 fn test_binary_set_pre_depends() {
1887 let mut control = Control::new();
1888 let mut binary = control.add_binary("foo");
1889 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1890 binary.set_pre_depends(Some(&relations));
1891 assert!(binary.pre_depends().is_some());
1892 }
1893
1894 #[test]
1895 fn test_binary_set_provides() {
1896 let mut control = Control::new();
1897 let mut binary = control.add_binary("foo");
1898 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1899 binary.set_provides(Some(&relations));
1900 assert!(binary.provides().is_some());
1901 }
1902
1903 #[test]
1904 fn test_source_is_qa_package() {
1905 let control: Control = "Source: foo\n\n".parse().unwrap();
1906 assert!(!control.source().unwrap().is_qa_package());
1907
1908 let control: Control = "Source: foo\nMaintainer: Jane Packager <jane@example.com>\n\n"
1909 .parse()
1910 .unwrap();
1911 assert!(!control.source().unwrap().is_qa_package());
1912
1913 let control: Control =
1914 "Source: foo\nMaintainer: Debian QA Group <packages@qa.debian.org>\n\n"
1915 .parse()
1916 .unwrap();
1917 assert!(control.source().unwrap().is_qa_package());
1918 }
1919
1920 #[test]
1921 fn test_source_build_conflicts() {
1922 let control: Control = r#"Source: blah
1923Build-Conflicts: foo, bar (>= 1.0.0)
1924
1925"#
1926 .parse()
1927 .unwrap();
1928 let source = control.source().unwrap();
1929 let conflicts = source.build_conflicts();
1930 assert!(conflicts.is_some());
1931 }
1932
1933 #[test]
1934 fn test_source_vcs_svn() {
1935 let control: Control = r#"Source: blah
1936Vcs-Svn: https://example.com/svn/repo
1937
1938"#
1939 .parse()
1940 .unwrap();
1941 let source = control.source().unwrap();
1942 assert_eq!(
1943 source.vcs_svn(),
1944 Some("https://example.com/svn/repo".to_string())
1945 );
1946 }
1947
1948 #[test]
1949 fn test_control_from_conversion() {
1950 let deb822_data = r#"Source: test
1951Section: libs
1952
1953"#;
1954 let deb822: Deb822 = deb822_data.parse().unwrap();
1955 let control = Control::from(deb822);
1956 assert!(control.source().is_some());
1957 }
1958
1959 #[test]
1960 fn test_fields_in_range() {
1961 let control_text = r#"Source: test-package
1962Maintainer: Test User <test@example.com>
1963Build-Depends: debhelper (>= 12)
1964
1965Package: test-binary
1966Architecture: any
1967Depends: ${shlibs:Depends}
1968Description: Test package
1969 This is a test package
1970"#;
1971 let control: Control = control_text.parse().unwrap();
1972
1973 let source_start = 0;
1975 let source_end = "Source: test-package".len();
1976 let source_range = TextRange::new((source_start as u32).into(), (source_end as u32).into());
1977
1978 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1979 assert_eq!(fields.len(), 1);
1980 assert_eq!(fields[0].key(), Some("Source".to_string()));
1981
1982 let maintainer_start = control_text.find("Maintainer:").unwrap();
1984 let build_depends_end = control_text
1985 .find("Build-Depends: debhelper (>= 12)")
1986 .unwrap()
1987 + "Build-Depends: debhelper (>= 12)".len();
1988 let multi_range = TextRange::new(
1989 (maintainer_start as u32).into(),
1990 (build_depends_end as u32).into(),
1991 );
1992
1993 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1994 assert_eq!(fields.len(), 2);
1995 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1996 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1997
1998 let cross_para_start = control_text.find("Build-Depends:").unwrap();
2000 let cross_para_end =
2001 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
2002 let cross_range = TextRange::new(
2003 (cross_para_start as u32).into(),
2004 (cross_para_end as u32).into(),
2005 );
2006
2007 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
2008 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
2010 assert_eq!(fields[1].key(), Some("Package".to_string()));
2011 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
2012
2013 let empty_range = TextRange::new(1000.into(), 1001.into());
2015 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
2016 assert_eq!(fields.len(), 0);
2017 }
2018
2019 #[test]
2020 fn test_source_overlaps_range() {
2021 let control_text = r#"Source: test-package
2022Maintainer: Test User <test@example.com>
2023
2024Package: test-binary
2025Architecture: any
2026"#;
2027 let control: Control = control_text.parse().unwrap();
2028 let source = control.source().unwrap();
2029
2030 let overlap_range = TextRange::new(10.into(), 30.into());
2032 assert!(source.overlaps_range(overlap_range));
2033
2034 let binary_start = control_text.find("Package:").unwrap();
2036 let no_overlap_range = TextRange::new(
2037 (binary_start as u32).into(),
2038 ((binary_start + 20) as u32).into(),
2039 );
2040 assert!(!source.overlaps_range(no_overlap_range));
2041
2042 let partial_overlap = TextRange::new(0.into(), 15.into());
2044 assert!(source.overlaps_range(partial_overlap));
2045 }
2046
2047 #[test]
2048 fn test_source_fields_in_range() {
2049 let control_text = r#"Source: test-package
2050Maintainer: Test User <test@example.com>
2051Build-Depends: debhelper (>= 12)
2052
2053Package: test-binary
2054"#;
2055 let control: Control = control_text.parse().unwrap();
2056 let source = control.source().unwrap();
2057
2058 let maintainer_start = control_text.find("Maintainer:").unwrap();
2060 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
2061 let maintainer_range = TextRange::new(
2062 (maintainer_start as u32).into(),
2063 (maintainer_end as u32).into(),
2064 );
2065
2066 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
2067 assert_eq!(fields.len(), 1);
2068 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
2069
2070 let all_source_range = TextRange::new(0.into(), 100.into());
2072 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
2073 assert_eq!(fields.len(), 3); }
2075
2076 #[test]
2077 fn test_binary_overlaps_range() {
2078 let control_text = r#"Source: test-package
2079
2080Package: test-binary
2081Architecture: any
2082Depends: ${shlibs:Depends}
2083"#;
2084 let control: Control = control_text.parse().unwrap();
2085 let binary = control.binaries().next().unwrap();
2086
2087 let package_start = control_text.find("Package:").unwrap();
2089 let overlap_range = TextRange::new(
2090 (package_start as u32).into(),
2091 ((package_start + 30) as u32).into(),
2092 );
2093 assert!(binary.overlaps_range(overlap_range));
2094
2095 let no_overlap_range = TextRange::new(0.into(), 10.into());
2097 assert!(!binary.overlaps_range(no_overlap_range));
2098 }
2099
2100 #[test]
2101 fn test_binary_fields_in_range() {
2102 let control_text = r#"Source: test-package
2103
2104Package: test-binary
2105Architecture: any
2106Depends: ${shlibs:Depends}
2107Description: Test binary
2108 This is a test binary package
2109"#;
2110 let control: Control = control_text.parse().unwrap();
2111 let binary = control.binaries().next().unwrap();
2112
2113 let arch_start = control_text.find("Architecture:").unwrap();
2115 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
2116 + "Depends: ${shlibs:Depends}".len();
2117 let range = TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
2118
2119 let fields: Vec<_> = binary.fields_in_range(range).collect();
2120 assert_eq!(fields.len(), 2);
2121 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
2122 assert_eq!(fields[1].key(), Some("Depends".to_string()));
2123
2124 let desc_start = control_text.find("Description:").unwrap();
2126 let partial_range = TextRange::new(
2127 ((desc_start + 5) as u32).into(),
2128 ((desc_start + 15) as u32).into(),
2129 );
2130 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
2131 assert_eq!(fields.len(), 1);
2132 assert_eq!(fields[0].key(), Some("Description".to_string()));
2133 }
2134
2135 #[test]
2136 fn test_incremental_parsing_use_case() {
2137 let control_text = r#"Source: example
2139Maintainer: John Doe <john@example.com>
2140Standards-Version: 4.6.0
2141Build-Depends: debhelper-compat (= 13)
2142
2143Package: example-bin
2144Architecture: all
2145Depends: ${misc:Depends}
2146Description: Example package
2147 This is an example.
2148"#;
2149 let control: Control = control_text.parse().unwrap();
2150
2151 let change_start = control_text.find("Standards-Version:").unwrap();
2153 let change_end = change_start + "Standards-Version: 4.6.0".len();
2154 let change_range = TextRange::new((change_start as u32).into(), (change_end as u32).into());
2155
2156 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
2158 assert_eq!(affected_fields.len(), 1);
2159 assert_eq!(
2160 affected_fields[0].key(),
2161 Some("Standards-Version".to_string())
2162 );
2163
2164 for entry in &affected_fields {
2166 let key = entry.key().unwrap();
2167 assert_ne!(key, "Maintainer");
2168 assert_ne!(key, "Build-Depends");
2169 assert_ne!(key, "Architecture");
2170 }
2171 }
2172
2173 #[test]
2174 fn test_positioned_parse_errors() {
2175 let input = "Invalid: field\nBroken field without colon";
2177 let parsed = Control::parse(input);
2178
2179 let positioned_errors = parsed.positioned_errors();
2181 assert!(
2182 !positioned_errors.is_empty(),
2183 "Should have positioned errors"
2184 );
2185
2186 for error in positioned_errors {
2188 let start_offset: u32 = error.range.start().into();
2189 let end_offset: u32 = error.range.end().into();
2190
2191 assert!(!error.message.is_empty());
2193
2194 assert!(start_offset <= end_offset);
2196 assert!(end_offset <= input.len() as u32);
2197
2198 assert!(error.code.is_some());
2200
2201 println!(
2202 "Error at {:?}: {} (code: {:?})",
2203 error.range, error.message, error.code
2204 );
2205 }
2206
2207 let string_errors = parsed.errors();
2209 assert!(!string_errors.is_empty());
2210 assert_eq!(string_errors.len(), positioned_errors.len());
2211 }
2212
2213 #[test]
2214 fn test_sort_binaries_basic() {
2215 let input = r#"Source: foo
2216
2217Package: libfoo
2218Architecture: all
2219
2220Package: libbar
2221Architecture: all
2222"#;
2223
2224 let mut control: Control = input.parse().unwrap();
2225 control.sort_binaries(false);
2226
2227 let binaries: Vec<_> = control.binaries().collect();
2228 assert_eq!(binaries.len(), 2);
2229 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
2230 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
2231 }
2232
2233 #[test]
2234 fn test_sort_binaries_keep_first() {
2235 let input = r#"Source: foo
2236
2237Package: zzz-first
2238Architecture: all
2239
2240Package: libbar
2241Architecture: all
2242
2243Package: libaaa
2244Architecture: all
2245"#;
2246
2247 let mut control: Control = input.parse().unwrap();
2248 control.sort_binaries(true);
2249
2250 let binaries: Vec<_> = control.binaries().collect();
2251 assert_eq!(binaries.len(), 3);
2252 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
2254 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
2256 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
2257 }
2258
2259 #[test]
2260 fn test_sort_binaries_already_sorted() {
2261 let input = r#"Source: foo
2262
2263Package: aaa
2264Architecture: all
2265
2266Package: bbb
2267Architecture: all
2268
2269Package: ccc
2270Architecture: all
2271"#;
2272
2273 let mut control: Control = input.parse().unwrap();
2274 control.sort_binaries(false);
2275
2276 let binaries: Vec<_> = control.binaries().collect();
2277 assert_eq!(binaries.len(), 3);
2278 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2279 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
2280 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
2281 }
2282
2283 #[test]
2284 fn test_sort_binaries_no_binaries() {
2285 let input = r#"Source: foo
2286Maintainer: test@example.com
2287"#;
2288
2289 let mut control: Control = input.parse().unwrap();
2290 control.sort_binaries(false);
2291
2292 assert_eq!(control.binaries().count(), 0);
2294 }
2295
2296 #[test]
2297 fn test_sort_binaries_one_binary() {
2298 let input = r#"Source: foo
2299
2300Package: bar
2301Architecture: all
2302"#;
2303
2304 let mut control: Control = input.parse().unwrap();
2305 control.sort_binaries(false);
2306
2307 let binaries: Vec<_> = control.binaries().collect();
2308 assert_eq!(binaries.len(), 1);
2309 assert_eq!(binaries[0].name(), Some("bar".to_string()));
2310 }
2311
2312 #[test]
2313 fn test_sort_binaries_preserves_fields() {
2314 let input = r#"Source: foo
2315
2316Package: zzz
2317Architecture: any
2318Depends: libc6
2319Description: ZZZ package
2320
2321Package: aaa
2322Architecture: all
2323Depends: ${misc:Depends}
2324Description: AAA package
2325"#;
2326
2327 let mut control: Control = input.parse().unwrap();
2328 control.sort_binaries(false);
2329
2330 let binaries: Vec<_> = control.binaries().collect();
2331 assert_eq!(binaries.len(), 2);
2332
2333 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2335 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
2336 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
2337
2338 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
2340 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2341 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2342 }
2343
2344 #[test]
2345 fn test_remove_binary_basic() {
2346 let mut control = Control::new();
2347 control.add_binary("foo");
2348 assert_eq!(control.binaries().count(), 1);
2349 assert!(control.remove_binary("foo"));
2350 assert_eq!(control.binaries().count(), 0);
2351 }
2352
2353 #[test]
2354 fn test_remove_binary_nonexistent() {
2355 let mut control = Control::new();
2356 control.add_binary("foo");
2357 assert!(!control.remove_binary("bar"));
2358 assert_eq!(control.binaries().count(), 1);
2359 }
2360
2361 #[test]
2362 fn test_remove_binary_multiple() {
2363 let mut control = Control::new();
2364 control.add_binary("foo");
2365 control.add_binary("bar");
2366 control.add_binary("baz");
2367 assert_eq!(control.binaries().count(), 3);
2368
2369 assert!(control.remove_binary("bar"));
2370 assert_eq!(control.binaries().count(), 2);
2371
2372 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2373 assert_eq!(names, vec!["foo", "baz"]);
2374 }
2375
2376 #[test]
2377 fn test_remove_binary_preserves_source() {
2378 let input = r#"Source: mypackage
2379
2380Package: foo
2381Architecture: all
2382
2383Package: bar
2384Architecture: all
2385"#;
2386 let mut control: Control = input.parse().unwrap();
2387 assert!(control.source().is_some());
2388 assert_eq!(control.binaries().count(), 2);
2389
2390 assert!(control.remove_binary("foo"));
2391
2392 assert!(control.source().is_some());
2394 assert_eq!(
2395 control.source().unwrap().name(),
2396 Some("mypackage".to_string())
2397 );
2398
2399 assert_eq!(control.binaries().count(), 1);
2401 assert_eq!(
2402 control.binaries().next().unwrap().name(),
2403 Some("bar".to_string())
2404 );
2405 }
2406
2407 #[test]
2408 fn test_remove_binary_from_parsed() {
2409 let input = r#"Source: test
2410
2411Package: test-bin
2412Architecture: any
2413Depends: libc6
2414Description: Test binary
2415
2416Package: test-lib
2417Architecture: all
2418Description: Test library
2419"#;
2420 let mut control: Control = input.parse().unwrap();
2421 assert_eq!(control.binaries().count(), 2);
2422
2423 assert!(control.remove_binary("test-bin"));
2424
2425 let output = control.to_string();
2426 assert!(!output.contains("test-bin"));
2427 assert!(output.contains("test-lib"));
2428 assert!(output.contains("Source: test"));
2429 }
2430
2431 #[test]
2432 fn test_build_depends_preserves_indentation_after_removal() {
2433 let input = r#"Source: acpi-support
2434Section: admin
2435Priority: optional
2436Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2437Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2438 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2439"#;
2440 let control: Control = input.parse().unwrap();
2441 let mut source = control.source().unwrap();
2442
2443 let mut build_depends = source.build_depends().unwrap();
2445
2446 let mut to_remove = Vec::new();
2448 for (idx, entry) in build_depends.entries().enumerate() {
2449 for relation in entry.relations() {
2450 if relation.name() == "dh-systemd" {
2451 to_remove.push(idx);
2452 break;
2453 }
2454 }
2455 }
2456
2457 for idx in to_remove.into_iter().rev() {
2458 build_depends.remove_entry(idx);
2459 }
2460
2461 source.set_build_depends(&build_depends);
2463
2464 let output = source.to_string();
2465
2466 assert!(
2468 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2469 "Expected 4-space indentation to be preserved, but got:\n{}",
2470 output
2471 );
2472 }
2473
2474 #[test]
2475 fn test_build_depends_direct_string_set_loses_indentation() {
2476 let input = r#"Source: acpi-support
2477Section: admin
2478Priority: optional
2479Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2480Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2481 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2482"#;
2483 let control: Control = input.parse().unwrap();
2484 let mut source = control.source().unwrap();
2485
2486 let mut build_depends = source.build_depends().unwrap();
2488
2489 let mut to_remove = Vec::new();
2491 for (idx, entry) in build_depends.entries().enumerate() {
2492 for relation in entry.relations() {
2493 if relation.name() == "dh-systemd" {
2494 to_remove.push(idx);
2495 break;
2496 }
2497 }
2498 }
2499
2500 for idx in to_remove.into_iter().rev() {
2501 build_depends.remove_entry(idx);
2502 }
2503
2504 source.set("Build-Depends", &build_depends.to_string());
2506
2507 let output = source.to_string();
2508 println!("Output with string set:");
2509 println!("{}", output);
2510
2511 assert!(
2514 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2515 "Expected 4-space indentation to be preserved, but got:\n{}",
2516 output
2517 );
2518 }
2519
2520 #[test]
2521 fn test_parse_mode_strict_default() {
2522 let control = Control::new();
2523 assert_eq!(control.parse_mode(), ParseMode::Strict);
2524
2525 let control: Control = "Source: test\n".parse().unwrap();
2526 assert_eq!(control.parse_mode(), ParseMode::Strict);
2527 }
2528
2529 #[test]
2530 fn test_parse_mode_new_with_mode() {
2531 let control_relaxed = Control::new_with_mode(ParseMode::Relaxed);
2532 assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed);
2533
2534 let control_substvar = Control::new_with_mode(ParseMode::Substvar);
2535 assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar);
2536 }
2537
2538 #[test]
2539 fn test_relaxed_mode_handles_broken_relations() {
2540 let input = r#"Source: test-package
2541Build-Depends: debhelper, @@@broken@@@, python3
2542
2543Package: test-pkg
2544Depends: libfoo, %%%invalid%%%, libbar
2545"#;
2546
2547 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2548 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2549
2550 if let Some(source) = control.source() {
2552 let bd = source.build_depends();
2553 assert!(bd.is_some());
2554 let relations = bd.unwrap();
2555 assert!(relations.len() >= 2); }
2558
2559 for binary in control.binaries() {
2560 let deps = binary.depends();
2561 assert!(deps.is_some());
2562 let relations = deps.unwrap();
2563 assert!(relations.len() >= 2); }
2566 }
2567
2568 #[test]
2569 fn test_substvar_mode_via_parse() {
2570 let input = r#"Source: test-package
2574Build-Depends: debhelper, ${misc:Depends}
2575
2576Package: test-pkg
2577Depends: ${shlibs:Depends}, libfoo
2578"#;
2579
2580 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2582
2583 if let Some(source) = control.source() {
2584 let bd = source.build_depends();
2586 assert!(bd.is_some());
2587 }
2588
2589 for binary in control.binaries() {
2590 let deps = binary.depends();
2591 assert!(deps.is_some());
2592 }
2593 }
2594
2595 #[test]
2596 #[should_panic]
2597 fn test_strict_mode_panics_on_broken_syntax() {
2598 let input = r#"Source: test-package
2599Build-Depends: debhelper, @@@broken@@@
2600"#;
2601
2602 let control: Control = input.parse().unwrap();
2604
2605 if let Some(source) = control.source() {
2606 let _ = source.build_depends();
2608 }
2609 }
2610
2611 #[test]
2612 fn test_from_file_relaxed_sets_relaxed_mode() {
2613 let input = r#"Source: test-package
2614Maintainer: Test <test@example.com>
2615"#;
2616
2617 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2618 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2619 }
2620
2621 #[test]
2622 fn test_parse_mode_propagates_to_paragraphs() {
2623 let input = r#"Source: test-package
2624Build-Depends: debhelper, @@@invalid@@@, python3
2625
2626Package: test-pkg
2627Depends: libfoo, %%%bad%%%, libbar
2628"#;
2629
2630 let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap();
2632
2633 if let Some(source) = control.source() {
2636 assert!(source.build_depends().is_some());
2637 }
2638
2639 for binary in control.binaries() {
2640 assert!(binary.depends().is_some());
2641 }
2642 }
2643
2644 #[test]
2645 fn test_preserves_final_newline() {
2646 let input_with_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2648 let control: Control = input_with_newline.parse().unwrap();
2649 let output = control.to_string();
2650 assert_eq!(output, input_with_newline);
2651 }
2652
2653 #[test]
2654 fn test_preserves_no_final_newline() {
2655 let input_without_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any";
2657 let control: Control = input_without_newline.parse().unwrap();
2658 let output = control.to_string();
2659 assert_eq!(output, input_without_newline);
2660 }
2661
2662 #[test]
2663 fn test_final_newline_after_modifications() {
2664 let input = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2666 let control: Control = input.parse().unwrap();
2667
2668 let mut source = control.source().unwrap();
2670 source.set_section(Some("utils"));
2671
2672 let output = control.to_string();
2673 let expected = "Source: test-package\nSection: utils\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2674 assert_eq!(output, expected);
2675 }
2676
2677 #[test]
2678 fn test_source_in_range() {
2679 let input = r#"Source: test-package
2681Maintainer: Test <test@example.com>
2682Section: utils
2683
2684Package: test-pkg
2685Architecture: any
2686"#;
2687 let control: Control = input.parse().unwrap();
2688
2689 let source = control.source().unwrap();
2691 let source_range = source.as_deb822().text_range();
2692
2693 let result = control.source_in_range(source_range);
2695 assert!(result.is_some());
2696 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2697
2698 let overlap_range = TextRange::new(0.into(), 20.into());
2700 let result = control.source_in_range(overlap_range);
2701 assert!(result.is_some());
2702 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2703
2704 let no_overlap_range = TextRange::new(100.into(), 150.into());
2706 let result = control.source_in_range(no_overlap_range);
2707 assert!(result.is_none());
2708 }
2709
2710 #[test]
2711 fn test_binaries_in_range_single() {
2712 let input = r#"Source: test-package
2714Maintainer: Test <test@example.com>
2715
2716Package: test-pkg
2717Architecture: any
2718
2719Package: another-pkg
2720Architecture: all
2721"#;
2722 let control: Control = input.parse().unwrap();
2723
2724 let first_binary = control.binaries().next().unwrap();
2726 let binary_range = first_binary.as_deb822().text_range();
2727
2728 let binaries: Vec<_> = control.binaries_in_range(binary_range).collect();
2730 assert_eq!(binaries.len(), 1);
2731 assert_eq!(binaries[0].name(), Some("test-pkg".to_string()));
2732 }
2733
2734 #[test]
2735 fn test_binaries_in_range_multiple() {
2736 let input = r#"Source: test-package
2738Maintainer: Test <test@example.com>
2739
2740Package: test-pkg
2741Architecture: any
2742
2743Package: another-pkg
2744Architecture: all
2745
2746Package: third-pkg
2747Architecture: any
2748"#;
2749 let control: Control = input.parse().unwrap();
2750
2751 let range = TextRange::new(50.into(), 130.into());
2753
2754 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2756 assert!(binaries.len() >= 2);
2757 assert!(binaries
2758 .iter()
2759 .any(|b| b.name() == Some("test-pkg".to_string())));
2760 assert!(binaries
2761 .iter()
2762 .any(|b| b.name() == Some("another-pkg".to_string())));
2763 }
2764
2765 #[test]
2766 fn test_binaries_in_range_none() {
2767 let input = r#"Source: test-package
2769Maintainer: Test <test@example.com>
2770
2771Package: test-pkg
2772Architecture: any
2773"#;
2774 let control: Control = input.parse().unwrap();
2775
2776 let range = TextRange::new(1000.into(), 2000.into());
2778
2779 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2781 assert_eq!(binaries.len(), 0);
2782 }
2783
2784 #[test]
2785 fn test_binaries_in_range_all() {
2786 let input = r#"Source: test-package
2788Maintainer: Test <test@example.com>
2789
2790Package: test-pkg
2791Architecture: any
2792
2793Package: another-pkg
2794Architecture: all
2795"#;
2796 let control: Control = input.parse().unwrap();
2797
2798 let range = TextRange::new(0.into(), input.len().try_into().unwrap());
2800
2801 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2803 assert_eq!(binaries.len(), 2);
2804 }
2805
2806 #[test]
2807 fn test_source_in_range_partial_overlap() {
2808 let input = r#"Source: test-package
2810Maintainer: Test <test@example.com>
2811
2812Package: test-pkg
2813Architecture: any
2814"#;
2815 let control: Control = input.parse().unwrap();
2816
2817 let range = TextRange::new(10.into(), 30.into());
2819
2820 let result = control.source_in_range(range);
2822 assert!(result.is_some());
2823 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2824 }
2825
2826 #[test]
2827 fn test_wrap_and_sort_long_build_depends_wraps_to_one_per_line() {
2828 let input = r#"Source: test-package
2832Maintainer: Test <test@example.com>
2833Build-Depends: debhelper-compat (= 13), aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj
2834
2835"#;
2836 let mut control: Control = input.parse().unwrap();
2837 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2838
2839 let expected = r#"Source: test-package
2840Maintainer: Test <test@example.com>
2841Build-Depends: aaaa,
2842 bbbb,
2843 cccc,
2844 dddd,
2845 debhelper-compat (= 13),
2846 eeee,
2847 ffff,
2848 gggg,
2849 hhhh,
2850 iiii,
2851 jjjj
2852"#;
2853 assert_eq!(control.to_string(), expected);
2854 }
2855
2856 #[test]
2857 fn test_wrap_and_sort_short_build_depends_stays_one_line() {
2858 let input = r#"Source: test-package
2861Maintainer: Test <test@example.com>
2862Build-Depends: debhelper-compat (= 13), foo, bar
2863
2864"#;
2865 let mut control: Control = input.parse().unwrap();
2866 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2867
2868 let expected = "Source: test-package\nMaintainer: Test <test@example.com>\nBuild-Depends:bar, debhelper-compat (= 13), foo\n";
2871 assert_eq!(control.to_string(), expected);
2872 }
2873
2874 #[test]
2875 fn test_wrap_and_sort_long_build_depends_keeps_brackets_intact() {
2876 let value = "foo (>= 1.0), bar [amd64 arm64], baz <stage1 !nocheck>, qux, quux, corge, grault, garply, waldo, fred";
2882 let input = format!(
2883 "Source: test-package\nMaintainer: Test <test@example.com>\nBuild-Depends: {}\n\n",
2884 value
2885 );
2886 let mut control: Control = input.parse().unwrap();
2887 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2888 let out = control.to_string();
2889 assert!(out.contains("bar [amd64 arm64],\n"), "out was: {}", out);
2890 assert!(
2891 out.contains(" baz <stage1 !nocheck>,\n"),
2892 "out was: {}",
2893 out
2894 );
2895 assert!(out.contains(" foo (>= 1.0),\n"), "out was: {}", out);
2896 }
2897
2898 #[test]
2899 fn test_wrap_and_sort_with_malformed_relations() {
2900 let input = r#"Source: test-package
2903Maintainer: Test <test@example.com>
2904Build-Depends: some invalid relation syntax here
2905
2906Package: test-pkg
2907Architecture: any
2908"#;
2909 let mut control: Control = input.parse().unwrap();
2910
2911 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
2913
2914 let output = control.to_string();
2916 let expected = r#"Source: test-package
2917Maintainer: Test <test@example.com>
2918Build-Depends: some invalid relation syntax here
2919
2920Package: test-pkg
2921Architecture: any
2922"#;
2923 assert_eq!(output, expected);
2924 }
2925}