1use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph, TextRange};
38use rowan::ast::AstNode;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Identity {
44 pub name: String,
46 pub email: String,
48 pub email_range: TextRange,
50}
51
52fn identities_in_field(paragraph: &Paragraph, field: &str) -> Vec<Identity> {
53 let Some(entry) = paragraph.get_entry(field) else {
54 return Vec::new();
55 };
56 use deb822_lossless::SyntaxKind;
60 use rowan::ast::AstNode;
61 let mut joined = String::new();
62 let mut segments: Vec<(usize, u32)> = Vec::new(); for tok in entry
64 .syntax()
65 .children_with_tokens()
66 .filter_map(|it| it.into_token())
67 .filter(|t| t.kind() == SyntaxKind::VALUE)
68 {
69 let absolute: u32 = tok.text_range().start().into();
70 if !joined.is_empty() {
71 joined.push('\n');
72 }
73 segments.push((joined.len(), absolute));
74 joined.push_str(tok.text());
75 }
76 if joined.is_empty() {
77 return Vec::new();
78 }
79 let mut out = Vec::new();
80 let mut cursor: usize = 0;
81 for piece in joined.split(',') {
82 let piece_offset = cursor;
83 cursor += piece.len() + 1;
84 let Ok((name, email)) = crate::parse_identity(piece.trim()) else {
85 continue;
86 };
87 if email.is_empty() {
88 continue;
89 }
90 let Some(rel) = piece.find(email) else {
91 continue;
92 };
93 let email_joined_start = piece_offset + rel;
94 let Some(absolute_start) = joined_offset_to_absolute(&segments, email_joined_start) else {
95 continue;
96 };
97 out.push(Identity {
98 name: name.to_string(),
99 email: email.to_string(),
100 email_range: TextRange::new(
101 absolute_start.into(),
102 (absolute_start + email.len() as u32).into(),
103 ),
104 });
105 }
106 out
107}
108
109fn joined_offset_to_absolute(segments: &[(usize, u32)], joined_offset: usize) -> Option<u32> {
110 let mut last = segments.first()?;
111 for seg in segments {
112 if seg.0 > joined_offset {
113 break;
114 }
115 last = seg;
116 }
117 Some(last.1 + (joined_offset - last.0) as u32)
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum ParseMode {
123 Strict,
125 Relaxed,
127 Substvar,
129}
130
131pub const SOURCE_FIELD_ORDER: &[&str] = &[
133 "Source",
134 "Section",
135 "Priority",
136 "Maintainer",
137 "Uploaders",
138 "Build-Depends",
139 "Build-Depends-Indep",
140 "Build-Depends-Arch",
141 "Build-Conflicts",
142 "Build-Conflicts-Indep",
143 "Build-Conflicts-Arch",
144 "Standards-Version",
145 "Vcs-Browser",
146 "Vcs-Git",
147 "Vcs-Svn",
148 "Vcs-Bzr",
149 "Vcs-Hg",
150 "Vcs-Darcs",
151 "Vcs-Cvs",
152 "Vcs-Arch",
153 "Vcs-Mtn",
154 "Homepage",
155 "Rules-Requires-Root",
156 "Testsuite",
157 "Testsuite-Triggers",
158];
159
160pub const BINARY_FIELD_ORDER: &[&str] = &[
162 "Package",
163 "Architecture",
164 "Section",
165 "Priority",
166 "Multi-Arch",
167 "Essential",
168 "Build-Profiles",
169 "Built-Using",
170 "Static-Built-Using",
171 "Pre-Depends",
172 "Depends",
173 "Recommends",
174 "Suggests",
175 "Enhances",
176 "Conflicts",
177 "Breaks",
178 "Replaces",
179 "Provides",
180 "Description",
181];
182
183fn format_field(name: &str, value: &str, max_line_length_one_liner: Option<usize>) -> String {
184 match name {
185 "Uploaders" => value
186 .split(',')
187 .map(|s| s.trim().to_string())
188 .collect::<Vec<_>>()
189 .join(",\n"),
190 "Build-Depends"
191 | "Build-Depends-Indep"
192 | "Build-Depends-Arch"
193 | "Build-Conflicts"
194 | "Build-Conflicts-Indep"
195 | "Build-Conflics-Arch"
196 | "Depends"
197 | "Recommends"
198 | "Suggests"
199 | "Enhances"
200 | "Pre-Depends"
201 | "Breaks" => {
202 let relations = match value.parse::<Relations>() {
205 Ok(r) => r.wrap_and_sort(),
206 Err(_) => return value.to_string(),
207 };
208 let one_line = relations.to_string();
209
210 if let Some(mll) = max_line_length_one_liner {
215 if name.len() + 2 + one_line.len() > mll {
216 return relations
217 .entries()
218 .map(|e| e.to_string())
219 .collect::<Vec<_>>()
220 .join(",\n");
221 }
222 }
223 one_line
224 }
225 _ => value.to_string(),
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct Control {
232 deb822: Deb822,
233 parse_mode: ParseMode,
234}
235
236impl Control {
237 pub fn snapshot(&self) -> Self {
244 Control {
245 deb822: self.deb822.snapshot(),
246 parse_mode: self.parse_mode,
247 }
248 }
249
250 pub fn tree_eq(&self, other: &Self) -> bool {
254 self.deb822.tree_eq(&other.deb822)
255 }
256
257 pub fn new() -> Self {
259 Control {
260 deb822: Deb822::new(),
261 parse_mode: ParseMode::Strict,
262 }
263 }
264
265 pub fn new_with_mode(parse_mode: ParseMode) -> Self {
267 Control {
268 deb822: Deb822::new(),
269 parse_mode,
270 }
271 }
272
273 pub fn parse_mode(&self) -> ParseMode {
275 self.parse_mode
276 }
277
278 pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
280 &mut self.deb822
281 }
282
283 pub fn as_deb822(&self) -> &Deb822 {
285 &self.deb822
286 }
287
288 pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
290 let deb822_parse = Deb822::parse(text);
291 let green = deb822_parse.green().clone();
293 let errors = deb822_parse.errors().to_vec();
294 let positioned_errors = deb822_parse.positioned_errors().to_vec();
295 deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
296 }
297
298 pub fn source(&self) -> Option<Source> {
300 let parse_mode = self.parse_mode;
301 self.deb822
302 .paragraphs()
303 .find(|p| p.get("Source").is_some())
304 .map(|paragraph| Source {
305 paragraph,
306 parse_mode,
307 })
308 }
309
310 pub fn binaries(&self) -> impl Iterator<Item = Binary> + '_ {
312 let parse_mode = self.parse_mode;
313 self.deb822
314 .paragraphs()
315 .filter(|p| p.get("Package").is_some())
316 .map(move |paragraph| Binary {
317 paragraph,
318 parse_mode,
319 })
320 }
321
322 pub fn source_in_range(&self, range: TextRange) -> Option<Source> {
330 self.source().filter(|s| {
331 let para_range = s.as_deb822().text_range();
332 para_range.start() < range.end() && para_range.end() > range.start()
333 })
334 }
335
336 pub fn binaries_in_range(&self, range: TextRange) -> impl Iterator<Item = Binary> + '_ {
344 self.binaries().filter(move |b| {
345 let para_range = b.as_deb822().text_range();
346 para_range.start() < range.end() && para_range.end() > range.start()
347 })
348 }
349
350 pub fn add_source(&mut self, name: &str) -> Source {
366 let mut p = self.deb822.add_paragraph();
367 p.set("Source", name);
368 self.source().unwrap()
369 }
370
371 pub fn add_binary(&mut self, name: &str) -> Binary {
387 let mut p = self.deb822.add_paragraph();
388 p.set("Package", name);
389 Binary {
390 paragraph: p,
391 parse_mode: ParseMode::Strict,
392 }
393 }
394
395 pub fn remove_binary(&mut self, name: &str) -> bool {
413 let index = self
414 .deb822
415 .paragraphs()
416 .position(|p| p.get("Package").as_deref() == Some(name));
417
418 if let Some(index) = index {
419 self.deb822.remove_paragraph(index);
420 true
421 } else {
422 false
423 }
424 }
425
426 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
428 Ok(Control {
429 deb822: Deb822::from_file(path)?,
430 parse_mode: ParseMode::Strict,
431 })
432 }
433
434 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
436 path: P,
437 ) -> Result<(Self, Vec<String>), std::io::Error> {
438 let (deb822, errors) = Deb822::from_file_relaxed(path)?;
439 Ok((
440 Control {
441 deb822,
442 parse_mode: ParseMode::Relaxed,
443 },
444 errors,
445 ))
446 }
447
448 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
450 Ok(Control {
451 deb822: Deb822::read(&mut r)?,
452 parse_mode: ParseMode::Strict,
453 })
454 }
455
456 pub fn read_relaxed<R: std::io::Read>(
458 mut r: R,
459 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
460 let (deb822, errors) = Deb822::read_relaxed(&mut r)?;
461 Ok((
462 Control {
463 deb822,
464 parse_mode: ParseMode::Relaxed,
465 },
466 errors,
467 ))
468 }
469
470 pub fn wrap_and_sort(
477 &mut self,
478 indentation: deb822_lossless::Indentation,
479 immediate_empty_line: bool,
480 max_line_length_one_liner: Option<usize>,
481 ) {
482 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
483 let a_is_source = a.get("Source").is_some();
485 let b_is_source = b.get("Source").is_some();
486
487 if a_is_source && !b_is_source {
488 return std::cmp::Ordering::Less;
489 } else if !a_is_source && b_is_source {
490 return std::cmp::Ordering::Greater;
491 } else if a_is_source && b_is_source {
492 return a.get("Source").cmp(&b.get("Source"));
493 }
494
495 a.get("Package").cmp(&b.get("Package"))
496 };
497
498 let format = |name: &str, value: &str| -> String {
499 format_field(name, value, max_line_length_one_liner)
500 };
501 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
502 p.wrap_and_sort(
505 indentation,
506 immediate_empty_line,
507 max_line_length_one_liner,
508 None,
509 Some(&format),
510 )
511 };
512
513 self.deb822 = self
514 .deb822
515 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
516 }
517
518 pub fn sort_binaries(&mut self, keep_first: bool) {
549 let mut paragraphs: Vec<_> = self.deb822.paragraphs().collect();
550
551 if paragraphs.len() <= 1 {
552 return; }
554
555 let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
557 let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
558
559 let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
561 binary_start + 1
562 } else {
563 binary_start
564 };
565
566 if sort_start >= paragraphs.len() {
567 return; }
569
570 paragraphs[sort_start..].sort_by(|a, b| {
572 let a_name = a.get("Package");
573 let b_name = b.get("Package");
574 a_name.cmp(&b_name)
575 });
576
577 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
579 let a_pos = paragraphs.iter().position(|p| p == a);
580 let b_pos = paragraphs.iter().position(|p| p == b);
581 a_pos.cmp(&b_pos)
582 };
583
584 self.deb822 = self.deb822.wrap_and_sort(Some(&sort_paragraphs), None);
585 }
586
587 pub fn fields_in_range(
616 &self,
617 range: TextRange,
618 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
619 self.deb822
620 .paragraphs()
621 .flat_map(move |p| p.entries().collect::<Vec<_>>())
622 .filter(move |entry| {
623 let entry_range = entry.syntax().text_range();
624 entry_range.start() < range.end() && range.start() < entry_range.end()
626 })
627 }
628}
629
630impl From<Control> for Deb822 {
631 fn from(c: Control) -> Self {
632 c.deb822
633 }
634}
635
636impl From<Deb822> for Control {
637 fn from(d: Deb822) -> Self {
638 Control {
639 deb822: d,
640 parse_mode: ParseMode::Strict,
641 }
642 }
643}
644
645impl Default for Control {
646 fn default() -> Self {
647 Self::new()
648 }
649}
650
651impl std::str::FromStr for Control {
652 type Err = deb822_lossless::ParseError;
653
654 fn from_str(s: &str) -> Result<Self, Self::Err> {
655 Control::parse(s).to_result()
656 }
657}
658
659#[derive(Debug, Clone, PartialEq, Eq)]
661pub struct Source {
662 paragraph: Paragraph,
663 parse_mode: ParseMode,
664}
665
666impl From<Source> for Paragraph {
667 fn from(s: Source) -> Self {
668 s.paragraph
669 }
670}
671
672impl From<Paragraph> for Source {
673 fn from(p: Paragraph) -> Self {
674 Source {
675 paragraph: p,
676 parse_mode: ParseMode::Strict,
677 }
678 }
679}
680
681impl std::fmt::Display for Source {
682 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
683 self.paragraph.fmt(f)
684 }
685}
686
687impl Source {
688 fn parse_relations(&self, s: &str) -> Relations {
690 match self.parse_mode {
691 ParseMode::Strict => s.parse().unwrap(),
692 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
693 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
694 }
695 }
696
697 pub fn name(&self) -> Option<String> {
699 self.paragraph.get("Source")
700 }
701
702 pub fn wrap_and_sort(
704 &mut self,
705 indentation: deb822_lossless::Indentation,
706 immediate_empty_line: bool,
707 max_line_length_one_liner: Option<usize>,
708 ) {
709 let format = |name: &str, value: &str| -> String {
710 format_field(name, value, max_line_length_one_liner)
711 };
712 self.paragraph = self.paragraph.wrap_and_sort(
713 indentation,
714 immediate_empty_line,
715 max_line_length_one_liner,
716 None,
717 Some(&format),
718 );
719 }
720
721 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
723 &mut self.paragraph
724 }
725
726 pub fn as_deb822(&self) -> &Paragraph {
728 &self.paragraph
729 }
730
731 pub fn set_name(&mut self, name: &str) {
733 self.set("Source", name);
734 }
735
736 pub fn section(&self) -> Option<String> {
738 self.paragraph.get("Section")
739 }
740
741 pub fn set_section(&mut self, section: Option<&str>) {
743 if let Some(section) = section {
744 self.set("Section", section);
745 } else {
746 self.paragraph.remove("Section");
747 }
748 }
749
750 pub fn priority(&self) -> Option<Priority> {
752 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
753 }
754
755 pub fn set_priority(&mut self, priority: Option<Priority>) {
757 if let Some(priority) = priority {
758 self.set("Priority", priority.to_string().as_str());
759 } else {
760 self.paragraph.remove("Priority");
761 }
762 }
763
764 pub fn maintainer(&self) -> Option<String> {
766 self.paragraph.get("Maintainer")
767 }
768
769 pub fn set_maintainer(&mut self, maintainer: &str) {
771 self.set("Maintainer", maintainer);
772 }
773
774 pub fn is_qa_package(&self) -> bool {
779 self.maintainer()
780 .as_deref()
781 .and_then(|m| crate::parse_identity(m).ok())
782 .map(|(_, email)| email.eq_ignore_ascii_case("packages@qa.debian.org"))
783 .unwrap_or(false)
784 }
785
786 pub fn build_depends(&self) -> Option<Relations> {
788 self.paragraph
789 .get_with_comments("Build-Depends")
790 .map(|s| self.parse_relations(&s))
791 }
792
793 pub fn set_build_depends(&mut self, relations: &Relations) {
795 self.set("Build-Depends", relations.to_string().as_str());
796 }
797
798 pub fn build_depends_indep(&self) -> Option<Relations> {
800 self.paragraph
801 .get_with_comments("Build-Depends-Indep")
802 .map(|s| self.parse_relations(&s))
803 }
804
805 pub fn set_build_depends_indep(&mut self, relations: &Relations) {
807 self.set("Build-Depends-Indep", relations.to_string().as_str());
808 }
809
810 pub fn build_depends_arch(&self) -> Option<Relations> {
812 self.paragraph
813 .get_with_comments("Build-Depends-Arch")
814 .map(|s| self.parse_relations(&s))
815 }
816
817 pub fn set_build_depends_arch(&mut self, relations: &Relations) {
819 self.set("Build-Depends-Arch", relations.to_string().as_str());
820 }
821
822 pub fn build_conflicts(&self) -> Option<Relations> {
824 self.paragraph
825 .get_with_comments("Build-Conflicts")
826 .map(|s| self.parse_relations(&s))
827 }
828
829 pub fn set_build_conflicts(&mut self, relations: &Relations) {
831 self.set("Build-Conflicts", relations.to_string().as_str());
832 }
833
834 pub fn build_conflicts_indep(&self) -> Option<Relations> {
836 self.paragraph
837 .get_with_comments("Build-Conflicts-Indep")
838 .map(|s| self.parse_relations(&s))
839 }
840
841 pub fn set_build_conflicts_indep(&mut self, relations: &Relations) {
843 self.set("Build-Conflicts-Indep", relations.to_string().as_str());
844 }
845
846 pub fn build_conflicts_arch(&self) -> Option<Relations> {
848 self.paragraph
849 .get_with_comments("Build-Conflicts-Arch")
850 .map(|s| self.parse_relations(&s))
851 }
852
853 pub fn standards_version(&self) -> Option<String> {
855 self.paragraph.get("Standards-Version")
856 }
857
858 pub fn set_standards_version(&mut self, version: &str) {
860 self.set("Standards-Version", version);
861 }
862
863 pub fn homepage(&self) -> Option<url::Url> {
865 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
866 }
867
868 pub fn set_homepage(&mut self, homepage: &url::Url) {
870 self.set("Homepage", homepage.to_string().as_str());
871 }
872
873 pub fn vcs_git(&self) -> Option<String> {
875 self.paragraph.get("Vcs-Git")
876 }
877
878 pub fn set_vcs_git(&mut self, url: &str) {
880 self.set("Vcs-Git", url);
881 }
882
883 pub fn vcs_svn(&self) -> Option<String> {
885 self.paragraph.get("Vcs-Svn").map(|s| s.to_string())
886 }
887
888 pub fn set_vcs_svn(&mut self, url: &str) {
890 self.set("Vcs-Svn", url);
891 }
892
893 pub fn vcs_bzr(&self) -> Option<String> {
895 self.paragraph.get("Vcs-Bzr").map(|s| s.to_string())
896 }
897
898 pub fn set_vcs_bzr(&mut self, url: &str) {
900 self.set("Vcs-Bzr", url);
901 }
902
903 pub fn vcs_arch(&self) -> Option<String> {
905 self.paragraph.get("Vcs-Arch").map(|s| s.to_string())
906 }
907
908 pub fn set_vcs_arch(&mut self, url: &str) {
910 self.set("Vcs-Arch", url);
911 }
912
913 pub fn vcs_svk(&self) -> Option<String> {
915 self.paragraph.get("Vcs-Svk").map(|s| s.to_string())
916 }
917
918 pub fn set_vcs_svk(&mut self, url: &str) {
920 self.set("Vcs-Svk", url);
921 }
922
923 pub fn vcs_darcs(&self) -> Option<String> {
925 self.paragraph.get("Vcs-Darcs").map(|s| s.to_string())
926 }
927
928 pub fn set_vcs_darcs(&mut self, url: &str) {
930 self.set("Vcs-Darcs", url);
931 }
932
933 pub fn vcs_mtn(&self) -> Option<String> {
935 self.paragraph.get("Vcs-Mtn").map(|s| s.to_string())
936 }
937
938 pub fn set_vcs_mtn(&mut self, url: &str) {
940 self.set("Vcs-Mtn", url);
941 }
942
943 pub fn vcs_cvs(&self) -> Option<String> {
945 self.paragraph.get("Vcs-Cvs").map(|s| s.to_string())
946 }
947
948 pub fn set_vcs_cvs(&mut self, url: &str) {
950 self.set("Vcs-Cvs", url);
951 }
952
953 pub fn vcs_hg(&self) -> Option<String> {
955 self.paragraph.get("Vcs-Hg").map(|s| s.to_string())
956 }
957
958 pub fn set_vcs_hg(&mut self, url: &str) {
960 self.set("Vcs-Hg", url);
961 }
962
963 pub fn set(&mut self, key: &str, value: &str) {
965 self.paragraph
966 .set_with_field_order(key, value, SOURCE_FIELD_ORDER);
967 }
968
969 pub fn get(&self, key: &str) -> Option<String> {
971 self.paragraph.get(key)
972 }
973
974 pub fn vcs_browser(&self) -> Option<String> {
976 self.paragraph.get("Vcs-Browser")
977 }
978
979 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
981 for (name, value) in self.paragraph.items() {
982 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
983 return crate::vcs::Vcs::from_field(&name, &value).ok();
984 }
985 }
986 None
987 }
988
989 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
991 if let Some(url) = url {
992 self.set("Vcs-Browser", url);
993 } else {
994 self.paragraph.remove("Vcs-Browser");
995 }
996 }
997
998 pub fn uploaders(&self) -> Option<Vec<String>> {
1000 self.paragraph
1001 .get("Uploaders")
1002 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
1003 }
1004
1005 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
1007 self.set(
1008 "Uploaders",
1009 uploaders
1010 .iter()
1011 .map(|s| s.to_string())
1012 .collect::<Vec<_>>()
1013 .join(", ")
1014 .as_str(),
1015 );
1016 }
1017
1018 pub fn maintainer_identities(&self) -> Vec<Identity> {
1025 identities_in_field(&self.paragraph, "Maintainer")
1026 }
1027
1028 pub fn uploaders_identities(&self) -> Vec<Identity> {
1032 identities_in_field(&self.paragraph, "Uploaders")
1033 }
1034
1035 pub fn architecture(&self) -> Option<String> {
1037 self.paragraph.get("Architecture")
1038 }
1039
1040 pub fn set_architecture(&mut self, arch: Option<&str>) {
1042 if let Some(arch) = arch {
1043 self.set("Architecture", arch);
1044 } else {
1045 self.paragraph.remove("Architecture");
1046 }
1047 }
1048
1049 pub fn rules_requires_root(&self) -> Option<bool> {
1051 self.paragraph
1052 .get("Rules-Requires-Root")
1053 .map(|s| match s.to_lowercase().as_str() {
1054 "yes" => true,
1055 "no" => false,
1056 _ => panic!("invalid Rules-Requires-Root value"),
1057 })
1058 }
1059
1060 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
1062 self.set(
1063 "Rules-Requires-Root",
1064 if requires_root { "yes" } else { "no" },
1065 );
1066 }
1067
1068 pub fn testsuite(&self) -> Option<String> {
1070 self.paragraph.get("Testsuite")
1071 }
1072
1073 pub fn set_testsuite(&mut self, testsuite: &str) {
1075 self.set("Testsuite", testsuite);
1076 }
1077
1078 pub fn overlaps_range(&self, range: TextRange) -> bool {
1086 let para_range = self.paragraph.syntax().text_range();
1087 para_range.start() < range.end() && range.start() < para_range.end()
1088 }
1089
1090 pub fn fields_in_range(
1098 &self,
1099 range: TextRange,
1100 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1101 self.paragraph.entries().filter(move |entry| {
1102 let entry_range = entry.syntax().text_range();
1103 entry_range.start() < range.end() && range.start() < entry_range.end()
1104 })
1105 }
1106}
1107
1108#[cfg(feature = "python-debian")]
1109impl<'py> pyo3::IntoPyObject<'py> for Source {
1110 type Target = pyo3::PyAny;
1111 type Output = pyo3::Bound<'py, Self::Target>;
1112 type Error = pyo3::PyErr;
1113
1114 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1115 self.paragraph.into_pyobject(py)
1116 }
1117}
1118
1119#[cfg(feature = "python-debian")]
1120impl<'py> pyo3::IntoPyObject<'py> for &Source {
1121 type Target = pyo3::PyAny;
1122 type Output = pyo3::Bound<'py, Self::Target>;
1123 type Error = pyo3::PyErr;
1124
1125 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1126 (&self.paragraph).into_pyobject(py)
1127 }
1128}
1129
1130#[cfg(feature = "python-debian")]
1131impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
1132 type Error = pyo3::PyErr;
1133
1134 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1135 Ok(Source {
1136 paragraph: ob.extract()?,
1137 parse_mode: ParseMode::Strict,
1138 })
1139 }
1140}
1141
1142impl std::fmt::Display for Control {
1143 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1144 self.deb822.fmt(f)
1145 }
1146}
1147
1148impl AstNode for Control {
1149 type Language = deb822_lossless::Lang;
1150
1151 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
1152 Deb822::can_cast(kind)
1153 }
1154
1155 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
1156 Deb822::cast(syntax).map(|deb822| Control {
1157 deb822,
1158 parse_mode: ParseMode::Strict,
1159 })
1160 }
1161
1162 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
1163 self.deb822.syntax()
1164 }
1165}
1166
1167#[derive(Debug, Clone, PartialEq, Eq)]
1169pub struct Binary {
1170 paragraph: Paragraph,
1171 parse_mode: ParseMode,
1172}
1173
1174impl From<Binary> for Paragraph {
1175 fn from(b: Binary) -> Self {
1176 b.paragraph
1177 }
1178}
1179
1180impl From<Paragraph> for Binary {
1181 fn from(p: Paragraph) -> Self {
1182 Binary {
1183 paragraph: p,
1184 parse_mode: ParseMode::Strict,
1185 }
1186 }
1187}
1188
1189#[cfg(feature = "python-debian")]
1190impl<'py> pyo3::IntoPyObject<'py> for Binary {
1191 type Target = pyo3::PyAny;
1192 type Output = pyo3::Bound<'py, Self::Target>;
1193 type Error = pyo3::PyErr;
1194
1195 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1196 self.paragraph.into_pyobject(py)
1197 }
1198}
1199
1200#[cfg(feature = "python-debian")]
1201impl<'py> pyo3::IntoPyObject<'py> for &Binary {
1202 type Target = pyo3::PyAny;
1203 type Output = pyo3::Bound<'py, Self::Target>;
1204 type Error = pyo3::PyErr;
1205
1206 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
1207 (&self.paragraph).into_pyobject(py)
1208 }
1209}
1210
1211#[cfg(feature = "python-debian")]
1212impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
1213 type Error = pyo3::PyErr;
1214
1215 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
1216 Ok(Binary {
1217 paragraph: ob.extract()?,
1218 parse_mode: ParseMode::Strict,
1219 })
1220 }
1221}
1222
1223impl Default for Binary {
1224 fn default() -> Self {
1225 Self::new()
1226 }
1227}
1228
1229impl std::fmt::Display for Binary {
1230 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1231 self.paragraph.fmt(f)
1232 }
1233}
1234
1235impl Binary {
1236 fn parse_relations(&self, s: &str) -> Relations {
1238 match self.parse_mode {
1239 ParseMode::Strict => s.parse().unwrap(),
1240 ParseMode::Relaxed => Relations::parse_relaxed(s, false).0,
1241 ParseMode::Substvar => Relations::parse_relaxed(s, true).0,
1242 }
1243 }
1244
1245 pub fn new() -> Self {
1247 Binary {
1248 paragraph: Paragraph::new(),
1249 parse_mode: ParseMode::Strict,
1250 }
1251 }
1252
1253 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
1255 &mut self.paragraph
1256 }
1257
1258 pub fn as_deb822(&self) -> &Paragraph {
1260 &self.paragraph
1261 }
1262
1263 pub fn wrap_and_sort(
1265 &mut self,
1266 indentation: deb822_lossless::Indentation,
1267 immediate_empty_line: bool,
1268 max_line_length_one_liner: Option<usize>,
1269 ) {
1270 let format = |name: &str, value: &str| -> String {
1271 format_field(name, value, max_line_length_one_liner)
1272 };
1273 self.paragraph = self.paragraph.wrap_and_sort(
1274 indentation,
1275 immediate_empty_line,
1276 max_line_length_one_liner,
1277 None,
1278 Some(&format),
1279 );
1280 }
1281
1282 pub fn name(&self) -> Option<String> {
1284 self.paragraph.get("Package")
1285 }
1286
1287 pub fn set_name(&mut self, name: &str) {
1289 self.set("Package", name);
1290 }
1291
1292 pub fn section(&self) -> Option<String> {
1294 self.paragraph.get("Section")
1295 }
1296
1297 pub fn set_section(&mut self, section: Option<&str>) {
1299 if let Some(section) = section {
1300 self.set("Section", section);
1301 } else {
1302 self.paragraph.remove("Section");
1303 }
1304 }
1305
1306 pub fn priority(&self) -> Option<Priority> {
1308 self.paragraph.get("Priority").and_then(|v| v.parse().ok())
1309 }
1310
1311 pub fn set_priority(&mut self, priority: Option<Priority>) {
1313 if let Some(priority) = priority {
1314 self.set("Priority", priority.to_string().as_str());
1315 } else {
1316 self.paragraph.remove("Priority");
1317 }
1318 }
1319
1320 pub fn architecture(&self) -> Option<String> {
1322 self.paragraph.get("Architecture")
1323 }
1324
1325 pub fn set_architecture(&mut self, arch: Option<&str>) {
1327 if let Some(arch) = arch {
1328 self.set("Architecture", arch);
1329 } else {
1330 self.paragraph.remove("Architecture");
1331 }
1332 }
1333
1334 pub fn depends(&self) -> Option<Relations> {
1336 self.paragraph
1337 .get_with_comments("Depends")
1338 .map(|s| self.parse_relations(&s))
1339 }
1340
1341 pub fn set_depends(&mut self, depends: Option<&Relations>) {
1343 if let Some(depends) = depends {
1344 self.set("Depends", depends.to_string().as_str());
1345 } else {
1346 self.paragraph.remove("Depends");
1347 }
1348 }
1349
1350 pub fn recommends(&self) -> Option<Relations> {
1352 self.paragraph
1353 .get_with_comments("Recommends")
1354 .map(|s| self.parse_relations(&s))
1355 }
1356
1357 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
1359 if let Some(recommends) = recommends {
1360 self.set("Recommends", recommends.to_string().as_str());
1361 } else {
1362 self.paragraph.remove("Recommends");
1363 }
1364 }
1365
1366 pub fn suggests(&self) -> Option<Relations> {
1368 self.paragraph
1369 .get_with_comments("Suggests")
1370 .map(|s| self.parse_relations(&s))
1371 }
1372
1373 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
1375 if let Some(suggests) = suggests {
1376 self.set("Suggests", suggests.to_string().as_str());
1377 } else {
1378 self.paragraph.remove("Suggests");
1379 }
1380 }
1381
1382 pub fn enhances(&self) -> Option<Relations> {
1384 self.paragraph
1385 .get_with_comments("Enhances")
1386 .map(|s| self.parse_relations(&s))
1387 }
1388
1389 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1391 if let Some(enhances) = enhances {
1392 self.set("Enhances", enhances.to_string().as_str());
1393 } else {
1394 self.paragraph.remove("Enhances");
1395 }
1396 }
1397
1398 pub fn pre_depends(&self) -> Option<Relations> {
1400 self.paragraph
1401 .get_with_comments("Pre-Depends")
1402 .map(|s| self.parse_relations(&s))
1403 }
1404
1405 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1407 if let Some(pre_depends) = pre_depends {
1408 self.set("Pre-Depends", pre_depends.to_string().as_str());
1409 } else {
1410 self.paragraph.remove("Pre-Depends");
1411 }
1412 }
1413
1414 pub fn breaks(&self) -> Option<Relations> {
1416 self.paragraph
1417 .get_with_comments("Breaks")
1418 .map(|s| self.parse_relations(&s))
1419 }
1420
1421 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1423 if let Some(breaks) = breaks {
1424 self.set("Breaks", breaks.to_string().as_str());
1425 } else {
1426 self.paragraph.remove("Breaks");
1427 }
1428 }
1429
1430 pub fn conflicts(&self) -> Option<Relations> {
1432 self.paragraph
1433 .get_with_comments("Conflicts")
1434 .map(|s| self.parse_relations(&s))
1435 }
1436
1437 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1439 if let Some(conflicts) = conflicts {
1440 self.set("Conflicts", conflicts.to_string().as_str());
1441 } else {
1442 self.paragraph.remove("Conflicts");
1443 }
1444 }
1445
1446 pub fn replaces(&self) -> Option<Relations> {
1448 self.paragraph
1449 .get_with_comments("Replaces")
1450 .map(|s| self.parse_relations(&s))
1451 }
1452
1453 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1455 if let Some(replaces) = replaces {
1456 self.set("Replaces", replaces.to_string().as_str());
1457 } else {
1458 self.paragraph.remove("Replaces");
1459 }
1460 }
1461
1462 pub fn provides(&self) -> Option<Relations> {
1464 self.paragraph
1465 .get_with_comments("Provides")
1466 .map(|s| self.parse_relations(&s))
1467 }
1468
1469 pub fn set_provides(&mut self, provides: Option<&Relations>) {
1471 if let Some(provides) = provides {
1472 self.set("Provides", provides.to_string().as_str());
1473 } else {
1474 self.paragraph.remove("Provides");
1475 }
1476 }
1477
1478 pub fn built_using(&self) -> Option<Relations> {
1480 self.paragraph
1481 .get_with_comments("Built-Using")
1482 .map(|s| self.parse_relations(&s))
1483 }
1484
1485 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1487 if let Some(built_using) = built_using {
1488 self.set("Built-Using", built_using.to_string().as_str());
1489 } else {
1490 self.paragraph.remove("Built-Using");
1491 }
1492 }
1493
1494 pub fn static_built_using(&self) -> Option<Relations> {
1496 self.paragraph
1497 .get_with_comments("Static-Built-Using")
1498 .map(|s| self.parse_relations(&s))
1499 }
1500
1501 pub fn set_static_built_using(&mut self, static_built_using: Option<&Relations>) {
1503 if let Some(static_built_using) = static_built_using {
1504 self.set(
1505 "Static-Built-Using",
1506 static_built_using.to_string().as_str(),
1507 );
1508 } else {
1509 self.paragraph.remove("Static-Built-Using");
1510 }
1511 }
1512
1513 pub fn multi_arch(&self) -> Option<MultiArch> {
1515 self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap())
1516 }
1517
1518 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1520 if let Some(multi_arch) = multi_arch {
1521 self.set("Multi-Arch", multi_arch.to_string().as_str());
1522 } else {
1523 self.paragraph.remove("Multi-Arch");
1524 }
1525 }
1526
1527 pub fn essential(&self) -> bool {
1529 self.paragraph
1530 .get("Essential")
1531 .map(|s| s == "yes")
1532 .unwrap_or(false)
1533 }
1534
1535 pub fn set_essential(&mut self, essential: bool) {
1537 if essential {
1538 self.set("Essential", "yes");
1539 } else {
1540 self.paragraph.remove("Essential");
1541 }
1542 }
1543
1544 pub fn description(&self) -> Option<String> {
1546 self.paragraph.get_multiline("Description")
1547 }
1548
1549 pub fn set_description(&mut self, description: Option<&str>) {
1551 if let Some(description) = description {
1552 self.paragraph.set_with_indent_pattern(
1553 "Description",
1554 description,
1555 Some(&deb822_lossless::IndentPattern::Fixed(1)),
1556 Some(BINARY_FIELD_ORDER),
1557 );
1558 } else {
1559 self.paragraph.remove("Description");
1560 }
1561 }
1562
1563 pub fn homepage(&self) -> Option<url::Url> {
1565 self.paragraph.get("Homepage").and_then(|s| s.parse().ok())
1566 }
1567
1568 pub fn set_homepage(&mut self, url: &url::Url) {
1570 self.set("Homepage", url.as_str());
1571 }
1572
1573 pub fn set(&mut self, key: &str, value: &str) {
1575 self.paragraph
1576 .set_with_field_order(key, value, BINARY_FIELD_ORDER);
1577 }
1578
1579 pub fn get(&self, key: &str) -> Option<String> {
1581 self.paragraph.get(key)
1582 }
1583
1584 pub fn overlaps_range(&self, range: TextRange) -> bool {
1592 let para_range = self.paragraph.syntax().text_range();
1593 para_range.start() < range.end() && range.start() < para_range.end()
1594 }
1595
1596 pub fn fields_in_range(
1604 &self,
1605 range: TextRange,
1606 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1607 self.paragraph.entries().filter(move |entry| {
1608 let entry_range = entry.syntax().text_range();
1609 entry_range.start() < range.end() && range.start() < entry_range.end()
1610 })
1611 }
1612}
1613
1614#[cfg(test)]
1615mod tests {
1616 use super::*;
1617 use crate::relations::VersionConstraint;
1618
1619 #[test]
1620 fn maintainer_and_uploaders_identities_have_email_ranges() {
1621 let text = "\
1622Source: hello
1623Maintainer: Alice <alice@example.org>
1624Uploaders: Bob <bob@example.org>,
1625 Carol <carol@example.org>
1626";
1627 let control = Control::parse(text).to_result().unwrap();
1628 let source = control.source().unwrap();
1629
1630 let m = source.maintainer_identities();
1631 assert_eq!(m.len(), 1);
1632 assert_eq!(m[0].name, "Alice");
1633 assert_eq!(m[0].email, "alice@example.org");
1634 let r = m[0].email_range;
1635 assert_eq!(&text[r], "alice@example.org");
1636
1637 let u = source.uploaders_identities();
1638 assert_eq!(u.len(), 2);
1639 assert_eq!(u[0].email, "bob@example.org");
1640 assert_eq!(u[1].email, "carol@example.org");
1641 assert_eq!(&text[u[0].email_range], "bob@example.org");
1642 assert_eq!(&text[u[1].email_range], "carol@example.org");
1643 }
1644
1645 #[test]
1646 fn test_source_set_field_ordering() {
1647 let mut control = Control::new();
1648 let mut source = control.add_source("mypackage");
1649
1650 source.set("Homepage", "https://example.com");
1652 source.set("Build-Depends", "debhelper");
1653 source.set("Standards-Version", "4.5.0");
1654 source.set("Maintainer", "Test <test@example.com>");
1655
1656 let output = source.to_string();
1658 let lines: Vec<&str> = output.lines().collect();
1659
1660 assert!(lines[0].starts_with("Source:"));
1662
1663 let maintainer_pos = lines
1665 .iter()
1666 .position(|l| l.starts_with("Maintainer:"))
1667 .unwrap();
1668 let build_depends_pos = lines
1669 .iter()
1670 .position(|l| l.starts_with("Build-Depends:"))
1671 .unwrap();
1672 let standards_pos = lines
1673 .iter()
1674 .position(|l| l.starts_with("Standards-Version:"))
1675 .unwrap();
1676 let homepage_pos = lines
1677 .iter()
1678 .position(|l| l.starts_with("Homepage:"))
1679 .unwrap();
1680
1681 assert!(maintainer_pos < build_depends_pos);
1683 assert!(build_depends_pos < standards_pos);
1684 assert!(standards_pos < homepage_pos);
1685 }
1686
1687 #[test]
1688 fn test_binary_set_field_ordering() {
1689 let mut control = Control::new();
1690 let mut binary = control.add_binary("mypackage");
1691
1692 binary.set("Description", "A test package");
1694 binary.set("Architecture", "amd64");
1695 binary.set("Depends", "libc6");
1696 binary.set("Section", "utils");
1697
1698 let output = binary.to_string();
1700 let lines: Vec<&str> = output.lines().collect();
1701
1702 assert!(lines[0].starts_with("Package:"));
1704
1705 let arch_pos = lines
1707 .iter()
1708 .position(|l| l.starts_with("Architecture:"))
1709 .unwrap();
1710 let section_pos = lines
1711 .iter()
1712 .position(|l| l.starts_with("Section:"))
1713 .unwrap();
1714 let depends_pos = lines
1715 .iter()
1716 .position(|l| l.starts_with("Depends:"))
1717 .unwrap();
1718 let desc_pos = lines
1719 .iter()
1720 .position(|l| l.starts_with("Description:"))
1721 .unwrap();
1722
1723 assert!(arch_pos < section_pos);
1725 assert!(section_pos < depends_pos);
1726 assert!(depends_pos < desc_pos);
1727 }
1728
1729 #[test]
1730 fn test_source_specific_set_methods_use_field_ordering() {
1731 let mut control = Control::new();
1732 let mut source = control.add_source("mypackage");
1733
1734 source.set_homepage(&"https://example.com".parse().unwrap());
1736 source.set_maintainer("Test <test@example.com>");
1737 source.set_standards_version("4.5.0");
1738 source.set_vcs_git("https://github.com/example/repo");
1739
1740 let output = source.to_string();
1742 let lines: Vec<&str> = output.lines().collect();
1743
1744 let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1746 let maintainer_pos = lines
1747 .iter()
1748 .position(|l| l.starts_with("Maintainer:"))
1749 .unwrap();
1750 let standards_pos = lines
1751 .iter()
1752 .position(|l| l.starts_with("Standards-Version:"))
1753 .unwrap();
1754 let vcs_git_pos = lines
1755 .iter()
1756 .position(|l| l.starts_with("Vcs-Git:"))
1757 .unwrap();
1758 let homepage_pos = lines
1759 .iter()
1760 .position(|l| l.starts_with("Homepage:"))
1761 .unwrap();
1762
1763 assert!(source_pos < maintainer_pos);
1765 assert!(maintainer_pos < standards_pos);
1766 assert!(standards_pos < vcs_git_pos);
1767 assert!(vcs_git_pos < homepage_pos);
1768 }
1769
1770 #[test]
1771 fn test_binary_specific_set_methods_use_field_ordering() {
1772 let mut control = Control::new();
1773 let mut binary = control.add_binary("mypackage");
1774
1775 binary.set_description(Some("A test package"));
1777 binary.set_architecture(Some("amd64"));
1778 let depends = "libc6".parse().unwrap();
1779 binary.set_depends(Some(&depends));
1780 binary.set_section(Some("utils"));
1781 binary.set_priority(Some(Priority::Optional));
1782
1783 let output = binary.to_string();
1785 let lines: Vec<&str> = output.lines().collect();
1786
1787 let package_pos = lines
1789 .iter()
1790 .position(|l| l.starts_with("Package:"))
1791 .unwrap();
1792 let arch_pos = lines
1793 .iter()
1794 .position(|l| l.starts_with("Architecture:"))
1795 .unwrap();
1796 let section_pos = lines
1797 .iter()
1798 .position(|l| l.starts_with("Section:"))
1799 .unwrap();
1800 let priority_pos = lines
1801 .iter()
1802 .position(|l| l.starts_with("Priority:"))
1803 .unwrap();
1804 let depends_pos = lines
1805 .iter()
1806 .position(|l| l.starts_with("Depends:"))
1807 .unwrap();
1808 let desc_pos = lines
1809 .iter()
1810 .position(|l| l.starts_with("Description:"))
1811 .unwrap();
1812
1813 assert!(package_pos < arch_pos);
1815 assert!(arch_pos < section_pos);
1816 assert!(section_pos < priority_pos);
1817 assert!(priority_pos < depends_pos);
1818 assert!(depends_pos < desc_pos);
1819 }
1820
1821 #[test]
1822 fn test_parse() {
1823 let control: Control = r#"Source: foo
1824Section: libs
1825Priority: optional
1826Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1827Homepage: https://example.com
1828
1829"#
1830 .parse()
1831 .unwrap();
1832 let source = control.source().unwrap();
1833
1834 assert_eq!(source.name(), Some("foo".to_owned()));
1835 assert_eq!(source.section(), Some("libs".to_owned()));
1836 assert_eq!(source.priority(), Some(super::Priority::Optional));
1837 assert_eq!(
1838 source.homepage(),
1839 Some("https://example.com".parse().unwrap())
1840 );
1841 let bd = source.build_depends().unwrap();
1842 let entries = bd.entries().collect::<Vec<_>>();
1843 assert_eq!(entries.len(), 2);
1844 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1845 assert_eq!(rel.name(), "bar");
1846 assert_eq!(
1847 rel.version(),
1848 Some((
1849 VersionConstraint::GreaterThanEqual,
1850 "1.0.0".parse().unwrap()
1851 ))
1852 );
1853 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1854 assert_eq!(rel.name(), "baz");
1855 assert_eq!(
1856 rel.version(),
1857 Some((
1858 VersionConstraint::GreaterThanEqual,
1859 "1.0.0".parse().unwrap()
1860 ))
1861 );
1862 }
1863
1864 #[test]
1865 fn test_description() {
1866 let control: Control = r#"Source: foo
1867
1868Package: foo
1869Description: this is the short description
1870 And the longer one
1871 .
1872 is on the next lines
1873"#
1874 .parse()
1875 .unwrap();
1876 let binary = control.binaries().next().unwrap();
1877 assert_eq!(
1878 binary.description(),
1879 Some(
1880 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1881 .to_owned()
1882 )
1883 );
1884 }
1885
1886 #[test]
1887 fn test_set_description_on_package_without_description() {
1888 let control: Control = r#"Source: foo
1889
1890Package: foo
1891Architecture: amd64
1892"#
1893 .parse()
1894 .unwrap();
1895 let mut binary = control.binaries().next().unwrap();
1896
1897 binary.set_description(Some(
1899 "Short description\nLonger description\n.\nAnother line",
1900 ));
1901
1902 let output = binary.to_string();
1903
1904 assert_eq!(
1906 binary.description(),
1907 Some("Short description\nLonger description\n.\nAnother line".to_owned())
1908 );
1909
1910 assert_eq!(
1912 output,
1913 "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1914 );
1915 }
1916
1917 #[test]
1918 fn test_as_mut_deb822() {
1919 let mut control = Control::new();
1920 let deb822 = control.as_mut_deb822();
1921 let mut p = deb822.add_paragraph();
1922 p.set("Source", "foo");
1923 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1924 }
1925
1926 #[test]
1927 fn test_as_deb822() {
1928 let control = Control::new();
1929 let _deb822: &Deb822 = control.as_deb822();
1930 }
1931
1932 #[test]
1933 fn test_set_depends() {
1934 let mut control = Control::new();
1935 let mut binary = control.add_binary("foo");
1936 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1937 binary.set_depends(Some(&relations));
1938 }
1939
1940 #[test]
1941 fn test_wrap_and_sort() {
1942 let mut control: Control = r#"Package: blah
1943Section: libs
1944
1945
1946
1947Package: foo
1948Description: this is a
1949 bar
1950 blah
1951"#
1952 .parse()
1953 .unwrap();
1954 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1955 let expected = r#"Package: blah
1956Section: libs
1957
1958Package: foo
1959Description: this is a
1960 bar
1961 blah
1962"#
1963 .to_owned();
1964 assert_eq!(control.to_string(), expected);
1965 }
1966
1967 #[test]
1968 fn test_wrap_and_sort_source() {
1969 let mut control: Control = r#"Source: blah
1970Depends: foo, bar (<= 1.0.0)
1971
1972"#
1973 .parse()
1974 .unwrap();
1975 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1976 let expected = r#"Source: blah
1977Depends: bar (<= 1.0.0), foo
1978"#
1979 .to_owned();
1980 assert_eq!(control.to_string(), expected);
1981 }
1982
1983 #[test]
1984 fn test_source_wrap_and_sort() {
1985 let control: Control = r#"Source: blah
1986Build-Depends: foo, bar (>= 1.0.0)
1987
1988"#
1989 .parse()
1990 .unwrap();
1991 let mut source = control.source().unwrap();
1992 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1993 assert!(source.build_depends().is_some());
1997 }
1998
1999 #[test]
2000 fn test_binary_set_breaks() {
2001 let mut control = Control::new();
2002 let mut binary = control.add_binary("foo");
2003 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
2004 binary.set_breaks(Some(&relations));
2005 assert!(binary.breaks().is_some());
2006 }
2007
2008 #[test]
2009 fn test_binary_set_pre_depends() {
2010 let mut control = Control::new();
2011 let mut binary = control.add_binary("foo");
2012 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
2013 binary.set_pre_depends(Some(&relations));
2014 assert!(binary.pre_depends().is_some());
2015 }
2016
2017 #[test]
2018 fn test_binary_set_provides() {
2019 let mut control = Control::new();
2020 let mut binary = control.add_binary("foo");
2021 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
2022 binary.set_provides(Some(&relations));
2023 assert!(binary.provides().is_some());
2024 }
2025
2026 #[test]
2027 fn test_source_is_qa_package() {
2028 let control: Control = "Source: foo\n\n".parse().unwrap();
2029 assert!(!control.source().unwrap().is_qa_package());
2030
2031 let control: Control = "Source: foo\nMaintainer: Jane Packager <jane@example.com>\n\n"
2032 .parse()
2033 .unwrap();
2034 assert!(!control.source().unwrap().is_qa_package());
2035
2036 let control: Control =
2037 "Source: foo\nMaintainer: Debian QA Group <packages@qa.debian.org>\n\n"
2038 .parse()
2039 .unwrap();
2040 assert!(control.source().unwrap().is_qa_package());
2041 }
2042
2043 #[test]
2044 fn test_source_build_conflicts() {
2045 let control: Control = r#"Source: blah
2046Build-Conflicts: foo, bar (>= 1.0.0)
2047
2048"#
2049 .parse()
2050 .unwrap();
2051 let source = control.source().unwrap();
2052 let conflicts = source.build_conflicts();
2053 assert!(conflicts.is_some());
2054 }
2055
2056 #[test]
2057 fn test_source_vcs_svn() {
2058 let control: Control = r#"Source: blah
2059Vcs-Svn: https://example.com/svn/repo
2060
2061"#
2062 .parse()
2063 .unwrap();
2064 let source = control.source().unwrap();
2065 assert_eq!(
2066 source.vcs_svn(),
2067 Some("https://example.com/svn/repo".to_string())
2068 );
2069 }
2070
2071 #[test]
2072 fn test_control_from_conversion() {
2073 let deb822_data = r#"Source: test
2074Section: libs
2075
2076"#;
2077 let deb822: Deb822 = deb822_data.parse().unwrap();
2078 let control = Control::from(deb822);
2079 assert!(control.source().is_some());
2080 }
2081
2082 #[test]
2083 fn test_fields_in_range() {
2084 let control_text = r#"Source: test-package
2085Maintainer: Test User <test@example.com>
2086Build-Depends: debhelper (>= 12)
2087
2088Package: test-binary
2089Architecture: any
2090Depends: ${shlibs:Depends}
2091Description: Test package
2092 This is a test package
2093"#;
2094 let control: Control = control_text.parse().unwrap();
2095
2096 let source_start = 0;
2098 let source_end = "Source: test-package".len();
2099 let source_range = TextRange::new((source_start as u32).into(), (source_end as u32).into());
2100
2101 let fields: Vec<_> = control.fields_in_range(source_range).collect();
2102 assert_eq!(fields.len(), 1);
2103 assert_eq!(fields[0].key(), Some("Source".to_string()));
2104
2105 let maintainer_start = control_text.find("Maintainer:").unwrap();
2107 let build_depends_end = control_text
2108 .find("Build-Depends: debhelper (>= 12)")
2109 .unwrap()
2110 + "Build-Depends: debhelper (>= 12)".len();
2111 let multi_range = TextRange::new(
2112 (maintainer_start as u32).into(),
2113 (build_depends_end as u32).into(),
2114 );
2115
2116 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
2117 assert_eq!(fields.len(), 2);
2118 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
2119 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
2120
2121 let cross_para_start = control_text.find("Build-Depends:").unwrap();
2123 let cross_para_end =
2124 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
2125 let cross_range = TextRange::new(
2126 (cross_para_start as u32).into(),
2127 (cross_para_end as u32).into(),
2128 );
2129
2130 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
2131 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
2133 assert_eq!(fields[1].key(), Some("Package".to_string()));
2134 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
2135
2136 let empty_range = TextRange::new(1000.into(), 1001.into());
2138 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
2139 assert_eq!(fields.len(), 0);
2140 }
2141
2142 #[test]
2143 fn test_source_overlaps_range() {
2144 let control_text = r#"Source: test-package
2145Maintainer: Test User <test@example.com>
2146
2147Package: test-binary
2148Architecture: any
2149"#;
2150 let control: Control = control_text.parse().unwrap();
2151 let source = control.source().unwrap();
2152
2153 let overlap_range = TextRange::new(10.into(), 30.into());
2155 assert!(source.overlaps_range(overlap_range));
2156
2157 let binary_start = control_text.find("Package:").unwrap();
2159 let no_overlap_range = TextRange::new(
2160 (binary_start as u32).into(),
2161 ((binary_start + 20) as u32).into(),
2162 );
2163 assert!(!source.overlaps_range(no_overlap_range));
2164
2165 let partial_overlap = TextRange::new(0.into(), 15.into());
2167 assert!(source.overlaps_range(partial_overlap));
2168 }
2169
2170 #[test]
2171 fn test_source_fields_in_range() {
2172 let control_text = r#"Source: test-package
2173Maintainer: Test User <test@example.com>
2174Build-Depends: debhelper (>= 12)
2175
2176Package: test-binary
2177"#;
2178 let control: Control = control_text.parse().unwrap();
2179 let source = control.source().unwrap();
2180
2181 let maintainer_start = control_text.find("Maintainer:").unwrap();
2183 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
2184 let maintainer_range = TextRange::new(
2185 (maintainer_start as u32).into(),
2186 (maintainer_end as u32).into(),
2187 );
2188
2189 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
2190 assert_eq!(fields.len(), 1);
2191 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
2192
2193 let all_source_range = TextRange::new(0.into(), 100.into());
2195 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
2196 assert_eq!(fields.len(), 3); }
2198
2199 #[test]
2200 fn test_binary_overlaps_range() {
2201 let control_text = r#"Source: test-package
2202
2203Package: test-binary
2204Architecture: any
2205Depends: ${shlibs:Depends}
2206"#;
2207 let control: Control = control_text.parse().unwrap();
2208 let binary = control.binaries().next().unwrap();
2209
2210 let package_start = control_text.find("Package:").unwrap();
2212 let overlap_range = TextRange::new(
2213 (package_start as u32).into(),
2214 ((package_start + 30) as u32).into(),
2215 );
2216 assert!(binary.overlaps_range(overlap_range));
2217
2218 let no_overlap_range = TextRange::new(0.into(), 10.into());
2220 assert!(!binary.overlaps_range(no_overlap_range));
2221 }
2222
2223 #[test]
2224 fn test_binary_fields_in_range() {
2225 let control_text = r#"Source: test-package
2226
2227Package: test-binary
2228Architecture: any
2229Depends: ${shlibs:Depends}
2230Description: Test binary
2231 This is a test binary package
2232"#;
2233 let control: Control = control_text.parse().unwrap();
2234 let binary = control.binaries().next().unwrap();
2235
2236 let arch_start = control_text.find("Architecture:").unwrap();
2238 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
2239 + "Depends: ${shlibs:Depends}".len();
2240 let range = TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
2241
2242 let fields: Vec<_> = binary.fields_in_range(range).collect();
2243 assert_eq!(fields.len(), 2);
2244 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
2245 assert_eq!(fields[1].key(), Some("Depends".to_string()));
2246
2247 let desc_start = control_text.find("Description:").unwrap();
2249 let partial_range = TextRange::new(
2250 ((desc_start + 5) as u32).into(),
2251 ((desc_start + 15) as u32).into(),
2252 );
2253 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
2254 assert_eq!(fields.len(), 1);
2255 assert_eq!(fields[0].key(), Some("Description".to_string()));
2256 }
2257
2258 #[test]
2259 fn test_incremental_parsing_use_case() {
2260 let control_text = r#"Source: example
2262Maintainer: John Doe <john@example.com>
2263Standards-Version: 4.6.0
2264Build-Depends: debhelper-compat (= 13)
2265
2266Package: example-bin
2267Architecture: all
2268Depends: ${misc:Depends}
2269Description: Example package
2270 This is an example.
2271"#;
2272 let control: Control = control_text.parse().unwrap();
2273
2274 let change_start = control_text.find("Standards-Version:").unwrap();
2276 let change_end = change_start + "Standards-Version: 4.6.0".len();
2277 let change_range = TextRange::new((change_start as u32).into(), (change_end as u32).into());
2278
2279 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
2281 assert_eq!(affected_fields.len(), 1);
2282 assert_eq!(
2283 affected_fields[0].key(),
2284 Some("Standards-Version".to_string())
2285 );
2286
2287 for entry in &affected_fields {
2289 let key = entry.key().unwrap();
2290 assert_ne!(key, "Maintainer");
2291 assert_ne!(key, "Build-Depends");
2292 assert_ne!(key, "Architecture");
2293 }
2294 }
2295
2296 #[test]
2297 fn test_positioned_parse_errors() {
2298 let input = "Invalid: field\nBroken field without colon";
2300 let parsed = Control::parse(input);
2301
2302 let positioned_errors = parsed.positioned_errors();
2304 assert!(
2305 !positioned_errors.is_empty(),
2306 "Should have positioned errors"
2307 );
2308
2309 for error in positioned_errors {
2311 let start_offset: u32 = error.range.start().into();
2312 let end_offset: u32 = error.range.end().into();
2313
2314 assert!(!error.message.is_empty());
2316
2317 assert!(start_offset <= end_offset);
2319 assert!(end_offset <= input.len() as u32);
2320
2321 assert!(error.code.is_some());
2323
2324 println!(
2325 "Error at {:?}: {} (code: {:?})",
2326 error.range, error.message, error.code
2327 );
2328 }
2329
2330 let string_errors = parsed.errors();
2332 assert!(!string_errors.is_empty());
2333 assert_eq!(string_errors.len(), positioned_errors.len());
2334 }
2335
2336 #[test]
2337 fn test_sort_binaries_basic() {
2338 let input = r#"Source: foo
2339
2340Package: libfoo
2341Architecture: all
2342
2343Package: libbar
2344Architecture: all
2345"#;
2346
2347 let mut control: Control = input.parse().unwrap();
2348 control.sort_binaries(false);
2349
2350 let binaries: Vec<_> = control.binaries().collect();
2351 assert_eq!(binaries.len(), 2);
2352 assert_eq!(binaries[0].name(), Some("libbar".to_string()));
2353 assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
2354 }
2355
2356 #[test]
2357 fn test_sort_binaries_keep_first() {
2358 let input = r#"Source: foo
2359
2360Package: zzz-first
2361Architecture: all
2362
2363Package: libbar
2364Architecture: all
2365
2366Package: libaaa
2367Architecture: all
2368"#;
2369
2370 let mut control: Control = input.parse().unwrap();
2371 control.sort_binaries(true);
2372
2373 let binaries: Vec<_> = control.binaries().collect();
2374 assert_eq!(binaries.len(), 3);
2375 assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
2377 assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
2379 assert_eq!(binaries[2].name(), Some("libbar".to_string()));
2380 }
2381
2382 #[test]
2383 fn test_sort_binaries_already_sorted() {
2384 let input = r#"Source: foo
2385
2386Package: aaa
2387Architecture: all
2388
2389Package: bbb
2390Architecture: all
2391
2392Package: ccc
2393Architecture: all
2394"#;
2395
2396 let mut control: Control = input.parse().unwrap();
2397 control.sort_binaries(false);
2398
2399 let binaries: Vec<_> = control.binaries().collect();
2400 assert_eq!(binaries.len(), 3);
2401 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2402 assert_eq!(binaries[1].name(), Some("bbb".to_string()));
2403 assert_eq!(binaries[2].name(), Some("ccc".to_string()));
2404 }
2405
2406 #[test]
2407 fn test_sort_binaries_no_binaries() {
2408 let input = r#"Source: foo
2409Maintainer: test@example.com
2410"#;
2411
2412 let mut control: Control = input.parse().unwrap();
2413 control.sort_binaries(false);
2414
2415 assert_eq!(control.binaries().count(), 0);
2417 }
2418
2419 #[test]
2420 fn test_sort_binaries_one_binary() {
2421 let input = r#"Source: foo
2422
2423Package: bar
2424Architecture: all
2425"#;
2426
2427 let mut control: Control = input.parse().unwrap();
2428 control.sort_binaries(false);
2429
2430 let binaries: Vec<_> = control.binaries().collect();
2431 assert_eq!(binaries.len(), 1);
2432 assert_eq!(binaries[0].name(), Some("bar".to_string()));
2433 }
2434
2435 #[test]
2436 fn test_sort_binaries_preserves_fields() {
2437 let input = r#"Source: foo
2438
2439Package: zzz
2440Architecture: any
2441Depends: libc6
2442Description: ZZZ package
2443
2444Package: aaa
2445Architecture: all
2446Depends: ${misc:Depends}
2447Description: AAA package
2448"#;
2449
2450 let mut control: Control = input.parse().unwrap();
2451 control.sort_binaries(false);
2452
2453 let binaries: Vec<_> = control.binaries().collect();
2454 assert_eq!(binaries.len(), 2);
2455
2456 assert_eq!(binaries[0].name(), Some("aaa".to_string()));
2458 assert_eq!(binaries[0].architecture(), Some("all".to_string()));
2459 assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
2460
2461 assert_eq!(binaries[1].name(), Some("zzz".to_string()));
2463 assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2464 assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2465 }
2466
2467 #[test]
2468 fn test_remove_binary_basic() {
2469 let mut control = Control::new();
2470 control.add_binary("foo");
2471 assert_eq!(control.binaries().count(), 1);
2472 assert!(control.remove_binary("foo"));
2473 assert_eq!(control.binaries().count(), 0);
2474 }
2475
2476 #[test]
2477 fn test_remove_binary_nonexistent() {
2478 let mut control = Control::new();
2479 control.add_binary("foo");
2480 assert!(!control.remove_binary("bar"));
2481 assert_eq!(control.binaries().count(), 1);
2482 }
2483
2484 #[test]
2485 fn test_remove_binary_multiple() {
2486 let mut control = Control::new();
2487 control.add_binary("foo");
2488 control.add_binary("bar");
2489 control.add_binary("baz");
2490 assert_eq!(control.binaries().count(), 3);
2491
2492 assert!(control.remove_binary("bar"));
2493 assert_eq!(control.binaries().count(), 2);
2494
2495 let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2496 assert_eq!(names, vec!["foo", "baz"]);
2497 }
2498
2499 #[test]
2500 fn test_remove_binary_preserves_source() {
2501 let input = r#"Source: mypackage
2502
2503Package: foo
2504Architecture: all
2505
2506Package: bar
2507Architecture: all
2508"#;
2509 let mut control: Control = input.parse().unwrap();
2510 assert!(control.source().is_some());
2511 assert_eq!(control.binaries().count(), 2);
2512
2513 assert!(control.remove_binary("foo"));
2514
2515 assert!(control.source().is_some());
2517 assert_eq!(
2518 control.source().unwrap().name(),
2519 Some("mypackage".to_string())
2520 );
2521
2522 assert_eq!(control.binaries().count(), 1);
2524 assert_eq!(
2525 control.binaries().next().unwrap().name(),
2526 Some("bar".to_string())
2527 );
2528 }
2529
2530 #[test]
2531 fn test_remove_binary_from_parsed() {
2532 let input = r#"Source: test
2533
2534Package: test-bin
2535Architecture: any
2536Depends: libc6
2537Description: Test binary
2538
2539Package: test-lib
2540Architecture: all
2541Description: Test library
2542"#;
2543 let mut control: Control = input.parse().unwrap();
2544 assert_eq!(control.binaries().count(), 2);
2545
2546 assert!(control.remove_binary("test-bin"));
2547
2548 let output = control.to_string();
2549 assert!(!output.contains("test-bin"));
2550 assert!(output.contains("test-lib"));
2551 assert!(output.contains("Source: test"));
2552 }
2553
2554 #[test]
2555 fn test_build_depends_preserves_indentation_after_removal() {
2556 let input = r#"Source: acpi-support
2557Section: admin
2558Priority: optional
2559Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2560Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2561 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2562"#;
2563 let control: Control = input.parse().unwrap();
2564 let mut source = control.source().unwrap();
2565
2566 let mut build_depends = source.build_depends().unwrap();
2568
2569 let mut to_remove = Vec::new();
2571 for (idx, entry) in build_depends.entries().enumerate() {
2572 for relation in entry.relations() {
2573 if relation.name() == "dh-systemd" {
2574 to_remove.push(idx);
2575 break;
2576 }
2577 }
2578 }
2579
2580 for idx in to_remove.into_iter().rev() {
2581 build_depends.remove_entry(idx);
2582 }
2583
2584 source.set_build_depends(&build_depends);
2586
2587 let output = source.to_string();
2588
2589 assert!(
2591 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2592 "Expected 4-space indentation to be preserved, but got:\n{}",
2593 output
2594 );
2595 }
2596
2597 #[test]
2598 fn test_build_depends_direct_string_set_loses_indentation() {
2599 let input = r#"Source: acpi-support
2600Section: admin
2601Priority: optional
2602Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2603Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2604 libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2605"#;
2606 let control: Control = input.parse().unwrap();
2607 let mut source = control.source().unwrap();
2608
2609 let mut build_depends = source.build_depends().unwrap();
2611
2612 let mut to_remove = Vec::new();
2614 for (idx, entry) in build_depends.entries().enumerate() {
2615 for relation in entry.relations() {
2616 if relation.name() == "dh-systemd" {
2617 to_remove.push(idx);
2618 break;
2619 }
2620 }
2621 }
2622
2623 for idx in to_remove.into_iter().rev() {
2624 build_depends.remove_entry(idx);
2625 }
2626
2627 source.set("Build-Depends", &build_depends.to_string());
2629
2630 let output = source.to_string();
2631 println!("Output with string set:");
2632 println!("{}", output);
2633
2634 assert!(
2637 output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"),
2638 "Expected 4-space indentation to be preserved, but got:\n{}",
2639 output
2640 );
2641 }
2642
2643 #[test]
2644 fn test_parse_mode_strict_default() {
2645 let control = Control::new();
2646 assert_eq!(control.parse_mode(), ParseMode::Strict);
2647
2648 let control: Control = "Source: test\n".parse().unwrap();
2649 assert_eq!(control.parse_mode(), ParseMode::Strict);
2650 }
2651
2652 #[test]
2653 fn test_parse_mode_new_with_mode() {
2654 let control_relaxed = Control::new_with_mode(ParseMode::Relaxed);
2655 assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed);
2656
2657 let control_substvar = Control::new_with_mode(ParseMode::Substvar);
2658 assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar);
2659 }
2660
2661 #[test]
2662 fn test_relaxed_mode_handles_broken_relations() {
2663 let input = r#"Source: test-package
2664Build-Depends: debhelper, @@@broken@@@, python3
2665
2666Package: test-pkg
2667Depends: libfoo, %%%invalid%%%, libbar
2668"#;
2669
2670 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2671 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2672
2673 if let Some(source) = control.source() {
2675 let bd = source.build_depends();
2676 assert!(bd.is_some());
2677 let relations = bd.unwrap();
2678 assert!(relations.len() >= 2); }
2681
2682 for binary in control.binaries() {
2683 let deps = binary.depends();
2684 assert!(deps.is_some());
2685 let relations = deps.unwrap();
2686 assert!(relations.len() >= 2); }
2689 }
2690
2691 #[test]
2692 fn test_substvar_mode_via_parse() {
2693 let input = r#"Source: test-package
2697Build-Depends: debhelper, ${misc:Depends}
2698
2699Package: test-pkg
2700Depends: ${shlibs:Depends}, libfoo
2701"#;
2702
2703 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2705
2706 if let Some(source) = control.source() {
2707 let bd = source.build_depends();
2709 assert!(bd.is_some());
2710 }
2711
2712 for binary in control.binaries() {
2713 let deps = binary.depends();
2714 assert!(deps.is_some());
2715 }
2716 }
2717
2718 #[test]
2719 #[should_panic]
2720 fn test_strict_mode_panics_on_broken_syntax() {
2721 let input = r#"Source: test-package
2722Build-Depends: debhelper, @@@broken@@@
2723"#;
2724
2725 let control: Control = input.parse().unwrap();
2727
2728 if let Some(source) = control.source() {
2729 let _ = source.build_depends();
2731 }
2732 }
2733
2734 #[test]
2735 fn test_from_file_relaxed_sets_relaxed_mode() {
2736 let input = r#"Source: test-package
2737Maintainer: Test <test@example.com>
2738"#;
2739
2740 let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap();
2741 assert_eq!(control.parse_mode(), ParseMode::Relaxed);
2742 }
2743
2744 #[test]
2745 fn test_parse_mode_propagates_to_paragraphs() {
2746 let input = r#"Source: test-package
2747Build-Depends: debhelper, @@@invalid@@@, python3
2748
2749Package: test-pkg
2750Depends: libfoo, %%%bad%%%, libbar
2751"#;
2752
2753 let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap();
2755
2756 if let Some(source) = control.source() {
2759 assert!(source.build_depends().is_some());
2760 }
2761
2762 for binary in control.binaries() {
2763 assert!(binary.depends().is_some());
2764 }
2765 }
2766
2767 #[test]
2768 fn test_preserves_final_newline() {
2769 let input_with_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2771 let control: Control = input_with_newline.parse().unwrap();
2772 let output = control.to_string();
2773 assert_eq!(output, input_with_newline);
2774 }
2775
2776 #[test]
2777 fn test_preserves_no_final_newline() {
2778 let input_without_newline = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any";
2780 let control: Control = input_without_newline.parse().unwrap();
2781 let output = control.to_string();
2782 assert_eq!(output, input_without_newline);
2783 }
2784
2785 #[test]
2786 fn test_final_newline_after_modifications() {
2787 let input = "Source: test-package\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2789 let control: Control = input.parse().unwrap();
2790
2791 let mut source = control.source().unwrap();
2793 source.set_section(Some("utils"));
2794
2795 let output = control.to_string();
2796 let expected = "Source: test-package\nSection: utils\nMaintainer: Test <test@example.com>\n\nPackage: test-pkg\nArchitecture: any\n";
2797 assert_eq!(output, expected);
2798 }
2799
2800 #[test]
2801 fn test_source_in_range() {
2802 let input = r#"Source: test-package
2804Maintainer: Test <test@example.com>
2805Section: utils
2806
2807Package: test-pkg
2808Architecture: any
2809"#;
2810 let control: Control = input.parse().unwrap();
2811
2812 let source = control.source().unwrap();
2814 let source_range = source.as_deb822().text_range();
2815
2816 let result = control.source_in_range(source_range);
2818 assert!(result.is_some());
2819 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2820
2821 let overlap_range = TextRange::new(0.into(), 20.into());
2823 let result = control.source_in_range(overlap_range);
2824 assert!(result.is_some());
2825 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2826
2827 let no_overlap_range = TextRange::new(100.into(), 150.into());
2829 let result = control.source_in_range(no_overlap_range);
2830 assert!(result.is_none());
2831 }
2832
2833 #[test]
2834 fn test_binaries_in_range_single() {
2835 let input = r#"Source: test-package
2837Maintainer: Test <test@example.com>
2838
2839Package: test-pkg
2840Architecture: any
2841
2842Package: another-pkg
2843Architecture: all
2844"#;
2845 let control: Control = input.parse().unwrap();
2846
2847 let first_binary = control.binaries().next().unwrap();
2849 let binary_range = first_binary.as_deb822().text_range();
2850
2851 let binaries: Vec<_> = control.binaries_in_range(binary_range).collect();
2853 assert_eq!(binaries.len(), 1);
2854 assert_eq!(binaries[0].name(), Some("test-pkg".to_string()));
2855 }
2856
2857 #[test]
2858 fn test_binaries_in_range_multiple() {
2859 let input = r#"Source: test-package
2861Maintainer: Test <test@example.com>
2862
2863Package: test-pkg
2864Architecture: any
2865
2866Package: another-pkg
2867Architecture: all
2868
2869Package: third-pkg
2870Architecture: any
2871"#;
2872 let control: Control = input.parse().unwrap();
2873
2874 let range = TextRange::new(50.into(), 130.into());
2876
2877 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2879 assert!(binaries.len() >= 2);
2880 assert!(binaries
2881 .iter()
2882 .any(|b| b.name() == Some("test-pkg".to_string())));
2883 assert!(binaries
2884 .iter()
2885 .any(|b| b.name() == Some("another-pkg".to_string())));
2886 }
2887
2888 #[test]
2889 fn test_binaries_in_range_none() {
2890 let input = r#"Source: test-package
2892Maintainer: Test <test@example.com>
2893
2894Package: test-pkg
2895Architecture: any
2896"#;
2897 let control: Control = input.parse().unwrap();
2898
2899 let range = TextRange::new(1000.into(), 2000.into());
2901
2902 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2904 assert_eq!(binaries.len(), 0);
2905 }
2906
2907 #[test]
2908 fn test_binaries_in_range_all() {
2909 let input = r#"Source: test-package
2911Maintainer: Test <test@example.com>
2912
2913Package: test-pkg
2914Architecture: any
2915
2916Package: another-pkg
2917Architecture: all
2918"#;
2919 let control: Control = input.parse().unwrap();
2920
2921 let range = TextRange::new(0.into(), input.len().try_into().unwrap());
2923
2924 let binaries: Vec<_> = control.binaries_in_range(range).collect();
2926 assert_eq!(binaries.len(), 2);
2927 }
2928
2929 #[test]
2930 fn test_source_in_range_partial_overlap() {
2931 let input = r#"Source: test-package
2933Maintainer: Test <test@example.com>
2934
2935Package: test-pkg
2936Architecture: any
2937"#;
2938 let control: Control = input.parse().unwrap();
2939
2940 let range = TextRange::new(10.into(), 30.into());
2942
2943 let result = control.source_in_range(range);
2945 assert!(result.is_some());
2946 assert_eq!(result.unwrap().name(), Some("test-package".to_string()));
2947 }
2948
2949 #[test]
2950 fn test_wrap_and_sort_long_build_depends_wraps_to_one_per_line() {
2951 let input = r#"Source: test-package
2955Maintainer: Test <test@example.com>
2956Build-Depends: debhelper-compat (= 13), aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj
2957
2958"#;
2959 let mut control: Control = input.parse().unwrap();
2960 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2961
2962 let expected = r#"Source: test-package
2963Maintainer: Test <test@example.com>
2964Build-Depends: aaaa,
2965 bbbb,
2966 cccc,
2967 dddd,
2968 debhelper-compat (= 13),
2969 eeee,
2970 ffff,
2971 gggg,
2972 hhhh,
2973 iiii,
2974 jjjj
2975"#;
2976 assert_eq!(control.to_string(), expected);
2977 }
2978
2979 #[test]
2980 fn test_wrap_and_sort_short_build_depends_stays_one_line() {
2981 let input = r#"Source: test-package
2984Maintainer: Test <test@example.com>
2985Build-Depends: debhelper-compat (= 13), foo, bar
2986
2987"#;
2988 let mut control: Control = input.parse().unwrap();
2989 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
2990
2991 let expected = "Source: test-package\nMaintainer: Test <test@example.com>\nBuild-Depends:bar, debhelper-compat (= 13), foo\n";
2994 assert_eq!(control.to_string(), expected);
2995 }
2996
2997 #[test]
2998 fn test_wrap_and_sort_long_build_depends_keeps_brackets_intact() {
2999 let value = "foo (>= 1.0), bar [amd64 arm64], baz <stage1 !nocheck>, qux, quux, corge, grault, garply, waldo, fred";
3005 let input = format!(
3006 "Source: test-package\nMaintainer: Test <test@example.com>\nBuild-Depends: {}\n\n",
3007 value
3008 );
3009 let mut control: Control = input.parse().unwrap();
3010 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, Some(79));
3011 let out = control.to_string();
3012 assert!(out.contains("bar [amd64 arm64],\n"), "out was: {}", out);
3013 assert!(
3014 out.contains(" baz <stage1 !nocheck>,\n"),
3015 "out was: {}",
3016 out
3017 );
3018 assert!(out.contains(" foo (>= 1.0),\n"), "out was: {}", out);
3019 }
3020
3021 #[test]
3022 fn test_wrap_and_sort_with_malformed_relations() {
3023 let input = r#"Source: test-package
3026Maintainer: Test <test@example.com>
3027Build-Depends: some invalid relation syntax here
3028
3029Package: test-pkg
3030Architecture: any
3031"#;
3032 let mut control: Control = input.parse().unwrap();
3033
3034 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
3036
3037 let output = control.to_string();
3039 let expected = r#"Source: test-package
3040Maintainer: Test <test@example.com>
3041Build-Depends: some invalid relation syntax here
3042
3043Package: test-pkg
3044Architecture: any
3045"#;
3046 assert_eq!(output, expected);
3047 }
3048}