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