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 set_build_depends_indep(&mut self, relations: &Relations) {
642 self.set("Build-Depends-Indep", relations.to_string().as_str());
643 }
644
645 pub fn build_depends_arch(&self) -> Option<Relations> {
647 self.paragraph
648 .get("Build-Depends-Arch")
649 .map(|s| self.parse_relations(&s))
650 }
651
652 pub fn set_build_depends_arch(&mut self, relations: &Relations) {
654 self.set("Build-Depends-Arch", relations.to_string().as_str());
655 }
656
657 pub fn build_conflicts(&self) -> Option<Relations> {
659 self.paragraph
660 .get("Build-Conflicts")
661 .map(|s| self.parse_relations(&s))
662 }
663
664 pub fn set_build_conflicts(&mut self, relations: &Relations) {
666 self.set("Build-Conflicts", relations.to_string().as_str());
667 }
668
669 pub fn build_conflicts_indep(&self) -> Option<Relations> {
671 self.paragraph
672 .get("Build-Conflicts-Indep")
673 .map(|s| self.parse_relations(&s))
674 }
675
676 pub fn set_build_conflicts_indep(&mut self, relations: &Relations) {
678 self.set("Build-Conflicts-Indep", relations.to_string().as_str());
679 }
680
681 pub fn build_conflicts_arch(&self) -> Option<Relations> {
683 self.paragraph
684 .get("Build-Conflicts-Arch")
685 .map(|s| self.parse_relations(&s))
686 }
687
688 pub fn standards_version(&self) -> Option<String> {
690 self.paragraph.get("Standards-Version")
691 }
692
693 pub fn set_standards_version(&mut self, version: &str) {
695 self.set("Standards-Version", version);
696 }
697
698 pub fn homepage(&self) -> Option<url::Url> {
700 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
701 }
702
703 pub fn set_homepage(&mut self, homepage: &url::Url) {
705 self.set("Homepage", homepage.to_string().as_str());
706 }
707
708 pub fn vcs_git(&self) -> Option<String> {
710 self.paragraph.get("Vcs-Git")
711 }
712
713 pub fn set_vcs_git(&mut self, url: &str) {
715 self.set("Vcs-Git", url);
716 }
717
718 pub fn vcs_svn(&self) -> Option<String> {
720 self.paragraph.get("Vcs-Svn").map(|s| s.to_string())
721 }
722
723 pub fn set_vcs_svn(&mut self, url: &str) {
725 self.set("Vcs-Svn", url);
726 }
727
728 pub fn vcs_bzr(&self) -> Option<String> {
730 self.paragraph.get("Vcs-Bzr").map(|s| s.to_string())
731 }
732
733 pub fn set_vcs_bzr(&mut self, url: &str) {
735 self.set("Vcs-Bzr", url);
736 }
737
738 pub fn vcs_arch(&self) -> Option<String> {
740 self.paragraph.get("Vcs-Arch").map(|s| s.to_string())
741 }
742
743 pub fn set_vcs_arch(&mut self, url: &str) {
745 self.set("Vcs-Arch", url);
746 }
747
748 pub fn vcs_svk(&self) -> Option<String> {
750 self.paragraph.get("Vcs-Svk").map(|s| s.to_string())
751 }
752
753 pub fn set_vcs_svk(&mut self, url: &str) {
755 self.set("Vcs-Svk", url);
756 }
757
758 pub fn vcs_darcs(&self) -> Option<String> {
760 self.paragraph.get("Vcs-Darcs").map(|s| s.to_string())
761 }
762
763 pub fn set_vcs_darcs(&mut self, url: &str) {
765 self.set("Vcs-Darcs", url);
766 }
767
768 pub fn vcs_mtn(&self) -> Option<String> {
770 self.paragraph.get("Vcs-Mtn").map(|s| s.to_string())
771 }
772
773 pub fn set_vcs_mtn(&mut self, url: &str) {
775 self.set("Vcs-Mtn", url);
776 }
777
778 pub fn vcs_cvs(&self) -> Option<String> {
780 self.paragraph.get("Vcs-Cvs").map(|s| s.to_string())
781 }
782
783 pub fn set_vcs_cvs(&mut self, url: &str) {
785 self.set("Vcs-Cvs", url);
786 }
787
788 pub fn vcs_hg(&self) -> Option<String> {
790 self.paragraph.get("Vcs-Hg").map(|s| s.to_string())
791 }
792
793 pub fn set_vcs_hg(&mut self, url: &str) {
795 self.set("Vcs-Hg", url);
796 }
797
798 pub fn set(&mut self, key: &str, value: &str) {
800 self.paragraph
801 .set_with_field_order(key, value, SOURCE_FIELD_ORDER);
802 }
803
804 pub fn get(&self, key: &str) -> Option<String> {
806 self.paragraph.get(key)
807 }
808
809 pub fn vcs_browser(&self) -> Option<String> {
811 self.paragraph.get("Vcs-Browser")
812 }
813
814 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
816 for (name, value) in self.paragraph.items() {
817 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
818 return crate::vcs::Vcs::from_field(&name, &value).ok();
819 }
820 }
821 None
822 }
823
824 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
826 if let Some(url) = url {
827 self.set("Vcs-Browser", url);
828 } else {
829 self.paragraph.remove("Vcs-Browser");
830 }
831 }
832
833 pub fn uploaders(&self) -> Option<Vec<String>> {
835 self.paragraph
836 .get("Uploaders")
837 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
838 }
839
840 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
842 self.set(
843 "Uploaders",
844 uploaders
845 .iter()
846 .map(|s| s.to_string())
847 .collect::<Vec<_>>()
848 .join(", ")
849 .as_str(),
850 );
851 }
852
853 pub fn architecture(&self) -> Option<String> {
855 self.paragraph.get("Architecture")
856 }
857
858 pub fn set_architecture(&mut self, arch: Option<&str>) {
860 if let Some(arch) = arch {
861 self.set("Architecture", arch);
862 } else {
863 self.paragraph.remove("Architecture");
864 }
865 }
866
867 pub fn rules_requires_root(&self) -> Option<bool> {
869 self.paragraph
870 .get("Rules-Requires-Root")
871 .map(|s| match s.to_lowercase().as_str() {
872 "yes" => true,
873 "no" => false,
874 _ => panic!("invalid Rules-Requires-Root value"),
875 })
876 }
877
878 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
880 self.set(
881 "Rules-Requires-Root",
882 if requires_root { "yes" } else { "no" },
883 );
884 }
885
886 pub fn testsuite(&self) -> Option<String> {
888 self.paragraph.get("Testsuite")
889 }
890
891 pub fn set_testsuite(&mut self, testsuite: &str) {
893 self.set("Testsuite", testsuite);
894 }
895
896 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
904 let para_range = self.paragraph.syntax().text_range();
905 para_range.start() < range.end() && range.start() < para_range.end()
906 }
907
908 pub fn fields_in_range(
916 &self,
917 range: rowan::TextRange,
918 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
919 self.paragraph.entries().filter(move |entry| {
920 let entry_range = entry.syntax().text_range();
921 entry_range.start() < range.end() && range.start() < entry_range.end()
922 })
923 }
924}
925
926#[cfg(feature = "python-debian")]
927impl<'py> pyo3::IntoPyObject<'py> for Source {
928 type Target = pyo3::PyAny;
929 type Output = pyo3::Bound<'py, Self::Target>;
930 type Error = pyo3::PyErr;
931
932 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
933 self.paragraph.into_pyobject(py)
934 }
935}
936
937#[cfg(feature = "python-debian")]
938impl<'py> pyo3::IntoPyObject<'py> for &Source {
939 type Target = pyo3::PyAny;
940 type Output = pyo3::Bound<'py, Self::Target>;
941 type Error = pyo3::PyErr;
942
943 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
944 (&self.paragraph).into_pyobject(py)
945 }
946}
947
948#[cfg(feature = "python-debian")]
949impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
950 type Error = pyo3::PyErr;
951
952 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
953 Ok(Source {
954 paragraph: ob.extract()?,
955 parse_mode: ParseMode::Strict,
956 })
957 }
958}
959
960impl std::fmt::Display for Control {
961 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
962 self.deb822.fmt(f)
963 }
964}
965
966impl AstNode for Control {
967 type Language = deb822_lossless::Lang;
968
969 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
970 Deb822::can_cast(kind)
971 }
972
973 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
974 Deb822::cast(syntax).map(|deb822| Control {
975 deb822,
976 parse_mode: ParseMode::Strict,
977 })
978 }
979
980 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
981 self.deb822.syntax()
982 }
983}
984
985#[derive(Debug, Clone, PartialEq, Eq)]
987pub struct Binary {
988 paragraph: Paragraph,
989 parse_mode: ParseMode,
990}
991
992impl From<Binary> for Paragraph {
993 fn from(b: Binary) -> Self {
994 b.paragraph
995 }
996}
997
998impl From<Paragraph> for Binary {
999 fn from(p: Paragraph) -> Self {
1000 Binary {
1001 paragraph: p,
1002 parse_mode: ParseMode::Strict,
1003 }
1004 }
1005}
1006
1007#[cfg(feature = "python-debian")]
1008impl<'py> pyo3::IntoPyObject<'py> for Binary {
1009 type Target = pyo3::PyAny;
1010 type Output = pyo3::Bound<'py, Self::Target>;
1011 type Error = pyo3::PyErr;
1012
1013 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1014 self.paragraph.into_pyobject(py)
1015 }
1016}
1017
1018#[cfg(feature = "python-debian")]
1019impl<'py> pyo3::IntoPyObject<'py> for &Binary {
1020 type Target = pyo3::PyAny;
1021 type Output = pyo3::Bound<'py, Self::Target>;
1022 type Error = pyo3::PyErr;
1023
1024 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1025 (&self.paragraph).into_pyobject(py)
1026 }
1027}
1028
1029#[cfg(feature = "python-debian")]
1030impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
1031 type Error = pyo3::PyErr;
1032
1033 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1034 Ok(Binary {
1035 paragraph: ob.extract()?,
1036 parse_mode: ParseMode::Strict,
1037 })
1038 }
1039}
1040
1041impl Default for Binary {
1042 fn default() -> Self {
1043 Self::new()
1044 }
1045}
1046
1047impl std::fmt::Display for Binary {
1048 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1049 self.paragraph.fmt(f)
1050 }
1051}
1052
1053impl Binary {
1054 fn parse_relations(&self, s: &str) -> Relations {
1056 match self.parse_mode {
1057 ParseMode::Strict => s.parse().unwrap(),
1058 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
1059 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
1060 }
1061 }
1062
1063 pub fn new() -> Self {
1065 Binary {
1066 paragraph: Paragraph::new(),
1067 parse_mode: ParseMode::Strict,
1068 }
1069 }
1070
1071 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
1073 &mut self.paragraph
1074 }
1075
1076 pub fn as_deb822(&self) -> &Paragraph {
1078 &self.paragraph
1079 }
1080
1081 pub fn wrap_and_sort(
1083 &mut self,
1084 indentation: deb822_lossless::Indentation,
1085 immediate_empty_line: bool,
1086 max_line_length_one_liner: Option<usize>,
1087 ) {
1088 self.paragraph = self.paragraph.wrap_and_sort(
1089 indentation,
1090 immediate_empty_line,
1091 max_line_length_one_liner,
1092 None,
1093 Some(&format_field),
1094 );
1095 }
1096
1097 pub fn name(&self) -> Option<String> {
1099 self.paragraph.get("Package")
1100 }
1101
1102 pub fn set_name(&mut self, name: &str) {
1104 self.set("Package", name);
1105 }
1106
1107 pub fn section(&self) -> Option<String> {
1109 self.paragraph.get("Section")
1110 }
1111
1112 pub fn set_section(&mut self, section: Option<&str>) {
1114 if let Some(section) = section {
1115 self.set("Section", section);
1116 } else {
1117 self.paragraph.remove("Section");
1118 }
1119 }
1120
1121 pub fn priority(&self) -> Option<Priority> {
1123 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
1124 }
1125
1126 pub fn set_priority(&mut self, priority: Option<Priority>) {
1128 if let Some(priority) = priority {
1129 self.set("Priority", priority.to_string().as_str());
1130 } else {
1131 self.paragraph.remove("Priority");
1132 }
1133 }
1134
1135 pub fn architecture(&self) -> Option<String> {
1137 self.paragraph.get("Architecture")
1138 }
1139
1140 pub fn set_architecture(&mut self, arch: Option<&str>) {
1142 if let Some(arch) = arch {
1143 self.set("Architecture", arch);
1144 } else {
1145 self.paragraph.remove("Architecture");
1146 }
1147 }
1148
1149 pub fn depends(&self) -> Option<Relations> {
1151 self.paragraph
1152 .get("Depends")
1153 .map(|s| self.parse_relations(&s))
1154 }
1155
1156 pub fn set_depends(&mut self, depends: Option<&Relations>) {
1158 if let Some(depends) = depends {
1159 self.set("Depends", depends.to_string().as_str());
1160 } else {
1161 self.paragraph.remove("Depends");
1162 }
1163 }
1164
1165 pub fn recommends(&self) -> Option<Relations> {
1167 self.paragraph
1168 .get("Recommends")
1169 .map(|s| self.parse_relations(&s))
1170 }
1171
1172 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
1174 if let Some(recommends) = recommends {
1175 self.set("Recommends", recommends.to_string().as_str());
1176 } else {
1177 self.paragraph.remove("Recommends");
1178 }
1179 }
1180
1181 pub fn suggests(&self) -> Option<Relations> {
1183 self.paragraph
1184 .get("Suggests")
1185 .map(|s| self.parse_relations(&s))
1186 }
1187
1188 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
1190 if let Some(suggests) = suggests {
1191 self.set("Suggests", suggests.to_string().as_str());
1192 } else {
1193 self.paragraph.remove("Suggests");
1194 }
1195 }
1196
1197 pub fn enhances(&self) -> Option<Relations> {
1199 self.paragraph
1200 .get("Enhances")
1201 .map(|s| self.parse_relations(&s))
1202 }
1203
1204 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1206 if let Some(enhances) = enhances {
1207 self.set("Enhances", enhances.to_string().as_str());
1208 } else {
1209 self.paragraph.remove("Enhances");
1210 }
1211 }
1212
1213 pub fn pre_depends(&self) -> Option<Relations> {
1215 self.paragraph
1216 .get("Pre-Depends")
1217 .map(|s| self.parse_relations(&s))
1218 }
1219
1220 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1222 if let Some(pre_depends) = pre_depends {
1223 self.set("Pre-Depends", pre_depends.to_string().as_str());
1224 } else {
1225 self.paragraph.remove("Pre-Depends");
1226 }
1227 }
1228
1229 pub fn breaks(&self) -> Option<Relations> {
1231 self.paragraph
1232 .get("Breaks")
1233 .map(|s| self.parse_relations(&s))
1234 }
1235
1236 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1238 if let Some(breaks) = breaks {
1239 self.set("Breaks", breaks.to_string().as_str());
1240 } else {
1241 self.paragraph.remove("Breaks");
1242 }
1243 }
1244
1245 pub fn conflicts(&self) -> Option<Relations> {
1247 self.paragraph
1248 .get("Conflicts")
1249 .map(|s| self.parse_relations(&s))
1250 }
1251
1252 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1254 if let Some(conflicts) = conflicts {
1255 self.set("Conflicts", conflicts.to_string().as_str());
1256 } else {
1257 self.paragraph.remove("Conflicts");
1258 }
1259 }
1260
1261 pub fn replaces(&self) -> Option<Relations> {
1263 self.paragraph
1264 .get("Replaces")
1265 .map(|s| self.parse_relations(&s))
1266 }
1267
1268 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1270 if let Some(replaces) = replaces {
1271 self.set("Replaces", replaces.to_string().as_str());
1272 } else {
1273 self.paragraph.remove("Replaces");
1274 }
1275 }
1276
1277 pub fn provides(&self) -> Option<Relations> {
1279 self.paragraph
1280 .get("Provides")
1281 .map(|s| self.parse_relations(&s))
1282 }
1283
1284 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1286 if let Some(provides) = provides {
1287 self.set("Provides", provides.to_string().as_str());
1288 } else {
1289 self.paragraph.remove("Provides");
1290 }
1291 }
1292
1293 pub fn built_using(&self) -> Option<Relations> {
1295 self.paragraph
1296 .get("Built-Using")
1297 .map(|s| self.parse_relations(&s))
1298 }
1299
1300 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1302 if let Some(built_using) = built_using {
1303 self.set("Built-Using", built_using.to_string().as_str());
1304 } else {
1305 self.paragraph.remove("Built-Using");
1306 }
1307 }
1308
1309 pub fn static_built_using(&self) -> Option<Relations> {
1311 self.paragraph
1312 .get("Static-Built-Using")
1313 .map(|s| self.parse_relations(&s))
1314 }
1315
1316 pub fn set_static_built_using(&mut self, static_built_using: Option<&Relations>) {
1318 if let Some(static_built_using) = static_built_using {
1319 self.set(
1320 "Static-Built-Using",
1321 static_built_using.to_string().as_str(),
1322 );
1323 } else {
1324 self.paragraph.remove("Static-Built-Using");
1325 }
1326 }
1327
1328 pub fn multi_arch(&self) -> Option<MultiArch> {
1330 self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap())
1331 }
1332
1333 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1335 if let Some(multi_arch) = multi_arch {
1336 self.set("Multi-Arch", multi_arch.to_string().as_str());
1337 } else {
1338 self.paragraph.remove("Multi-Arch");
1339 }
1340 }
1341
1342 pub fn essential(&self) -> bool {
1344 self.paragraph
1345 .get("Essential")
1346 .map(|s| s == "yes")
1347 .unwrap_or(false)
1348 }
1349
1350 pub fn set_essential(&mut self, essential: bool) {
1352 if essential {
1353 self.set("Essential", "yes");
1354 } else {
1355 self.paragraph.remove("Essential");
1356 }
1357 }
1358
1359 pub fn description(&self) -> Option<String> {
1361 self.paragraph.get_multiline("Description")
1362 }
1363
1364 pub fn set_description(&mut self, description: Option<&str>) {
1366 if let Some(description) = description {
1367 self.paragraph.set_with_indent_pattern(
1368 "Description",
1369 description,
1370 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1371 Some(BINARY_FIELD_ORDER),
1372 );
1373 } else {
1374 self.paragraph.remove("Description");
1375 }
1376 }
1377
1378 pub fn homepage(&self) -> Option<url::Url> {
1380 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
1381 }
1382
1383 pub fn set_homepage(&mut self, url: &url::Url) {
1385 self.set("Homepage", url.as_str());
1386 }
1387
1388 pub fn set(&mut self, key: &str, value: &str) {
1390 self.paragraph
1391 .set_with_field_order(key, value, BINARY_FIELD_ORDER);
1392 }
1393
1394 pub fn get(&self, key: &str) -> Option<String> {
1396 self.paragraph.get(key)
1397 }
1398
1399 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1407 let para_range = self.paragraph.syntax().text_range();
1408 para_range.start() < range.end() && range.start() < para_range.end()
1409 }
1410
1411 pub fn fields_in_range(
1419 &self,
1420 range: rowan::TextRange,
1421 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1422 self.paragraph.entries().filter(move |entry| {
1423 let entry_range = entry.syntax().text_range();
1424 entry_range.start() < range.end() && range.start() < entry_range.end()
1425 })
1426 }
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use super::*;
1432 use crate::relations::VersionConstraint;
1433
1434 #[test]
1435 fn test_source_set_field_ordering() {
1436 let mut control = Control::new();
1437 let mut source = control.add_source("mypackage");
1438
1439 source.set("Homepage", "https://example.com");
1441 source.set("Build-Depends", "debhelper");
1442 source.set("Standards-Version", "4.5.0");
1443 source.set("Maintainer", "Test <test@example.com>");
1444
1445 let output = source.to_string();
1447 let lines: Vec<&str> = output.lines().collect();
1448
1449 assert!(lines[0].starts_with("Source:"));
1451
1452 let maintainer_pos = lines
1454 .iter()
1455 .position(|l| l.starts_with("Maintainer:"))
1456 .unwrap();
1457 let build_depends_pos = lines
1458 .iter()
1459 .position(|l| l.starts_with("Build-Depends:"))
1460 .unwrap();
1461 let standards_pos = lines
1462 .iter()
1463 .position(|l| l.starts_with("Standards-Version:"))
1464 .unwrap();
1465 let homepage_pos = lines
1466 .iter()
1467 .position(|l| l.starts_with("Homepage:"))
1468 .unwrap();
1469
1470 assert!(maintainer_pos < build_depends_pos);
1472 assert!(build_depends_pos < standards_pos);
1473 assert!(standards_pos < homepage_pos);
1474 }
1475
1476 #[test]
1477 fn test_binary_set_field_ordering() {
1478 let mut control = Control::new();
1479 let mut binary = control.add_binary("mypackage");
1480
1481 binary.set("Description", "A test package");
1483 binary.set("Architecture", "amd64");
1484 binary.set("Depends", "libc6");
1485 binary.set("Section", "utils");
1486
1487 let output = binary.to_string();
1489 let lines: Vec<&str> = output.lines().collect();
1490
1491 assert!(lines[0].starts_with("Package:"));
1493
1494 let arch_pos = lines
1496 .iter()
1497 .position(|l| l.starts_with("Architecture:"))
1498 .unwrap();
1499 let section_pos = lines
1500 .iter()
1501 .position(|l| l.starts_with("Section:"))
1502 .unwrap();
1503 let depends_pos = lines
1504 .iter()
1505 .position(|l| l.starts_with("Depends:"))
1506 .unwrap();
1507 let desc_pos = lines
1508 .iter()
1509 .position(|l| l.starts_with("Description:"))
1510 .unwrap();
1511
1512 assert!(arch_pos < section_pos);
1514 assert!(section_pos < depends_pos);
1515 assert!(depends_pos < desc_pos);
1516 }
1517
1518 #[test]
1519 fn test_source_specific_set_methods_use_field_ordering() {
1520 let mut control = Control::new();
1521 let mut source = control.add_source("mypackage");
1522
1523 source.set_homepage(&"https://example.com".parse().unwrap());
1525 source.set_maintainer("Test <test@example.com>");
1526 source.set_standards_version("4.5.0");
1527 source.set_vcs_git("https://github.com/example/repo");
1528
1529 let output = source.to_string();
1531 let lines: Vec<&str> = output.lines().collect();
1532
1533 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1535 let maintainer_pos = lines
1536 .iter()
1537 .position(|l| l.starts_with("Maintainer:"))
1538 .unwrap();
1539 let standards_pos = lines
1540 .iter()
1541 .position(|l| l.starts_with("Standards-Version:"))
1542 .unwrap();
1543 let vcs_git_pos = lines
1544 .iter()
1545 .position(|l| l.starts_with("Vcs-Git:"))
1546 .unwrap();
1547 let homepage_pos = lines
1548 .iter()
1549 .position(|l| l.starts_with("Homepage:"))
1550 .unwrap();
1551
1552 assert!(source_pos < maintainer_pos);
1554 assert!(maintainer_pos < standards_pos);
1555 assert!(standards_pos < vcs_git_pos);
1556 assert!(vcs_git_pos < homepage_pos);
1557 }
1558
1559 #[test]
1560 fn test_binary_specific_set_methods_use_field_ordering() {
1561 let mut control = Control::new();
1562 let mut binary = control.add_binary("mypackage");
1563
1564 binary.set_description(Some("A test package"));
1566 binary.set_architecture(Some("amd64"));
1567 let depends = "libc6".parse().unwrap();
1568 binary.set_depends(Some(&depends));
1569 binary.set_section(Some("utils"));
1570 binary.set_priority(Some(Priority::Optional));
1571
1572 let output = binary.to_string();
1574 let lines: Vec<&str> = output.lines().collect();
1575
1576 let package_pos = lines
1578 .iter()
1579 .position(|l| l.starts_with("Package:"))
1580 .unwrap();
1581 let arch_pos = lines
1582 .iter()
1583 .position(|l| l.starts_with("Architecture:"))
1584 .unwrap();
1585 let section_pos = lines
1586 .iter()
1587 .position(|l| l.starts_with("Section:"))
1588 .unwrap();
1589 let priority_pos = lines
1590 .iter()
1591 .position(|l| l.starts_with("Priority:"))
1592 .unwrap();
1593 let depends_pos = lines
1594 .iter()
1595 .position(|l| l.starts_with("Depends:"))
1596 .unwrap();
1597 let desc_pos = lines
1598 .iter()
1599 .position(|l| l.starts_with("Description:"))
1600 .unwrap();
1601
1602 assert!(package_pos < arch_pos);
1604 assert!(arch_pos < section_pos);
1605 assert!(section_pos < priority_pos);
1606 assert!(priority_pos < depends_pos);
1607 assert!(depends_pos < desc_pos);
1608 }
1609
1610 #[test]
1611 fn test_parse() {
1612 let control: Control = r#"Source: foo
1613Section: libs
1614Priority: optional
1615Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1616Homepage: https://example.com
1617
1618"#
1619 .parse()
1620 .unwrap();
1621 let source = control.source().unwrap();
1622
1623 assert_eq!(source.name(), Some("foo".to_owned()));
1624 assert_eq!(source.section(), Some("libs".to_owned()));
1625 assert_eq!(source.priority(), Some(super::Priority::Optional));
1626 assert_eq!(
1627 source.homepage(),
1628 Some("https://example.com".parse().unwrap())
1629 );
1630 let bd = source.build_depends().unwrap();
1631 let entries = bd.entries().collect::<Vec<_>>();
1632 assert_eq!(entries.len(), 2);
1633 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1634 assert_eq!(rel.name(), "bar");
1635 assert_eq!(
1636 rel.version(),
1637 Some((
1638 VersionConstraint::GreaterThanEqual,
1639 "1.0.0".parse().unwrap()
1640 ))
1641 );
1642 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1643 assert_eq!(rel.name(), "baz");
1644 assert_eq!(
1645 rel.version(),
1646 Some((
1647 VersionConstraint::GreaterThanEqual,
1648 "1.0.0".parse().unwrap()
1649 ))
1650 );
1651 }
1652
1653 #[test]
1654 fn test_description() {
1655 let control: Control = r#"Source: foo
1656
1657Package: foo
1658Description: this is the short description
1659 And the longer one
1660 .
1661 is on the next lines
1662"#
1663 .parse()
1664 .unwrap();
1665 let binary = control.binaries().next().unwrap();
1666 assert_eq!(
1667 binary.description(),
1668 Some(
1669 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1670 .to_owned()
1671 )
1672 );
1673 }
1674
1675 #[test]
1676 fn test_set_description_on_package_without_description() {
1677 let control: Control = r#"Source: foo
1678
1679Package: foo
1680Architecture: amd64
1681"#
1682 .parse()
1683 .unwrap();
1684 let mut binary = control.binaries().next().unwrap();
1685
1686 binary.set_description(Some(
1688 "Short description\nLonger description\n.\nAnother line",
1689 ));
1690
1691 let output = binary.to_string();
1692
1693 assert_eq!(
1695 binary.description(),
1696 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1697 );
1698
1699 assert_eq!(
1701 output,
1702 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1703 );
1704 }
1705
1706 #[test]
1707 fn test_as_mut_deb822() {
1708 let mut control = Control::new();
1709 let deb822 = control.as_mut_deb822();
1710 let mut p = deb822.add_paragraph();
1711 p.set("Source", "foo");
1712 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1713 }
1714
1715 #[test]
1716 fn test_as_deb822() {
1717 let control = Control::new();
1718 let _deb822: &Deb822 = control.as_deb822();
1719 }
1720
1721 #[test]
1722 fn test_set_depends() {
1723 let mut control = Control::new();
1724 let mut binary = control.add_binary("foo");
1725 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1726 binary.set_depends(Some(&relations));
1727 }
1728
1729 #[test]
1730 fn test_wrap_and_sort() {
1731 let mut control: Control = r#"Package: blah
1732Section: libs
1733
1734
1735
1736Package: foo
1737Description: this is a
1738 bar
1739 blah
1740"#
1741 .parse()
1742 .unwrap();
1743 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1744 let expected = r#"Package: blah
1745Section: libs
1746
1747Package: foo
1748Description: this is a
1749 bar
1750 blah
1751"#
1752 .to_owned();
1753 assert_eq!(control.to_string(), expected);
1754 }
1755
1756 #[test]
1757 fn test_wrap_and_sort_source() {
1758 let mut control: Control = r#"Source: blah
1759Depends: foo, bar (<= 1.0.0)
1760
1761"#
1762 .parse()
1763 .unwrap();
1764 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1765 let expected = r#"Source: blah
1766Depends: bar (<= 1.0.0), foo
1767"#
1768 .to_owned();
1769 assert_eq!(control.to_string(), expected);
1770 }
1771
1772 #[test]
1773 fn test_source_wrap_and_sort() {
1774 let control: Control = r#"Source: blah
1775Build-Depends: foo, bar (>= 1.0.0)
1776
1777"#
1778 .parse()
1779 .unwrap();
1780 let mut source = control.source().unwrap();
1781 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1782 assert!(source.build_depends().is_some());
1786 }
1787
1788 #[test]
1789 fn test_binary_set_breaks() {
1790 let mut control = Control::new();
1791 let mut binary = control.add_binary("foo");
1792 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1793 binary.set_breaks(Some(&relations));
1794 assert!(binary.breaks().is_some());
1795 }
1796
1797 #[test]
1798 fn test_binary_set_pre_depends() {
1799 let mut control = Control::new();
1800 let mut binary = control.add_binary("foo");
1801 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1802 binary.set_pre_depends(Some(&relations));
1803 assert!(binary.pre_depends().is_some());
1804 }
1805
1806 #[test]
1807 fn test_binary_set_provides() {
1808 let mut control = Control::new();
1809 let mut binary = control.add_binary("foo");
1810 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1811 binary.set_provides(Some(&relations));
1812 assert!(binary.provides().is_some());
1813 }
1814
1815 #[test]
1816 fn test_source_build_conflicts() {
1817 let control: Control = r#"Source: blah
1818Build-Conflicts: foo, bar (>= 1.0.0)
1819
1820"#
1821 .parse()
1822 .unwrap();
1823 let source = control.source().unwrap();
1824 let conflicts = source.build_conflicts();
1825 assert!(conflicts.is_some());
1826 }
1827
1828 #[test]
1829 fn test_source_vcs_svn() {
1830 let control: Control = r#"Source: blah
1831Vcs-Svn: https://example.com/svn/repo
1832
1833"#
1834 .parse()
1835 .unwrap();
1836 let source = control.source().unwrap();
1837 assert_eq!(
1838 source.vcs_svn(),
1839 Some("https://example.com/svn/repo".to_string())
1840 );
1841 }
1842
1843 #[test]
1844 fn test_control_from_conversion() {
1845 let deb822_data = r#"Source: test
1846Section: libs
1847
1848"#;
1849 let deb822: Deb822 = deb822_data.parse().unwrap();
1850 let control = Control::from(deb822);
1851 assert!(control.source().is_some());
1852 }
1853
1854 #[test]
1855 fn test_fields_in_range() {
1856 let control_text = r#"Source: test-package
1857Maintainer: Test User <test@example.com>
1858Build-Depends: debhelper (>= 12)
1859
1860Package: test-binary
1861Architecture: any
1862Depends: ${shlibs:Depends}
1863Description: Test package
1864 This is a test package
1865"#;
1866 let control: Control = control_text.parse().unwrap();
1867
1868 let source_start = 0;
1870 let source_end = "Source: test-package".len();
1871 let source_range =
1872 rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1873
1874 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1875 assert_eq!(fields.len(), 1);
1876 assert_eq!(fields[0].key(), Some("Source".to_string()));
1877
1878 let maintainer_start = control_text.find("Maintainer:").unwrap();
1880 let build_depends_end = control_text
1881 .find("Build-Depends: debhelper (>= 12)")
1882 .unwrap()
1883 + "Build-Depends: debhelper (>= 12)".len();
1884 let multi_range = rowan::TextRange::new(
1885 (maintainer_start as u32).into(),
1886 (build_depends_end as u32).into(),
1887 );
1888
1889 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1890 assert_eq!(fields.len(), 2);
1891 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1892 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1893
1894 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1896 let cross_para_end =
1897 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1898 let cross_range = rowan::TextRange::new(
1899 (cross_para_start as u32).into(),
1900 (cross_para_end as u32).into(),
1901 );
1902
1903 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1904 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1906 assert_eq!(fields[1].key(), Some("Package".to_string()));
1907 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1908
1909 let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1911 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1912 assert_eq!(fields.len(), 0);
1913 }
1914
1915 #[test]
1916 fn test_source_overlaps_range() {
1917 let control_text = r#"Source: test-package
1918Maintainer: Test User <test@example.com>
1919
1920Package: test-binary
1921Architecture: any
1922"#;
1923 let control: Control = control_text.parse().unwrap();
1924 let source = control.source().unwrap();
1925
1926 let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1928 assert!(source.overlaps_range(overlap_range));
1929
1930 let binary_start = control_text.find("Package:").unwrap();
1932 let no_overlap_range = rowan::TextRange::new(
1933 (binary_start as u32).into(),
1934 ((binary_start + 20) as u32).into(),
1935 );
1936 assert!(!source.overlaps_range(no_overlap_range));
1937
1938 let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1940 assert!(source.overlaps_range(partial_overlap));
1941 }
1942
1943 #[test]
1944 fn test_source_fields_in_range() {
1945 let control_text = r#"Source: test-package
1946Maintainer: Test User <test@example.com>
1947Build-Depends: debhelper (>= 12)
1948
1949Package: test-binary
1950"#;
1951 let control: Control = control_text.parse().unwrap();
1952 let source = control.source().unwrap();
1953
1954 let maintainer_start = control_text.find("Maintainer:").unwrap();
1956 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1957 let maintainer_range = rowan::TextRange::new(
1958 (maintainer_start as u32).into(),
1959 (maintainer_end as u32).into(),
1960 );
1961
1962 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1963 assert_eq!(fields.len(), 1);
1964 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1965
1966 let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1968 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1969 assert_eq!(fields.len(), 3); }
1971
1972 #[test]
1973 fn test_binary_overlaps_range() {
1974 let control_text = r#"Source: test-package
1975
1976Package: test-binary
1977Architecture: any
1978Depends: ${shlibs:Depends}
1979"#;
1980 let control: Control = control_text.parse().unwrap();
1981 let binary = control.binaries().next().unwrap();
1982
1983 let package_start = control_text.find("Package:").unwrap();
1985 let overlap_range = rowan::TextRange::new(
1986 (package_start as u32).into(),
1987 ((package_start + 30) as u32).into(),
1988 );
1989 assert!(binary.overlaps_range(overlap_range));
1990
1991 let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1993 assert!(!binary.overlaps_range(no_overlap_range));
1994 }
1995
1996 #[test]
1997 fn test_binary_fields_in_range() {
1998 let control_text = r#"Source: test-package
1999
2000Package: test-binary
2001Architecture: any
2002Depends: ${shlibs:Depends}
2003Description: Test binary
2004 This is a test binary package
2005"#;
2006 let control: Control = control_text.parse().unwrap();
2007 let binary = control.binaries().next().unwrap();
2008
2009 let arch_start = control_text.find("Architecture:").unwrap();
2011 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
2012 + "Depends: ${shlibs:Depends}".len();
2013 let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
2014
2015 let fields: Vec<_> = binary.fields_in_range(range).collect();
2016 assert_eq!(fields.len(), 2);
2017 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
2018 assert_eq!(fields[1].key(), Some("Depends".to_string()));
2019
2020 let desc_start = control_text.find("Description:").unwrap();
2022 let partial_range = rowan::TextRange::new(
2023 ((desc_start + 5) as u32).into(),
2024 ((desc_start + 15) as u32).into(),
2025 );
2026 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
2027 assert_eq!(fields.len(), 1);
2028 assert_eq!(fields[0].key(), Some("Description".to_string()));
2029 }
2030
2031 #[test]
2032 fn test_incremental_parsing_use_case() {
2033 let control_text = r#"Source: example
2035Maintainer: John Doe <john@example.com>
2036Standards-Version: 4.6.0
2037Build-Depends: debhelper-compat (= 13)
2038
2039Package: example-bin
2040Architecture: all
2041Depends: ${misc:Depends}
2042Description: Example package
2043 This is an example.
2044"#;
2045 let control: Control = control_text.parse().unwrap();
2046
2047 let change_start = control_text.find("Standards-Version:").unwrap();
2049 let change_end = change_start + "Standards-Version: 4.6.0".len();
2050 let change_range =
2051 rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
2052
2053 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
2055 assert_eq!(affected_fields.len(), 1);
2056 assert_eq!(
2057 affected_fields[0].key(),
2058 Some("Standards-Version".to_string())
2059 );
2060
2061 for entry in &affected_fields {
2063 let key = entry.key().unwrap();
2064 assert_ne!(key, "Maintainer");
2065 assert_ne!(key, "Build-Depends");
2066 assert_ne!(key, "Architecture");
2067 }
2068 }
2069
2070 #[test]
2071 fn test_positioned_parse_errors() {
2072 let input = "Invalid: field\nBroken field without colon";
2074 let parsed = Control::parse(input);
2075
2076 let positioned_errors = parsed.positioned_errors();
2078 assert!(
2079 !positioned_errors.is_empty(),
2080 "Should have positioned errors"
2081 );
2082
2083 for error in positioned_errors {
2085 let start_offset: u32 = error.range.start().into();
2086 let end_offset: u32 = error.range.end().into();
2087
2088 assert!(!error.message.is_empty());
2090
2091 assert!(start_offset <= end_offset);
2093 assert!(end_offset <= input.len() as u32);
2094
2095 assert!(error.code.is_some());
2097
2098 println!(
2099 "Error at {:?}: {} (code: {:?})",
2100 error.range, error.message, error.code
2101 );
2102 }
2103
2104 let string_errors = parsed.errors();
2106 assert!(!string_errors.is_empty());
2107 assert_eq!(string_errors.len(), positioned_errors.len());
2108 }
2109
2110 #[test]
2111 fn test_sort_binaries_basic() {
2112 let input = r#"Source: foo
2113
2114Package: libfoo
2115Architecture: all
2116
2117Package: libbar
2118Architecture: all
2119"#;
2120
2121 let mut control: Control = input.parse().unwrap();
2122 control.sort_binaries(false);
2123
2124 let binaries: Vec<_> = control.binaries().collect();
2125 assert_eq!(binaries.len(), 2);
2126 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
2127 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
2128 }
2129
2130 #[test]
2131 fn test_sort_binaries_keep_first() {
2132 let input = r#"Source: foo
2133
2134Package: zzz-first
2135Architecture: all
2136
2137Package: libbar
2138Architecture: all
2139
2140Package: libaaa
2141Architecture: all
2142"#;
2143
2144 let mut control: Control = input.parse().unwrap();
2145 control.sort_binaries(true);
2146
2147 let binaries: Vec<_> = control.binaries().collect();
2148 assert_eq!(binaries.len(), 3);
2149 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
2151 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
2153 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
2154 }
2155
2156 #[test]
2157 fn test_sort_binaries_already_sorted() {
2158 let input = r#"Source: foo
2159
2160Package: aaa
2161Architecture: all
2162
2163Package: bbb
2164Architecture: all
2165
2166Package: ccc
2167Architecture: all
2168"#;
2169
2170 let mut control: Control = input.parse().unwrap();
2171 control.sort_binaries(false);
2172
2173 let binaries: Vec<_> = control.binaries().collect();
2174 assert_eq!(binaries.len(), 3);
2175 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2176 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
2177 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
2178 }
2179
2180 #[test]
2181 fn test_sort_binaries_no_binaries() {
2182 let input = r#"Source: foo
2183Maintainer: test@example.com
2184"#;
2185
2186 let mut control: Control = input.parse().unwrap();
2187 control.sort_binaries(false);
2188
2189 assert_eq!(control.binaries().count(), 0);
2191 }
2192
2193 #[test]
2194 fn test_sort_binaries_one_binary() {
2195 let input = r#"Source: foo
2196
2197Package: bar
2198Architecture: all
2199"#;
2200
2201 let mut control: Control = input.parse().unwrap();
2202 control.sort_binaries(false);
2203
2204 let binaries: Vec<_> = control.binaries().collect();
2205 assert_eq!(binaries.len(), 1);
2206 assert_eq!(binaries[0].name(), Some("bar".to_string()));
2207 }
2208
2209 #[test]
2210 fn test_sort_binaries_preserves_fields() {
2211 let input = r#"Source: foo
2212
2213Package: zzz
2214Architecture: any
2215Depends: libc6
2216Description: ZZZ package
2217
2218Package: aaa
2219Architecture: all
2220Depends: ${misc:Depends}
2221Description: AAA package
2222"#;
2223
2224 let mut control: Control = input.parse().unwrap();
2225 control.sort_binaries(false);
2226
2227 let binaries: Vec<_> = control.binaries().collect();
2228 assert_eq!(binaries.len(), 2);
2229
2230 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2232 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
2233 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
2234
2235 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
2237 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2238 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2239 }
2240
2241 #[test]
2242 fn test_remove_binary_basic() {
2243 let mut control = Control::new();
2244 control.add_binary("foo");
2245 assert_eq!(control.binaries().count(), 1);
2246 assert!(control.remove_binary("foo"));
2247 assert_eq!(control.binaries().count(), 0);
2248 }
2249
2250 #[test]
2251 fn test_remove_binary_nonexistent() {
2252 let mut control = Control::new();
2253 control.add_binary("foo");
2254 assert!(!control.remove_binary("bar"));
2255 assert_eq!(control.binaries().count(), 1);
2256 }
2257
2258 #[test]
2259 fn test_remove_binary_multiple() {
2260 let mut control = Control::new();
2261 control.add_binary("foo");
2262 control.add_binary("bar");
2263 control.add_binary("baz");
2264 assert_eq!(control.binaries().count(), 3);
2265
2266 assert!(control.remove_binary("bar"));
2267 assert_eq!(control.binaries().count(), 2);
2268
2269 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2270 assert_eq!(names, vec!["foo", "baz"]);
2271 }
2272
2273 #[test]
2274 fn test_remove_binary_preserves_source() {
2275 let input = r#"Source: mypackage
2276
2277Package: foo
2278Architecture: all
2279
2280Package: bar
2281Architecture: all
2282"#;
2283 let mut control: Control = input.parse().unwrap();
2284 assert!(control.source().is_some());
2285 assert_eq!(control.binaries().count(), 2);
2286
2287 assert!(control.remove_binary("foo"));
2288
2289 assert!(control.source().is_some());
2291 assert_eq!(
2292 control.source().unwrap().name(),
2293 Some("mypackage".to_string())
2294 );
2295
2296 assert_eq!(control.binaries().count(), 1);
2298 assert_eq!(
2299 control.binaries().next().unwrap().name(),
2300 Some("bar".to_string())
2301 );
2302 }
2303
2304 #[test]
2305 fn test_remove_binary_from_parsed() {
2306 let input = r#"Source: test
2307
2308Package: test-bin
2309Architecture: any
2310Depends: libc6
2311Description: Test binary
2312
2313Package: test-lib
2314Architecture: all
2315Description: Test library
2316"#;
2317 let mut control: Control = input.parse().unwrap();
2318 assert_eq!(control.binaries().count(), 2);
2319
2320 assert!(control.remove_binary("test-bin"));
2321
2322 let output = control.to_string();
2323 assert!(!output.contains("test-bin"));
2324 assert!(output.contains("test-lib"));
2325 assert!(output.contains("Source: test"));
2326 }
2327
2328 #[test]
2329 fn test_build_depends_preserves_indentation_after_removal() {
2330 let input = r#"Source: acpi-support
2331Section: admin
2332Priority: optional
2333Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2334Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2335 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2336"#;
2337 let control: Control = input.parse().unwrap();
2338 let mut source = control.source().unwrap();
2339
2340 let mut build_depends = source.build_depends().unwrap();
2342
2343 let mut to_remove = Vec::new();
2345 for (idx, entry) in build_depends.entries().enumerate() {
2346 for relation in entry.relations() {
2347 if relation.name() == "dh-systemd" {
2348 to_remove.push(idx);
2349 break;
2350 }
2351 }
2352 }
2353
2354 for idx in to_remove.into_iter().rev() {
2355 build_depends.remove_entry(idx);
2356 }
2357
2358 source.set_build_depends(&build_depends);
2360
2361 let output = source.to_string();
2362
2363 assert!(
2365 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2366 "Expected 4-space indentation to be preserved, but got:\n{}",
2367 output
2368 );
2369 }
2370
2371 #[test]
2372 fn test_build_depends_direct_string_set_loses_indentation() {
2373 let input = r#"Source: acpi-support
2374Section: admin
2375Priority: optional
2376Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2377Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2378 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2379"#;
2380 let control: Control = input.parse().unwrap();
2381 let mut source = control.source().unwrap();
2382
2383 let mut build_depends = source.build_depends().unwrap();
2385
2386 let mut to_remove = Vec::new();
2388 for (idx, entry) in build_depends.entries().enumerate() {
2389 for relation in entry.relations() {
2390 if relation.name() == "dh-systemd" {
2391 to_remove.push(idx);
2392 break;
2393 }
2394 }
2395 }
2396
2397 for idx in to_remove.into_iter().rev() {
2398 build_depends.remove_entry(idx);
2399 }
2400
2401 source.set("Build-Depends", &build_depends.to_string());
2403
2404 let output = source.to_string();
2405 println!("Output with string set:");
2406 println!("{}", output);
2407
2408 assert!(
2411 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2412 "Expected 4-space indentation to be preserved, but got:\n{}",
2413 output
2414 );
2415 }
2416
2417 #[test]
2418 fn test_parse_mode_strict_default() {
2419 let control = Control::new();
2420 assert_eq!(control.parse_mode(), ParseMode::Strict);
2421
2422 let control: Control = "Source: test\n".parse().unwrap();
2423 assert_eq!(control.parse_mode(), ParseMode::Strict);
2424 }
2425
2426 #[test]
2427 fn test_parse_mode_new_with_mode() {
2428 let control_relaxed = Control::new_with_mode(ParseMode::Relaxed);
2429 assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed);
2430
2431 let control_substvar = Control::new_with_mode(ParseMode::Substvar);
2432 assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar);
2433 }
2434
2435 #[test]
2436 fn test_relaxed_mode_handles_broken_relations() {
2437 let input = r#"Source: test-package
2438Build-Depends: debhelper, @@@broken@@@, python3
2439
2440Package: test-pkg
2441Depends: libfoo, %%%invalid%%%, libbar
2442"#;
2443
2444 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2445 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2446
2447 if let Some(source) = control.source() {
2449 let bd = source.build_depends();
2450 assert!(bd.is_some());
2451 let relations = bd.unwrap();
2452 assert!(relations.len() >= 2); }
2455
2456 for binary in control.binaries() {
2457 let deps = binary.depends();
2458 assert!(deps.is_some());
2459 let relations = deps.unwrap();
2460 assert!(relations.len() >= 2); }
2463 }
2464
2465 #[test]
2466 fn test_substvar_mode_via_parse() {
2467 let input = r#"Source: test-package
2471Build-Depends: debhelper, ${misc:Depends}
2472
2473Package: test-pkg
2474Depends: ${shlibs:Depends}, libfoo
2475"#;
2476
2477 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2479
2480 if let Some(source) = control.source() {
2481 let bd = source.build_depends();
2483 assert!(bd.is_some());
2484 }
2485
2486 for binary in control.binaries() {
2487 let deps = binary.depends();
2488 assert!(deps.is_some());
2489 }
2490 }
2491
2492 #[test]
2493 #[should_panic]
2494 fn test_strict_mode_panics_on_broken_syntax() {
2495 let input = r#"Source: test-package
2496Build-Depends: debhelper, @@@broken@@@
2497"#;
2498
2499 let control: Control = input.parse().unwrap();
2501
2502 if let Some(source) = control.source() {
2503 let _ = source.build_depends();
2505 }
2506 }
2507
2508 #[test]
2509 fn test_from_file_relaxed_sets_relaxed_mode() {
2510 let input = r#"Source: test-package
2511Maintainer: Test <test@example.com>
2512"#;
2513
2514 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2515 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2516 }
2517
2518 #[test]
2519 fn test_parse_mode_propagates_to_paragraphs() {
2520 let input = r#"Source: test-package
2521Build-Depends: debhelper, @@@invalid@@@, python3
2522
2523Package: test-pkg
2524Depends: libfoo, %%%bad%%%, libbar
2525"#;
2526
2527 let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap();
2529
2530 if let Some(source) = control.source() {
2533 assert!(source.build_depends().is_some());
2534 }
2535
2536 for binary in control.binaries() {
2537 assert!(binary.depends().is_some());
2538 }
2539 }
2540
2541 #[test]
2542 fn test_preserves_final_newline() {
2543 let input_with_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2545 let control: Control = input_with_newline.parse().unwrap();
2546 let output = control.to_string();
2547 assert_eq!(output, input_with_newline);
2548 }
2549
2550 #[test]
2551 fn test_preserves_no_final_newline() {
2552 let input_without_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any";
2554 let control: Control = input_without_newline.parse().unwrap();
2555 let output = control.to_string();
2556 assert_eq!(output, input_without_newline);
2557 }
2558
2559 #[test]
2560 fn test_final_newline_after_modifications() {
2561 let input = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2563 let control: Control = input.parse().unwrap();
2564
2565 let mut source = control.source().unwrap();
2567 source.set_section(Some("utils"));
2568
2569 let output = control.to_string();
2570 let expected = "Source: test-package\nSection: utils\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2571 assert_eq!(output, expected);
2572 }
2573}