1use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph};
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 from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
157 Ok(Control(Deb822::from_file(path)?))
158 }
159
160 pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
162 path: P,
163 ) -> Result<(Self, Vec<String>), std::io::Error> {
164 let (control, errors) = Deb822::from_file_relaxed(path)?;
165 Ok((Control(control), errors))
166 }
167
168 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
170 Ok(Control(Deb822::read(&mut r)?))
171 }
172
173 pub fn read_relaxed<R: std::io::Read>(
175 mut r: R,
176 ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
177 let (control, errors) = Deb822::read_relaxed(&mut r)?;
178 Ok((Self(control), errors))
179 }
180
181 pub fn wrap_and_sort(
188 &mut self,
189 indentation: deb822_lossless::Indentation,
190 immediate_empty_line: bool,
191 max_line_length_one_liner: Option<usize>,
192 ) {
193 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
194 let a_is_source = a.get("Source").is_some();
196 let b_is_source = b.get("Source").is_some();
197
198 if a_is_source && !b_is_source {
199 return std::cmp::Ordering::Less;
200 } else if !a_is_source && b_is_source {
201 return std::cmp::Ordering::Greater;
202 } else if a_is_source && b_is_source {
203 return a.get("Source").cmp(&b.get("Source"));
204 }
205
206 a.get("Package").cmp(&b.get("Package"))
207 };
208
209 let wrap_paragraph = |p: &Paragraph| -> Paragraph {
210 p.wrap_and_sort(
213 indentation,
214 immediate_empty_line,
215 max_line_length_one_liner,
216 None,
217 Some(&format_field),
218 )
219 };
220
221 self.0 = self
222 .0
223 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
224 }
225
226 pub fn fields_in_range(
255 &self,
256 range: rowan::TextRange,
257 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
258 self.0
259 .paragraphs()
260 .flat_map(move |p| p.entries().collect::<Vec<_>>())
261 .filter(move |entry| {
262 let entry_range = entry.syntax().text_range();
263 entry_range.start() < range.end() && range.start() < entry_range.end()
265 })
266 }
267}
268
269impl From<Control> for Deb822 {
270 fn from(c: Control) -> Self {
271 c.0
272 }
273}
274
275impl From<Deb822> for Control {
276 fn from(d: Deb822) -> Self {
277 Control(d)
278 }
279}
280
281impl Default for Control {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287impl std::str::FromStr for Control {
288 type Err = deb822_lossless::ParseError;
289
290 fn from_str(s: &str) -> Result<Self, Self::Err> {
291 Control::parse(s).to_result()
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct Source(Paragraph);
298
299impl From<Source> for Paragraph {
300 fn from(s: Source) -> Self {
301 s.0
302 }
303}
304
305impl From<Paragraph> for Source {
306 fn from(p: Paragraph) -> Self {
307 Source(p)
308 }
309}
310
311impl std::fmt::Display for Source {
312 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
313 self.0.fmt(f)
314 }
315}
316
317impl Source {
318 pub fn name(&self) -> Option<String> {
320 self.0.get("Source")
321 }
322
323 pub fn wrap_and_sort(
325 &mut self,
326 indentation: deb822_lossless::Indentation,
327 immediate_empty_line: bool,
328 max_line_length_one_liner: Option<usize>,
329 ) {
330 self.0 = self.0.wrap_and_sort(
331 indentation,
332 immediate_empty_line,
333 max_line_length_one_liner,
334 None,
335 Some(&format_field),
336 );
337 }
338
339 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
341 &mut self.0
342 }
343
344 pub fn as_deb822(&self) -> &Paragraph {
346 &self.0
347 }
348
349 pub fn set_name(&mut self, name: &str) {
351 self.0.set("Source", name);
352 }
353
354 pub fn section(&self) -> Option<String> {
356 self.0.get("Section")
357 }
358
359 pub fn set_section(&mut self, section: Option<&str>) {
361 if let Some(section) = section {
362 self.0.set("Section", section);
363 } else {
364 self.0.remove("Section");
365 }
366 }
367
368 pub fn priority(&self) -> Option<Priority> {
370 self.0.get("Priority").and_then(|v| v.parse().ok())
371 }
372
373 pub fn set_priority(&mut self, priority: Option<Priority>) {
375 if let Some(priority) = priority {
376 self.0.set("Priority", priority.to_string().as_str());
377 } else {
378 self.0.remove("Priority");
379 }
380 }
381
382 pub fn maintainer(&self) -> Option<String> {
384 self.0.get("Maintainer")
385 }
386
387 pub fn set_maintainer(&mut self, maintainer: &str) {
389 self.0.set("Maintainer", maintainer);
390 }
391
392 pub fn build_depends(&self) -> Option<Relations> {
394 self.0.get("Build-Depends").map(|s| s.parse().unwrap())
395 }
396
397 pub fn set_build_depends(&mut self, relations: &Relations) {
399 self.0.set("Build-Depends", relations.to_string().as_str());
400 }
401
402 pub fn build_depends_indep(&self) -> Option<Relations> {
404 self.0
405 .get("Build-Depends-Indep")
406 .map(|s| s.parse().unwrap())
407 }
408
409 pub fn build_depends_arch(&self) -> Option<Relations> {
411 self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
412 }
413
414 pub fn build_conflicts(&self) -> Option<Relations> {
416 self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
417 }
418
419 pub fn build_conflicts_indep(&self) -> Option<Relations> {
421 self.0
422 .get("Build-Conflicts-Indep")
423 .map(|s| s.parse().unwrap())
424 }
425
426 pub fn build_conflicts_arch(&self) -> Option<Relations> {
428 self.0
429 .get("Build-Conflicts-Arch")
430 .map(|s| s.parse().unwrap())
431 }
432
433 pub fn standards_version(&self) -> Option<String> {
435 self.0.get("Standards-Version")
436 }
437
438 pub fn set_standards_version(&mut self, version: &str) {
440 self.0.set("Standards-Version", version);
441 }
442
443 pub fn homepage(&self) -> Option<url::Url> {
445 self.0.get("Homepage").and_then(|s| s.parse().ok())
446 }
447
448 pub fn set_homepage(&mut self, homepage: &url::Url) {
450 self.0.set("Homepage", homepage.to_string().as_str());
451 }
452
453 pub fn vcs_git(&self) -> Option<String> {
455 self.0.get("Vcs-Git")
456 }
457
458 pub fn set_vcs_git(&mut self, url: &str) {
460 self.0.set("Vcs-Git", url);
461 }
462
463 pub fn vcs_svn(&self) -> Option<String> {
465 self.0.get("Vcs-Svn").map(|s| s.to_string())
466 }
467
468 pub fn set_vcs_svn(&mut self, url: &str) {
470 self.0.set("Vcs-Svn", url);
471 }
472
473 pub fn vcs_bzr(&self) -> Option<String> {
475 self.0.get("Vcs-Bzr").map(|s| s.to_string())
476 }
477
478 pub fn set_vcs_bzr(&mut self, url: &str) {
480 self.0.set("Vcs-Bzr", url);
481 }
482
483 pub fn vcs_arch(&self) -> Option<String> {
485 self.0.get("Vcs-Arch").map(|s| s.to_string())
486 }
487
488 pub fn set_vcs_arch(&mut self, url: &str) {
490 self.0.set("Vcs-Arch", url);
491 }
492
493 pub fn vcs_svk(&self) -> Option<String> {
495 self.0.get("Vcs-Svk").map(|s| s.to_string())
496 }
497
498 pub fn set_vcs_svk(&mut self, url: &str) {
500 self.0.set("Vcs-Svk", url);
501 }
502
503 pub fn vcs_darcs(&self) -> Option<String> {
505 self.0.get("Vcs-Darcs").map(|s| s.to_string())
506 }
507
508 pub fn set_vcs_darcs(&mut self, url: &str) {
510 self.0.set("Vcs-Darcs", url);
511 }
512
513 pub fn vcs_mtn(&self) -> Option<String> {
515 self.0.get("Vcs-Mtn").map(|s| s.to_string())
516 }
517
518 pub fn set_vcs_mtn(&mut self, url: &str) {
520 self.0.set("Vcs-Mtn", url);
521 }
522
523 pub fn vcs_cvs(&self) -> Option<String> {
525 self.0.get("Vcs-Cvs").map(|s| s.to_string())
526 }
527
528 pub fn set_vcs_cvs(&mut self, url: &str) {
530 self.0.set("Vcs-Cvs", url);
531 }
532
533 pub fn vcs_hg(&self) -> Option<String> {
535 self.0.get("Vcs-Hg").map(|s| s.to_string())
536 }
537
538 pub fn set_vcs_hg(&mut self, url: &str) {
540 self.0.set("Vcs-Hg", url);
541 }
542
543 pub fn vcs_browser(&self) -> Option<String> {
545 self.0.get("Vcs-Browser")
546 }
547
548 pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
550 for (name, value) in self.0.items() {
551 if name.starts_with("Vcs-") && name != "Vcs-Browser" {
552 return crate::vcs::Vcs::from_field(&name, &value).ok();
553 }
554 }
555 None
556 }
557
558 pub fn set_vcs_browser(&mut self, url: Option<&str>) {
560 if let Some(url) = url {
561 self.0.set("Vcs-Browser", url);
562 } else {
563 self.0.remove("Vcs-Browser");
564 }
565 }
566
567 pub fn uploaders(&self) -> Option<Vec<String>> {
569 self.0
570 .get("Uploaders")
571 .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
572 }
573
574 pub fn set_uploaders(&mut self, uploaders: &[&str]) {
576 self.0.set(
577 "Uploaders",
578 uploaders
579 .iter()
580 .map(|s| s.to_string())
581 .collect::<Vec<_>>()
582 .join(", ")
583 .as_str(),
584 );
585 }
586
587 pub fn architecture(&self) -> Option<String> {
589 self.0.get("Architecture")
590 }
591
592 pub fn set_architecture(&mut self, arch: Option<&str>) {
594 if let Some(arch) = arch {
595 self.0.set("Architecture", arch);
596 } else {
597 self.0.remove("Architecture");
598 }
599 }
600
601 pub fn rules_requires_root(&self) -> Option<bool> {
603 self.0
604 .get("Rules-Requires-Root")
605 .map(|s| match s.to_lowercase().as_str() {
606 "yes" => true,
607 "no" => false,
608 _ => panic!("invalid Rules-Requires-Root value"),
609 })
610 }
611
612 pub fn set_rules_requires_root(&mut self, requires_root: bool) {
614 self.0.set(
615 "Rules-Requires-Root",
616 if requires_root { "yes" } else { "no" },
617 );
618 }
619
620 pub fn testsuite(&self) -> Option<String> {
622 self.0.get("Testsuite")
623 }
624
625 pub fn set_testsuite(&mut self, testsuite: &str) {
627 self.0.set("Testsuite", testsuite);
628 }
629
630 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
638 let para_range = self.0.syntax().text_range();
639 para_range.start() < range.end() && range.start() < para_range.end()
640 }
641
642 pub fn fields_in_range(
650 &self,
651 range: rowan::TextRange,
652 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
653 self.0.entries().filter(move |entry| {
654 let entry_range = entry.syntax().text_range();
655 entry_range.start() < range.end() && range.start() < entry_range.end()
656 })
657 }
658}
659
660#[cfg(feature = "python-debian")]
661impl<'py> pyo3::IntoPyObject<'py> for Source {
662 type Target = pyo3::PyAny;
663 type Output = pyo3::Bound<'py, Self::Target>;
664 type Error = pyo3::PyErr;
665
666 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
667 self.0.into_pyobject(py)
668 }
669}
670
671#[cfg(feature = "python-debian")]
672impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
673 type Target = pyo3::PyAny;
674 type Output = pyo3::Bound<'py, Self::Target>;
675 type Error = pyo3::PyErr;
676
677 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
678 (&self.0).into_pyobject(py)
679 }
680}
681
682#[cfg(feature = "python-debian")]
683impl pyo3::FromPyObject<'_> for Source {
684 fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
685 use pyo3::prelude::*;
686 Ok(Source(ob.extract()?))
687 }
688}
689
690impl std::fmt::Display for Control {
691 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
692 self.0.fmt(f)
693 }
694}
695
696impl AstNode for Control {
697 type Language = deb822_lossless::Lang;
698
699 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
700 Deb822::can_cast(kind)
701 }
702
703 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
704 Deb822::cast(syntax).map(Control)
705 }
706
707 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
708 self.0.syntax()
709 }
710}
711
712#[derive(Debug, Clone, PartialEq, Eq)]
714pub struct Binary(Paragraph);
715
716impl From<Binary> for Paragraph {
717 fn from(b: Binary) -> Self {
718 b.0
719 }
720}
721
722impl From<Paragraph> for Binary {
723 fn from(p: Paragraph) -> Self {
724 Binary(p)
725 }
726}
727
728#[cfg(feature = "python-debian")]
729impl<'py> pyo3::IntoPyObject<'py> for Binary {
730 type Target = pyo3::PyAny;
731 type Output = pyo3::Bound<'py, Self::Target>;
732 type Error = pyo3::PyErr;
733
734 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
735 self.0.into_pyobject(py)
736 }
737}
738
739#[cfg(feature = "python-debian")]
740impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
741 type Target = pyo3::PyAny;
742 type Output = pyo3::Bound<'py, Self::Target>;
743 type Error = pyo3::PyErr;
744
745 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
746 (&self.0).into_pyobject(py)
747 }
748}
749
750#[cfg(feature = "python-debian")]
751impl pyo3::FromPyObject<'_> for Binary {
752 fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
753 use pyo3::prelude::*;
754 Ok(Binary(ob.extract()?))
755 }
756}
757
758impl Default for Binary {
759 fn default() -> Self {
760 Self::new()
761 }
762}
763
764impl Binary {
765 pub fn new() -> Self {
767 Binary(Paragraph::new())
768 }
769
770 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
772 &mut self.0
773 }
774
775 pub fn as_deb822(&self) -> &Paragraph {
777 &self.0
778 }
779
780 pub fn wrap_and_sort(
782 &mut self,
783 indentation: deb822_lossless::Indentation,
784 immediate_empty_line: bool,
785 max_line_length_one_liner: Option<usize>,
786 ) {
787 self.0 = self.0.wrap_and_sort(
788 indentation,
789 immediate_empty_line,
790 max_line_length_one_liner,
791 None,
792 Some(&format_field),
793 );
794 }
795
796 pub fn name(&self) -> Option<String> {
798 self.0.get("Package")
799 }
800
801 pub fn set_name(&mut self, name: &str) {
803 self.0.set("Package", name);
804 }
805
806 pub fn section(&self) -> Option<String> {
808 self.0.get("Section")
809 }
810
811 pub fn set_section(&mut self, section: Option<&str>) {
813 if let Some(section) = section {
814 self.0.set("Section", section);
815 } else {
816 self.0.remove("Section");
817 }
818 }
819
820 pub fn priority(&self) -> Option<Priority> {
822 self.0.get("Priority").and_then(|v| v.parse().ok())
823 }
824
825 pub fn set_priority(&mut self, priority: Option<Priority>) {
827 if let Some(priority) = priority {
828 self.0.set("Priority", priority.to_string().as_str());
829 } else {
830 self.0.remove("Priority");
831 }
832 }
833
834 pub fn architecture(&self) -> Option<String> {
836 self.0.get("Architecture")
837 }
838
839 pub fn set_architecture(&mut self, arch: Option<&str>) {
841 if let Some(arch) = arch {
842 self.0.set("Architecture", arch);
843 } else {
844 self.0.remove("Architecture");
845 }
846 }
847
848 pub fn depends(&self) -> Option<Relations> {
850 self.0.get("Depends").map(|s| s.parse().unwrap())
851 }
852
853 pub fn set_depends(&mut self, depends: Option<&Relations>) {
855 if let Some(depends) = depends {
856 self.0.set("Depends", depends.to_string().as_str());
857 } else {
858 self.0.remove("Depends");
859 }
860 }
861
862 pub fn recommends(&self) -> Option<Relations> {
864 self.0.get("Recommends").map(|s| s.parse().unwrap())
865 }
866
867 pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
869 if let Some(recommends) = recommends {
870 self.0.set("Recommends", recommends.to_string().as_str());
871 } else {
872 self.0.remove("Recommends");
873 }
874 }
875
876 pub fn suggests(&self) -> Option<Relations> {
878 self.0.get("Suggests").map(|s| s.parse().unwrap())
879 }
880
881 pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
883 if let Some(suggests) = suggests {
884 self.0.set("Suggests", suggests.to_string().as_str());
885 } else {
886 self.0.remove("Suggests");
887 }
888 }
889
890 pub fn enhances(&self) -> Option<Relations> {
892 self.0.get("Enhances").map(|s| s.parse().unwrap())
893 }
894
895 pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
897 if let Some(enhances) = enhances {
898 self.0.set("Enhances", enhances.to_string().as_str());
899 } else {
900 self.0.remove("Enhances");
901 }
902 }
903
904 pub fn pre_depends(&self) -> Option<Relations> {
906 self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
907 }
908
909 pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
911 if let Some(pre_depends) = pre_depends {
912 self.0.set("Pre-Depends", pre_depends.to_string().as_str());
913 } else {
914 self.0.remove("Pre-Depends");
915 }
916 }
917
918 pub fn breaks(&self) -> Option<Relations> {
920 self.0.get("Breaks").map(|s| s.parse().unwrap())
921 }
922
923 pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
925 if let Some(breaks) = breaks {
926 self.0.set("Breaks", breaks.to_string().as_str());
927 } else {
928 self.0.remove("Breaks");
929 }
930 }
931
932 pub fn conflicts(&self) -> Option<Relations> {
934 self.0.get("Conflicts").map(|s| s.parse().unwrap())
935 }
936
937 pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
939 if let Some(conflicts) = conflicts {
940 self.0.set("Conflicts", conflicts.to_string().as_str());
941 } else {
942 self.0.remove("Conflicts");
943 }
944 }
945
946 pub fn replaces(&self) -> Option<Relations> {
948 self.0.get("Replaces").map(|s| s.parse().unwrap())
949 }
950
951 pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
953 if let Some(replaces) = replaces {
954 self.0.set("Replaces", replaces.to_string().as_str());
955 } else {
956 self.0.remove("Replaces");
957 }
958 }
959
960 pub fn provides(&self) -> Option<Relations> {
962 self.0.get("Provides").map(|s| s.parse().unwrap())
963 }
964
965 pub fn set_provides(&mut self, provides: Option<&Relations>) {
967 if let Some(provides) = provides {
968 self.0.set("Provides", provides.to_string().as_str());
969 } else {
970 self.0.remove("Provides");
971 }
972 }
973
974 pub fn built_using(&self) -> Option<Relations> {
976 self.0.get("Built-Using").map(|s| s.parse().unwrap())
977 }
978
979 pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
981 if let Some(built_using) = built_using {
982 self.0.set("Built-Using", built_using.to_string().as_str());
983 } else {
984 self.0.remove("Built-Using");
985 }
986 }
987
988 pub fn multi_arch(&self) -> Option<MultiArch> {
990 self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
991 }
992
993 pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
995 if let Some(multi_arch) = multi_arch {
996 self.0.set("Multi-Arch", multi_arch.to_string().as_str());
997 } else {
998 self.0.remove("Multi-Arch");
999 }
1000 }
1001
1002 pub fn essential(&self) -> bool {
1004 self.0.get("Essential").map(|s| s == "yes").unwrap_or(false)
1005 }
1006
1007 pub fn set_essential(&mut self, essential: bool) {
1009 if essential {
1010 self.0.set("Essential", "yes");
1011 } else {
1012 self.0.remove("Essential");
1013 }
1014 }
1015
1016 pub fn description(&self) -> Option<String> {
1018 self.0.get("Description")
1019 }
1020
1021 pub fn set_description(&mut self, description: Option<&str>) {
1023 if let Some(description) = description {
1024 self.0.set("Description", description);
1025 } else {
1026 self.0.remove("Description");
1027 }
1028 }
1029
1030 pub fn homepage(&self) -> Option<url::Url> {
1032 self.0.get("Homepage").and_then(|s| s.parse().ok())
1033 }
1034
1035 pub fn set_homepage(&mut self, url: &url::Url) {
1037 self.0.set("Homepage", url.as_str());
1038 }
1039
1040 pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1048 let para_range = self.0.syntax().text_range();
1049 para_range.start() < range.end() && range.start() < para_range.end()
1050 }
1051
1052 pub fn fields_in_range(
1060 &self,
1061 range: rowan::TextRange,
1062 ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1063 self.0.entries().filter(move |entry| {
1064 let entry_range = entry.syntax().text_range();
1065 entry_range.start() < range.end() && range.start() < entry_range.end()
1066 })
1067 }
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use super::*;
1073 use crate::relations::VersionConstraint;
1074 #[test]
1075 fn test_parse() {
1076 let control: Control = r#"Source: foo
1077Section: libs
1078Priority: optional
1079Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1080Homepage: https://example.com
1081
1082"#
1083 .parse()
1084 .unwrap();
1085 let source = control.source().unwrap();
1086
1087 assert_eq!(source.name(), Some("foo".to_owned()));
1088 assert_eq!(source.section(), Some("libs".to_owned()));
1089 assert_eq!(source.priority(), Some(super::Priority::Optional));
1090 assert_eq!(
1091 source.homepage(),
1092 Some("https://example.com".parse().unwrap())
1093 );
1094 let bd = source.build_depends().unwrap();
1095 let entries = bd.entries().collect::<Vec<_>>();
1096 assert_eq!(entries.len(), 2);
1097 let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1098 assert_eq!(rel.name(), "bar");
1099 assert_eq!(
1100 rel.version(),
1101 Some((
1102 VersionConstraint::GreaterThanEqual,
1103 "1.0.0".parse().unwrap()
1104 ))
1105 );
1106 let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1107 assert_eq!(rel.name(), "baz");
1108 assert_eq!(
1109 rel.version(),
1110 Some((
1111 VersionConstraint::GreaterThanEqual,
1112 "1.0.0".parse().unwrap()
1113 ))
1114 );
1115 }
1116
1117 #[test]
1118 fn test_description() {
1119 let control: Control = r#"Source: foo
1120
1121Package: foo
1122Description: this is the short description
1123 And the longer one
1124 .
1125 is on the next lines
1126"#
1127 .parse()
1128 .unwrap();
1129 let binary = control.binaries().next().unwrap();
1130 assert_eq!(
1131 binary.description(),
1132 Some(
1133 "this is the short description\nAnd the longer one\n.\nis on the next lines"
1134 .to_owned()
1135 )
1136 );
1137 }
1138
1139 #[test]
1140 fn test_as_mut_deb822() {
1141 let mut control = Control::new();
1142 let deb822 = control.as_mut_deb822();
1143 let mut p = deb822.add_paragraph();
1144 p.set("Source", "foo");
1145 assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1146 }
1147
1148 #[test]
1149 fn test_as_deb822() {
1150 let control = Control::new();
1151 let _deb822: &Deb822 = control.as_deb822();
1152 }
1153
1154 #[test]
1155 fn test_set_depends() {
1156 let mut control = Control::new();
1157 let mut binary = control.add_binary("foo");
1158 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1159 binary.set_depends(Some(&relations));
1160 }
1161
1162 #[test]
1163 fn test_wrap_and_sort() {
1164 let mut control: Control = r#"Package: blah
1165Section: libs
1166
1167
1168
1169Package: foo
1170Description: this is a
1171 bar
1172 blah
1173"#
1174 .parse()
1175 .unwrap();
1176 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1177 let expected = r#"Package: blah
1178Section: libs
1179
1180Package: foo
1181Description: this is a
1182 bar
1183 blah
1184"#
1185 .to_owned();
1186 assert_eq!(control.to_string(), expected);
1187 }
1188
1189 #[test]
1190 fn test_wrap_and_sort_source() {
1191 let mut control: Control = r#"Source: blah
1192Depends: foo, bar (<= 1.0.0)
1193
1194"#
1195 .parse()
1196 .unwrap();
1197 control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1198 let expected = r#"Source: blah
1199Depends: bar (<= 1.0.0), foo
1200"#
1201 .to_owned();
1202 assert_eq!(control.to_string(), expected);
1203 }
1204
1205 #[test]
1206 fn test_source_wrap_and_sort() {
1207 let control: Control = r#"Source: blah
1208Build-Depends: foo, bar (>= 1.0.0)
1209
1210"#
1211 .parse()
1212 .unwrap();
1213 let mut source = control.source().unwrap();
1214 source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1215 assert!(source.build_depends().is_some());
1219 }
1220
1221 #[test]
1222 fn test_binary_set_breaks() {
1223 let mut control = Control::new();
1224 let mut binary = control.add_binary("foo");
1225 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1226 binary.set_breaks(Some(&relations));
1227 assert!(binary.breaks().is_some());
1228 }
1229
1230 #[test]
1231 fn test_binary_set_pre_depends() {
1232 let mut control = Control::new();
1233 let mut binary = control.add_binary("foo");
1234 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1235 binary.set_pre_depends(Some(&relations));
1236 assert!(binary.pre_depends().is_some());
1237 }
1238
1239 #[test]
1240 fn test_binary_set_provides() {
1241 let mut control = Control::new();
1242 let mut binary = control.add_binary("foo");
1243 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1244 binary.set_provides(Some(&relations));
1245 assert!(binary.provides().is_some());
1246 }
1247
1248 #[test]
1249 fn test_source_build_conflicts() {
1250 let control: Control = r#"Source: blah
1251Build-Conflicts: foo, bar (>= 1.0.0)
1252
1253"#
1254 .parse()
1255 .unwrap();
1256 let source = control.source().unwrap();
1257 let conflicts = source.build_conflicts();
1258 assert!(conflicts.is_some());
1259 }
1260
1261 #[test]
1262 fn test_source_vcs_svn() {
1263 let control: Control = r#"Source: blah
1264Vcs-Svn: https://example.com/svn/repo
1265
1266"#
1267 .parse()
1268 .unwrap();
1269 let source = control.source().unwrap();
1270 assert_eq!(
1271 source.vcs_svn(),
1272 Some("https://example.com/svn/repo".to_string())
1273 );
1274 }
1275
1276 #[test]
1277 fn test_control_from_conversion() {
1278 let deb822_data = r#"Source: test
1279Section: libs
1280
1281"#;
1282 let deb822: Deb822 = deb822_data.parse().unwrap();
1283 let control = Control::from(deb822);
1284 assert!(control.source().is_some());
1285 }
1286
1287 #[test]
1288 fn test_fields_in_range() {
1289 let control_text = r#"Source: test-package
1290Maintainer: Test User <test@example.com>
1291Build-Depends: debhelper (>= 12)
1292
1293Package: test-binary
1294Architecture: any
1295Depends: ${shlibs:Depends}
1296Description: Test package
1297 This is a test package
1298"#;
1299 let control: Control = control_text.parse().unwrap();
1300
1301 let source_start = 0;
1303 let source_end = "Source: test-package".len();
1304 let source_range =
1305 rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1306
1307 let fields: Vec<_> = control.fields_in_range(source_range).collect();
1308 assert_eq!(fields.len(), 1);
1309 assert_eq!(fields[0].key(), Some("Source".to_string()));
1310
1311 let maintainer_start = control_text.find("Maintainer:").unwrap();
1313 let build_depends_end = control_text
1314 .find("Build-Depends: debhelper (>= 12)")
1315 .unwrap()
1316 + "Build-Depends: debhelper (>= 12)".len();
1317 let multi_range = rowan::TextRange::new(
1318 (maintainer_start as u32).into(),
1319 (build_depends_end as u32).into(),
1320 );
1321
1322 let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1323 assert_eq!(fields.len(), 2);
1324 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1325 assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1326
1327 let cross_para_start = control_text.find("Build-Depends:").unwrap();
1329 let cross_para_end =
1330 control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1331 let cross_range = rowan::TextRange::new(
1332 (cross_para_start as u32).into(),
1333 (cross_para_end as u32).into(),
1334 );
1335
1336 let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1337 assert_eq!(fields.len(), 3); assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1339 assert_eq!(fields[1].key(), Some("Package".to_string()));
1340 assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1341
1342 let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1344 let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1345 assert_eq!(fields.len(), 0);
1346 }
1347
1348 #[test]
1349 fn test_source_overlaps_range() {
1350 let control_text = r#"Source: test-package
1351Maintainer: Test User <test@example.com>
1352
1353Package: test-binary
1354Architecture: any
1355"#;
1356 let control: Control = control_text.parse().unwrap();
1357 let source = control.source().unwrap();
1358
1359 let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1361 assert!(source.overlaps_range(overlap_range));
1362
1363 let binary_start = control_text.find("Package:").unwrap();
1365 let no_overlap_range = rowan::TextRange::new(
1366 (binary_start as u32).into(),
1367 ((binary_start + 20) as u32).into(),
1368 );
1369 assert!(!source.overlaps_range(no_overlap_range));
1370
1371 let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1373 assert!(source.overlaps_range(partial_overlap));
1374 }
1375
1376 #[test]
1377 fn test_source_fields_in_range() {
1378 let control_text = r#"Source: test-package
1379Maintainer: Test User <test@example.com>
1380Build-Depends: debhelper (>= 12)
1381
1382Package: test-binary
1383"#;
1384 let control: Control = control_text.parse().unwrap();
1385 let source = control.source().unwrap();
1386
1387 let maintainer_start = control_text.find("Maintainer:").unwrap();
1389 let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1390 let maintainer_range = rowan::TextRange::new(
1391 (maintainer_start as u32).into(),
1392 (maintainer_end as u32).into(),
1393 );
1394
1395 let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1396 assert_eq!(fields.len(), 1);
1397 assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1398
1399 let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1401 let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1402 assert_eq!(fields.len(), 3); }
1404
1405 #[test]
1406 fn test_binary_overlaps_range() {
1407 let control_text = r#"Source: test-package
1408
1409Package: test-binary
1410Architecture: any
1411Depends: ${shlibs:Depends}
1412"#;
1413 let control: Control = control_text.parse().unwrap();
1414 let binary = control.binaries().next().unwrap();
1415
1416 let package_start = control_text.find("Package:").unwrap();
1418 let overlap_range = rowan::TextRange::new(
1419 (package_start as u32).into(),
1420 ((package_start + 30) as u32).into(),
1421 );
1422 assert!(binary.overlaps_range(overlap_range));
1423
1424 let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1426 assert!(!binary.overlaps_range(no_overlap_range));
1427 }
1428
1429 #[test]
1430 fn test_binary_fields_in_range() {
1431 let control_text = r#"Source: test-package
1432
1433Package: test-binary
1434Architecture: any
1435Depends: ${shlibs:Depends}
1436Description: Test binary
1437 This is a test binary package
1438"#;
1439 let control: Control = control_text.parse().unwrap();
1440 let binary = control.binaries().next().unwrap();
1441
1442 let arch_start = control_text.find("Architecture:").unwrap();
1444 let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1445 + "Depends: ${shlibs:Depends}".len();
1446 let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1447
1448 let fields: Vec<_> = binary.fields_in_range(range).collect();
1449 assert_eq!(fields.len(), 2);
1450 assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1451 assert_eq!(fields[1].key(), Some("Depends".to_string()));
1452
1453 let desc_start = control_text.find("Description:").unwrap();
1455 let partial_range = rowan::TextRange::new(
1456 ((desc_start + 5) as u32).into(),
1457 ((desc_start + 15) as u32).into(),
1458 );
1459 let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1460 assert_eq!(fields.len(), 1);
1461 assert_eq!(fields[0].key(), Some("Description".to_string()));
1462 }
1463
1464 #[test]
1465 fn test_incremental_parsing_use_case() {
1466 let control_text = r#"Source: example
1468Maintainer: John Doe <john@example.com>
1469Standards-Version: 4.6.0
1470Build-Depends: debhelper-compat (= 13)
1471
1472Package: example-bin
1473Architecture: all
1474Depends: ${misc:Depends}
1475Description: Example package
1476 This is an example.
1477"#;
1478 let control: Control = control_text.parse().unwrap();
1479
1480 let change_start = control_text.find("Standards-Version:").unwrap();
1482 let change_end = change_start + "Standards-Version: 4.6.0".len();
1483 let change_range =
1484 rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
1485
1486 let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
1488 assert_eq!(affected_fields.len(), 1);
1489 assert_eq!(
1490 affected_fields[0].key(),
1491 Some("Standards-Version".to_string())
1492 );
1493
1494 for entry in &affected_fields {
1496 let key = entry.key().unwrap();
1497 assert_ne!(key, "Maintainer");
1498 assert_ne!(key, "Build-Depends");
1499 assert_ne!(key, "Architecture");
1500 }
1501 }
1502
1503 #[test]
1504 fn test_positioned_parse_errors() {
1505 let input = "Invalid: field\nBroken field without colon";
1507 let parsed = Control::parse(input);
1508
1509 let positioned_errors = parsed.positioned_errors();
1511 assert!(
1512 !positioned_errors.is_empty(),
1513 "Should have positioned errors"
1514 );
1515
1516 for error in positioned_errors {
1518 let start_offset: u32 = error.range.start().into();
1519 let end_offset: u32 = error.range.end().into();
1520
1521 assert!(!error.message.is_empty());
1523
1524 assert!(start_offset <= end_offset);
1526 assert!(end_offset <= input.len() as u32);
1527
1528 assert!(error.code.is_some());
1530
1531 println!(
1532 "Error at {:?}: {} (code: {:?})",
1533 error.range, error.message, error.code
1534 );
1535 }
1536
1537 let string_errors = parsed.errors();
1539 assert!(!string_errors.is_empty());
1540 assert_eq!(string_errors.len(), positioned_errors.len());
1541 }
1542}