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 build_depends(&self) -> Option<Relations> {
696 self.paragraph
697 .get_with_comments("Build-Depends")
698 .map(|s| self.parse_relations(&s))
699 }
700
701 pub fn set_build_depends(&mut self, relations: &Relations) {
703 self.set("Build-Depends", relations.to_string().as_str());
704 }
705
706 pub fn build_depends_indep(&self) -> Option<Relations> {
708 self.paragraph
709 .get_with_comments("Build-Depends-Indep")
710 .map(|s| self.parse_relations(&s))
711 }
712
713 pub fn set_build_depends_indep(&mut self, relations: &Relations) {
715 self.set("Build-Depends-Indep", relations.to_string().as_str());
716 }
717
718 pub fn build_depends_arch(&self) -> Option<Relations> {
720 self.paragraph
721 .get_with_comments("Build-Depends-Arch")
722 .map(|s| self.parse_relations(&s))
723 }
724
725 pub fn set_build_depends_arch(&mut self, relations: &Relations) {
727 self.set("Build-Depends-Arch", relations.to_string().as_str());
728 }
729
730 pub fn build_conflicts(&self) -> Option<Relations> {
732 self.paragraph
733 .get_with_comments("Build-Conflicts")
734 .map(|s| self.parse_relations(&s))
735 }
736
737 pub fn set_build_conflicts(&mut self, relations: &Relations) {
739 self.set("Build-Conflicts", relations.to_string().as_str());
740 }
741
742 pub fn build_conflicts_indep(&self) -> Option<Relations> {
744 self.paragraph
745 .get_with_comments("Build-Conflicts-Indep")
746 .map(|s| self.parse_relations(&s))
747 }
748
749 pub fn set_build_conflicts_indep(&mut self, relations: &Relations) {
751 self.set("Build-Conflicts-Indep", relations.to_string().as_str());
752 }
753
754 pub fn build_conflicts_arch(&self) -> Option<Relations> {
756 self.paragraph
757 .get_with_comments("Build-Conflicts-Arch")
758 .map(|s| self.parse_relations(&s))
759 }
760
761 pub fn standards_version(&self) -> Option<String> {
763 self.paragraph.get("Standards-Version")
764 }
765
766 pub fn set_standards_version(&mut self, version: &str) {
768 self.set("Standards-Version", version);
769 }
770
771 pub fn homepage(&self) -> Option<url::Url> {
773 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
774 }
775
776 pub fn set_homepage(&mut self, homepage: &url::Url) {
778 self.set("Homepage", homepage.to_string().as_str());
779 }
780
781 pub fn vcs_git(&self) -> Option<String> {
783 self.paragraph.get("Vcs-Git")
784 }
785
786 pub fn set_vcs_git(&mut self, url: &str) {
788 self.set("Vcs-Git", url);
789 }
790
791 pub fn vcs_svn(&self) -> Option<String> {
793 self.paragraph.get("Vcs-Svn").map(|s| s.to_string())
794 }
795
796 pub fn set_vcs_svn(&mut self, url: &str) {
798 self.set("Vcs-Svn", url);
799 }
800
801 pub fn vcs_bzr(&self) -> Option<String> {
803 self.paragraph.get("Vcs-Bzr").map(|s| s.to_string())
804 }
805
806 pub fn set_vcs_bzr(&mut self, url: &str) {
808 self.set("Vcs-Bzr", url);
809 }
810
811 pub fn vcs_arch(&self) -> Option<String> {
813 self.paragraph.get("Vcs-Arch").map(|s| s.to_string())
814 }
815
816 pub fn set_vcs_arch(&mut self, url: &str) {
818 self.set("Vcs-Arch", url);
819 }
820
821 pub fn vcs_svk(&self) -> Option<String> {
823 self.paragraph.get("Vcs-Svk").map(|s| s.to_string())
824 }
825
826 pub fn set_vcs_svk(&mut self, url: &str) {
828 self.set("Vcs-Svk", url);
829 }
830
831 pub fn vcs_darcs(&self) -> Option<String> {
833 self.paragraph.get("Vcs-Darcs").map(|s| s.to_string())
834 }
835
836 pub fn set_vcs_darcs(&mut self, url: &str) {
838 self.set("Vcs-Darcs", url);
839 }
840
841 pub fn vcs_mtn(&self) -> Option<String> {
843 self.paragraph.get("Vcs-Mtn").map(|s| s.to_string())
844 }
845
846 pub fn set_vcs_mtn(&mut self, url: &str) {
848 self.set("Vcs-Mtn", url);
849 }
850
851 pub fn vcs_cvs(&self) -> Option<String> {
853 self.paragraph.get("Vcs-Cvs").map(|s| s.to_string())
854 }
855
856 pub fn set_vcs_cvs(&mut self, url: &str) {
858 self.set("Vcs-Cvs", url);
859 }
860
861 pub fn vcs_hg(&self) -> Option<String> {
863 self.paragraph.get("Vcs-Hg").map(|s| s.to_string())
864 }
865
866 pub fn set_vcs_hg(&mut self, url: &str) {
868 self.set("Vcs-Hg", url);
869 }
870
871 pub fn set(&mut self, key: &str, value: &str) {
873 self.paragraph
874 .set_with_field_order(key, value, SOURCE_FIELD_ORDER);
875 }
876
877 pub fn get(&self, key: &str) -> Option<String> {
879 self.paragraph.get(key)
880 }
881
882 pub fn vcs_browser(&self) -> Option<String> {
884 self.paragraph.get("Vcs-Browser")
885 }
886
887 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
889 for (name, value) in self.paragraph.items() {
890 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
891 return crate::vcs::Vcs::from_field(&name, &value).ok();
892 }
893 }
894 None
895 }
896
897 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
899 if let Some(url) = url {
900 self.set("Vcs-Browser", url);
901 } else {
902 self.paragraph.remove("Vcs-Browser");
903 }
904 }
905
906 pub fn uploaders(&self) -> Option<Vec<String>> {
908 self.paragraph
909 .get("Uploaders")
910 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
911 }
912
913 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
915 self.set(
916 "Uploaders",
917 uploaders
918 .iter()
919 .map(|s| s.to_string())
920 .collect::<Vec<_>>()
921 .join(", ")
922 .as_str(),
923 );
924 }
925
926 pub fn architecture(&self) -> Option<String> {
928 self.paragraph.get("Architecture")
929 }
930
931 pub fn set_architecture(&mut self, arch: Option<&str>) {
933 if let Some(arch) = arch {
934 self.set("Architecture", arch);
935 } else {
936 self.paragraph.remove("Architecture");
937 }
938 }
939
940 pub fn rules_requires_root(&self) -> Option<bool> {
942 self.paragraph
943 .get("Rules-Requires-Root")
944 .map(|s| match s.to_lowercase().as_str() {
945 "yes" => true,
946 "no" => false,
947 _ => panic!("invalid Rules-Requires-Root value"),
948 })
949 }
950
951 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
953 self.set(
954 "Rules-Requires-Root",
955 if requires_root { "yes" } else { "no" },
956 );
957 }
958
959 pub fn testsuite(&self) -> Option<String> {
961 self.paragraph.get("Testsuite")
962 }
963
964 pub fn set_testsuite(&mut self, testsuite: &str) {
966 self.set("Testsuite", testsuite);
967 }
968
969 pub fn overlaps_range(&self, range: TextRange) -> bool {
977 let para_range = self.paragraph.syntax().text_range();
978 para_range.start() < range.end() && range.start() < para_range.end()
979 }
980
981 pub fn fields_in_range(
989 &self,
990 range: TextRange,
991 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
992 self.paragraph.entries().filter(move |entry| {
993 let entry_range = entry.syntax().text_range();
994 entry_range.start() < range.end() && range.start() < entry_range.end()
995 })
996 }
997}
998
999#[cfg(feature = "python-debian")]
1000impl<'py> pyo3::IntoPyObject<'py> for Source {
1001 type Target = pyo3::PyAny;
1002 type Output = pyo3::Bound<'py, Self::Target>;
1003 type Error = pyo3::PyErr;
1004
1005 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1006 self.paragraph.into_pyobject(py)
1007 }
1008}
1009
1010#[cfg(feature = "python-debian")]
1011impl<'py> pyo3::IntoPyObject<'py> for &Source {
1012 type Target = pyo3::PyAny;
1013 type Output = pyo3::Bound<'py, Self::Target>;
1014 type Error = pyo3::PyErr;
1015
1016 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1017 (&self.paragraph).into_pyobject(py)
1018 }
1019}
1020
1021#[cfg(feature = "python-debian")]
1022impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
1023 type Error = pyo3::PyErr;
1024
1025 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1026 Ok(Source {
1027 paragraph: ob.extract()?,
1028 parse_mode: ParseMode::Strict,
1029 })
1030 }
1031}
1032
1033impl std::fmt::Display for Control {
1034 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1035 self.deb822.fmt(f)
1036 }
1037}
1038
1039impl AstNode for Control {
1040 type Language = deb822_lossless::Lang;
1041
1042 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
1043 Deb822::can_cast(kind)
1044 }
1045
1046 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
1047 Deb822::cast(syntax).map(|deb822| Control {
1048 deb822,
1049 parse_mode: ParseMode::Strict,
1050 })
1051 }
1052
1053 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
1054 self.deb822.syntax()
1055 }
1056}
1057
1058#[derive(Debug, Clone, PartialEq, Eq)]
1060pub struct Binary {
1061 paragraph: Paragraph,
1062 parse_mode: ParseMode,
1063}
1064
1065impl From<Binary> for Paragraph {
1066 fn from(b: Binary) -> Self {
1067 b.paragraph
1068 }
1069}
1070
1071impl From<Paragraph> for Binary {
1072 fn from(p: Paragraph) -> Self {
1073 Binary {
1074 paragraph: p,
1075 parse_mode: ParseMode::Strict,
1076 }
1077 }
1078}
1079
1080#[cfg(feature = "python-debian")]
1081impl<'py> pyo3::IntoPyObject<'py> for Binary {
1082 type Target = pyo3::PyAny;
1083 type Output = pyo3::Bound<'py, Self::Target>;
1084 type Error = pyo3::PyErr;
1085
1086 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1087 self.paragraph.into_pyobject(py)
1088 }
1089}
1090
1091#[cfg(feature = "python-debian")]
1092impl<'py> pyo3::IntoPyObject<'py> for &Binary {
1093 type Target = pyo3::PyAny;
1094 type Output = pyo3::Bound<'py, Self::Target>;
1095 type Error = pyo3::PyErr;
1096
1097 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1098 (&self.paragraph).into_pyobject(py)
1099 }
1100}
1101
1102#[cfg(feature = "python-debian")]
1103impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
1104 type Error = pyo3::PyErr;
1105
1106 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1107 Ok(Binary {
1108 paragraph: ob.extract()?,
1109 parse_mode: ParseMode::Strict,
1110 })
1111 }
1112}
1113
1114impl Default for Binary {
1115 fn default() -> Self {
1116 Self::new()
1117 }
1118}
1119
1120impl std::fmt::Display for Binary {
1121 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1122 self.paragraph.fmt(f)
1123 }
1124}
1125
1126impl Binary {
1127 fn parse_relations(&self, s: &str) -> Relations {
1129 match self.parse_mode {
1130 ParseMode::Strict => s.parse().unwrap(),
1131 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
1132 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
1133 }
1134 }
1135
1136 pub fn new() -> Self {
1138 Binary {
1139 paragraph: Paragraph::new(),
1140 parse_mode: ParseMode::Strict,
1141 }
1142 }
1143
1144 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
1146 &mut self.paragraph
1147 }
1148
1149 pub fn as_deb822(&self) -> &Paragraph {
1151 &self.paragraph
1152 }
1153
1154 pub fn wrap_and_sort(
1156 &mut self,
1157 indentation: deb822_lossless::Indentation,
1158 immediate_empty_line: bool,
1159 max_line_length_one_liner: Option<usize>,
1160 ) {
1161 let format = |name: &str, value: &str| -> String {
1162 format_field(name, value, max_line_length_one_liner)
1163 };
1164 self.paragraph = self.paragraph.wrap_and_sort(
1165 indentation,
1166 immediate_empty_line,
1167 max_line_length_one_liner,
1168 None,
1169 Some(&format),
1170 );
1171 }
1172
1173 pub fn name(&self) -> Option<String> {
1175 self.paragraph.get("Package")
1176 }
1177
1178 pub fn set_name(&mut self, name: &str) {
1180 self.set("Package", name);
1181 }
1182
1183 pub fn section(&self) -> Option<String> {
1185 self.paragraph.get("Section")
1186 }
1187
1188 pub fn set_section(&mut self, section: Option<&str>) {
1190 if let Some(section) = section {
1191 self.set("Section", section);
1192 } else {
1193 self.paragraph.remove("Section");
1194 }
1195 }
1196
1197 pub fn priority(&self) -> Option<Priority> {
1199 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
1200 }
1201
1202 pub fn set_priority(&mut self, priority: Option<Priority>) {
1204 if let Some(priority) = priority {
1205 self.set("Priority", priority.to_string().as_str());
1206 } else {
1207 self.paragraph.remove("Priority");
1208 }
1209 }
1210
1211 pub fn architecture(&self) -> Option<String> {
1213 self.paragraph.get("Architecture")
1214 }
1215
1216 pub fn set_architecture(&mut self, arch: Option<&str>) {
1218 if let Some(arch) = arch {
1219 self.set("Architecture", arch);
1220 } else {
1221 self.paragraph.remove("Architecture");
1222 }
1223 }
1224
1225 pub fn depends(&self) -> Option<Relations> {
1227 self.paragraph
1228 .get_with_comments("Depends")
1229 .map(|s| self.parse_relations(&s))
1230 }
1231
1232 pub fn set_depends(&mut self, depends: Option<&Relations>) {
1234 if let Some(depends) = depends {
1235 self.set("Depends", depends.to_string().as_str());
1236 } else {
1237 self.paragraph.remove("Depends");
1238 }
1239 }
1240
1241 pub fn recommends(&self) -> Option<Relations> {
1243 self.paragraph
1244 .get_with_comments("Recommends")
1245 .map(|s| self.parse_relations(&s))
1246 }
1247
1248 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
1250 if let Some(recommends) = recommends {
1251 self.set("Recommends", recommends.to_string().as_str());
1252 } else {
1253 self.paragraph.remove("Recommends");
1254 }
1255 }
1256
1257 pub fn suggests(&self) -> Option<Relations> {
1259 self.paragraph
1260 .get_with_comments("Suggests")
1261 .map(|s| self.parse_relations(&s))
1262 }
1263
1264 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
1266 if let Some(suggests) = suggests {
1267 self.set("Suggests", suggests.to_string().as_str());
1268 } else {
1269 self.paragraph.remove("Suggests");
1270 }
1271 }
1272
1273 pub fn enhances(&self) -> Option<Relations> {
1275 self.paragraph
1276 .get_with_comments("Enhances")
1277 .map(|s| self.parse_relations(&s))
1278 }
1279
1280 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1282 if let Some(enhances) = enhances {
1283 self.set("Enhances", enhances.to_string().as_str());
1284 } else {
1285 self.paragraph.remove("Enhances");
1286 }
1287 }
1288
1289 pub fn pre_depends(&self) -> Option<Relations> {
1291 self.paragraph
1292 .get_with_comments("Pre-Depends")
1293 .map(|s| self.parse_relations(&s))
1294 }
1295
1296 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1298 if let Some(pre_depends) = pre_depends {
1299 self.set("Pre-Depends", pre_depends.to_string().as_str());
1300 } else {
1301 self.paragraph.remove("Pre-Depends");
1302 }
1303 }
1304
1305 pub fn breaks(&self) -> Option<Relations> {
1307 self.paragraph
1308 .get_with_comments("Breaks")
1309 .map(|s| self.parse_relations(&s))
1310 }
1311
1312 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1314 if let Some(breaks) = breaks {
1315 self.set("Breaks", breaks.to_string().as_str());
1316 } else {
1317 self.paragraph.remove("Breaks");
1318 }
1319 }
1320
1321 pub fn conflicts(&self) -> Option<Relations> {
1323 self.paragraph
1324 .get_with_comments("Conflicts")
1325 .map(|s| self.parse_relations(&s))
1326 }
1327
1328 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1330 if let Some(conflicts) = conflicts {
1331 self.set("Conflicts", conflicts.to_string().as_str());
1332 } else {
1333 self.paragraph.remove("Conflicts");
1334 }
1335 }
1336
1337 pub fn replaces(&self) -> Option<Relations> {
1339 self.paragraph
1340 .get_with_comments("Replaces")
1341 .map(|s| self.parse_relations(&s))
1342 }
1343
1344 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1346 if let Some(replaces) = replaces {
1347 self.set("Replaces", replaces.to_string().as_str());
1348 } else {
1349 self.paragraph.remove("Replaces");
1350 }
1351 }
1352
1353 pub fn provides(&self) -> Option<Relations> {
1355 self.paragraph
1356 .get_with_comments("Provides")
1357 .map(|s| self.parse_relations(&s))
1358 }
1359
1360 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1362 if let Some(provides) = provides {
1363 self.set("Provides", provides.to_string().as_str());
1364 } else {
1365 self.paragraph.remove("Provides");
1366 }
1367 }
1368
1369 pub fn built_using(&self) -> Option<Relations> {
1371 self.paragraph
1372 .get_with_comments("Built-Using")
1373 .map(|s| self.parse_relations(&s))
1374 }
1375
1376 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1378 if let Some(built_using) = built_using {
1379 self.set("Built-Using", built_using.to_string().as_str());
1380 } else {
1381 self.paragraph.remove("Built-Using");
1382 }
1383 }
1384
1385 pub fn static_built_using(&self) -> Option<Relations> {
1387 self.paragraph
1388 .get_with_comments("Static-Built-Using")
1389 .map(|s| self.parse_relations(&s))
1390 }
1391
1392 pub fn set_static_built_using(&mut self, static_built_using: Option<&Relations>) {
1394 if let Some(static_built_using) = static_built_using {
1395 self.set(
1396 "Static-Built-Using",
1397 static_built_using.to_string().as_str(),
1398 );
1399 } else {
1400 self.paragraph.remove("Static-Built-Using");
1401 }
1402 }
1403
1404 pub fn multi_arch(&self) -> Option<MultiArch> {
1406 self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap())
1407 }
1408
1409 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1411 if let Some(multi_arch) = multi_arch {
1412 self.set("Multi-Arch", multi_arch.to_string().as_str());
1413 } else {
1414 self.paragraph.remove("Multi-Arch");
1415 }
1416 }
1417
1418 pub fn essential(&self) -> bool {
1420 self.paragraph
1421 .get("Essential")
1422 .map(|s| s == "yes")
1423 .unwrap_or(false)
1424 }
1425
1426 pub fn set_essential(&mut self, essential: bool) {
1428 if essential {
1429 self.set("Essential", "yes");
1430 } else {
1431 self.paragraph.remove("Essential");
1432 }
1433 }
1434
1435 pub fn description(&self) -> Option<String> {
1437 self.paragraph.get_multiline("Description")
1438 }
1439
1440 pub fn set_description(&mut self, description: Option<&str>) {
1442 if let Some(description) = description {
1443 self.paragraph.set_with_indent_pattern(
1444 "Description",
1445 description,
1446 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1447 Some(BINARY_FIELD_ORDER),
1448 );
1449 } else {
1450 self.paragraph.remove("Description");
1451 }
1452 }
1453
1454 pub fn homepage(&self) -> Option<url::Url> {
1456 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
1457 }
1458
1459 pub fn set_homepage(&mut self, url: &url::Url) {
1461 self.set("Homepage", url.as_str());
1462 }
1463
1464 pub fn set(&mut self, key: &str, value: &str) {
1466 self.paragraph
1467 .set_with_field_order(key, value, BINARY_FIELD_ORDER);
1468 }
1469
1470 pub fn get(&self, key: &str) -> Option<String> {
1472 self.paragraph.get(key)
1473 }
1474
1475 pub fn overlaps_range(&self, range: TextRange) -> bool {
1483 let para_range = self.paragraph.syntax().text_range();
1484 para_range.start() < range.end() && range.start() < para_range.end()
1485 }
1486
1487 pub fn fields_in_range(
1495 &self,
1496 range: TextRange,
1497 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1498 self.paragraph.entries().filter(move |entry| {
1499 let entry_range = entry.syntax().text_range();
1500 entry_range.start() < range.end() && range.start() < entry_range.end()
1501 })
1502 }
1503}
1504
1505#[cfg(test)]
1506mod tests {
1507 use super::*;
1508 use crate::relations::VersionConstraint;
1509
1510 #[test]
1511 fn test_source_set_field_ordering() {
1512 let mut control = Control::new();
1513 let mut source = control.add_source("mypackage");
1514
1515 source.set("Homepage", "https://example.com");
1517 source.set("Build-Depends", "debhelper");
1518 source.set("Standards-Version", "4.5.0");
1519 source.set("Maintainer", "Test <test@example.com>");
1520
1521 let output = source.to_string();
1523 let lines: Vec<&str> = output.lines().collect();
1524
1525 assert!(lines[0].starts_with("Source:"));
1527
1528 let maintainer_pos = lines
1530 .iter()
1531 .position(|l| l.starts_with("Maintainer:"))
1532 .unwrap();
1533 let build_depends_pos = lines
1534 .iter()
1535 .position(|l| l.starts_with("Build-Depends:"))
1536 .unwrap();
1537 let standards_pos = lines
1538 .iter()
1539 .position(|l| l.starts_with("Standards-Version:"))
1540 .unwrap();
1541 let homepage_pos = lines
1542 .iter()
1543 .position(|l| l.starts_with("Homepage:"))
1544 .unwrap();
1545
1546 assert!(maintainer_pos < build_depends_pos);
1548 assert!(build_depends_pos < standards_pos);
1549 assert!(standards_pos < homepage_pos);
1550 }
1551
1552 #[test]
1553 fn test_binary_set_field_ordering() {
1554 let mut control = Control::new();
1555 let mut binary = control.add_binary("mypackage");
1556
1557 binary.set("Description", "A test package");
1559 binary.set("Architecture", "amd64");
1560 binary.set("Depends", "libc6");
1561 binary.set("Section", "utils");
1562
1563 let output = binary.to_string();
1565 let lines: Vec<&str> = output.lines().collect();
1566
1567 assert!(lines[0].starts_with("Package:"));
1569
1570 let arch_pos = lines
1572 .iter()
1573 .position(|l| l.starts_with("Architecture:"))
1574 .unwrap();
1575 let section_pos = lines
1576 .iter()
1577 .position(|l| l.starts_with("Section:"))
1578 .unwrap();
1579 let depends_pos = lines
1580 .iter()
1581 .position(|l| l.starts_with("Depends:"))
1582 .unwrap();
1583 let desc_pos = lines
1584 .iter()
1585 .position(|l| l.starts_with("Description:"))
1586 .unwrap();
1587
1588 assert!(arch_pos < section_pos);
1590 assert!(section_pos < depends_pos);
1591 assert!(depends_pos < desc_pos);
1592 }
1593
1594 #[test]
1595 fn test_source_specific_set_methods_use_field_ordering() {
1596 let mut control = Control::new();
1597 let mut source = control.add_source("mypackage");
1598
1599 source.set_homepage(&"https://example.com".parse().unwrap());
1601 source.set_maintainer("Test <test@example.com>");
1602 source.set_standards_version("4.5.0");
1603 source.set_vcs_git("https://github.com/example/repo");
1604
1605 let output = source.to_string();
1607 let lines: Vec<&str> = output.lines().collect();
1608
1609 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1611 let maintainer_pos = lines
1612 .iter()
1613 .position(|l| l.starts_with("Maintainer:"))
1614 .unwrap();
1615 let standards_pos = lines
1616 .iter()
1617 .position(|l| l.starts_with("Standards-Version:"))
1618 .unwrap();
1619 let vcs_git_pos = lines
1620 .iter()
1621 .position(|l| l.starts_with("Vcs-Git:"))
1622 .unwrap();
1623 let homepage_pos = lines
1624 .iter()
1625 .position(|l| l.starts_with("Homepage:"))
1626 .unwrap();
1627
1628 assert!(source_pos < maintainer_pos);
1630 assert!(maintainer_pos < standards_pos);
1631 assert!(standards_pos < vcs_git_pos);
1632 assert!(vcs_git_pos < homepage_pos);
1633 }
1634
1635 #[test]
1636 fn test_binary_specific_set_methods_use_field_ordering() {
1637 let mut control = Control::new();
1638 let mut binary = control.add_binary("mypackage");
1639
1640 binary.set_description(Some("A test package"));
1642 binary.set_architecture(Some("amd64"));
1643 let depends = "libc6".parse().unwrap();
1644 binary.set_depends(Some(&depends));
1645 binary.set_section(Some("utils"));
1646 binary.set_priority(Some(Priority::Optional));
1647
1648 let output = binary.to_string();
1650 let lines: Vec<&str> = output.lines().collect();
1651
1652 let package_pos = lines
1654 .iter()
1655 .position(|l| l.starts_with("Package:"))
1656 .unwrap();
1657 let arch_pos = lines
1658 .iter()
1659 .position(|l| l.starts_with("Architecture:"))
1660 .unwrap();
1661 let section_pos = lines
1662 .iter()
1663 .position(|l| l.starts_with("Section:"))
1664 .unwrap();
1665 let priority_pos = lines
1666 .iter()
1667 .position(|l| l.starts_with("Priority:"))
1668 .unwrap();
1669 let depends_pos = lines
1670 .iter()
1671 .position(|l| l.starts_with("Depends:"))
1672 .unwrap();
1673 let desc_pos = lines
1674 .iter()
1675 .position(|l| l.starts_with("Description:"))
1676 .unwrap();
1677
1678 assert!(package_pos < arch_pos);
1680 assert!(arch_pos < section_pos);
1681 assert!(section_pos < priority_pos);
1682 assert!(priority_pos < depends_pos);
1683 assert!(depends_pos < desc_pos);
1684 }
1685
1686 #[test]
1687 fn test_parse() {
1688 let control: Control = r#"Source: foo
1689Section: libs
1690Priority: optional
1691Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1692Homepage: https://example.com
1693
1694"#
1695 .parse()
1696 .unwrap();
1697 let source = control.source().unwrap();
1698
1699 assert_eq!(source.name(), Some("foo".to_owned()));
1700 assert_eq!(source.section(), Some("libs".to_owned()));
1701 assert_eq!(source.priority(), Some(super::Priority::Optional));
1702 assert_eq!(
1703 source.homepage(),
1704 Some("https://example.com".parse().unwrap())
1705 );
1706 let bd = source.build_depends().unwrap();
1707 let entries = bd.entries().collect::<Vec<_>>();
1708 assert_eq!(entries.len(), 2);
1709 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1710 assert_eq!(rel.name(), "bar");
1711 assert_eq!(
1712 rel.version(),
1713 Some((
1714 VersionConstraint::GreaterThanEqual,
1715 "1.0.0".parse().unwrap()
1716 ))
1717 );
1718 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1719 assert_eq!(rel.name(), "baz");
1720 assert_eq!(
1721 rel.version(),
1722 Some((
1723 VersionConstraint::GreaterThanEqual,
1724 "1.0.0".parse().unwrap()
1725 ))
1726 );
1727 }
1728
1729 #[test]
1730 fn test_description() {
1731 let control: Control = r#"Source: foo
1732
1733Package: foo
1734Description: this is the short description
1735 And the longer one
1736 .
1737 is on the next lines
1738"#
1739 .parse()
1740 .unwrap();
1741 let binary = control.binaries().next().unwrap();
1742 assert_eq!(
1743 binary.description(),
1744 Some(
1745 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1746 .to_owned()
1747 )
1748 );
1749 }
1750
1751 #[test]
1752 fn test_set_description_on_package_without_description() {
1753 let control: Control = r#"Source: foo
1754
1755Package: foo
1756Architecture: amd64
1757"#
1758 .parse()
1759 .unwrap();
1760 let mut binary = control.binaries().next().unwrap();
1761
1762 binary.set_description(Some(
1764 "Short description\nLonger description\n.\nAnother line",
1765 ));
1766
1767 let output = binary.to_string();
1768
1769 assert_eq!(
1771 binary.description(),
1772 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1773 );
1774
1775 assert_eq!(
1777 output,
1778 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1779 );
1780 }
1781
1782 #[test]
1783 fn test_as_mut_deb822() {
1784 let mut control = Control::new();
1785 let deb822 = control.as_mut_deb822();
1786 let mut p = deb822.add_paragraph();
1787 p.set("Source", "foo");
1788 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1789 }
1790
1791 #[test]
1792 fn test_as_deb822() {
1793 let control = Control::new();
1794 let _deb822: &Deb822 = control.as_deb822();
1795 }
1796
1797 #[test]
1798 fn test_set_depends() {
1799 let mut control = Control::new();
1800 let mut binary = control.add_binary("foo");
1801 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1802 binary.set_depends(Some(&relations));
1803 }
1804
1805 #[test]
1806 fn test_wrap_and_sort() {
1807 let mut control: Control = r#"Package: blah
1808Section: libs
1809
1810
1811
1812Package: foo
1813Description: this is a
1814 bar
1815 blah
1816"#
1817 .parse()
1818 .unwrap();
1819 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1820 let expected = r#"Package: blah
1821Section: libs
1822
1823Package: foo
1824Description: this is a
1825 bar
1826 blah
1827"#
1828 .to_owned();
1829 assert_eq!(control.to_string(), expected);
1830 }
1831
1832 #[test]
1833 fn test_wrap_and_sort_source() {
1834 let mut control: Control = r#"Source: blah
1835Depends: foo, bar (<= 1.0.0)
1836
1837"#
1838 .parse()
1839 .unwrap();
1840 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1841 let expected = r#"Source: blah
1842Depends: bar (<= 1.0.0), foo
1843"#
1844 .to_owned();
1845 assert_eq!(control.to_string(), expected);
1846 }
1847
1848 #[test]
1849 fn test_source_wrap_and_sort() {
1850 let control: Control = r#"Source: blah
1851Build-Depends: foo, bar (>= 1.0.0)
1852
1853"#
1854 .parse()
1855 .unwrap();
1856 let mut source = control.source().unwrap();
1857 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1858 assert!(source.build_depends().is_some());
1862 }
1863
1864 #[test]
1865 fn test_binary_set_breaks() {
1866 let mut control = Control::new();
1867 let mut binary = control.add_binary("foo");
1868 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1869 binary.set_breaks(Some(&relations));
1870 assert!(binary.breaks().is_some());
1871 }
1872
1873 #[test]
1874 fn test_binary_set_pre_depends() {
1875 let mut control = Control::new();
1876 let mut binary = control.add_binary("foo");
1877 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1878 binary.set_pre_depends(Some(&relations));
1879 assert!(binary.pre_depends().is_some());
1880 }
1881
1882 #[test]
1883 fn test_binary_set_provides() {
1884 let mut control = Control::new();
1885 let mut binary = control.add_binary("foo");
1886 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1887 binary.set_provides(Some(&relations));
1888 assert!(binary.provides().is_some());
1889 }
1890
1891 #[test]
1892 fn test_source_build_conflicts() {
1893 let control: Control = r#"Source: blah
1894Build-Conflicts: foo, bar (>= 1.0.0)
1895
1896"#
1897 .parse()
1898 .unwrap();
1899 let source = control.source().unwrap();
1900 let conflicts = source.build_conflicts();
1901 assert!(conflicts.is_some());
1902 }
1903
1904 #[test]
1905 fn test_source_vcs_svn() {
1906 let control: Control = r#"Source: blah
1907Vcs-Svn: https://example.com/svn/repo
1908
1909"#
1910 .parse()
1911 .unwrap();
1912 let source = control.source().unwrap();
1913 assert_eq!(
1914 source.vcs_svn(),
1915 Some("https://example.com/svn/repo".to_string())
1916 );
1917 }
1918
1919 #[test]
1920 fn test_control_from_conversion() {
1921 let deb822_data = r#"Source: test
1922Section: libs
1923
1924"#;
1925 let deb822: Deb822 = deb822_data.parse().unwrap();
1926 let control = Control::from(deb822);
1927 assert!(control.source().is_some());
1928 }
1929
1930 #[test]
1931 fn test_fields_in_range() {
1932 let control_text = r#"Source: test-package
1933Maintainer: Test User <test@example.com>
1934Build-Depends: debhelper (>= 12)
1935
1936Package: test-binary
1937Architecture: any
1938Depends: ${shlibs:Depends}
1939Description: Test package
1940 This is a test package
1941"#;
1942 let control: Control = control_text.parse().unwrap();
1943
1944 let source_start = 0;
1946 let source_end = "Source: test-package".len();
1947 let source_range = TextRange::new((source_start as u32).into(), (source_end as u32).into());
1948
1949 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1950 assert_eq!(fields.len(), 1);
1951 assert_eq!(fields[0].key(), Some("Source".to_string()));
1952
1953 let maintainer_start = control_text.find("Maintainer:").unwrap();
1955 let build_depends_end = control_text
1956 .find("Build-Depends: debhelper (>= 12)")
1957 .unwrap()
1958 + "Build-Depends: debhelper (>= 12)".len();
1959 let multi_range = TextRange::new(
1960 (maintainer_start as u32).into(),
1961 (build_depends_end as u32).into(),
1962 );
1963
1964 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1965 assert_eq!(fields.len(), 2);
1966 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1967 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1968
1969 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1971 let cross_para_end =
1972 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1973 let cross_range = TextRange::new(
1974 (cross_para_start as u32).into(),
1975 (cross_para_end as u32).into(),
1976 );
1977
1978 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1979 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1981 assert_eq!(fields[1].key(), Some("Package".to_string()));
1982 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1983
1984 let empty_range = TextRange::new(1000.into(), 1001.into());
1986 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1987 assert_eq!(fields.len(), 0);
1988 }
1989
1990 #[test]
1991 fn test_source_overlaps_range() {
1992 let control_text = r#"Source: test-package
1993Maintainer: Test User <test@example.com>
1994
1995Package: test-binary
1996Architecture: any
1997"#;
1998 let control: Control = control_text.parse().unwrap();
1999 let source = control.source().unwrap();
2000
2001 let overlap_range = TextRange::new(10.into(), 30.into());
2003 assert!(source.overlaps_range(overlap_range));
2004
2005 let binary_start = control_text.find("Package:").unwrap();
2007 let no_overlap_range = TextRange::new(
2008 (binary_start as u32).into(),
2009 ((binary_start + 20) as u32).into(),
2010 );
2011 assert!(!source.overlaps_range(no_overlap_range));
2012
2013 let partial_overlap = TextRange::new(0.into(), 15.into());
2015 assert!(source.overlaps_range(partial_overlap));
2016 }
2017
2018 #[test]
2019 fn test_source_fields_in_range() {
2020 let control_text = r#"Source: test-package
2021Maintainer: Test User <test@example.com>
2022Build-Depends: debhelper (>= 12)
2023
2024Package: test-binary
2025"#;
2026 let control: Control = control_text.parse().unwrap();
2027 let source = control.source().unwrap();
2028
2029 let maintainer_start = control_text.find("Maintainer:").unwrap();
2031 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
2032 let maintainer_range = TextRange::new(
2033 (maintainer_start as u32).into(),
2034 (maintainer_end as u32).into(),
2035 );
2036
2037 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
2038 assert_eq!(fields.len(), 1);
2039 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
2040
2041 let all_source_range = TextRange::new(0.into(), 100.into());
2043 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
2044 assert_eq!(fields.len(), 3); }
2046
2047 #[test]
2048 fn test_binary_overlaps_range() {
2049 let control_text = r#"Source: test-package
2050
2051Package: test-binary
2052Architecture: any
2053Depends: ${shlibs:Depends}
2054"#;
2055 let control: Control = control_text.parse().unwrap();
2056 let binary = control.binaries().next().unwrap();
2057
2058 let package_start = control_text.find("Package:").unwrap();
2060 let overlap_range = TextRange::new(
2061 (package_start as u32).into(),
2062 ((package_start + 30) as u32).into(),
2063 );
2064 assert!(binary.overlaps_range(overlap_range));
2065
2066 let no_overlap_range = TextRange::new(0.into(), 10.into());
2068 assert!(!binary.overlaps_range(no_overlap_range));
2069 }
2070
2071 #[test]
2072 fn test_binary_fields_in_range() {
2073 let control_text = r#"Source: test-package
2074
2075Package: test-binary
2076Architecture: any
2077Depends: ${shlibs:Depends}
2078Description: Test binary
2079 This is a test binary package
2080"#;
2081 let control: Control = control_text.parse().unwrap();
2082 let binary = control.binaries().next().unwrap();
2083
2084 let arch_start = control_text.find("Architecture:").unwrap();
2086 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
2087 + "Depends: ${shlibs:Depends}".len();
2088 let range = TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
2089
2090 let fields: Vec<_> = binary.fields_in_range(range).collect();
2091 assert_eq!(fields.len(), 2);
2092 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
2093 assert_eq!(fields[1].key(), Some("Depends".to_string()));
2094
2095 let desc_start = control_text.find("Description:").unwrap();
2097 let partial_range = TextRange::new(
2098 ((desc_start + 5) as u32).into(),
2099 ((desc_start + 15) as u32).into(),
2100 );
2101 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
2102 assert_eq!(fields.len(), 1);
2103 assert_eq!(fields[0].key(), Some("Description".to_string()));
2104 }
2105
2106 #[test]
2107 fn test_incremental_parsing_use_case() {
2108 let control_text = r#"Source: example
2110Maintainer: John Doe <john@example.com>
2111Standards-Version: 4.6.0
2112Build-Depends: debhelper-compat (= 13)
2113
2114Package: example-bin
2115Architecture: all
2116Depends: ${misc:Depends}
2117Description: Example package
2118 This is an example.
2119"#;
2120 let control: Control = control_text.parse().unwrap();
2121
2122 let change_start = control_text.find("Standards-Version:").unwrap();
2124 let change_end = change_start + "Standards-Version: 4.6.0".len();
2125 let change_range = TextRange::new((change_start as u32).into(), (change_end as u32).into());
2126
2127 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
2129 assert_eq!(affected_fields.len(), 1);
2130 assert_eq!(
2131 affected_fields[0].key(),
2132 Some("Standards-Version".to_string())
2133 );
2134
2135 for entry in &affected_fields {
2137 let key = entry.key().unwrap();
2138 assert_ne!(key, "Maintainer");
2139 assert_ne!(key, "Build-Depends");
2140 assert_ne!(key, "Architecture");
2141 }
2142 }
2143
2144 #[test]
2145 fn test_positioned_parse_errors() {
2146 let input = "Invalid: field\nBroken field without colon";
2148 let parsed = Control::parse(input);
2149
2150 let positioned_errors = parsed.positioned_errors();
2152 assert!(
2153 !positioned_errors.is_empty(),
2154 "Should have positioned errors"
2155 );
2156
2157 for error in positioned_errors {
2159 let start_offset: u32 = error.range.start().into();
2160 let end_offset: u32 = error.range.end().into();
2161
2162 assert!(!error.message.is_empty());
2164
2165 assert!(start_offset <= end_offset);
2167 assert!(end_offset <= input.len() as u32);
2168
2169 assert!(error.code.is_some());
2171
2172 println!(
2173 "Error at {:?}: {} (code: {:?})",
2174 error.range, error.message, error.code
2175 );
2176 }
2177
2178 let string_errors = parsed.errors();
2180 assert!(!string_errors.is_empty());
2181 assert_eq!(string_errors.len(), positioned_errors.len());
2182 }
2183
2184 #[test]
2185 fn test_sort_binaries_basic() {
2186 let input = r#"Source: foo
2187
2188Package: libfoo
2189Architecture: all
2190
2191Package: libbar
2192Architecture: all
2193"#;
2194
2195 let mut control: Control = input.parse().unwrap();
2196 control.sort_binaries(false);
2197
2198 let binaries: Vec<_> = control.binaries().collect();
2199 assert_eq!(binaries.len(), 2);
2200 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
2201 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
2202 }
2203
2204 #[test]
2205 fn test_sort_binaries_keep_first() {
2206 let input = r#"Source: foo
2207
2208Package: zzz-first
2209Architecture: all
2210
2211Package: libbar
2212Architecture: all
2213
2214Package: libaaa
2215Architecture: all
2216"#;
2217
2218 let mut control: Control = input.parse().unwrap();
2219 control.sort_binaries(true);
2220
2221 let binaries: Vec<_> = control.binaries().collect();
2222 assert_eq!(binaries.len(), 3);
2223 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
2225 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
2227 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
2228 }
2229
2230 #[test]
2231 fn test_sort_binaries_already_sorted() {
2232 let input = r#"Source: foo
2233
2234Package: aaa
2235Architecture: all
2236
2237Package: bbb
2238Architecture: all
2239
2240Package: ccc
2241Architecture: all
2242"#;
2243
2244 let mut control: Control = input.parse().unwrap();
2245 control.sort_binaries(false);
2246
2247 let binaries: Vec<_> = control.binaries().collect();
2248 assert_eq!(binaries.len(), 3);
2249 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2250 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
2251 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
2252 }
2253
2254 #[test]
2255 fn test_sort_binaries_no_binaries() {
2256 let input = r#"Source: foo
2257Maintainer: test@example.com
2258"#;
2259
2260 let mut control: Control = input.parse().unwrap();
2261 control.sort_binaries(false);
2262
2263 assert_eq!(control.binaries().count(), 0);
2265 }
2266
2267 #[test]
2268 fn test_sort_binaries_one_binary() {
2269 let input = r#"Source: foo
2270
2271Package: bar
2272Architecture: all
2273"#;
2274
2275 let mut control: Control = input.parse().unwrap();
2276 control.sort_binaries(false);
2277
2278 let binaries: Vec<_> = control.binaries().collect();
2279 assert_eq!(binaries.len(), 1);
2280 assert_eq!(binaries[0].name(), Some("bar".to_string()));
2281 }
2282
2283 #[test]
2284 fn test_sort_binaries_preserves_fields() {
2285 let input = r#"Source: foo
2286
2287Package: zzz
2288Architecture: any
2289Depends: libc6
2290Description: ZZZ package
2291
2292Package: aaa
2293Architecture: all
2294Depends: ${misc:Depends}
2295Description: AAA package
2296"#;
2297
2298 let mut control: Control = input.parse().unwrap();
2299 control.sort_binaries(false);
2300
2301 let binaries: Vec<_> = control.binaries().collect();
2302 assert_eq!(binaries.len(), 2);
2303
2304 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2306 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
2307 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
2308
2309 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
2311 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2312 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2313 }
2314
2315 #[test]
2316 fn test_remove_binary_basic() {
2317 let mut control = Control::new();
2318 control.add_binary("foo");
2319 assert_eq!(control.binaries().count(), 1);
2320 assert!(control.remove_binary("foo"));
2321 assert_eq!(control.binaries().count(), 0);
2322 }
2323
2324 #[test]
2325 fn test_remove_binary_nonexistent() {
2326 let mut control = Control::new();
2327 control.add_binary("foo");
2328 assert!(!control.remove_binary("bar"));
2329 assert_eq!(control.binaries().count(), 1);
2330 }
2331
2332 #[test]
2333 fn test_remove_binary_multiple() {
2334 let mut control = Control::new();
2335 control.add_binary("foo");
2336 control.add_binary("bar");
2337 control.add_binary("baz");
2338 assert_eq!(control.binaries().count(), 3);
2339
2340 assert!(control.remove_binary("bar"));
2341 assert_eq!(control.binaries().count(), 2);
2342
2343 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2344 assert_eq!(names, vec!["foo", "baz"]);
2345 }
2346
2347 #[test]
2348 fn test_remove_binary_preserves_source() {
2349 let input = r#"Source: mypackage
2350
2351Package: foo
2352Architecture: all
2353
2354Package: bar
2355Architecture: all
2356"#;
2357 let mut control: Control = input.parse().unwrap();
2358 assert!(control.source().is_some());
2359 assert_eq!(control.binaries().count(), 2);
2360
2361 assert!(control.remove_binary("foo"));
2362
2363 assert!(control.source().is_some());
2365 assert_eq!(
2366 control.source().unwrap().name(),
2367 Some("mypackage".to_string())
2368 );
2369
2370 assert_eq!(control.binaries().count(), 1);
2372 assert_eq!(
2373 control.binaries().next().unwrap().name(),
2374 Some("bar".to_string())
2375 );
2376 }
2377
2378 #[test]
2379 fn test_remove_binary_from_parsed() {
2380 let input = r#"Source: test
2381
2382Package: test-bin
2383Architecture: any
2384Depends: libc6
2385Description: Test binary
2386
2387Package: test-lib
2388Architecture: all
2389Description: Test library
2390"#;
2391 let mut control: Control = input.parse().unwrap();
2392 assert_eq!(control.binaries().count(), 2);
2393
2394 assert!(control.remove_binary("test-bin"));
2395
2396 let output = control.to_string();
2397 assert!(!output.contains("test-bin"));
2398 assert!(output.contains("test-lib"));
2399 assert!(output.contains("Source: test"));
2400 }
2401
2402 #[test]
2403 fn test_build_depends_preserves_indentation_after_removal() {
2404 let input = r#"Source: acpi-support
2405Section: admin
2406Priority: optional
2407Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2408Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2409 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2410"#;
2411 let control: Control = input.parse().unwrap();
2412 let mut source = control.source().unwrap();
2413
2414 let mut build_depends = source.build_depends().unwrap();
2416
2417 let mut to_remove = Vec::new();
2419 for (idx, entry) in build_depends.entries().enumerate() {
2420 for relation in entry.relations() {
2421 if relation.name() == "dh-systemd" {
2422 to_remove.push(idx);
2423 break;
2424 }
2425 }
2426 }
2427
2428 for idx in to_remove.into_iter().rev() {
2429 build_depends.remove_entry(idx);
2430 }
2431
2432 source.set_build_depends(&build_depends);
2434
2435 let output = source.to_string();
2436
2437 assert!(
2439 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2440 "Expected 4-space indentation to be preserved, but got:\n{}",
2441 output
2442 );
2443 }
2444
2445 #[test]
2446 fn test_build_depends_direct_string_set_loses_indentation() {
2447 let input = r#"Source: acpi-support
2448Section: admin
2449Priority: optional
2450Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2451Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2452 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2453"#;
2454 let control: Control = input.parse().unwrap();
2455 let mut source = control.source().unwrap();
2456
2457 let mut build_depends = source.build_depends().unwrap();
2459
2460 let mut to_remove = Vec::new();
2462 for (idx, entry) in build_depends.entries().enumerate() {
2463 for relation in entry.relations() {
2464 if relation.name() == "dh-systemd" {
2465 to_remove.push(idx);
2466 break;
2467 }
2468 }
2469 }
2470
2471 for idx in to_remove.into_iter().rev() {
2472 build_depends.remove_entry(idx);
2473 }
2474
2475 source.set("Build-Depends", &build_depends.to_string());
2477
2478 let output = source.to_string();
2479 println!("Output with string set:");
2480 println!("{}", output);
2481
2482 assert!(
2485 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2486 "Expected 4-space indentation to be preserved, but got:\n{}",
2487 output
2488 );
2489 }
2490
2491 #[test]
2492 fn test_parse_mode_strict_default() {
2493 let control = Control::new();
2494 assert_eq!(control.parse_mode(), ParseMode::Strict);
2495
2496 let control: Control = "Source: test\n".parse().unwrap();
2497 assert_eq!(control.parse_mode(), ParseMode::Strict);
2498 }
2499
2500 #[test]
2501 fn test_parse_mode_new_with_mode() {
2502 let control_relaxed = Control::new_with_mode(ParseMode::Relaxed);
2503 assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed);
2504
2505 let control_substvar = Control::new_with_mode(ParseMode::Substvar);
2506 assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar);
2507 }
2508
2509 #[test]
2510 fn test_relaxed_mode_handles_broken_relations() {
2511 let input = r#"Source: test-package
2512Build-Depends: debhelper, @@@broken@@@, python3
2513
2514Package: test-pkg
2515Depends: libfoo, %%%invalid%%%, libbar
2516"#;
2517
2518 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2519 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2520
2521 if let Some(source) = control.source() {
2523 let bd = source.build_depends();
2524 assert!(bd.is_some());
2525 let relations = bd.unwrap();
2526 assert!(relations.len() >= 2); }
2529
2530 for binary in control.binaries() {
2531 let deps = binary.depends();
2532 assert!(deps.is_some());
2533 let relations = deps.unwrap();
2534 assert!(relations.len() >= 2); }
2537 }
2538
2539 #[test]
2540 fn test_substvar_mode_via_parse() {
2541 let input = r#"Source: test-package
2545Build-Depends: debhelper, ${misc:Depends}
2546
2547Package: test-pkg
2548Depends: ${shlibs:Depends}, libfoo
2549"#;
2550
2551 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2553
2554 if let Some(source) = control.source() {
2555 let bd = source.build_depends();
2557 assert!(bd.is_some());
2558 }
2559
2560 for binary in control.binaries() {
2561 let deps = binary.depends();
2562 assert!(deps.is_some());
2563 }
2564 }
2565
2566 #[test]
2567 #[should_panic]
2568 fn test_strict_mode_panics_on_broken_syntax() {
2569 let input = r#"Source: test-package
2570Build-Depends: debhelper, @@@broken@@@
2571"#;
2572
2573 let control: Control = input.parse().unwrap();
2575
2576 if let Some(source) = control.source() {
2577 let _ = source.build_depends();
2579 }
2580 }
2581
2582 #[test]
2583 fn test_from_file_relaxed_sets_relaxed_mode() {
2584 let input = r#"Source: test-package
2585Maintainer: Test <test@example.com>
2586"#;
2587
2588 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2589 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2590 }
2591
2592 #[test]
2593 fn test_parse_mode_propagates_to_paragraphs() {
2594 let input = r#"Source: test-package
2595Build-Depends: debhelper, @@@invalid@@@, python3
2596
2597Package: test-pkg
2598Depends: libfoo, %%%bad%%%, libbar
2599"#;
2600
2601 let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap();
2603
2604 if let Some(source) = control.source() {
2607 assert!(source.build_depends().is_some());
2608 }
2609
2610 for binary in control.binaries() {
2611 assert!(binary.depends().is_some());
2612 }
2613 }
2614
2615 #[test]
2616 fn test_preserves_final_newline() {
2617 let input_with_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2619 let control: Control = input_with_newline.parse().unwrap();
2620 let output = control.to_string();
2621 assert_eq!(output, input_with_newline);
2622 }
2623
2624 #[test]
2625 fn test_preserves_no_final_newline() {
2626 let input_without_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any";
2628 let control: Control = input_without_newline.parse().unwrap();
2629 let output = control.to_string();
2630 assert_eq!(output, input_without_newline);
2631 }
2632
2633 #[test]
2634 fn test_final_newline_after_modifications() {
2635 let input = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2637 let control: Control = input.parse().unwrap();
2638
2639 let mut source = control.source().unwrap();
2641 source.set_section(Some("utils"));
2642
2643 let output = control.to_string();
2644 let expected = "Source: test-package\nSection: utils\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2645 assert_eq!(output, expected);
2646 }
2647
2648 #[test]
2649 fn test_source_in_range() {
2650 let input = r#"Source: test-package
2652Maintainer: Test <test@example.com>
2653Section: utils
2654
2655Package: test-pkg
2656Architecture: any
2657"#;
2658 let control: Control = input.parse().unwrap();
2659
2660 let source = control.source().unwrap();
2662 let source_range = source.as_deb822().text_range();
2663
2664 let result = control.source_in_range(source_range);
2666 assert!(result.is_some());
2667 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2668
2669 let overlap_range = TextRange::new(0.into(), 20.into());
2671 let result = control.source_in_range(overlap_range);
2672 assert!(result.is_some());
2673 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2674
2675 let no_overlap_range = TextRange::new(100.into(), 150.into());
2677 let result = control.source_in_range(no_overlap_range);
2678 assert!(result.is_none());
2679 }
2680
2681 #[test]
2682 fn test_binaries_in_range_single() {
2683 let input = r#"Source: test-package
2685Maintainer: Test <test@example.com>
2686
2687Package: test-pkg
2688Architecture: any
2689
2690Package: another-pkg
2691Architecture: all
2692"#;
2693 let control: Control = input.parse().unwrap();
2694
2695 let first_binary = control.binaries().next().unwrap();
2697 let binary_range = first_binary.as_deb822().text_range();
2698
2699 let binaries: Vec<_> = control.binaries_in_range(binary_range).collect();
2701 assert_eq!(binaries.len(), 1);
2702 assert_eq!(binaries[0].name(), Some("test-pkg".to_string()));
2703 }
2704
2705 #[test]
2706 fn test_binaries_in_range_multiple() {
2707 let input = r#"Source: test-package
2709Maintainer: Test <test@example.com>
2710
2711Package: test-pkg
2712Architecture: any
2713
2714Package: another-pkg
2715Architecture: all
2716
2717Package: third-pkg
2718Architecture: any
2719"#;
2720 let control: Control = input.parse().unwrap();
2721
2722 let range = TextRange::new(50.into(), 130.into());
2724
2725 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2727 assert!(binaries.len() >= 2);
2728 assert!(binaries
2729 .iter()
2730 .any(|b| b.name() == Some("test-pkg".to_string())));
2731 assert!(binaries
2732 .iter()
2733 .any(|b| b.name() == Some("another-pkg".to_string())));
2734 }
2735
2736 #[test]
2737 fn test_binaries_in_range_none() {
2738 let input = r#"Source: test-package
2740Maintainer: Test <test@example.com>
2741
2742Package: test-pkg
2743Architecture: any
2744"#;
2745 let control: Control = input.parse().unwrap();
2746
2747 let range = TextRange::new(1000.into(), 2000.into());
2749
2750 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2752 assert_eq!(binaries.len(), 0);
2753 }
2754
2755 #[test]
2756 fn test_binaries_in_range_all() {
2757 let input = r#"Source: test-package
2759Maintainer: Test <test@example.com>
2760
2761Package: test-pkg
2762Architecture: any
2763
2764Package: another-pkg
2765Architecture: all
2766"#;
2767 let control: Control = input.parse().unwrap();
2768
2769 let range = TextRange::new(0.into(), input.len().try_into().unwrap());
2771
2772 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2774 assert_eq!(binaries.len(), 2);
2775 }
2776
2777 #[test]
2778 fn test_source_in_range_partial_overlap() {
2779 let input = r#"Source: test-package
2781Maintainer: Test <test@example.com>
2782
2783Package: test-pkg
2784Architecture: any
2785"#;
2786 let control: Control = input.parse().unwrap();
2787
2788 let range = TextRange::new(10.into(), 30.into());
2790
2791 let result = control.source_in_range(range);
2793 assert!(result.is_some());
2794 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2795 }
2796
2797 #[test]
2798 fn test_wrap_and_sort_long_build_depends_wraps_to_one_per_line() {
2799 let input = r#"Source: test-package
2803Maintainer: Test <test@example.com>
2804Build-Depends: debhelper-compat (= 13), aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj
2805
2806"#;
2807 let mut control: Control = input.parse().unwrap();
2808 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2809
2810 let expected = r#"Source: test-package
2811Maintainer: Test <test@example.com>
2812Build-Depends: aaaa,
2813 bbbb,
2814 cccc,
2815 dddd,
2816 debhelper-compat (= 13),
2817 eeee,
2818 ffff,
2819 gggg,
2820 hhhh,
2821 iiii,
2822 jjjj
2823"#;
2824 assert_eq!(control.to_string(), expected);
2825 }
2826
2827 #[test]
2828 fn test_wrap_and_sort_short_build_depends_stays_one_line() {
2829 let input = r#"Source: test-package
2832Maintainer: Test <test@example.com>
2833Build-Depends: debhelper-compat (= 13), foo, bar
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 = "Source: test-package\nMaintainer: Test <test@example.com>\nBuild-Depends:bar, debhelper-compat (= 13), foo\n";
2842 assert_eq!(control.to_string(), expected);
2843 }
2844
2845 #[test]
2846 fn test_wrap_and_sort_long_build_depends_keeps_brackets_intact() {
2847 let value = "foo (>= 1.0), bar [amd64 arm64], baz <stage1 !nocheck>, qux, quux, corge, grault, garply, waldo, fred";
2853 let input = format!(
2854 "Source: test-package\nMaintainer: Test <test@example.com>\nBuild-Depends: {}\n\n",
2855 value
2856 );
2857 let mut control: Control = input.parse().unwrap();
2858 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2859 let out = control.to_string();
2860 assert!(out.contains("bar [amd64 arm64],\n"), "out was: {}", out);
2861 assert!(
2862 out.contains(" baz <stage1 !nocheck>,\n"),
2863 "out was: {}",
2864 out
2865 );
2866 assert!(out.contains(" foo (>= 1.0),\n"), "out was: {}", out);
2867 }
2868
2869 #[test]
2870 fn test_wrap_and_sort_with_malformed_relations() {
2871 let input = r#"Source: test-package
2874Maintainer: Test <test@example.com>
2875Build-Depends: some invalid relation syntax here
2876
2877Package: test-pkg
2878Architecture: any
2879"#;
2880 let mut control: Control = input.parse().unwrap();
2881
2882 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
2884
2885 let output = control.to_string();
2887 let expected = r#"Source: test-package
2888Maintainer: Test <test@example.com>
2889Build-Depends: some invalid relation syntax here
2890
2891Package: test-pkg
2892Architecture: any
2893"#;
2894 assert_eq!(output, expected);
2895 }
2896}