1use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph};
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 "Pre-Depends",
91 "Depends",
92 "Recommends",
93 "Suggests",
94 "Enhances",
95 "Conflicts",
96 "Breaks",
97 "Replaces",
98 "Provides",
99 "Description",
100];
101
102fn format_field(name: &str, value: &str) -> String {
103 match name {
104 "Uploaders" => value
105 .split(',')
106 .map(|s| s.trim().to_string())
107 .collect::<Vec<_>>()
108 .join(",\n"),
109 "Build-Depends"
110 | "Build-Depends-Indep"
111 | "Build-Depends-Arch"
112 | "Build-Conflicts"
113 | "Build-Conflicts-Indep"
114 | "Build-Conflics-Arch"
115 | "Depends"
116 | "Recommends"
117 | "Suggests"
118 | "Enhances"
119 | "Pre-Depends"
120 | "Breaks" => {
121 let relations: Relations = value.parse().unwrap();
122 let relations = relations.wrap_and_sort();
123 relations.to_string()
124 }
125 _ => value.to_string(),
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct Control {
132 deb822: Deb822,
133 parse_mode: ParseMode,
134}
135
136impl Control {
137 pub fn new() -> Self {
139 Control {
140 deb822: Deb822::new(),
141 parse_mode: ParseMode::Strict,
142 }
143 }
144
145 pub fn new_with_mode(parse_mode: ParseMode) -> Self {
147 Control {
148 deb822: Deb822::new(),
149 parse_mode,
150 }
151 }
152
153 pub fn parse_mode(&self) -> ParseMode {
155 self.parse_mode
156 }
157
158 pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
160 &mut self.deb822
161 }
162
163 pub fn as_deb822(&self) -> &Deb822 {
165 &self.deb822
166 }
167
168 pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
170 let deb822_parse = Deb822::parse(text);
171 let green = deb822_parse.green().clone();
173 let errors = deb822_parse.errors().to_vec();
174 let positioned_errors = deb822_parse.positioned_errors().to_vec();
175 deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
176 }
177
178 pub fn source(&self) -> Option<Source> {
180 let parse_mode = self.parse_mode;
181 self.deb822
182 .paragraphs()
183 .find(|p| p.get("Source").is_some())
184 .map(|paragraph| Source {
185 paragraph,
186 parse_mode,
187 })
188 }
189
190 pub fn binaries(&self) -> impl Iterator<Item = Binary> + '_ {
192 let parse_mode = self.parse_mode;
193 self.deb822
194 .paragraphs()
195 .filter(|p| p.get("Package").is_some())
196 .map(move |paragraph| Binary {
197 paragraph,
198 parse_mode,
199 })
200 }
201
202 pub fn add_source(&mut self, name: &str) -> Source {
218 let mut p = self.deb822.add_paragraph();
219 p.set("Source", name);
220 self.source().unwrap()
221 }
222
223 pub fn add_binary(&mut self, name: &str) -> Binary {
239 let mut p = self.deb822.add_paragraph();
240 p.set("Package", name);
241 Binary {
242 paragraph: p,
243 parse_mode: ParseMode::Strict,
244 }
245 }
246
247 pub fn remove_binary(&mut self, name: &str) -> bool {
265 let index = self
266 .deb822
267 .paragraphs()
268 .position(|p| p.get("Package").as_deref() == Some(name));
269
270 if let Some(index) = index {
271 self.deb822.remove_paragraph(index);
272 true
273 } else {
274 false
275 }
276 }
277
278 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
280 Ok(Control {
281 deb822: Deb822::from_file(path)?,
282 parse_mode: ParseMode::Strict,
283 })
284 }
285
286 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
288 path: P,
289 ) -> Result<(Self, Vec<String>), std::io::Error> {
290 let (deb822, errors) = Deb822::from_file_relaxed(path)?;
291 Ok((
292 Control {
293 deb822,
294 parse_mode: ParseMode::Relaxed,
295 },
296 errors,
297 ))
298 }
299
300 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
302 Ok(Control {
303 deb822: Deb822::read(&mut r)?,
304 parse_mode: ParseMode::Strict,
305 })
306 }
307
308 pub fn read_relaxed<R: std::io::Read>(
310 mut r: R,
311 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
312 let (deb822, errors) = Deb822::read_relaxed(&mut r)?;
313 Ok((
314 Control {
315 deb822,
316 parse_mode: ParseMode::Relaxed,
317 },
318 errors,
319 ))
320 }
321
322 pub fn wrap_and_sort(
329 &mut self,
330 indentation: deb822_lossless::Indentation,
331 immediate_empty_line: bool,
332 max_line_length_one_liner: Option<usize>,
333 ) {
334 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
335 let a_is_source = a.get("Source").is_some();
337 let b_is_source = b.get("Source").is_some();
338
339 if a_is_source && !b_is_source {
340 return std::cmp::Ordering::Less;
341 } else if !a_is_source && b_is_source {
342 return std::cmp::Ordering::Greater;
343 } else if a_is_source && b_is_source {
344 return a.get("Source").cmp(&b.get("Source"));
345 }
346
347 a.get("Package").cmp(&b.get("Package"))
348 };
349
350 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
351 p.wrap_and_sort(
354 indentation,
355 immediate_empty_line,
356 max_line_length_one_liner,
357 None,
358 Some(&format_field),
359 )
360 };
361
362 self.deb822 = self
363 .deb822
364 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
365 }
366
367 pub fn sort_binaries(&mut self, keep_first: bool) {
398 let mut paragraphs: Vec<_> = self.deb822.paragraphs().collect();
399
400 if paragraphs.len() <= 1 {
401 return; }
403
404 let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
406 let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
407
408 let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
410 binary_start + 1
411 } else {
412 binary_start
413 };
414
415 if sort_start >= paragraphs.len() {
416 return; }
418
419 paragraphs[sort_start..].sort_by(|a, b| {
421 let a_name = a.get("Package");
422 let b_name = b.get("Package");
423 a_name.cmp(&b_name)
424 });
425
426 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
428 let a_pos = paragraphs.iter().position(|p| p == a);
429 let b_pos = paragraphs.iter().position(|p| p == b);
430 a_pos.cmp(&b_pos)
431 };
432
433 self.deb822 = self.deb822.wrap_and_sort(Some(&sort_paragraphs), None);
434 }
435
436 pub fn fields_in_range(
465 &self,
466 range: rowan::TextRange,
467 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
468 self.deb822
469 .paragraphs()
470 .flat_map(move |p| p.entries().collect::<Vec<_>>())
471 .filter(move |entry| {
472 let entry_range = entry.syntax().text_range();
473 entry_range.start() < range.end() && range.start() < entry_range.end()
475 })
476 }
477}
478
479impl From<Control> for Deb822 {
480 fn from(c: Control) -> Self {
481 c.deb822
482 }
483}
484
485impl From<Deb822> for Control {
486 fn from(d: Deb822) -> Self {
487 Control {
488 deb822: d,
489 parse_mode: ParseMode::Strict,
490 }
491 }
492}
493
494impl Default for Control {
495 fn default() -> Self {
496 Self::new()
497 }
498}
499
500impl std::str::FromStr for Control {
501 type Err = deb822_lossless::ParseError;
502
503 fn from_str(s: &str) -> Result<Self, Self::Err> {
504 Control::parse(s).to_result()
505 }
506}
507
508#[derive(Debug, Clone, PartialEq, Eq)]
510pub struct Source {
511 paragraph: Paragraph,
512 parse_mode: ParseMode,
513}
514
515impl From<Source> for Paragraph {
516 fn from(s: Source) -> Self {
517 s.paragraph
518 }
519}
520
521impl From<Paragraph> for Source {
522 fn from(p: Paragraph) -> Self {
523 Source {
524 paragraph: p,
525 parse_mode: ParseMode::Strict,
526 }
527 }
528}
529
530impl std::fmt::Display for Source {
531 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
532 self.paragraph.fmt(f)
533 }
534}
535
536impl Source {
537 fn parse_relations(&self, s: &str) -> Relations {
539 match self.parse_mode {
540 ParseMode::Strict => s.parse().unwrap(),
541 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
542 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
543 }
544 }
545
546 pub fn name(&self) -> Option<String> {
548 self.paragraph.get("Source")
549 }
550
551 pub fn wrap_and_sort(
553 &mut self,
554 indentation: deb822_lossless::Indentation,
555 immediate_empty_line: bool,
556 max_line_length_one_liner: Option<usize>,
557 ) {
558 self.paragraph = self.paragraph.wrap_and_sort(
559 indentation,
560 immediate_empty_line,
561 max_line_length_one_liner,
562 None,
563 Some(&format_field),
564 );
565 }
566
567 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
569 &mut self.paragraph
570 }
571
572 pub fn as_deb822(&self) -> &Paragraph {
574 &self.paragraph
575 }
576
577 pub fn set_name(&mut self, name: &str) {
579 self.set("Source", name);
580 }
581
582 pub fn section(&self) -> Option<String> {
584 self.paragraph.get("Section")
585 }
586
587 pub fn set_section(&mut self, section: Option<&str>) {
589 if let Some(section) = section {
590 self.set("Section", section);
591 } else {
592 self.paragraph.remove("Section");
593 }
594 }
595
596 pub fn priority(&self) -> Option<Priority> {
598 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
599 }
600
601 pub fn set_priority(&mut self, priority: Option<Priority>) {
603 if let Some(priority) = priority {
604 self.set("Priority", priority.to_string().as_str());
605 } else {
606 self.paragraph.remove("Priority");
607 }
608 }
609
610 pub fn maintainer(&self) -> Option<String> {
612 self.paragraph.get("Maintainer")
613 }
614
615 pub fn set_maintainer(&mut self, maintainer: &str) {
617 self.set("Maintainer", maintainer);
618 }
619
620 pub fn build_depends(&self) -> Option<Relations> {
622 self.paragraph
623 .get("Build-Depends")
624 .map(|s| self.parse_relations(&s))
625 }
626
627 pub fn set_build_depends(&mut self, relations: &Relations) {
629 self.set("Build-Depends", relations.to_string().as_str());
630 }
631
632 pub fn build_depends_indep(&self) -> Option<Relations> {
634 self.paragraph
635 .get("Build-Depends-Indep")
636 .map(|s| self.parse_relations(&s))
637 }
638
639 pub fn build_depends_arch(&self) -> Option<Relations> {
641 self.paragraph
642 .get("Build-Depends-Arch")
643 .map(|s| self.parse_relations(&s))
644 }
645
646 pub fn build_conflicts(&self) -> Option<Relations> {
648 self.paragraph
649 .get("Build-Conflicts")
650 .map(|s| self.parse_relations(&s))
651 }
652
653 pub fn build_conflicts_indep(&self) -> Option<Relations> {
655 self.paragraph
656 .get("Build-Conflicts-Indep")
657 .map(|s| self.parse_relations(&s))
658 }
659
660 pub fn build_conflicts_arch(&self) -> Option<Relations> {
662 self.paragraph
663 .get("Build-Conflicts-Arch")
664 .map(|s| self.parse_relations(&s))
665 }
666
667 pub fn standards_version(&self) -> Option<String> {
669 self.paragraph.get("Standards-Version")
670 }
671
672 pub fn set_standards_version(&mut self, version: &str) {
674 self.set("Standards-Version", version);
675 }
676
677 pub fn homepage(&self) -> Option<url::Url> {
679 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
680 }
681
682 pub fn set_homepage(&mut self, homepage: &url::Url) {
684 self.set("Homepage", homepage.to_string().as_str());
685 }
686
687 pub fn vcs_git(&self) -> Option<String> {
689 self.paragraph.get("Vcs-Git")
690 }
691
692 pub fn set_vcs_git(&mut self, url: &str) {
694 self.set("Vcs-Git", url);
695 }
696
697 pub fn vcs_svn(&self) -> Option<String> {
699 self.paragraph.get("Vcs-Svn").map(|s| s.to_string())
700 }
701
702 pub fn set_vcs_svn(&mut self, url: &str) {
704 self.set("Vcs-Svn", url);
705 }
706
707 pub fn vcs_bzr(&self) -> Option<String> {
709 self.paragraph.get("Vcs-Bzr").map(|s| s.to_string())
710 }
711
712 pub fn set_vcs_bzr(&mut self, url: &str) {
714 self.set("Vcs-Bzr", url);
715 }
716
717 pub fn vcs_arch(&self) -> Option<String> {
719 self.paragraph.get("Vcs-Arch").map(|s| s.to_string())
720 }
721
722 pub fn set_vcs_arch(&mut self, url: &str) {
724 self.set("Vcs-Arch", url);
725 }
726
727 pub fn vcs_svk(&self) -> Option<String> {
729 self.paragraph.get("Vcs-Svk").map(|s| s.to_string())
730 }
731
732 pub fn set_vcs_svk(&mut self, url: &str) {
734 self.set("Vcs-Svk", url);
735 }
736
737 pub fn vcs_darcs(&self) -> Option<String> {
739 self.paragraph.get("Vcs-Darcs").map(|s| s.to_string())
740 }
741
742 pub fn set_vcs_darcs(&mut self, url: &str) {
744 self.set("Vcs-Darcs", url);
745 }
746
747 pub fn vcs_mtn(&self) -> Option<String> {
749 self.paragraph.get("Vcs-Mtn").map(|s| s.to_string())
750 }
751
752 pub fn set_vcs_mtn(&mut self, url: &str) {
754 self.set("Vcs-Mtn", url);
755 }
756
757 pub fn vcs_cvs(&self) -> Option<String> {
759 self.paragraph.get("Vcs-Cvs").map(|s| s.to_string())
760 }
761
762 pub fn set_vcs_cvs(&mut self, url: &str) {
764 self.set("Vcs-Cvs", url);
765 }
766
767 pub fn vcs_hg(&self) -> Option<String> {
769 self.paragraph.get("Vcs-Hg").map(|s| s.to_string())
770 }
771
772 pub fn set_vcs_hg(&mut self, url: &str) {
774 self.set("Vcs-Hg", url);
775 }
776
777 pub fn set(&mut self, key: &str, value: &str) {
779 self.paragraph
780 .set_with_field_order(key, value, SOURCE_FIELD_ORDER);
781 }
782
783 pub fn get(&self, key: &str) -> Option<String> {
785 self.paragraph.get(key)
786 }
787
788 pub fn vcs_browser(&self) -> Option<String> {
790 self.paragraph.get("Vcs-Browser")
791 }
792
793 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
795 for (name, value) in self.paragraph.items() {
796 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
797 return crate::vcs::Vcs::from_field(&name, &value).ok();
798 }
799 }
800 None
801 }
802
803 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
805 if let Some(url) = url {
806 self.set("Vcs-Browser", url);
807 } else {
808 self.paragraph.remove("Vcs-Browser");
809 }
810 }
811
812 pub fn uploaders(&self) -> Option<Vec<String>> {
814 self.paragraph
815 .get("Uploaders")
816 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
817 }
818
819 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
821 self.set(
822 "Uploaders",
823 uploaders
824 .iter()
825 .map(|s| s.to_string())
826 .collect::<Vec<_>>()
827 .join(", ")
828 .as_str(),
829 );
830 }
831
832 pub fn architecture(&self) -> Option<String> {
834 self.paragraph.get("Architecture")
835 }
836
837 pub fn set_architecture(&mut self, arch: Option<&str>) {
839 if let Some(arch) = arch {
840 self.set("Architecture", arch);
841 } else {
842 self.paragraph.remove("Architecture");
843 }
844 }
845
846 pub fn rules_requires_root(&self) -> Option<bool> {
848 self.paragraph
849 .get("Rules-Requires-Root")
850 .map(|s| match s.to_lowercase().as_str() {
851 "yes" => true,
852 "no" => false,
853 _ => panic!("invalid Rules-Requires-Root value"),
854 })
855 }
856
857 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
859 self.set(
860 "Rules-Requires-Root",
861 if requires_root { "yes" } else { "no" },
862 );
863 }
864
865 pub fn testsuite(&self) -> Option<String> {
867 self.paragraph.get("Testsuite")
868 }
869
870 pub fn set_testsuite(&mut self, testsuite: &str) {
872 self.set("Testsuite", testsuite);
873 }
874
875 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
883 let para_range = self.paragraph.syntax().text_range();
884 para_range.start() < range.end() && range.start() < para_range.end()
885 }
886
887 pub fn fields_in_range(
895 &self,
896 range: rowan::TextRange,
897 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
898 self.paragraph.entries().filter(move |entry| {
899 let entry_range = entry.syntax().text_range();
900 entry_range.start() < range.end() && range.start() < entry_range.end()
901 })
902 }
903}
904
905#[cfg(feature = "python-debian")]
906impl<'py> pyo3::IntoPyObject<'py> for Source {
907 type Target = pyo3::PyAny;
908 type Output = pyo3::Bound<'py, Self::Target>;
909 type Error = pyo3::PyErr;
910
911 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
912 self.paragraph.into_pyobject(py)
913 }
914}
915
916#[cfg(feature = "python-debian")]
917impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
918 type Target = pyo3::PyAny;
919 type Output = pyo3::Bound<'py, Self::Target>;
920 type Error = pyo3::PyErr;
921
922 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
923 (&self.paragraph).into_pyobject(py)
924 }
925}
926
927#[cfg(feature = "python-debian")]
928impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
929 type Error = pyo3::PyErr;
930
931 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
932 Ok(Source {
933 paragraph: ob.extract()?,
934 parse_mode: ParseMode::Strict,
935 })
936 }
937}
938
939impl std::fmt::Display for Control {
940 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
941 self.deb822.fmt(f)
942 }
943}
944
945impl AstNode for Control {
946 type Language = deb822_lossless::Lang;
947
948 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
949 Deb822::can_cast(kind)
950 }
951
952 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
953 Deb822::cast(syntax).map(|deb822| Control {
954 deb822,
955 parse_mode: ParseMode::Strict,
956 })
957 }
958
959 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
960 self.deb822.syntax()
961 }
962}
963
964#[derive(Debug, Clone, PartialEq, Eq)]
966pub struct Binary {
967 paragraph: Paragraph,
968 parse_mode: ParseMode,
969}
970
971impl From<Binary> for Paragraph {
972 fn from(b: Binary) -> Self {
973 b.paragraph
974 }
975}
976
977impl From<Paragraph> for Binary {
978 fn from(p: Paragraph) -> Self {
979 Binary {
980 paragraph: p,
981 parse_mode: ParseMode::Strict,
982 }
983 }
984}
985
986#[cfg(feature = "python-debian")]
987impl<'py> pyo3::IntoPyObject<'py> for Binary {
988 type Target = pyo3::PyAny;
989 type Output = pyo3::Bound<'py, Self::Target>;
990 type Error = pyo3::PyErr;
991
992 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
993 self.paragraph.into_pyobject(py)
994 }
995}
996
997#[cfg(feature = "python-debian")]
998impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
999 type Target = pyo3::PyAny;
1000 type Output = pyo3::Bound<'py, Self::Target>;
1001 type Error = pyo3::PyErr;
1002
1003 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1004 (&self.paragraph).into_pyobject(py)
1005 }
1006}
1007
1008#[cfg(feature = "python-debian")]
1009impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
1010 type Error = pyo3::PyErr;
1011
1012 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1013 Ok(Binary {
1014 paragraph: ob.extract()?,
1015 parse_mode: ParseMode::Strict,
1016 })
1017 }
1018}
1019
1020impl Default for Binary {
1021 fn default() -> Self {
1022 Self::new()
1023 }
1024}
1025
1026impl std::fmt::Display for Binary {
1027 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1028 self.paragraph.fmt(f)
1029 }
1030}
1031
1032impl Binary {
1033 fn parse_relations(&self, s: &str) -> Relations {
1035 match self.parse_mode {
1036 ParseMode::Strict => s.parse().unwrap(),
1037 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
1038 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
1039 }
1040 }
1041
1042 pub fn new() -> Self {
1044 Binary {
1045 paragraph: Paragraph::new(),
1046 parse_mode: ParseMode::Strict,
1047 }
1048 }
1049
1050 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
1052 &mut self.paragraph
1053 }
1054
1055 pub fn as_deb822(&self) -> &Paragraph {
1057 &self.paragraph
1058 }
1059
1060 pub fn wrap_and_sort(
1062 &mut self,
1063 indentation: deb822_lossless::Indentation,
1064 immediate_empty_line: bool,
1065 max_line_length_one_liner: Option<usize>,
1066 ) {
1067 self.paragraph = self.paragraph.wrap_and_sort(
1068 indentation,
1069 immediate_empty_line,
1070 max_line_length_one_liner,
1071 None,
1072 Some(&format_field),
1073 );
1074 }
1075
1076 pub fn name(&self) -> Option<String> {
1078 self.paragraph.get("Package")
1079 }
1080
1081 pub fn set_name(&mut self, name: &str) {
1083 self.set("Package", name);
1084 }
1085
1086 pub fn section(&self) -> Option<String> {
1088 self.paragraph.get("Section")
1089 }
1090
1091 pub fn set_section(&mut self, section: Option<&str>) {
1093 if let Some(section) = section {
1094 self.set("Section", section);
1095 } else {
1096 self.paragraph.remove("Section");
1097 }
1098 }
1099
1100 pub fn priority(&self) -> Option<Priority> {
1102 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
1103 }
1104
1105 pub fn set_priority(&mut self, priority: Option<Priority>) {
1107 if let Some(priority) = priority {
1108 self.set("Priority", priority.to_string().as_str());
1109 } else {
1110 self.paragraph.remove("Priority");
1111 }
1112 }
1113
1114 pub fn architecture(&self) -> Option<String> {
1116 self.paragraph.get("Architecture")
1117 }
1118
1119 pub fn set_architecture(&mut self, arch: Option<&str>) {
1121 if let Some(arch) = arch {
1122 self.set("Architecture", arch);
1123 } else {
1124 self.paragraph.remove("Architecture");
1125 }
1126 }
1127
1128 pub fn depends(&self) -> Option<Relations> {
1130 self.paragraph
1131 .get("Depends")
1132 .map(|s| self.parse_relations(&s))
1133 }
1134
1135 pub fn set_depends(&mut self, depends: Option<&Relations>) {
1137 if let Some(depends) = depends {
1138 self.set("Depends", depends.to_string().as_str());
1139 } else {
1140 self.paragraph.remove("Depends");
1141 }
1142 }
1143
1144 pub fn recommends(&self) -> Option<Relations> {
1146 self.paragraph
1147 .get("Recommends")
1148 .map(|s| self.parse_relations(&s))
1149 }
1150
1151 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
1153 if let Some(recommends) = recommends {
1154 self.set("Recommends", recommends.to_string().as_str());
1155 } else {
1156 self.paragraph.remove("Recommends");
1157 }
1158 }
1159
1160 pub fn suggests(&self) -> Option<Relations> {
1162 self.paragraph
1163 .get("Suggests")
1164 .map(|s| self.parse_relations(&s))
1165 }
1166
1167 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
1169 if let Some(suggests) = suggests {
1170 self.set("Suggests", suggests.to_string().as_str());
1171 } else {
1172 self.paragraph.remove("Suggests");
1173 }
1174 }
1175
1176 pub fn enhances(&self) -> Option<Relations> {
1178 self.paragraph
1179 .get("Enhances")
1180 .map(|s| self.parse_relations(&s))
1181 }
1182
1183 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1185 if let Some(enhances) = enhances {
1186 self.set("Enhances", enhances.to_string().as_str());
1187 } else {
1188 self.paragraph.remove("Enhances");
1189 }
1190 }
1191
1192 pub fn pre_depends(&self) -> Option<Relations> {
1194 self.paragraph
1195 .get("Pre-Depends")
1196 .map(|s| self.parse_relations(&s))
1197 }
1198
1199 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1201 if let Some(pre_depends) = pre_depends {
1202 self.set("Pre-Depends", pre_depends.to_string().as_str());
1203 } else {
1204 self.paragraph.remove("Pre-Depends");
1205 }
1206 }
1207
1208 pub fn breaks(&self) -> Option<Relations> {
1210 self.paragraph
1211 .get("Breaks")
1212 .map(|s| self.parse_relations(&s))
1213 }
1214
1215 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1217 if let Some(breaks) = breaks {
1218 self.set("Breaks", breaks.to_string().as_str());
1219 } else {
1220 self.paragraph.remove("Breaks");
1221 }
1222 }
1223
1224 pub fn conflicts(&self) -> Option<Relations> {
1226 self.paragraph
1227 .get("Conflicts")
1228 .map(|s| self.parse_relations(&s))
1229 }
1230
1231 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1233 if let Some(conflicts) = conflicts {
1234 self.set("Conflicts", conflicts.to_string().as_str());
1235 } else {
1236 self.paragraph.remove("Conflicts");
1237 }
1238 }
1239
1240 pub fn replaces(&self) -> Option<Relations> {
1242 self.paragraph
1243 .get("Replaces")
1244 .map(|s| self.parse_relations(&s))
1245 }
1246
1247 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1249 if let Some(replaces) = replaces {
1250 self.set("Replaces", replaces.to_string().as_str());
1251 } else {
1252 self.paragraph.remove("Replaces");
1253 }
1254 }
1255
1256 pub fn provides(&self) -> Option<Relations> {
1258 self.paragraph
1259 .get("Provides")
1260 .map(|s| self.parse_relations(&s))
1261 }
1262
1263 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1265 if let Some(provides) = provides {
1266 self.set("Provides", provides.to_string().as_str());
1267 } else {
1268 self.paragraph.remove("Provides");
1269 }
1270 }
1271
1272 pub fn built_using(&self) -> Option<Relations> {
1274 self.paragraph
1275 .get("Built-Using")
1276 .map(|s| self.parse_relations(&s))
1277 }
1278
1279 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1281 if let Some(built_using) = built_using {
1282 self.set("Built-Using", built_using.to_string().as_str());
1283 } else {
1284 self.paragraph.remove("Built-Using");
1285 }
1286 }
1287
1288 pub fn multi_arch(&self) -> Option<MultiArch> {
1290 self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap())
1291 }
1292
1293 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1295 if let Some(multi_arch) = multi_arch {
1296 self.set("Multi-Arch", multi_arch.to_string().as_str());
1297 } else {
1298 self.paragraph.remove("Multi-Arch");
1299 }
1300 }
1301
1302 pub fn essential(&self) -> bool {
1304 self.paragraph
1305 .get("Essential")
1306 .map(|s| s == "yes")
1307 .unwrap_or(false)
1308 }
1309
1310 pub fn set_essential(&mut self, essential: bool) {
1312 if essential {
1313 self.set("Essential", "yes");
1314 } else {
1315 self.paragraph.remove("Essential");
1316 }
1317 }
1318
1319 pub fn description(&self) -> Option<String> {
1321 self.paragraph.get_multiline("Description")
1322 }
1323
1324 pub fn set_description(&mut self, description: Option<&str>) {
1326 if let Some(description) = description {
1327 self.paragraph.set_with_indent_pattern(
1328 "Description",
1329 description,
1330 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1331 Some(BINARY_FIELD_ORDER),
1332 );
1333 } else {
1334 self.paragraph.remove("Description");
1335 }
1336 }
1337
1338 pub fn homepage(&self) -> Option<url::Url> {
1340 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
1341 }
1342
1343 pub fn set_homepage(&mut self, url: &url::Url) {
1345 self.set("Homepage", url.as_str());
1346 }
1347
1348 pub fn set(&mut self, key: &str, value: &str) {
1350 self.paragraph
1351 .set_with_field_order(key, value, BINARY_FIELD_ORDER);
1352 }
1353
1354 pub fn get(&self, key: &str) -> Option<String> {
1356 self.paragraph.get(key)
1357 }
1358
1359 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1367 let para_range = self.paragraph.syntax().text_range();
1368 para_range.start() < range.end() && range.start() < para_range.end()
1369 }
1370
1371 pub fn fields_in_range(
1379 &self,
1380 range: rowan::TextRange,
1381 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1382 self.paragraph.entries().filter(move |entry| {
1383 let entry_range = entry.syntax().text_range();
1384 entry_range.start() < range.end() && range.start() < entry_range.end()
1385 })
1386 }
1387}
1388
1389#[cfg(test)]
1390mod tests {
1391 use super::*;
1392 use crate::relations::VersionConstraint;
1393
1394 #[test]
1395 fn test_source_set_field_ordering() {
1396 let mut control = Control::new();
1397 let mut source = control.add_source("mypackage");
1398
1399 source.set("Homepage", "https://example.com");
1401 source.set("Build-Depends", "debhelper");
1402 source.set("Standards-Version", "4.5.0");
1403 source.set("Maintainer", "Test <test@example.com>");
1404
1405 let output = source.to_string();
1407 let lines: Vec<&str> = output.lines().collect();
1408
1409 assert!(lines[0].starts_with("Source:"));
1411
1412 let maintainer_pos = lines
1414 .iter()
1415 .position(|l| l.starts_with("Maintainer:"))
1416 .unwrap();
1417 let build_depends_pos = lines
1418 .iter()
1419 .position(|l| l.starts_with("Build-Depends:"))
1420 .unwrap();
1421 let standards_pos = lines
1422 .iter()
1423 .position(|l| l.starts_with("Standards-Version:"))
1424 .unwrap();
1425 let homepage_pos = lines
1426 .iter()
1427 .position(|l| l.starts_with("Homepage:"))
1428 .unwrap();
1429
1430 assert!(maintainer_pos < build_depends_pos);
1432 assert!(build_depends_pos < standards_pos);
1433 assert!(standards_pos < homepage_pos);
1434 }
1435
1436 #[test]
1437 fn test_binary_set_field_ordering() {
1438 let mut control = Control::new();
1439 let mut binary = control.add_binary("mypackage");
1440
1441 binary.set("Description", "A test package");
1443 binary.set("Architecture", "amd64");
1444 binary.set("Depends", "libc6");
1445 binary.set("Section", "utils");
1446
1447 let output = binary.to_string();
1449 let lines: Vec<&str> = output.lines().collect();
1450
1451 assert!(lines[0].starts_with("Package:"));
1453
1454 let arch_pos = lines
1456 .iter()
1457 .position(|l| l.starts_with("Architecture:"))
1458 .unwrap();
1459 let section_pos = lines
1460 .iter()
1461 .position(|l| l.starts_with("Section:"))
1462 .unwrap();
1463 let depends_pos = lines
1464 .iter()
1465 .position(|l| l.starts_with("Depends:"))
1466 .unwrap();
1467 let desc_pos = lines
1468 .iter()
1469 .position(|l| l.starts_with("Description:"))
1470 .unwrap();
1471
1472 assert!(arch_pos < section_pos);
1474 assert!(section_pos < depends_pos);
1475 assert!(depends_pos < desc_pos);
1476 }
1477
1478 #[test]
1479 fn test_source_specific_set_methods_use_field_ordering() {
1480 let mut control = Control::new();
1481 let mut source = control.add_source("mypackage");
1482
1483 source.set_homepage(&"https://example.com".parse().unwrap());
1485 source.set_maintainer("Test <test@example.com>");
1486 source.set_standards_version("4.5.0");
1487 source.set_vcs_git("https://github.com/example/repo");
1488
1489 let output = source.to_string();
1491 let lines: Vec<&str> = output.lines().collect();
1492
1493 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1495 let maintainer_pos = lines
1496 .iter()
1497 .position(|l| l.starts_with("Maintainer:"))
1498 .unwrap();
1499 let standards_pos = lines
1500 .iter()
1501 .position(|l| l.starts_with("Standards-Version:"))
1502 .unwrap();
1503 let vcs_git_pos = lines
1504 .iter()
1505 .position(|l| l.starts_with("Vcs-Git:"))
1506 .unwrap();
1507 let homepage_pos = lines
1508 .iter()
1509 .position(|l| l.starts_with("Homepage:"))
1510 .unwrap();
1511
1512 assert!(source_pos < maintainer_pos);
1514 assert!(maintainer_pos < standards_pos);
1515 assert!(standards_pos < vcs_git_pos);
1516 assert!(vcs_git_pos < homepage_pos);
1517 }
1518
1519 #[test]
1520 fn test_binary_specific_set_methods_use_field_ordering() {
1521 let mut control = Control::new();
1522 let mut binary = control.add_binary("mypackage");
1523
1524 binary.set_description(Some("A test package"));
1526 binary.set_architecture(Some("amd64"));
1527 let depends = "libc6".parse().unwrap();
1528 binary.set_depends(Some(&depends));
1529 binary.set_section(Some("utils"));
1530 binary.set_priority(Some(Priority::Optional));
1531
1532 let output = binary.to_string();
1534 let lines: Vec<&str> = output.lines().collect();
1535
1536 let package_pos = lines
1538 .iter()
1539 .position(|l| l.starts_with("Package:"))
1540 .unwrap();
1541 let arch_pos = lines
1542 .iter()
1543 .position(|l| l.starts_with("Architecture:"))
1544 .unwrap();
1545 let section_pos = lines
1546 .iter()
1547 .position(|l| l.starts_with("Section:"))
1548 .unwrap();
1549 let priority_pos = lines
1550 .iter()
1551 .position(|l| l.starts_with("Priority:"))
1552 .unwrap();
1553 let depends_pos = lines
1554 .iter()
1555 .position(|l| l.starts_with("Depends:"))
1556 .unwrap();
1557 let desc_pos = lines
1558 .iter()
1559 .position(|l| l.starts_with("Description:"))
1560 .unwrap();
1561
1562 assert!(package_pos < arch_pos);
1564 assert!(arch_pos < section_pos);
1565 assert!(section_pos < priority_pos);
1566 assert!(priority_pos < depends_pos);
1567 assert!(depends_pos < desc_pos);
1568 }
1569
1570 #[test]
1571 fn test_parse() {
1572 let control: Control = r#"Source: foo
1573Section: libs
1574Priority: optional
1575Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1576Homepage: https://example.com
1577
1578"#
1579 .parse()
1580 .unwrap();
1581 let source = control.source().unwrap();
1582
1583 assert_eq!(source.name(), Some("foo".to_owned()));
1584 assert_eq!(source.section(), Some("libs".to_owned()));
1585 assert_eq!(source.priority(), Some(super::Priority::Optional));
1586 assert_eq!(
1587 source.homepage(),
1588 Some("https://example.com".parse().unwrap())
1589 );
1590 let bd = source.build_depends().unwrap();
1591 let entries = bd.entries().collect::<Vec<_>>();
1592 assert_eq!(entries.len(), 2);
1593 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1594 assert_eq!(rel.name(), "bar");
1595 assert_eq!(
1596 rel.version(),
1597 Some((
1598 VersionConstraint::GreaterThanEqual,
1599 "1.0.0".parse().unwrap()
1600 ))
1601 );
1602 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1603 assert_eq!(rel.name(), "baz");
1604 assert_eq!(
1605 rel.version(),
1606 Some((
1607 VersionConstraint::GreaterThanEqual,
1608 "1.0.0".parse().unwrap()
1609 ))
1610 );
1611 }
1612
1613 #[test]
1614 fn test_description() {
1615 let control: Control = r#"Source: foo
1616
1617Package: foo
1618Description: this is the short description
1619 And the longer one
1620 .
1621 is on the next lines
1622"#
1623 .parse()
1624 .unwrap();
1625 let binary = control.binaries().next().unwrap();
1626 assert_eq!(
1627 binary.description(),
1628 Some(
1629 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1630 .to_owned()
1631 )
1632 );
1633 }
1634
1635 #[test]
1636 fn test_set_description_on_package_without_description() {
1637 let control: Control = r#"Source: foo
1638
1639Package: foo
1640Architecture: amd64
1641"#
1642 .parse()
1643 .unwrap();
1644 let mut binary = control.binaries().next().unwrap();
1645
1646 binary.set_description(Some(
1648 "Short description\nLonger description\n.\nAnother line",
1649 ));
1650
1651 let output = binary.to_string();
1652
1653 assert_eq!(
1655 binary.description(),
1656 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1657 );
1658
1659 assert_eq!(
1661 output,
1662 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1663 );
1664 }
1665
1666 #[test]
1667 fn test_as_mut_deb822() {
1668 let mut control = Control::new();
1669 let deb822 = control.as_mut_deb822();
1670 let mut p = deb822.add_paragraph();
1671 p.set("Source", "foo");
1672 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1673 }
1674
1675 #[test]
1676 fn test_as_deb822() {
1677 let control = Control::new();
1678 let _deb822: &Deb822 = control.as_deb822();
1679 }
1680
1681 #[test]
1682 fn test_set_depends() {
1683 let mut control = Control::new();
1684 let mut binary = control.add_binary("foo");
1685 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1686 binary.set_depends(Some(&relations));
1687 }
1688
1689 #[test]
1690 fn test_wrap_and_sort() {
1691 let mut control: Control = r#"Package: blah
1692Section: libs
1693
1694
1695
1696Package: foo
1697Description: this is a
1698 bar
1699 blah
1700"#
1701 .parse()
1702 .unwrap();
1703 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1704 let expected = r#"Package: blah
1705Section: libs
1706
1707Package: foo
1708Description: this is a
1709 bar
1710 blah
1711"#
1712 .to_owned();
1713 assert_eq!(control.to_string(), expected);
1714 }
1715
1716 #[test]
1717 fn test_wrap_and_sort_source() {
1718 let mut control: Control = r#"Source: blah
1719Depends: foo, bar (<= 1.0.0)
1720
1721"#
1722 .parse()
1723 .unwrap();
1724 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1725 let expected = r#"Source: blah
1726Depends: bar (<= 1.0.0), foo
1727"#
1728 .to_owned();
1729 assert_eq!(control.to_string(), expected);
1730 }
1731
1732 #[test]
1733 fn test_source_wrap_and_sort() {
1734 let control: Control = r#"Source: blah
1735Build-Depends: foo, bar (>= 1.0.0)
1736
1737"#
1738 .parse()
1739 .unwrap();
1740 let mut source = control.source().unwrap();
1741 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1742 assert!(source.build_depends().is_some());
1746 }
1747
1748 #[test]
1749 fn test_binary_set_breaks() {
1750 let mut control = Control::new();
1751 let mut binary = control.add_binary("foo");
1752 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1753 binary.set_breaks(Some(&relations));
1754 assert!(binary.breaks().is_some());
1755 }
1756
1757 #[test]
1758 fn test_binary_set_pre_depends() {
1759 let mut control = Control::new();
1760 let mut binary = control.add_binary("foo");
1761 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1762 binary.set_pre_depends(Some(&relations));
1763 assert!(binary.pre_depends().is_some());
1764 }
1765
1766 #[test]
1767 fn test_binary_set_provides() {
1768 let mut control = Control::new();
1769 let mut binary = control.add_binary("foo");
1770 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1771 binary.set_provides(Some(&relations));
1772 assert!(binary.provides().is_some());
1773 }
1774
1775 #[test]
1776 fn test_source_build_conflicts() {
1777 let control: Control = r#"Source: blah
1778Build-Conflicts: foo, bar (>= 1.0.0)
1779
1780"#
1781 .parse()
1782 .unwrap();
1783 let source = control.source().unwrap();
1784 let conflicts = source.build_conflicts();
1785 assert!(conflicts.is_some());
1786 }
1787
1788 #[test]
1789 fn test_source_vcs_svn() {
1790 let control: Control = r#"Source: blah
1791Vcs-Svn: https://example.com/svn/repo
1792
1793"#
1794 .parse()
1795 .unwrap();
1796 let source = control.source().unwrap();
1797 assert_eq!(
1798 source.vcs_svn(),
1799 Some("https://example.com/svn/repo".to_string())
1800 );
1801 }
1802
1803 #[test]
1804 fn test_control_from_conversion() {
1805 let deb822_data = r#"Source: test
1806Section: libs
1807
1808"#;
1809 let deb822: Deb822 = deb822_data.parse().unwrap();
1810 let control = Control::from(deb822);
1811 assert!(control.source().is_some());
1812 }
1813
1814 #[test]
1815 fn test_fields_in_range() {
1816 let control_text = r#"Source: test-package
1817Maintainer: Test User <test@example.com>
1818Build-Depends: debhelper (>= 12)
1819
1820Package: test-binary
1821Architecture: any
1822Depends: ${shlibs:Depends}
1823Description: Test package
1824 This is a test package
1825"#;
1826 let control: Control = control_text.parse().unwrap();
1827
1828 let source_start = 0;
1830 let source_end = "Source: test-package".len();
1831 let source_range =
1832 rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1833
1834 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1835 assert_eq!(fields.len(), 1);
1836 assert_eq!(fields[0].key(), Some("Source".to_string()));
1837
1838 let maintainer_start = control_text.find("Maintainer:").unwrap();
1840 let build_depends_end = control_text
1841 .find("Build-Depends: debhelper (>= 12)")
1842 .unwrap()
1843 + "Build-Depends: debhelper (>= 12)".len();
1844 let multi_range = rowan::TextRange::new(
1845 (maintainer_start as u32).into(),
1846 (build_depends_end as u32).into(),
1847 );
1848
1849 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1850 assert_eq!(fields.len(), 2);
1851 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1852 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1853
1854 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1856 let cross_para_end =
1857 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1858 let cross_range = rowan::TextRange::new(
1859 (cross_para_start as u32).into(),
1860 (cross_para_end as u32).into(),
1861 );
1862
1863 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1864 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1866 assert_eq!(fields[1].key(), Some("Package".to_string()));
1867 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1868
1869 let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1871 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1872 assert_eq!(fields.len(), 0);
1873 }
1874
1875 #[test]
1876 fn test_source_overlaps_range() {
1877 let control_text = r#"Source: test-package
1878Maintainer: Test User <test@example.com>
1879
1880Package: test-binary
1881Architecture: any
1882"#;
1883 let control: Control = control_text.parse().unwrap();
1884 let source = control.source().unwrap();
1885
1886 let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1888 assert!(source.overlaps_range(overlap_range));
1889
1890 let binary_start = control_text.find("Package:").unwrap();
1892 let no_overlap_range = rowan::TextRange::new(
1893 (binary_start as u32).into(),
1894 ((binary_start + 20) as u32).into(),
1895 );
1896 assert!(!source.overlaps_range(no_overlap_range));
1897
1898 let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1900 assert!(source.overlaps_range(partial_overlap));
1901 }
1902
1903 #[test]
1904 fn test_source_fields_in_range() {
1905 let control_text = r#"Source: test-package
1906Maintainer: Test User <test@example.com>
1907Build-Depends: debhelper (>= 12)
1908
1909Package: test-binary
1910"#;
1911 let control: Control = control_text.parse().unwrap();
1912 let source = control.source().unwrap();
1913
1914 let maintainer_start = control_text.find("Maintainer:").unwrap();
1916 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1917 let maintainer_range = rowan::TextRange::new(
1918 (maintainer_start as u32).into(),
1919 (maintainer_end as u32).into(),
1920 );
1921
1922 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1923 assert_eq!(fields.len(), 1);
1924 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1925
1926 let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1928 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1929 assert_eq!(fields.len(), 3); }
1931
1932 #[test]
1933 fn test_binary_overlaps_range() {
1934 let control_text = r#"Source: test-package
1935
1936Package: test-binary
1937Architecture: any
1938Depends: ${shlibs:Depends}
1939"#;
1940 let control: Control = control_text.parse().unwrap();
1941 let binary = control.binaries().next().unwrap();
1942
1943 let package_start = control_text.find("Package:").unwrap();
1945 let overlap_range = rowan::TextRange::new(
1946 (package_start as u32).into(),
1947 ((package_start + 30) as u32).into(),
1948 );
1949 assert!(binary.overlaps_range(overlap_range));
1950
1951 let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1953 assert!(!binary.overlaps_range(no_overlap_range));
1954 }
1955
1956 #[test]
1957 fn test_binary_fields_in_range() {
1958 let control_text = r#"Source: test-package
1959
1960Package: test-binary
1961Architecture: any
1962Depends: ${shlibs:Depends}
1963Description: Test binary
1964 This is a test binary package
1965"#;
1966 let control: Control = control_text.parse().unwrap();
1967 let binary = control.binaries().next().unwrap();
1968
1969 let arch_start = control_text.find("Architecture:").unwrap();
1971 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1972 + "Depends: ${shlibs:Depends}".len();
1973 let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1974
1975 let fields: Vec<_> = binary.fields_in_range(range).collect();
1976 assert_eq!(fields.len(), 2);
1977 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1978 assert_eq!(fields[1].key(), Some("Depends".to_string()));
1979
1980 let desc_start = control_text.find("Description:").unwrap();
1982 let partial_range = rowan::TextRange::new(
1983 ((desc_start + 5) as u32).into(),
1984 ((desc_start + 15) as u32).into(),
1985 );
1986 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1987 assert_eq!(fields.len(), 1);
1988 assert_eq!(fields[0].key(), Some("Description".to_string()));
1989 }
1990
1991 #[test]
1992 fn test_incremental_parsing_use_case() {
1993 let control_text = r#"Source: example
1995Maintainer: John Doe <john@example.com>
1996Standards-Version: 4.6.0
1997Build-Depends: debhelper-compat (= 13)
1998
1999Package: example-bin
2000Architecture: all
2001Depends: ${misc:Depends}
2002Description: Example package
2003 This is an example.
2004"#;
2005 let control: Control = control_text.parse().unwrap();
2006
2007 let change_start = control_text.find("Standards-Version:").unwrap();
2009 let change_end = change_start + "Standards-Version: 4.6.0".len();
2010 let change_range =
2011 rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
2012
2013 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
2015 assert_eq!(affected_fields.len(), 1);
2016 assert_eq!(
2017 affected_fields[0].key(),
2018 Some("Standards-Version".to_string())
2019 );
2020
2021 for entry in &affected_fields {
2023 let key = entry.key().unwrap();
2024 assert_ne!(key, "Maintainer");
2025 assert_ne!(key, "Build-Depends");
2026 assert_ne!(key, "Architecture");
2027 }
2028 }
2029
2030 #[test]
2031 fn test_positioned_parse_errors() {
2032 let input = "Invalid: field\nBroken field without colon";
2034 let parsed = Control::parse(input);
2035
2036 let positioned_errors = parsed.positioned_errors();
2038 assert!(
2039 !positioned_errors.is_empty(),
2040 "Should have positioned errors"
2041 );
2042
2043 for error in positioned_errors {
2045 let start_offset: u32 = error.range.start().into();
2046 let end_offset: u32 = error.range.end().into();
2047
2048 assert!(!error.message.is_empty());
2050
2051 assert!(start_offset <= end_offset);
2053 assert!(end_offset <= input.len() as u32);
2054
2055 assert!(error.code.is_some());
2057
2058 println!(
2059 "Error at {:?}: {} (code: {:?})",
2060 error.range, error.message, error.code
2061 );
2062 }
2063
2064 let string_errors = parsed.errors();
2066 assert!(!string_errors.is_empty());
2067 assert_eq!(string_errors.len(), positioned_errors.len());
2068 }
2069
2070 #[test]
2071 fn test_sort_binaries_basic() {
2072 let input = r#"Source: foo
2073
2074Package: libfoo
2075Architecture: all
2076
2077Package: libbar
2078Architecture: all
2079"#;
2080
2081 let mut control: Control = input.parse().unwrap();
2082 control.sort_binaries(false);
2083
2084 let binaries: Vec<_> = control.binaries().collect();
2085 assert_eq!(binaries.len(), 2);
2086 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
2087 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
2088 }
2089
2090 #[test]
2091 fn test_sort_binaries_keep_first() {
2092 let input = r#"Source: foo
2093
2094Package: zzz-first
2095Architecture: all
2096
2097Package: libbar
2098Architecture: all
2099
2100Package: libaaa
2101Architecture: all
2102"#;
2103
2104 let mut control: Control = input.parse().unwrap();
2105 control.sort_binaries(true);
2106
2107 let binaries: Vec<_> = control.binaries().collect();
2108 assert_eq!(binaries.len(), 3);
2109 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
2111 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
2113 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
2114 }
2115
2116 #[test]
2117 fn test_sort_binaries_already_sorted() {
2118 let input = r#"Source: foo
2119
2120Package: aaa
2121Architecture: all
2122
2123Package: bbb
2124Architecture: all
2125
2126Package: ccc
2127Architecture: all
2128"#;
2129
2130 let mut control: Control = input.parse().unwrap();
2131 control.sort_binaries(false);
2132
2133 let binaries: Vec<_> = control.binaries().collect();
2134 assert_eq!(binaries.len(), 3);
2135 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2136 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
2137 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
2138 }
2139
2140 #[test]
2141 fn test_sort_binaries_no_binaries() {
2142 let input = r#"Source: foo
2143Maintainer: test@example.com
2144"#;
2145
2146 let mut control: Control = input.parse().unwrap();
2147 control.sort_binaries(false);
2148
2149 assert_eq!(control.binaries().count(), 0);
2151 }
2152
2153 #[test]
2154 fn test_sort_binaries_one_binary() {
2155 let input = r#"Source: foo
2156
2157Package: bar
2158Architecture: all
2159"#;
2160
2161 let mut control: Control = input.parse().unwrap();
2162 control.sort_binaries(false);
2163
2164 let binaries: Vec<_> = control.binaries().collect();
2165 assert_eq!(binaries.len(), 1);
2166 assert_eq!(binaries[0].name(), Some("bar".to_string()));
2167 }
2168
2169 #[test]
2170 fn test_sort_binaries_preserves_fields() {
2171 let input = r#"Source: foo
2172
2173Package: zzz
2174Architecture: any
2175Depends: libc6
2176Description: ZZZ package
2177
2178Package: aaa
2179Architecture: all
2180Depends: ${misc:Depends}
2181Description: AAA package
2182"#;
2183
2184 let mut control: Control = input.parse().unwrap();
2185 control.sort_binaries(false);
2186
2187 let binaries: Vec<_> = control.binaries().collect();
2188 assert_eq!(binaries.len(), 2);
2189
2190 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2192 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
2193 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
2194
2195 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
2197 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2198 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2199 }
2200
2201 #[test]
2202 fn test_remove_binary_basic() {
2203 let mut control = Control::new();
2204 control.add_binary("foo");
2205 assert_eq!(control.binaries().count(), 1);
2206 assert!(control.remove_binary("foo"));
2207 assert_eq!(control.binaries().count(), 0);
2208 }
2209
2210 #[test]
2211 fn test_remove_binary_nonexistent() {
2212 let mut control = Control::new();
2213 control.add_binary("foo");
2214 assert!(!control.remove_binary("bar"));
2215 assert_eq!(control.binaries().count(), 1);
2216 }
2217
2218 #[test]
2219 fn test_remove_binary_multiple() {
2220 let mut control = Control::new();
2221 control.add_binary("foo");
2222 control.add_binary("bar");
2223 control.add_binary("baz");
2224 assert_eq!(control.binaries().count(), 3);
2225
2226 assert!(control.remove_binary("bar"));
2227 assert_eq!(control.binaries().count(), 2);
2228
2229 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2230 assert_eq!(names, vec!["foo", "baz"]);
2231 }
2232
2233 #[test]
2234 fn test_remove_binary_preserves_source() {
2235 let input = r#"Source: mypackage
2236
2237Package: foo
2238Architecture: all
2239
2240Package: bar
2241Architecture: all
2242"#;
2243 let mut control: Control = input.parse().unwrap();
2244 assert!(control.source().is_some());
2245 assert_eq!(control.binaries().count(), 2);
2246
2247 assert!(control.remove_binary("foo"));
2248
2249 assert!(control.source().is_some());
2251 assert_eq!(
2252 control.source().unwrap().name(),
2253 Some("mypackage".to_string())
2254 );
2255
2256 assert_eq!(control.binaries().count(), 1);
2258 assert_eq!(
2259 control.binaries().next().unwrap().name(),
2260 Some("bar".to_string())
2261 );
2262 }
2263
2264 #[test]
2265 fn test_remove_binary_from_parsed() {
2266 let input = r#"Source: test
2267
2268Package: test-bin
2269Architecture: any
2270Depends: libc6
2271Description: Test binary
2272
2273Package: test-lib
2274Architecture: all
2275Description: Test library
2276"#;
2277 let mut control: Control = input.parse().unwrap();
2278 assert_eq!(control.binaries().count(), 2);
2279
2280 assert!(control.remove_binary("test-bin"));
2281
2282 let output = control.to_string();
2283 assert!(!output.contains("test-bin"));
2284 assert!(output.contains("test-lib"));
2285 assert!(output.contains("Source: test"));
2286 }
2287
2288 #[test]
2289 fn test_build_depends_preserves_indentation_after_removal() {
2290 let input = r#"Source: acpi-support
2291Section: admin
2292Priority: optional
2293Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2294Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2295 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2296"#;
2297 let control: Control = input.parse().unwrap();
2298 let mut source = control.source().unwrap();
2299
2300 let mut build_depends = source.build_depends().unwrap();
2302
2303 let mut to_remove = Vec::new();
2305 for (idx, entry) in build_depends.entries().enumerate() {
2306 for relation in entry.relations() {
2307 if relation.name() == "dh-systemd" {
2308 to_remove.push(idx);
2309 break;
2310 }
2311 }
2312 }
2313
2314 for idx in to_remove.into_iter().rev() {
2315 build_depends.remove_entry(idx);
2316 }
2317
2318 source.set_build_depends(&build_depends);
2320
2321 let output = source.to_string();
2322
2323 assert!(
2325 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2326 "Expected 4-space indentation to be preserved, but got:\n{}",
2327 output
2328 );
2329 }
2330
2331 #[test]
2332 fn test_build_depends_direct_string_set_loses_indentation() {
2333 let input = r#"Source: acpi-support
2334Section: admin
2335Priority: optional
2336Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2337Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2338 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2339"#;
2340 let control: Control = input.parse().unwrap();
2341 let mut source = control.source().unwrap();
2342
2343 let mut build_depends = source.build_depends().unwrap();
2345
2346 let mut to_remove = Vec::new();
2348 for (idx, entry) in build_depends.entries().enumerate() {
2349 for relation in entry.relations() {
2350 if relation.name() == "dh-systemd" {
2351 to_remove.push(idx);
2352 break;
2353 }
2354 }
2355 }
2356
2357 for idx in to_remove.into_iter().rev() {
2358 build_depends.remove_entry(idx);
2359 }
2360
2361 source.set("Build-Depends", &build_depends.to_string());
2363
2364 let output = source.to_string();
2365 println!("Output with string set:");
2366 println!("{}", output);
2367
2368 assert!(
2371 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2372 "Expected 4-space indentation to be preserved, but got:\n{}",
2373 output
2374 );
2375 }
2376
2377 #[test]
2378 fn test_parse_mode_strict_default() {
2379 let control = Control::new();
2380 assert_eq!(control.parse_mode(), ParseMode::Strict);
2381
2382 let control: Control = "Source: test\n".parse().unwrap();
2383 assert_eq!(control.parse_mode(), ParseMode::Strict);
2384 }
2385
2386 #[test]
2387 fn test_parse_mode_new_with_mode() {
2388 let control_relaxed = Control::new_with_mode(ParseMode::Relaxed);
2389 assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed);
2390
2391 let control_substvar = Control::new_with_mode(ParseMode::Substvar);
2392 assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar);
2393 }
2394
2395 #[test]
2396 fn test_relaxed_mode_handles_broken_relations() {
2397 let input = r#"Source: test-package
2398Build-Depends: debhelper, @@@broken@@@, python3
2399
2400Package: test-pkg
2401Depends: libfoo, %%%invalid%%%, libbar
2402"#;
2403
2404 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2405 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2406
2407 if let Some(source) = control.source() {
2409 let bd = source.build_depends();
2410 assert!(bd.is_some());
2411 let relations = bd.unwrap();
2412 assert!(relations.len() >= 2); }
2415
2416 for binary in control.binaries() {
2417 let deps = binary.depends();
2418 assert!(deps.is_some());
2419 let relations = deps.unwrap();
2420 assert!(relations.len() >= 2); }
2423 }
2424
2425 #[test]
2426 fn test_substvar_mode_via_parse() {
2427 let input = r#"Source: test-package
2431Build-Depends: debhelper, ${misc:Depends}
2432
2433Package: test-pkg
2434Depends: ${shlibs:Depends}, libfoo
2435"#;
2436
2437 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2439
2440 if let Some(source) = control.source() {
2441 let bd = source.build_depends();
2443 assert!(bd.is_some());
2444 }
2445
2446 for binary in control.binaries() {
2447 let deps = binary.depends();
2448 assert!(deps.is_some());
2449 }
2450 }
2451
2452 #[test]
2453 #[should_panic]
2454 fn test_strict_mode_panics_on_broken_syntax() {
2455 let input = r#"Source: test-package
2456Build-Depends: debhelper, @@@broken@@@
2457"#;
2458
2459 let control: Control = input.parse().unwrap();
2461
2462 if let Some(source) = control.source() {
2463 let _ = source.build_depends();
2465 }
2466 }
2467
2468 #[test]
2469 fn test_from_file_relaxed_sets_relaxed_mode() {
2470 let input = r#"Source: test-package
2471Maintainer: Test <test@example.com>
2472"#;
2473
2474 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2475 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2476 }
2477
2478 #[test]
2479 fn test_parse_mode_propagates_to_paragraphs() {
2480 let input = r#"Source: test-package
2481Build-Depends: debhelper, @@@invalid@@@, python3
2482
2483Package: test-pkg
2484Depends: libfoo, %%%bad%%%, libbar
2485"#;
2486
2487 let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap();
2489
2490 if let Some(source) = control.source() {
2493 assert!(source.build_depends().is_some());
2494 }
2495
2496 for binary in control.binaries() {
2497 assert!(binary.depends().is_some());
2498 }
2499 }
2500
2501 #[test]
2502 fn test_preserves_final_newline() {
2503 let input_with_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2505 let control: Control = input_with_newline.parse().unwrap();
2506 let output = control.to_string();
2507 assert_eq!(output, input_with_newline);
2508 }
2509
2510 #[test]
2511 fn test_preserves_no_final_newline() {
2512 let input_without_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any";
2514 let control: Control = input_without_newline.parse().unwrap();
2515 let output = control.to_string();
2516 assert_eq!(output, input_without_newline);
2517 }
2518
2519 #[test]
2520 fn test_final_newline_after_modifications() {
2521 let input = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2523 let control: Control = input.parse().unwrap();
2524
2525 let mut source = control.source().unwrap();
2527 source.set_section(Some("utils"));
2528
2529 let output = control.to_string();
2530 let expected = "Source: test-package\nSection: utils\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2531 assert_eq!(output, expected);
2532 }
2533}