1use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph, BINARY_FIELD_ORDER, SOURCE_FIELD_ORDER};
38use rowan::ast::AstNode;
39
40fn format_field(name: &str, value: &str) -> String {
41 match name {
42 "Uploaders" => value
43 .split(',')
44 .map(|s| s.trim().to_string())
45 .collect::<Vec<_>>()
46 .join(",\n"),
47 "Build-Depends"
48 | "Build-Depends-Indep"
49 | "Build-Depends-Arch"
50 | "Build-Conflicts"
51 | "Build-Conflicts-Indep"
52 | "Build-Conflics-Arch"
53 | "Depends"
54 | "Recommends"
55 | "Suggests"
56 | "Enhances"
57 | "Pre-Depends"
58 | "Breaks" => {
59 let relations: Relations = value.parse().unwrap();
60 let relations = relations.wrap_and_sort();
61 relations.to_string()
62 }
63 _ => value.to_string(),
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Control(Deb822);
70
71impl Control {
72 pub fn new() -> Self {
74 Control(Deb822::new())
75 }
76
77 pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
79 &mut self.0
80 }
81
82 pub fn as_deb822(&self) -> &Deb822 {
84 &self.0
85 }
86
87 pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
89 let deb822_parse = Deb822::parse(text);
90 let green = deb822_parse.green().clone();
92 let errors = deb822_parse.errors().to_vec();
93 let positioned_errors = deb822_parse.positioned_errors().to_vec();
94 deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
95 }
96
97 pub fn source(&self) -> Option<Source> {
99 self.0
100 .paragraphs()
101 .find(|p| p.get("Source").is_some())
102 .map(Source)
103 }
104
105 pub fn binaries(&self) -> impl Iterator<Item = Binary> {
107 self.0
108 .paragraphs()
109 .filter(|p| p.get("Package").is_some())
110 .map(Binary)
111 }
112
113 pub fn add_source(&mut self, name: &str) -> Source {
129 let mut p = self.0.add_paragraph();
130 p.set("Source", name);
131 self.source().unwrap()
132 }
133
134 pub fn add_binary(&mut self, name: &str) -> Binary {
150 let mut p = self.0.add_paragraph();
151 p.set("Package", name);
152 Binary(p)
153 }
154
155 pub fn remove_binary(&mut self, name: &str) -> bool {
173 let index = self
174 .0
175 .paragraphs()
176 .position(|p| p.get("Package").as_deref() == Some(name));
177
178 if let Some(index) = index {
179 self.0.remove_paragraph(index);
180 true
181 } else {
182 false
183 }
184 }
185
186 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
188 Ok(Control(Deb822::from_file(path)?))
189 }
190
191 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
193 path: P,
194 ) -> Result<(Self, Vec<String>), std::io::Error> {
195 let (control, errors) = Deb822::from_file_relaxed(path)?;
196 Ok((Control(control), errors))
197 }
198
199 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
201 Ok(Control(Deb822::read(&mut r)?))
202 }
203
204 pub fn read_relaxed<R: std::io::Read>(
206 mut r: R,
207 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
208 let (control, errors) = Deb822::read_relaxed(&mut r)?;
209 Ok((Self(control), errors))
210 }
211
212 pub fn wrap_and_sort(
219 &mut self,
220 indentation: deb822_lossless::Indentation,
221 immediate_empty_line: bool,
222 max_line_length_one_liner: Option<usize>,
223 ) {
224 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
225 let a_is_source = a.get("Source").is_some();
227 let b_is_source = b.get("Source").is_some();
228
229 if a_is_source && !b_is_source {
230 return std::cmp::Ordering::Less;
231 } else if !a_is_source && b_is_source {
232 return std::cmp::Ordering::Greater;
233 } else if a_is_source && b_is_source {
234 return a.get("Source").cmp(&b.get("Source"));
235 }
236
237 a.get("Package").cmp(&b.get("Package"))
238 };
239
240 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
241 p.wrap_and_sort(
244 indentation,
245 immediate_empty_line,
246 max_line_length_one_liner,
247 None,
248 Some(&format_field),
249 )
250 };
251
252 self.0 = self
253 .0
254 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
255 }
256
257 pub fn sort_binaries(&mut self, keep_first: bool) {
288 let mut paragraphs: Vec<_> = self.0.paragraphs().collect();
289
290 if paragraphs.len() <= 1 {
291 return; }
293
294 let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
296 let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
297
298 let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
300 binary_start + 1
301 } else {
302 binary_start
303 };
304
305 if sort_start >= paragraphs.len() {
306 return; }
308
309 paragraphs[sort_start..].sort_by(|a, b| {
311 let a_name = a.get("Package");
312 let b_name = b.get("Package");
313 a_name.cmp(&b_name)
314 });
315
316 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
318 let a_pos = paragraphs.iter().position(|p| p == a);
319 let b_pos = paragraphs.iter().position(|p| p == b);
320 a_pos.cmp(&b_pos)
321 };
322
323 self.0 = self.0.wrap_and_sort(Some(&sort_paragraphs), None);
324 }
325
326 pub fn fields_in_range(
355 &self,
356 range: rowan::TextRange,
357 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
358 self.0
359 .paragraphs()
360 .flat_map(move |p| p.entries().collect::<Vec<_>>())
361 .filter(move |entry| {
362 let entry_range = entry.syntax().text_range();
363 entry_range.start() < range.end() && range.start() < entry_range.end()
365 })
366 }
367}
368
369impl From<Control> for Deb822 {
370 fn from(c: Control) -> Self {
371 c.0
372 }
373}
374
375impl From<Deb822> for Control {
376 fn from(d: Deb822) -> Self {
377 Control(d)
378 }
379}
380
381impl Default for Control {
382 fn default() -> Self {
383 Self::new()
384 }
385}
386
387impl std::str::FromStr for Control {
388 type Err = deb822_lossless::ParseError;
389
390 fn from_str(s: &str) -> Result<Self, Self::Err> {
391 Control::parse(s).to_result()
392 }
393}
394
395#[derive(Debug, Clone, PartialEq, Eq)]
397pub struct Source(Paragraph);
398
399impl From<Source> for Paragraph {
400 fn from(s: Source) -> Self {
401 s.0
402 }
403}
404
405impl From<Paragraph> for Source {
406 fn from(p: Paragraph) -> Self {
407 Source(p)
408 }
409}
410
411impl std::fmt::Display for Source {
412 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
413 self.0.fmt(f)
414 }
415}
416
417impl Source {
418 pub fn name(&self) -> Option<String> {
420 self.0.get("Source")
421 }
422
423 pub fn wrap_and_sort(
425 &mut self,
426 indentation: deb822_lossless::Indentation,
427 immediate_empty_line: bool,
428 max_line_length_one_liner: Option<usize>,
429 ) {
430 self.0 = self.0.wrap_and_sort(
431 indentation,
432 immediate_empty_line,
433 max_line_length_one_liner,
434 None,
435 Some(&format_field),
436 );
437 }
438
439 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
441 &mut self.0
442 }
443
444 pub fn as_deb822(&self) -> &Paragraph {
446 &self.0
447 }
448
449 pub fn set_name(&mut self, name: &str) {
451 self.set("Source", name);
452 }
453
454 pub fn section(&self) -> Option<String> {
456 self.0.get("Section")
457 }
458
459 pub fn set_section(&mut self, section: Option<&str>) {
461 if let Some(section) = section {
462 self.set("Section", section);
463 } else {
464 self.0.remove("Section");
465 }
466 }
467
468 pub fn priority(&self) -> Option<Priority> {
470 self.0.get("Priority").and_then(|v| v.parse().ok())
471 }
472
473 pub fn set_priority(&mut self, priority: Option<Priority>) {
475 if let Some(priority) = priority {
476 self.set("Priority", priority.to_string().as_str());
477 } else {
478 self.0.remove("Priority");
479 }
480 }
481
482 pub fn maintainer(&self) -> Option<String> {
484 self.0.get("Maintainer")
485 }
486
487 pub fn set_maintainer(&mut self, maintainer: &str) {
489 self.set("Maintainer", maintainer);
490 }
491
492 pub fn build_depends(&self) -> Option<Relations> {
494 self.0.get("Build-Depends").map(|s| s.parse().unwrap())
495 }
496
497 pub fn set_build_depends(&mut self, relations: &Relations) {
499 self.set("Build-Depends", relations.to_string().as_str());
500 }
501
502 pub fn build_depends_indep(&self) -> Option<Relations> {
504 self.0
505 .get("Build-Depends-Indep")
506 .map(|s| s.parse().unwrap())
507 }
508
509 pub fn build_depends_arch(&self) -> Option<Relations> {
511 self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
512 }
513
514 pub fn build_conflicts(&self) -> Option<Relations> {
516 self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
517 }
518
519 pub fn build_conflicts_indep(&self) -> Option<Relations> {
521 self.0
522 .get("Build-Conflicts-Indep")
523 .map(|s| s.parse().unwrap())
524 }
525
526 pub fn build_conflicts_arch(&self) -> Option<Relations> {
528 self.0
529 .get("Build-Conflicts-Arch")
530 .map(|s| s.parse().unwrap())
531 }
532
533 pub fn standards_version(&self) -> Option<String> {
535 self.0.get("Standards-Version")
536 }
537
538 pub fn set_standards_version(&mut self, version: &str) {
540 self.set("Standards-Version", version);
541 }
542
543 pub fn homepage(&self) -> Option<url::Url> {
545 self.0.get("Homepage").and_then(|s| s.parse().ok())
546 }
547
548 pub fn set_homepage(&mut self, homepage: &url::Url) {
550 self.set("Homepage", homepage.to_string().as_str());
551 }
552
553 pub fn vcs_git(&self) -> Option<String> {
555 self.0.get("Vcs-Git")
556 }
557
558 pub fn set_vcs_git(&mut self, url: &str) {
560 self.set("Vcs-Git", url);
561 }
562
563 pub fn vcs_svn(&self) -> Option<String> {
565 self.0.get("Vcs-Svn").map(|s| s.to_string())
566 }
567
568 pub fn set_vcs_svn(&mut self, url: &str) {
570 self.set("Vcs-Svn", url);
571 }
572
573 pub fn vcs_bzr(&self) -> Option<String> {
575 self.0.get("Vcs-Bzr").map(|s| s.to_string())
576 }
577
578 pub fn set_vcs_bzr(&mut self, url: &str) {
580 self.set("Vcs-Bzr", url);
581 }
582
583 pub fn vcs_arch(&self) -> Option<String> {
585 self.0.get("Vcs-Arch").map(|s| s.to_string())
586 }
587
588 pub fn set_vcs_arch(&mut self, url: &str) {
590 self.set("Vcs-Arch", url);
591 }
592
593 pub fn vcs_svk(&self) -> Option<String> {
595 self.0.get("Vcs-Svk").map(|s| s.to_string())
596 }
597
598 pub fn set_vcs_svk(&mut self, url: &str) {
600 self.set("Vcs-Svk", url);
601 }
602
603 pub fn vcs_darcs(&self) -> Option<String> {
605 self.0.get("Vcs-Darcs").map(|s| s.to_string())
606 }
607
608 pub fn set_vcs_darcs(&mut self, url: &str) {
610 self.set("Vcs-Darcs", url);
611 }
612
613 pub fn vcs_mtn(&self) -> Option<String> {
615 self.0.get("Vcs-Mtn").map(|s| s.to_string())
616 }
617
618 pub fn set_vcs_mtn(&mut self, url: &str) {
620 self.set("Vcs-Mtn", url);
621 }
622
623 pub fn vcs_cvs(&self) -> Option<String> {
625 self.0.get("Vcs-Cvs").map(|s| s.to_string())
626 }
627
628 pub fn set_vcs_cvs(&mut self, url: &str) {
630 self.set("Vcs-Cvs", url);
631 }
632
633 pub fn vcs_hg(&self) -> Option<String> {
635 self.0.get("Vcs-Hg").map(|s| s.to_string())
636 }
637
638 pub fn set_vcs_hg(&mut self, url: &str) {
640 self.set("Vcs-Hg", url);
641 }
642
643 pub fn set(&mut self, key: &str, value: &str) {
645 self.0.set_with_field_order(key, value, SOURCE_FIELD_ORDER);
646 }
647
648 pub fn vcs_browser(&self) -> Option<String> {
650 self.0.get("Vcs-Browser")
651 }
652
653 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
655 for (name, value) in self.0.items() {
656 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
657 return crate::vcs::Vcs::from_field(&name, &value).ok();
658 }
659 }
660 None
661 }
662
663 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
665 if let Some(url) = url {
666 self.set("Vcs-Browser", url);
667 } else {
668 self.0.remove("Vcs-Browser");
669 }
670 }
671
672 pub fn uploaders(&self) -> Option<Vec<String>> {
674 self.0
675 .get("Uploaders")
676 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
677 }
678
679 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
681 self.set(
682 "Uploaders",
683 uploaders
684 .iter()
685 .map(|s| s.to_string())
686 .collect::<Vec<_>>()
687 .join(", ")
688 .as_str(),
689 );
690 }
691
692 pub fn architecture(&self) -> Option<String> {
694 self.0.get("Architecture")
695 }
696
697 pub fn set_architecture(&mut self, arch: Option<&str>) {
699 if let Some(arch) = arch {
700 self.set("Architecture", arch);
701 } else {
702 self.0.remove("Architecture");
703 }
704 }
705
706 pub fn rules_requires_root(&self) -> Option<bool> {
708 self.0
709 .get("Rules-Requires-Root")
710 .map(|s| match s.to_lowercase().as_str() {
711 "yes" => true,
712 "no" => false,
713 _ => panic!("invalid Rules-Requires-Root value"),
714 })
715 }
716
717 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
719 self.set(
720 "Rules-Requires-Root",
721 if requires_root { "yes" } else { "no" },
722 );
723 }
724
725 pub fn testsuite(&self) -> Option<String> {
727 self.0.get("Testsuite")
728 }
729
730 pub fn set_testsuite(&mut self, testsuite: &str) {
732 self.set("Testsuite", testsuite);
733 }
734
735 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
743 let para_range = self.0.syntax().text_range();
744 para_range.start() < range.end() && range.start() < para_range.end()
745 }
746
747 pub fn fields_in_range(
755 &self,
756 range: rowan::TextRange,
757 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
758 self.0.entries().filter(move |entry| {
759 let entry_range = entry.syntax().text_range();
760 entry_range.start() < range.end() && range.start() < entry_range.end()
761 })
762 }
763}
764
765#[cfg(feature = "python-debian")]
766impl<'py> pyo3::IntoPyObject<'py> for Source {
767 type Target = pyo3::PyAny;
768 type Output = pyo3::Bound<'py, Self::Target>;
769 type Error = pyo3::PyErr;
770
771 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
772 self.0.into_pyobject(py)
773 }
774}
775
776#[cfg(feature = "python-debian")]
777impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
778 type Target = pyo3::PyAny;
779 type Output = pyo3::Bound<'py, Self::Target>;
780 type Error = pyo3::PyErr;
781
782 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
783 (&self.0).into_pyobject(py)
784 }
785}
786
787#[cfg(feature = "python-debian")]
788impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
789 type Error = pyo3::PyErr;
790
791 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
792 Ok(Source(ob.extract()?))
793 }
794}
795
796impl std::fmt::Display for Control {
797 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
798 self.0.fmt(f)
799 }
800}
801
802impl AstNode for Control {
803 type Language = deb822_lossless::Lang;
804
805 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
806 Deb822::can_cast(kind)
807 }
808
809 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
810 Deb822::cast(syntax).map(Control)
811 }
812
813 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
814 self.0.syntax()
815 }
816}
817
818#[derive(Debug, Clone, PartialEq, Eq)]
820pub struct Binary(Paragraph);
821
822impl From<Binary> for Paragraph {
823 fn from(b: Binary) -> Self {
824 b.0
825 }
826}
827
828impl From<Paragraph> for Binary {
829 fn from(p: Paragraph) -> Self {
830 Binary(p)
831 }
832}
833
834#[cfg(feature = "python-debian")]
835impl<'py> pyo3::IntoPyObject<'py> for Binary {
836 type Target = pyo3::PyAny;
837 type Output = pyo3::Bound<'py, Self::Target>;
838 type Error = pyo3::PyErr;
839
840 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
841 self.0.into_pyobject(py)
842 }
843}
844
845#[cfg(feature = "python-debian")]
846impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
847 type Target = pyo3::PyAny;
848 type Output = pyo3::Bound<'py, Self::Target>;
849 type Error = pyo3::PyErr;
850
851 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
852 (&self.0).into_pyobject(py)
853 }
854}
855
856#[cfg(feature = "python-debian")]
857impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
858 type Error = pyo3::PyErr;
859
860 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
861 Ok(Binary(ob.extract()?))
862 }
863}
864
865impl Default for Binary {
866 fn default() -> Self {
867 Self::new()
868 }
869}
870
871impl std::fmt::Display for Binary {
872 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
873 self.0.fmt(f)
874 }
875}
876
877impl Binary {
878 pub fn new() -> Self {
880 Binary(Paragraph::new())
881 }
882
883 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
885 &mut self.0
886 }
887
888 pub fn as_deb822(&self) -> &Paragraph {
890 &self.0
891 }
892
893 pub fn wrap_and_sort(
895 &mut self,
896 indentation: deb822_lossless::Indentation,
897 immediate_empty_line: bool,
898 max_line_length_one_liner: Option<usize>,
899 ) {
900 self.0 = self.0.wrap_and_sort(
901 indentation,
902 immediate_empty_line,
903 max_line_length_one_liner,
904 None,
905 Some(&format_field),
906 );
907 }
908
909 pub fn name(&self) -> Option<String> {
911 self.0.get("Package")
912 }
913
914 pub fn set_name(&mut self, name: &str) {
916 self.set("Package", name);
917 }
918
919 pub fn section(&self) -> Option<String> {
921 self.0.get("Section")
922 }
923
924 pub fn set_section(&mut self, section: Option<&str>) {
926 if let Some(section) = section {
927 self.set("Section", section);
928 } else {
929 self.0.remove("Section");
930 }
931 }
932
933 pub fn priority(&self) -> Option<Priority> {
935 self.0.get("Priority").and_then(|v| v.parse().ok())
936 }
937
938 pub fn set_priority(&mut self, priority: Option<Priority>) {
940 if let Some(priority) = priority {
941 self.set("Priority", priority.to_string().as_str());
942 } else {
943 self.0.remove("Priority");
944 }
945 }
946
947 pub fn architecture(&self) -> Option<String> {
949 self.0.get("Architecture")
950 }
951
952 pub fn set_architecture(&mut self, arch: Option<&str>) {
954 if let Some(arch) = arch {
955 self.set("Architecture", arch);
956 } else {
957 self.0.remove("Architecture");
958 }
959 }
960
961 pub fn depends(&self) -> Option<Relations> {
963 self.0.get("Depends").map(|s| s.parse().unwrap())
964 }
965
966 pub fn set_depends(&mut self, depends: Option<&Relations>) {
968 if let Some(depends) = depends {
969 self.set("Depends", depends.to_string().as_str());
970 } else {
971 self.0.remove("Depends");
972 }
973 }
974
975 pub fn recommends(&self) -> Option<Relations> {
977 self.0.get("Recommends").map(|s| s.parse().unwrap())
978 }
979
980 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
982 if let Some(recommends) = recommends {
983 self.set("Recommends", recommends.to_string().as_str());
984 } else {
985 self.0.remove("Recommends");
986 }
987 }
988
989 pub fn suggests(&self) -> Option<Relations> {
991 self.0.get("Suggests").map(|s| s.parse().unwrap())
992 }
993
994 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
996 if let Some(suggests) = suggests {
997 self.set("Suggests", suggests.to_string().as_str());
998 } else {
999 self.0.remove("Suggests");
1000 }
1001 }
1002
1003 pub fn enhances(&self) -> Option<Relations> {
1005 self.0.get("Enhances").map(|s| s.parse().unwrap())
1006 }
1007
1008 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1010 if let Some(enhances) = enhances {
1011 self.set("Enhances", enhances.to_string().as_str());
1012 } else {
1013 self.0.remove("Enhances");
1014 }
1015 }
1016
1017 pub fn pre_depends(&self) -> Option<Relations> {
1019 self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
1020 }
1021
1022 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1024 if let Some(pre_depends) = pre_depends {
1025 self.set("Pre-Depends", pre_depends.to_string().as_str());
1026 } else {
1027 self.0.remove("Pre-Depends");
1028 }
1029 }
1030
1031 pub fn breaks(&self) -> Option<Relations> {
1033 self.0.get("Breaks").map(|s| s.parse().unwrap())
1034 }
1035
1036 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1038 if let Some(breaks) = breaks {
1039 self.set("Breaks", breaks.to_string().as_str());
1040 } else {
1041 self.0.remove("Breaks");
1042 }
1043 }
1044
1045 pub fn conflicts(&self) -> Option<Relations> {
1047 self.0.get("Conflicts").map(|s| s.parse().unwrap())
1048 }
1049
1050 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1052 if let Some(conflicts) = conflicts {
1053 self.set("Conflicts", conflicts.to_string().as_str());
1054 } else {
1055 self.0.remove("Conflicts");
1056 }
1057 }
1058
1059 pub fn replaces(&self) -> Option<Relations> {
1061 self.0.get("Replaces").map(|s| s.parse().unwrap())
1062 }
1063
1064 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1066 if let Some(replaces) = replaces {
1067 self.set("Replaces", replaces.to_string().as_str());
1068 } else {
1069 self.0.remove("Replaces");
1070 }
1071 }
1072
1073 pub fn provides(&self) -> Option<Relations> {
1075 self.0.get("Provides").map(|s| s.parse().unwrap())
1076 }
1077
1078 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1080 if let Some(provides) = provides {
1081 self.set("Provides", provides.to_string().as_str());
1082 } else {
1083 self.0.remove("Provides");
1084 }
1085 }
1086
1087 pub fn built_using(&self) -> Option<Relations> {
1089 self.0.get("Built-Using").map(|s| s.parse().unwrap())
1090 }
1091
1092 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1094 if let Some(built_using) = built_using {
1095 self.set("Built-Using", built_using.to_string().as_str());
1096 } else {
1097 self.0.remove("Built-Using");
1098 }
1099 }
1100
1101 pub fn multi_arch(&self) -> Option<MultiArch> {
1103 self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
1104 }
1105
1106 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1108 if let Some(multi_arch) = multi_arch {
1109 self.set("Multi-Arch", multi_arch.to_string().as_str());
1110 } else {
1111 self.0.remove("Multi-Arch");
1112 }
1113 }
1114
1115 pub fn essential(&self) -> bool {
1117 self.0.get("Essential").map(|s| s == "yes").unwrap_or(false)
1118 }
1119
1120 pub fn set_essential(&mut self, essential: bool) {
1122 if essential {
1123 self.set("Essential", "yes");
1124 } else {
1125 self.0.remove("Essential");
1126 }
1127 }
1128
1129 pub fn description(&self) -> Option<String> {
1131 self.0.get("Description")
1132 }
1133
1134 pub fn set_description(&mut self, description: Option<&str>) {
1136 if let Some(description) = description {
1137 self.0.set_with_indent_pattern(
1138 "Description",
1139 description,
1140 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1141 Some(BINARY_FIELD_ORDER),
1142 );
1143 } else {
1144 self.0.remove("Description");
1145 }
1146 }
1147
1148 pub fn homepage(&self) -> Option<url::Url> {
1150 self.0.get("Homepage").and_then(|s| s.parse().ok())
1151 }
1152
1153 pub fn set_homepage(&mut self, url: &url::Url) {
1155 self.set("Homepage", url.as_str());
1156 }
1157
1158 pub fn set(&mut self, key: &str, value: &str) {
1160 self.0.set_with_field_order(key, value, BINARY_FIELD_ORDER);
1161 }
1162
1163 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1171 let para_range = self.0.syntax().text_range();
1172 para_range.start() < range.end() && range.start() < para_range.end()
1173 }
1174
1175 pub fn fields_in_range(
1183 &self,
1184 range: rowan::TextRange,
1185 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1186 self.0.entries().filter(move |entry| {
1187 let entry_range = entry.syntax().text_range();
1188 entry_range.start() < range.end() && range.start() < entry_range.end()
1189 })
1190 }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196 use crate::relations::VersionConstraint;
1197
1198 #[test]
1199 fn test_source_set_field_ordering() {
1200 let mut control = Control::new();
1201 let mut source = control.add_source("mypackage");
1202
1203 source.set("Homepage", "https://example.com");
1205 source.set("Build-Depends", "debhelper");
1206 source.set("Standards-Version", "4.5.0");
1207 source.set("Maintainer", "Test <test@example.com>");
1208
1209 let output = source.to_string();
1211 let lines: Vec<&str> = output.lines().collect();
1212
1213 assert!(lines[0].starts_with("Source:"));
1215
1216 let maintainer_pos = lines
1218 .iter()
1219 .position(|l| l.starts_with("Maintainer:"))
1220 .unwrap();
1221 let build_depends_pos = lines
1222 .iter()
1223 .position(|l| l.starts_with("Build-Depends:"))
1224 .unwrap();
1225 let standards_pos = lines
1226 .iter()
1227 .position(|l| l.starts_with("Standards-Version:"))
1228 .unwrap();
1229 let homepage_pos = lines
1230 .iter()
1231 .position(|l| l.starts_with("Homepage:"))
1232 .unwrap();
1233
1234 assert!(maintainer_pos < build_depends_pos);
1236 assert!(build_depends_pos < standards_pos);
1237 assert!(standards_pos < homepage_pos);
1238 }
1239
1240 #[test]
1241 fn test_binary_set_field_ordering() {
1242 let mut control = Control::new();
1243 let mut binary = control.add_binary("mypackage");
1244
1245 binary.set("Description", "A test package");
1247 binary.set("Architecture", "amd64");
1248 binary.set("Depends", "libc6");
1249 binary.set("Section", "utils");
1250
1251 let output = binary.to_string();
1253 let lines: Vec<&str> = output.lines().collect();
1254
1255 assert!(lines[0].starts_with("Package:"));
1257
1258 let arch_pos = lines
1260 .iter()
1261 .position(|l| l.starts_with("Architecture:"))
1262 .unwrap();
1263 let section_pos = lines
1264 .iter()
1265 .position(|l| l.starts_with("Section:"))
1266 .unwrap();
1267 let depends_pos = lines
1268 .iter()
1269 .position(|l| l.starts_with("Depends:"))
1270 .unwrap();
1271 let desc_pos = lines
1272 .iter()
1273 .position(|l| l.starts_with("Description:"))
1274 .unwrap();
1275
1276 assert!(arch_pos < section_pos);
1278 assert!(section_pos < depends_pos);
1279 assert!(depends_pos < desc_pos);
1280 }
1281
1282 #[test]
1283 fn test_source_specific_set_methods_use_field_ordering() {
1284 let mut control = Control::new();
1285 let mut source = control.add_source("mypackage");
1286
1287 source.set_homepage(&"https://example.com".parse().unwrap());
1289 source.set_maintainer("Test <test@example.com>");
1290 source.set_standards_version("4.5.0");
1291 source.set_vcs_git("https://github.com/example/repo");
1292
1293 let output = source.to_string();
1295 let lines: Vec<&str> = output.lines().collect();
1296
1297 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1299 let maintainer_pos = lines
1300 .iter()
1301 .position(|l| l.starts_with("Maintainer:"))
1302 .unwrap();
1303 let standards_pos = lines
1304 .iter()
1305 .position(|l| l.starts_with("Standards-Version:"))
1306 .unwrap();
1307 let vcs_git_pos = lines
1308 .iter()
1309 .position(|l| l.starts_with("Vcs-Git:"))
1310 .unwrap();
1311 let homepage_pos = lines
1312 .iter()
1313 .position(|l| l.starts_with("Homepage:"))
1314 .unwrap();
1315
1316 assert!(source_pos < maintainer_pos);
1318 assert!(maintainer_pos < standards_pos);
1319 assert!(standards_pos < vcs_git_pos);
1320 assert!(vcs_git_pos < homepage_pos);
1321 }
1322
1323 #[test]
1324 fn test_binary_specific_set_methods_use_field_ordering() {
1325 let mut control = Control::new();
1326 let mut binary = control.add_binary("mypackage");
1327
1328 binary.set_description(Some("A test package"));
1330 binary.set_architecture(Some("amd64"));
1331 let depends = "libc6".parse().unwrap();
1332 binary.set_depends(Some(&depends));
1333 binary.set_section(Some("utils"));
1334 binary.set_priority(Some(Priority::Optional));
1335
1336 let output = binary.to_string();
1338 let lines: Vec<&str> = output.lines().collect();
1339
1340 let package_pos = lines
1342 .iter()
1343 .position(|l| l.starts_with("Package:"))
1344 .unwrap();
1345 let arch_pos = lines
1346 .iter()
1347 .position(|l| l.starts_with("Architecture:"))
1348 .unwrap();
1349 let section_pos = lines
1350 .iter()
1351 .position(|l| l.starts_with("Section:"))
1352 .unwrap();
1353 let priority_pos = lines
1354 .iter()
1355 .position(|l| l.starts_with("Priority:"))
1356 .unwrap();
1357 let depends_pos = lines
1358 .iter()
1359 .position(|l| l.starts_with("Depends:"))
1360 .unwrap();
1361 let desc_pos = lines
1362 .iter()
1363 .position(|l| l.starts_with("Description:"))
1364 .unwrap();
1365
1366 assert!(package_pos < arch_pos);
1368 assert!(arch_pos < section_pos);
1369 assert!(section_pos < priority_pos);
1370 assert!(priority_pos < depends_pos);
1371 assert!(depends_pos < desc_pos);
1372 }
1373
1374 #[test]
1375 fn test_parse() {
1376 let control: Control = r#"Source: foo
1377Section: libs
1378Priority: optional
1379Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1380Homepage: https://example.com
1381
1382"#
1383 .parse()
1384 .unwrap();
1385 let source = control.source().unwrap();
1386
1387 assert_eq!(source.name(), Some("foo".to_owned()));
1388 assert_eq!(source.section(), Some("libs".to_owned()));
1389 assert_eq!(source.priority(), Some(super::Priority::Optional));
1390 assert_eq!(
1391 source.homepage(),
1392 Some("https://example.com".parse().unwrap())
1393 );
1394 let bd = source.build_depends().unwrap();
1395 let entries = bd.entries().collect::<Vec<_>>();
1396 assert_eq!(entries.len(), 2);
1397 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1398 assert_eq!(rel.name(), "bar");
1399 assert_eq!(
1400 rel.version(),
1401 Some((
1402 VersionConstraint::GreaterThanEqual,
1403 "1.0.0".parse().unwrap()
1404 ))
1405 );
1406 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1407 assert_eq!(rel.name(), "baz");
1408 assert_eq!(
1409 rel.version(),
1410 Some((
1411 VersionConstraint::GreaterThanEqual,
1412 "1.0.0".parse().unwrap()
1413 ))
1414 );
1415 }
1416
1417 #[test]
1418 fn test_description() {
1419 let control: Control = r#"Source: foo
1420
1421Package: foo
1422Description: this is the short description
1423 And the longer one
1424 .
1425 is on the next lines
1426"#
1427 .parse()
1428 .unwrap();
1429 let binary = control.binaries().next().unwrap();
1430 assert_eq!(
1431 binary.description(),
1432 Some(
1433 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1434 .to_owned()
1435 )
1436 );
1437 }
1438
1439 #[test]
1440 fn test_set_description_on_package_without_description() {
1441 let control: Control = r#"Source: foo
1442
1443Package: foo
1444Architecture: amd64
1445"#
1446 .parse()
1447 .unwrap();
1448 let mut binary = control.binaries().next().unwrap();
1449
1450 binary.set_description(Some("Short description\nLonger description\n.\nAnother line"));
1452
1453 let output = binary.to_string();
1454
1455 assert_eq!(
1457 binary.description(),
1458 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1459 );
1460
1461 assert_eq!(
1463 output,
1464 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1465 );
1466 }
1467
1468 #[test]
1469 fn test_as_mut_deb822() {
1470 let mut control = Control::new();
1471 let deb822 = control.as_mut_deb822();
1472 let mut p = deb822.add_paragraph();
1473 p.set("Source", "foo");
1474 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1475 }
1476
1477 #[test]
1478 fn test_as_deb822() {
1479 let control = Control::new();
1480 let _deb822: &Deb822 = control.as_deb822();
1481 }
1482
1483 #[test]
1484 fn test_set_depends() {
1485 let mut control = Control::new();
1486 let mut binary = control.add_binary("foo");
1487 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1488 binary.set_depends(Some(&relations));
1489 }
1490
1491 #[test]
1492 fn test_wrap_and_sort() {
1493 let mut control: Control = r#"Package: blah
1494Section: libs
1495
1496
1497
1498Package: foo
1499Description: this is a
1500 bar
1501 blah
1502"#
1503 .parse()
1504 .unwrap();
1505 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1506 let expected = r#"Package: blah
1507Section: libs
1508
1509Package: foo
1510Description: this is a
1511 bar
1512 blah
1513"#
1514 .to_owned();
1515 assert_eq!(control.to_string(), expected);
1516 }
1517
1518 #[test]
1519 fn test_wrap_and_sort_source() {
1520 let mut control: Control = r#"Source: blah
1521Depends: foo, bar (<= 1.0.0)
1522
1523"#
1524 .parse()
1525 .unwrap();
1526 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1527 let expected = r#"Source: blah
1528Depends: bar (<= 1.0.0), foo
1529"#
1530 .to_owned();
1531 assert_eq!(control.to_string(), expected);
1532 }
1533
1534 #[test]
1535 fn test_source_wrap_and_sort() {
1536 let control: Control = r#"Source: blah
1537Build-Depends: foo, bar (>= 1.0.0)
1538
1539"#
1540 .parse()
1541 .unwrap();
1542 let mut source = control.source().unwrap();
1543 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1544 assert!(source.build_depends().is_some());
1548 }
1549
1550 #[test]
1551 fn test_binary_set_breaks() {
1552 let mut control = Control::new();
1553 let mut binary = control.add_binary("foo");
1554 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1555 binary.set_breaks(Some(&relations));
1556 assert!(binary.breaks().is_some());
1557 }
1558
1559 #[test]
1560 fn test_binary_set_pre_depends() {
1561 let mut control = Control::new();
1562 let mut binary = control.add_binary("foo");
1563 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1564 binary.set_pre_depends(Some(&relations));
1565 assert!(binary.pre_depends().is_some());
1566 }
1567
1568 #[test]
1569 fn test_binary_set_provides() {
1570 let mut control = Control::new();
1571 let mut binary = control.add_binary("foo");
1572 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1573 binary.set_provides(Some(&relations));
1574 assert!(binary.provides().is_some());
1575 }
1576
1577 #[test]
1578 fn test_source_build_conflicts() {
1579 let control: Control = r#"Source: blah
1580Build-Conflicts: foo, bar (>= 1.0.0)
1581
1582"#
1583 .parse()
1584 .unwrap();
1585 let source = control.source().unwrap();
1586 let conflicts = source.build_conflicts();
1587 assert!(conflicts.is_some());
1588 }
1589
1590 #[test]
1591 fn test_source_vcs_svn() {
1592 let control: Control = r#"Source: blah
1593Vcs-Svn: https://example.com/svn/repo
1594
1595"#
1596 .parse()
1597 .unwrap();
1598 let source = control.source().unwrap();
1599 assert_eq!(
1600 source.vcs_svn(),
1601 Some("https://example.com/svn/repo".to_string())
1602 );
1603 }
1604
1605 #[test]
1606 fn test_control_from_conversion() {
1607 let deb822_data = r#"Source: test
1608Section: libs
1609
1610"#;
1611 let deb822: Deb822 = deb822_data.parse().unwrap();
1612 let control = Control::from(deb822);
1613 assert!(control.source().is_some());
1614 }
1615
1616 #[test]
1617 fn test_fields_in_range() {
1618 let control_text = r#"Source: test-package
1619Maintainer: Test User <test@example.com>
1620Build-Depends: debhelper (>= 12)
1621
1622Package: test-binary
1623Architecture: any
1624Depends: ${shlibs:Depends}
1625Description: Test package
1626 This is a test package
1627"#;
1628 let control: Control = control_text.parse().unwrap();
1629
1630 let source_start = 0;
1632 let source_end = "Source: test-package".len();
1633 let source_range =
1634 rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1635
1636 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1637 assert_eq!(fields.len(), 1);
1638 assert_eq!(fields[0].key(), Some("Source".to_string()));
1639
1640 let maintainer_start = control_text.find("Maintainer:").unwrap();
1642 let build_depends_end = control_text
1643 .find("Build-Depends: debhelper (>= 12)")
1644 .unwrap()
1645 + "Build-Depends: debhelper (>= 12)".len();
1646 let multi_range = rowan::TextRange::new(
1647 (maintainer_start as u32).into(),
1648 (build_depends_end as u32).into(),
1649 );
1650
1651 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1652 assert_eq!(fields.len(), 2);
1653 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1654 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1655
1656 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1658 let cross_para_end =
1659 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1660 let cross_range = rowan::TextRange::new(
1661 (cross_para_start as u32).into(),
1662 (cross_para_end as u32).into(),
1663 );
1664
1665 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1666 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1668 assert_eq!(fields[1].key(), Some("Package".to_string()));
1669 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1670
1671 let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1673 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1674 assert_eq!(fields.len(), 0);
1675 }
1676
1677 #[test]
1678 fn test_source_overlaps_range() {
1679 let control_text = r#"Source: test-package
1680Maintainer: Test User <test@example.com>
1681
1682Package: test-binary
1683Architecture: any
1684"#;
1685 let control: Control = control_text.parse().unwrap();
1686 let source = control.source().unwrap();
1687
1688 let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1690 assert!(source.overlaps_range(overlap_range));
1691
1692 let binary_start = control_text.find("Package:").unwrap();
1694 let no_overlap_range = rowan::TextRange::new(
1695 (binary_start as u32).into(),
1696 ((binary_start + 20) as u32).into(),
1697 );
1698 assert!(!source.overlaps_range(no_overlap_range));
1699
1700 let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1702 assert!(source.overlaps_range(partial_overlap));
1703 }
1704
1705 #[test]
1706 fn test_source_fields_in_range() {
1707 let control_text = r#"Source: test-package
1708Maintainer: Test User <test@example.com>
1709Build-Depends: debhelper (>= 12)
1710
1711Package: test-binary
1712"#;
1713 let control: Control = control_text.parse().unwrap();
1714 let source = control.source().unwrap();
1715
1716 let maintainer_start = control_text.find("Maintainer:").unwrap();
1718 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1719 let maintainer_range = rowan::TextRange::new(
1720 (maintainer_start as u32).into(),
1721 (maintainer_end as u32).into(),
1722 );
1723
1724 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1725 assert_eq!(fields.len(), 1);
1726 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1727
1728 let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1730 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1731 assert_eq!(fields.len(), 3); }
1733
1734 #[test]
1735 fn test_binary_overlaps_range() {
1736 let control_text = r#"Source: test-package
1737
1738Package: test-binary
1739Architecture: any
1740Depends: ${shlibs:Depends}
1741"#;
1742 let control: Control = control_text.parse().unwrap();
1743 let binary = control.binaries().next().unwrap();
1744
1745 let package_start = control_text.find("Package:").unwrap();
1747 let overlap_range = rowan::TextRange::new(
1748 (package_start as u32).into(),
1749 ((package_start + 30) as u32).into(),
1750 );
1751 assert!(binary.overlaps_range(overlap_range));
1752
1753 let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1755 assert!(!binary.overlaps_range(no_overlap_range));
1756 }
1757
1758 #[test]
1759 fn test_binary_fields_in_range() {
1760 let control_text = r#"Source: test-package
1761
1762Package: test-binary
1763Architecture: any
1764Depends: ${shlibs:Depends}
1765Description: Test binary
1766 This is a test binary package
1767"#;
1768 let control: Control = control_text.parse().unwrap();
1769 let binary = control.binaries().next().unwrap();
1770
1771 let arch_start = control_text.find("Architecture:").unwrap();
1773 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1774 + "Depends: ${shlibs:Depends}".len();
1775 let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1776
1777 let fields: Vec<_> = binary.fields_in_range(range).collect();
1778 assert_eq!(fields.len(), 2);
1779 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1780 assert_eq!(fields[1].key(), Some("Depends".to_string()));
1781
1782 let desc_start = control_text.find("Description:").unwrap();
1784 let partial_range = rowan::TextRange::new(
1785 ((desc_start + 5) as u32).into(),
1786 ((desc_start + 15) as u32).into(),
1787 );
1788 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1789 assert_eq!(fields.len(), 1);
1790 assert_eq!(fields[0].key(), Some("Description".to_string()));
1791 }
1792
1793 #[test]
1794 fn test_incremental_parsing_use_case() {
1795 let control_text = r#"Source: example
1797Maintainer: John Doe <john@example.com>
1798Standards-Version: 4.6.0
1799Build-Depends: debhelper-compat (= 13)
1800
1801Package: example-bin
1802Architecture: all
1803Depends: ${misc:Depends}
1804Description: Example package
1805 This is an example.
1806"#;
1807 let control: Control = control_text.parse().unwrap();
1808
1809 let change_start = control_text.find("Standards-Version:").unwrap();
1811 let change_end = change_start + "Standards-Version: 4.6.0".len();
1812 let change_range =
1813 rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
1814
1815 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
1817 assert_eq!(affected_fields.len(), 1);
1818 assert_eq!(
1819 affected_fields[0].key(),
1820 Some("Standards-Version".to_string())
1821 );
1822
1823 for entry in &affected_fields {
1825 let key = entry.key().unwrap();
1826 assert_ne!(key, "Maintainer");
1827 assert_ne!(key, "Build-Depends");
1828 assert_ne!(key, "Architecture");
1829 }
1830 }
1831
1832 #[test]
1833 fn test_positioned_parse_errors() {
1834 let input = "Invalid: field\nBroken field without colon";
1836 let parsed = Control::parse(input);
1837
1838 let positioned_errors = parsed.positioned_errors();
1840 assert!(
1841 !positioned_errors.is_empty(),
1842 "Should have positioned errors"
1843 );
1844
1845 for error in positioned_errors {
1847 let start_offset: u32 = error.range.start().into();
1848 let end_offset: u32 = error.range.end().into();
1849
1850 assert!(!error.message.is_empty());
1852
1853 assert!(start_offset <= end_offset);
1855 assert!(end_offset <= input.len() as u32);
1856
1857 assert!(error.code.is_some());
1859
1860 println!(
1861 "Error at {:?}: {} (code: {:?})",
1862 error.range, error.message, error.code
1863 );
1864 }
1865
1866 let string_errors = parsed.errors();
1868 assert!(!string_errors.is_empty());
1869 assert_eq!(string_errors.len(), positioned_errors.len());
1870 }
1871
1872 #[test]
1873 fn test_sort_binaries_basic() {
1874 let input = r#"Source: foo
1875
1876Package: libfoo
1877Architecture: all
1878
1879Package: libbar
1880Architecture: all
1881"#;
1882
1883 let mut control: Control = input.parse().unwrap();
1884 control.sort_binaries(false);
1885
1886 let binaries: Vec<_> = control.binaries().collect();
1887 assert_eq!(binaries.len(), 2);
1888 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
1889 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
1890 }
1891
1892 #[test]
1893 fn test_sort_binaries_keep_first() {
1894 let input = r#"Source: foo
1895
1896Package: zzz-first
1897Architecture: all
1898
1899Package: libbar
1900Architecture: all
1901
1902Package: libaaa
1903Architecture: all
1904"#;
1905
1906 let mut control: Control = input.parse().unwrap();
1907 control.sort_binaries(true);
1908
1909 let binaries: Vec<_> = control.binaries().collect();
1910 assert_eq!(binaries.len(), 3);
1911 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
1913 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
1915 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
1916 }
1917
1918 #[test]
1919 fn test_sort_binaries_already_sorted() {
1920 let input = r#"Source: foo
1921
1922Package: aaa
1923Architecture: all
1924
1925Package: bbb
1926Architecture: all
1927
1928Package: ccc
1929Architecture: all
1930"#;
1931
1932 let mut control: Control = input.parse().unwrap();
1933 control.sort_binaries(false);
1934
1935 let binaries: Vec<_> = control.binaries().collect();
1936 assert_eq!(binaries.len(), 3);
1937 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1938 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
1939 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
1940 }
1941
1942 #[test]
1943 fn test_sort_binaries_no_binaries() {
1944 let input = r#"Source: foo
1945Maintainer: test@example.com
1946"#;
1947
1948 let mut control: Control = input.parse().unwrap();
1949 control.sort_binaries(false);
1950
1951 assert_eq!(control.binaries().count(), 0);
1953 }
1954
1955 #[test]
1956 fn test_sort_binaries_one_binary() {
1957 let input = r#"Source: foo
1958
1959Package: bar
1960Architecture: all
1961"#;
1962
1963 let mut control: Control = input.parse().unwrap();
1964 control.sort_binaries(false);
1965
1966 let binaries: Vec<_> = control.binaries().collect();
1967 assert_eq!(binaries.len(), 1);
1968 assert_eq!(binaries[0].name(), Some("bar".to_string()));
1969 }
1970
1971 #[test]
1972 fn test_sort_binaries_preserves_fields() {
1973 let input = r#"Source: foo
1974
1975Package: zzz
1976Architecture: any
1977Depends: libc6
1978Description: ZZZ package
1979
1980Package: aaa
1981Architecture: all
1982Depends: ${misc:Depends}
1983Description: AAA package
1984"#;
1985
1986 let mut control: Control = input.parse().unwrap();
1987 control.sort_binaries(false);
1988
1989 let binaries: Vec<_> = control.binaries().collect();
1990 assert_eq!(binaries.len(), 2);
1991
1992 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1994 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
1995 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
1996
1997 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
1999 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2000 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2001 }
2002
2003 #[test]
2004 fn test_remove_binary_basic() {
2005 let mut control = Control::new();
2006 control.add_binary("foo");
2007 assert_eq!(control.binaries().count(), 1);
2008 assert!(control.remove_binary("foo"));
2009 assert_eq!(control.binaries().count(), 0);
2010 }
2011
2012 #[test]
2013 fn test_remove_binary_nonexistent() {
2014 let mut control = Control::new();
2015 control.add_binary("foo");
2016 assert!(!control.remove_binary("bar"));
2017 assert_eq!(control.binaries().count(), 1);
2018 }
2019
2020 #[test]
2021 fn test_remove_binary_multiple() {
2022 let mut control = Control::new();
2023 control.add_binary("foo");
2024 control.add_binary("bar");
2025 control.add_binary("baz");
2026 assert_eq!(control.binaries().count(), 3);
2027
2028 assert!(control.remove_binary("bar"));
2029 assert_eq!(control.binaries().count(), 2);
2030
2031 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2032 assert_eq!(names, vec!["foo", "baz"]);
2033 }
2034
2035 #[test]
2036 fn test_remove_binary_preserves_source() {
2037 let input = r#"Source: mypackage
2038
2039Package: foo
2040Architecture: all
2041
2042Package: bar
2043Architecture: all
2044"#;
2045 let mut control: Control = input.parse().unwrap();
2046 assert!(control.source().is_some());
2047 assert_eq!(control.binaries().count(), 2);
2048
2049 assert!(control.remove_binary("foo"));
2050
2051 assert!(control.source().is_some());
2053 assert_eq!(
2054 control.source().unwrap().name(),
2055 Some("mypackage".to_string())
2056 );
2057
2058 assert_eq!(control.binaries().count(), 1);
2060 assert_eq!(
2061 control.binaries().next().unwrap().name(),
2062 Some("bar".to_string())
2063 );
2064 }
2065
2066 #[test]
2067 fn test_remove_binary_from_parsed() {
2068 let input = r#"Source: test
2069
2070Package: test-bin
2071Architecture: any
2072Depends: libc6
2073Description: Test binary
2074
2075Package: test-lib
2076Architecture: all
2077Description: Test library
2078"#;
2079 let mut control: Control = input.parse().unwrap();
2080 assert_eq!(control.binaries().count(), 2);
2081
2082 assert!(control.remove_binary("test-bin"));
2083
2084 let output = control.to_string();
2085 assert!(!output.contains("test-bin"));
2086 assert!(output.contains("test-lib"));
2087 assert!(output.contains("Source: test"));
2088 }
2089
2090 #[test]
2091 fn test_build_depends_preserves_indentation_after_removal() {
2092 let input = r#"Source: acpi-support
2093Section: admin
2094Priority: optional
2095Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2096Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2097 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2098"#;
2099 let control: Control = input.parse().unwrap();
2100 let mut source = control.source().unwrap();
2101
2102 let mut build_depends = source.build_depends().unwrap();
2104
2105 let mut to_remove = Vec::new();
2107 for (idx, entry) in build_depends.entries().enumerate() {
2108 for relation in entry.relations() {
2109 if relation.name() == "dh-systemd" {
2110 to_remove.push(idx);
2111 break;
2112 }
2113 }
2114 }
2115
2116 for idx in to_remove.into_iter().rev() {
2117 build_depends.remove_entry(idx);
2118 }
2119
2120 source.set_build_depends(&build_depends);
2122
2123 let output = source.to_string();
2124
2125 assert!(
2127 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2128 "Expected 4-space indentation to be preserved, but got:\n{}",
2129 output
2130 );
2131 }
2132
2133 #[test]
2134 fn test_build_depends_direct_string_set_loses_indentation() {
2135 let input = r#"Source: acpi-support
2136Section: admin
2137Priority: optional
2138Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2139Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2140 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2141"#;
2142 let control: Control = input.parse().unwrap();
2143 let mut source = control.source().unwrap();
2144
2145 let mut build_depends = source.build_depends().unwrap();
2147
2148 let mut to_remove = Vec::new();
2150 for (idx, entry) in build_depends.entries().enumerate() {
2151 for relation in entry.relations() {
2152 if relation.name() == "dh-systemd" {
2153 to_remove.push(idx);
2154 break;
2155 }
2156 }
2157 }
2158
2159 for idx in to_remove.into_iter().rev() {
2160 build_depends.remove_entry(idx);
2161 }
2162
2163 source.set("Build-Depends", &build_depends.to_string());
2165
2166 let output = source.to_string();
2167 println!("Output with string set:");
2168 println!("{}", output);
2169
2170 assert!(
2173 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2174 "Expected 4-space indentation to be preserved, but got:\n{}",
2175 output
2176 );
2177 }
2178}