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