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