1use crate::fields::{
3 Md5Checksum, MultiArch, Priority, Sha1Checksum, Sha256Checksum, Sha512Checksum,
4};
5use crate::lossless::relations::Relations;
6use rowan::ast::AstNode;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Source(deb822_lossless::Paragraph);
11
12#[cfg(feature = "python-debian")]
13impl<'py> pyo3::IntoPyObject<'py> for Source {
14 type Target = pyo3::PyAny;
15 type Output = pyo3::Bound<'py, Self::Target>;
16 type Error = pyo3::PyErr;
17
18 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
19 use pyo3::prelude::*;
20 let d = self.0.into_pyobject(py)?;
21
22 let m = py.import("debian.deb822")?;
23 let cls = m.getattr("Sources")?;
24
25 cls.call1((d,))
26 }
27}
28
29#[cfg(feature = "python-debian")]
30impl<'py> pyo3::IntoPyObject<'py> for &Source {
31 type Target = pyo3::PyAny;
32 type Output = pyo3::Bound<'py, Self::Target>;
33 type Error = pyo3::PyErr;
34
35 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
36 use pyo3::prelude::*;
37 let d = (&self.0).into_pyobject(py)?;
38
39 let m = py.import("debian.deb822")?;
40 let cls = m.getattr("Sources")?;
41
42 cls.call1((d,))
43 }
44}
45
46#[cfg(feature = "python-debian")]
47impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
48 type Error = pyo3::PyErr;
49
50 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
51 Ok(Source(ob.extract()?))
52 }
53}
54
55impl From<deb822_lossless::Paragraph> for Source {
56 fn from(paragraph: deb822_lossless::Paragraph) -> Self {
57 Self(paragraph)
58 }
59}
60
61impl Default for Source {
62 fn default() -> Self {
63 Self(deb822_lossless::Paragraph::new())
64 }
65}
66
67impl Source {
68 pub fn new() -> Self {
70 Self(deb822_lossless::Paragraph::new())
71 }
72
73 pub fn parse(text: &str) -> deb822_lossless::Parse<Source> {
77 let deb822_parse = deb822_lossless::Deb822::parse(text);
80
81 let green = deb822_parse.green().clone();
83 let mut errors = deb822_parse.errors().to_vec();
84
85 if errors.is_empty() {
87 let deb822 = deb822_parse.tree();
88 let paragraph_count = deb822.paragraphs().count();
89 if paragraph_count == 0 {
90 errors.push("No paragraphs found".to_string());
91 } else if paragraph_count > 1 {
92 errors.push("Multiple paragraphs found, expected one".to_string());
93 }
94 }
95
96 deb822_lossless::Parse::new(green, errors)
97 }
98
99 pub fn package(&self) -> Option<String> {
101 self.0.get("Package").map(|s| s.to_string())
102 }
103
104 pub fn set_package(&mut self, package: &str) {
106 self.0.set("Package", package);
107 }
108
109 pub fn version(&self) -> Option<debversion::Version> {
111 self.0.get("Version").map(|s| s.parse().unwrap())
112 }
113
114 pub fn set_version(&mut self, version: debversion::Version) {
116 self.0.set("Version", &version.to_string());
117 }
118
119 pub fn maintainer(&self) -> Option<String> {
121 self.0.get("Maintainer").map(|s| s.to_string())
122 }
123
124 pub fn set_maintainer(&mut self, maintainer: &str) {
126 self.0.set("Maintainer", maintainer);
127 }
128
129 pub fn uploaders(&self) -> Option<Vec<String>> {
131 self.0.get("Uploaders").map(|s| {
132 s.split(',')
133 .map(|s| s.trim().to_string())
134 .collect::<Vec<String>>()
135 })
136 }
137
138 pub fn set_uploaders(&mut self, uploaders: Vec<String>) {
140 self.0.set("Uploaders", &uploaders.join(", "));
141 }
142
143 pub fn standards_version(&self) -> Option<String> {
145 self.0.get("Standards-Version").map(|s| s.to_string())
146 }
147
148 pub fn set_standards_version(&mut self, version: &str) {
150 self.0.set("Standards-Version", version);
151 }
152
153 pub fn format(&self) -> Option<String> {
155 self.0.get("Format").map(|s| s.to_string())
156 }
157
158 pub fn set_format(&mut self, format: &str) {
160 self.0.set("Format", format);
161 }
162
163 pub fn vcs_browser(&self) -> Option<String> {
165 self.0.get("Vcs-Browser").map(|s| s.to_string())
166 }
167
168 pub fn set_vcs_browser(&mut self, url: &str) {
170 self.0.set("Vcs-Browser", url);
171 }
172
173 pub fn vcs_git(&self) -> Option<String> {
175 self.0.get("Vcs-Git").map(|s| s.to_string())
176 }
177
178 pub fn set_vcs_git(&mut self, url: &str) {
180 self.0.set("Vcs-Git", url);
181 }
182
183 pub fn vcs_svn(&self) -> Option<String> {
185 self.0.get("Vcs-Svn").map(|s| s.to_string())
186 }
187
188 pub fn set_vcs_svn(&mut self, url: &str) {
190 self.0.set("Vcs-Svn", url);
191 }
192
193 pub fn vcs_hg(&self) -> Option<String> {
195 self.0.get("Vcs-Hg").map(|s| s.to_string())
196 }
197
198 pub fn set_vcs_hg(&mut self, url: &str) {
200 self.0.set("Vcs-Hg", url);
201 }
202
203 pub fn vcs_bzr(&self) -> Option<String> {
205 self.0.get("Vcs-Bzr").map(|s| s.to_string())
206 }
207
208 pub fn set_vcs_bzr(&mut self, url: &str) {
210 self.0.set("Vcs-Bzr", url);
211 }
212
213 pub fn vcs_arch(&self) -> Option<String> {
215 self.0.get("Vcs-Arch").map(|s| s.to_string())
216 }
217
218 pub fn set_vcs_arch(&mut self, url: &str) {
220 self.0.set("Vcs-Arch", url);
221 }
222
223 pub fn vcs_svk(&self) -> Option<String> {
225 self.0.get("Vcs-Svk").map(|s| s.to_string())
226 }
227
228 pub fn set_vcs_svk(&mut self, url: &str) {
230 self.0.set("Vcs-Svk", url);
231 }
232
233 pub fn vcs_darcs(&self) -> Option<String> {
235 self.0.get("Vcs-Darcs").map(|s| s.to_string())
236 }
237
238 pub fn set_vcs_darcs(&mut self, url: &str) {
240 self.0.set("Vcs-Darcs", url);
241 }
242
243 pub fn vcs_mtn(&self) -> Option<String> {
245 self.0.get("Vcs-Mtn").map(|s| s.to_string())
246 }
247
248 pub fn set_vcs_mtn(&mut self, url: &str) {
250 self.0.set("Vcs-Mtn", url);
251 }
252
253 pub fn vcs_cvs(&self) -> Option<String> {
255 self.0.get("Vcs-Cvs").map(|s| s.to_string())
256 }
257
258 pub fn set_vcs_cvs(&mut self, url: &str) {
260 self.0.set("Vcs-Cvs", url);
261 }
262
263 pub fn build_depends(&self) -> Option<Relations> {
265 self.0
266 .get_with_comments("Build-Depends")
267 .map(|s| s.parse().unwrap())
268 }
269
270 pub fn set_build_depends(&mut self, relations: Relations) {
272 self.0.set("Build-Depends", relations.to_string().as_str());
273 }
274
275 pub fn build_depends_indep(&self) -> Option<Relations> {
277 self.0
278 .get_with_comments("Build-Depends-Indep")
279 .map(|s| s.parse().unwrap())
280 }
281
282 pub fn set_build_depends_indep(&mut self, relations: Relations) {
284 self.0.set("Build-Depends-Indep", &relations.to_string());
285 }
286
287 pub fn build_depends_arch(&self) -> Option<Relations> {
289 self.0
290 .get_with_comments("Build-Depends-Arch")
291 .map(|s| s.parse().unwrap())
292 }
293
294 pub fn set_build_depends_arch(&mut self, relations: Relations) {
296 self.0.set("Build-Depends-Arch", &relations.to_string());
297 }
298
299 pub fn build_conflicts(&self) -> Option<Relations> {
301 self.0
302 .get_with_comments("Build-Conflicts")
303 .map(|s| s.parse().unwrap())
304 }
305
306 pub fn set_build_conflicts(&mut self, relations: Relations) {
308 self.0.set("Build-Conflicts", &relations.to_string());
309 }
310
311 pub fn build_conflicts_indep(&self) -> Option<Relations> {
313 self.0
314 .get_with_comments("Build-Conflicts-Indep")
315 .map(|s| s.parse().unwrap())
316 }
317
318 pub fn set_build_conflicts_indep(&mut self, relations: Relations) {
320 self.0.set("Build-Conflicts-Indep", &relations.to_string());
321 }
322
323 pub fn build_conflicts_arch(&self) -> Option<Relations> {
325 self.0
326 .get_with_comments("Build-Conflicts-Arch")
327 .map(|s| s.parse().unwrap())
328 }
329
330 pub fn set_build_conflicts_arch(&mut self, relations: Relations) {
332 self.0.set("Build-Conflicts-Arch", &relations.to_string());
333 }
334
335 pub fn binary(&self) -> Option<Relations> {
337 self.0.get("Binary").map(|s| s.parse().unwrap())
338 }
339
340 pub fn set_binary(&mut self, relations: Relations) {
342 self.0.set("Binary", &relations.to_string());
343 }
344
345 pub fn homepage(&self) -> Option<String> {
347 self.0.get("Homepage").map(|s| s.to_string())
348 }
349
350 pub fn set_homepage(&mut self, url: &str) {
352 self.0.set("Homepage", url);
353 }
354
355 pub fn section(&self) -> Option<String> {
357 self.0.get("Section").map(|s| s.to_string())
358 }
359
360 pub fn set_section(&mut self, section: &str) {
362 self.0.set("Section", section);
363 }
364
365 pub fn priority(&self) -> Option<Priority> {
367 self.0.get("Priority").and_then(|v| v.parse().ok())
368 }
369
370 pub fn set_priority(&mut self, priority: Priority) {
372 self.0.set("Priority", priority.to_string().as_str());
373 }
374
375 pub fn architecture(&self) -> Option<String> {
377 self.0.get("Architecture")
378 }
379
380 pub fn set_architecture(&mut self, arch: &str) {
382 self.0.set("Architecture", arch);
383 }
384
385 pub fn directory(&self) -> Option<String> {
387 self.0.get("Directory").map(|s| s.to_string())
388 }
389
390 pub fn set_directory(&mut self, dir: &str) {
392 self.0.set("Directory", dir);
393 }
394
395 pub fn testsuite(&self) -> Option<String> {
397 self.0.get("Testsuite").map(|s| s.to_string())
398 }
399
400 pub fn set_testsuite(&mut self, testsuite: &str) {
402 self.0.set("Testsuite", testsuite);
403 }
404
405 pub fn files(&self) -> Vec<Md5Checksum> {
407 self.0
408 .get("Files")
409 .map(|s| {
410 s.lines()
411 .map(|line| line.parse().unwrap())
412 .collect::<Vec<Md5Checksum>>()
413 })
414 .unwrap_or_default()
415 }
416
417 pub fn set_files(&mut self, files: Vec<Md5Checksum>) {
419 self.0.set(
420 "Files",
421 &files
422 .iter()
423 .map(|f| f.to_string())
424 .collect::<Vec<String>>()
425 .join("\n"),
426 );
427 }
428
429 pub fn checksums_sha1(&self) -> Vec<Sha1Checksum> {
431 self.0
432 .get("Checksums-Sha1")
433 .map(|s| {
434 s.lines()
435 .map(|line| line.parse().unwrap())
436 .collect::<Vec<Sha1Checksum>>()
437 })
438 .unwrap_or_default()
439 }
440
441 pub fn set_checksums_sha1(&mut self, checksums: Vec<Sha1Checksum>) {
443 self.0.set(
444 "Checksums-Sha1",
445 &checksums
446 .iter()
447 .map(|c| c.to_string())
448 .collect::<Vec<String>>()
449 .join("\n"),
450 );
451 }
452
453 pub fn checksums_sha256(&self) -> Vec<Sha256Checksum> {
455 self.0
456 .get("Checksums-Sha256")
457 .map(|s| {
458 s.lines()
459 .map(|line| line.parse().unwrap())
460 .collect::<Vec<Sha256Checksum>>()
461 })
462 .unwrap_or_default()
463 }
464
465 pub fn set_checksums_sha256(&mut self, checksums: Vec<Sha256Checksum>) {
467 self.0.set(
468 "Checksums-Sha256",
469 &checksums
470 .iter()
471 .map(|c| c.to_string())
472 .collect::<Vec<String>>()
473 .join("\n"),
474 );
475 }
476
477 pub fn checksums_sha512(&self) -> Vec<Sha512Checksum> {
479 self.0
480 .get("Checksums-Sha512")
481 .map(|s| {
482 s.lines()
483 .map(|line| line.parse().unwrap())
484 .collect::<Vec<Sha512Checksum>>()
485 })
486 .unwrap_or_default()
487 }
488
489 pub fn set_checksums_sha512(&mut self, checksums: Vec<Sha512Checksum>) {
491 self.0.set(
492 "Checksums-Sha512",
493 &checksums
494 .iter()
495 .map(|c| c.to_string())
496 .collect::<Vec<String>>()
497 .join("\n"),
498 );
499 }
500}
501
502impl std::str::FromStr for Source {
503 type Err = deb822_lossless::ParseError;
504
505 fn from_str(s: &str) -> Result<Self, Self::Err> {
506 Source::parse(s).to_result()
507 }
508}
509
510impl AstNode for Source {
511 type Language = deb822_lossless::Lang;
512
513 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
514 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
516 }
517
518 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
519 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
521 Some(Source(para))
522 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
523 deb822.paragraphs().next().map(Source)
525 } else {
526 None
527 }
528 }
529
530 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
531 self.0.syntax()
532 }
533}
534
535#[derive(Debug, Clone, PartialEq, Eq)]
537pub struct Package(deb822_lossless::Paragraph);
538
539#[cfg(feature = "python-debian")]
540impl<'py> pyo3::IntoPyObject<'py> for Package {
541 type Target = pyo3::PyAny;
542 type Output = pyo3::Bound<'py, Self::Target>;
543 type Error = pyo3::PyErr;
544
545 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
546 use pyo3::prelude::*;
547 let d = self.0.into_pyobject(py)?;
548
549 let m = py.import("debian.deb822")?;
550 let cls = m.getattr("Packages")?;
551
552 cls.call1((d,))
553 }
554}
555
556#[cfg(feature = "python-debian")]
557impl<'py> pyo3::IntoPyObject<'py> for &Package {
558 type Target = pyo3::PyAny;
559 type Output = pyo3::Bound<'py, Self::Target>;
560 type Error = pyo3::PyErr;
561
562 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
563 use pyo3::prelude::*;
564 let d = (&self.0).into_pyobject(py)?;
565
566 let m = py.import("debian.deb822")?;
567 let cls = m.getattr("Packages")?;
568
569 cls.call1((d,))
570 }
571}
572
573#[cfg(feature = "python-debian")]
574impl<'py> pyo3::FromPyObject<'_, 'py> for Package {
575 type Error = pyo3::PyErr;
576
577 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
578 Ok(Package(ob.extract()?))
579 }
580}
581
582impl Package {
583 pub fn new(paragraph: deb822_lossless::Paragraph) -> Self {
585 Self(paragraph)
586 }
587
588 pub fn parse(text: &str) -> deb822_lossless::Parse<Package> {
592 let deb822_parse = deb822_lossless::Deb822::parse(text);
593 let green = deb822_parse.green().clone();
594 let mut errors = deb822_parse.errors().to_vec();
595
596 if errors.is_empty() {
598 let deb822 = deb822_parse.tree();
599 let paragraph_count = deb822.paragraphs().count();
600 if paragraph_count == 0 {
601 errors.push("No paragraphs found".to_string());
602 } else if paragraph_count > 1 {
603 errors.push("Multiple paragraphs found, expected one".to_string());
604 }
605 }
606
607 deb822_lossless::Parse::new(green, errors)
608 }
609
610 pub fn name(&self) -> Option<String> {
612 self.0.get("Package").map(|s| s.to_string())
613 }
614
615 pub fn set_name(&mut self, name: &str) {
617 self.0.set("Package", name);
618 }
619
620 pub fn version(&self) -> Option<debversion::Version> {
622 self.0.get("Version").map(|s| s.parse().unwrap())
623 }
624
625 pub fn set_version(&mut self, version: debversion::Version) {
627 self.0.set("Version", &version.to_string());
628 }
629
630 pub fn installed_size(&self) -> Option<usize> {
632 self.0.get("Installed-Size").map(|s| s.parse().unwrap())
633 }
634
635 pub fn set_installed_size(&mut self, size: usize) {
637 self.0.set("Installed-Size", &size.to_string());
638 }
639
640 pub fn maintainer(&self) -> Option<String> {
642 self.0.get("Maintainer").map(|s| s.to_string())
643 }
644
645 pub fn set_maintainer(&mut self, maintainer: &str) {
647 self.0.set("Maintainer", maintainer);
648 }
649
650 pub fn architecture(&self) -> Option<String> {
652 self.0.get("Architecture").map(|s| s.to_string())
653 }
654
655 pub fn set_architecture(&mut self, arch: &str) {
657 self.0.set("Architecture", arch);
658 }
659
660 pub fn depends(&self) -> Option<Relations> {
662 self.0
663 .get_with_comments("Depends")
664 .map(|s| s.parse().unwrap())
665 }
666
667 pub fn set_depends(&mut self, relations: Relations) {
669 self.0.set("Depends", &relations.to_string());
670 }
671
672 pub fn recommends(&self) -> Option<Relations> {
674 self.0
675 .get_with_comments("Recommends")
676 .map(|s| s.parse().unwrap())
677 }
678
679 pub fn set_recommends(&mut self, relations: Relations) {
681 self.0.set("Recommends", &relations.to_string());
682 }
683
684 pub fn suggests(&self) -> Option<Relations> {
686 self.0
687 .get_with_comments("Suggests")
688 .map(|s| s.parse().unwrap())
689 }
690
691 pub fn set_suggests(&mut self, relations: Relations) {
693 self.0.set("Suggests", &relations.to_string());
694 }
695
696 pub fn enhances(&self) -> Option<Relations> {
698 self.0
699 .get_with_comments("Enhances")
700 .map(|s| s.parse().unwrap())
701 }
702
703 pub fn set_enhances(&mut self, relations: Relations) {
705 self.0.set("Enhances", &relations.to_string());
706 }
707
708 pub fn pre_depends(&self) -> Option<Relations> {
710 self.0
711 .get_with_comments("Pre-Depends")
712 .map(|s| s.parse().unwrap())
713 }
714
715 pub fn set_pre_depends(&mut self, relations: Relations) {
717 self.0.set("Pre-Depends", &relations.to_string());
718 }
719
720 pub fn breaks(&self) -> Option<Relations> {
722 self.0
723 .get_with_comments("Breaks")
724 .map(|s| s.parse().unwrap())
725 }
726
727 pub fn set_breaks(&mut self, relations: Relations) {
729 self.0.set("Breaks", &relations.to_string());
730 }
731
732 pub fn conflicts(&self) -> Option<Relations> {
734 self.0
735 .get_with_comments("Conflicts")
736 .map(|s| s.parse().unwrap())
737 }
738
739 pub fn set_conflicts(&mut self, relations: Relations) {
741 self.0.set("Conflicts", &relations.to_string());
742 }
743
744 pub fn replaces(&self) -> Option<Relations> {
746 self.0
747 .get_with_comments("Replaces")
748 .map(|s| s.parse().unwrap())
749 }
750
751 pub fn set_replaces(&mut self, relations: Relations) {
753 self.0.set("Replaces", &relations.to_string());
754 }
755
756 pub fn provides(&self) -> Option<Relations> {
758 self.0
759 .get_with_comments("Provides")
760 .map(|s| s.parse().unwrap())
761 }
762
763 pub fn set_provides(&mut self, relations: Relations) {
765 self.0.set("Provides", &relations.to_string());
766 }
767
768 pub fn section(&self) -> Option<String> {
770 self.0.get("Section").map(|s| s.to_string())
771 }
772
773 pub fn set_section(&mut self, section: &str) {
775 self.0.set("Section", section);
776 }
777
778 pub fn priority(&self) -> Option<Priority> {
780 self.0.get("Priority").and_then(|v| v.parse().ok())
781 }
782
783 pub fn set_priority(&mut self, priority: Priority) {
785 self.0.set("Priority", priority.to_string().as_str());
786 }
787
788 pub fn description(&self) -> Option<String> {
790 self.0.get_multiline("Description")
791 }
792
793 pub fn set_description(&mut self, description: &str) {
795 self.0.set("Description", description);
796 }
797
798 pub fn homepage(&self) -> Option<url::Url> {
800 self.0.get("Homepage").map(|s| s.parse().unwrap())
801 }
802
803 pub fn set_homepage(&mut self, url: &url::Url) {
805 self.0.set("Homepage", url.as_ref());
806 }
807
808 pub fn source(&self) -> Option<String> {
810 self.0.get("Source").map(|s| s.to_string())
811 }
812
813 pub fn set_source(&mut self, source: &str) {
815 self.0.set("Source", source);
816 }
817
818 pub fn description_md5(&self) -> Option<String> {
820 self.0.get("Description-md5").map(|s| s.to_string())
821 }
822
823 pub fn set_description_md5(&mut self, md5: &str) {
825 self.0.set("Description-md5", md5);
826 }
827
828 pub fn tags(&self, tag: &str) -> Option<Vec<String>> {
830 self.0
831 .get(tag)
832 .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
833 }
834
835 pub fn set_tags(&mut self, tag: &str, tags: Vec<String>) {
837 self.0.set(tag, &tags.join(", "));
838 }
839
840 pub fn filename(&self) -> Option<String> {
842 self.0.get("Filename").map(|s| s.to_string())
843 }
844
845 pub fn set_filename(&mut self, filename: &str) {
847 self.0.set("Filename", filename);
848 }
849
850 pub fn size(&self) -> Option<usize> {
852 self.0.get("Size").map(|s| s.parse().unwrap())
853 }
854
855 pub fn set_size(&mut self, size: usize) {
857 self.0.set("Size", &size.to_string());
858 }
859
860 pub fn md5sum(&self) -> Option<String> {
862 self.0.get("MD5sum").map(|s| s.to_string())
863 }
864
865 pub fn set_md5sum(&mut self, md5sum: &str) {
867 self.0.set("MD5sum", md5sum);
868 }
869
870 pub fn sha256(&self) -> Option<String> {
872 self.0.get("SHA256").map(|s| s.to_string())
873 }
874
875 pub fn set_sha256(&mut self, sha256: &str) {
877 self.0.set("SHA256", sha256);
878 }
879
880 pub fn multi_arch(&self) -> Option<MultiArch> {
882 self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
883 }
884
885 pub fn set_multi_arch(&mut self, arch: MultiArch) {
887 self.0.set("Multi-Arch", arch.to_string().as_str());
888 }
889}
890
891impl std::str::FromStr for Package {
892 type Err = deb822_lossless::ParseError;
893
894 fn from_str(s: &str) -> Result<Self, Self::Err> {
895 Package::parse(s).to_result()
896 }
897}
898
899impl AstNode for Package {
900 type Language = deb822_lossless::Lang;
901
902 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
903 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
904 }
905
906 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
907 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
908 Some(Package(para))
909 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
910 deb822.paragraphs().next().map(Package)
911 } else {
912 None
913 }
914 }
915
916 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
917 self.0.syntax()
918 }
919}
920
921pub struct Release(deb822_lossless::Paragraph);
923
924#[cfg(feature = "python-debian")]
925impl<'py> pyo3::IntoPyObject<'py> for Release {
926 type Target = pyo3::PyAny;
927 type Output = pyo3::Bound<'py, Self::Target>;
928 type Error = pyo3::PyErr;
929
930 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
931 use pyo3::prelude::*;
932 let d = self.0.into_pyobject(py)?;
933
934 let m = py.import("debian.deb822")?;
935 let cls = m.getattr("Release")?;
936
937 cls.call1((d,))
938 }
939}
940
941#[cfg(feature = "python-debian")]
942impl<'py> pyo3::IntoPyObject<'py> for &Release {
943 type Target = pyo3::PyAny;
944 type Output = pyo3::Bound<'py, Self::Target>;
945 type Error = pyo3::PyErr;
946
947 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
948 use pyo3::prelude::*;
949 let d = (&self.0).into_pyobject(py)?;
950
951 let m = py.import("debian.deb822")?;
952 let cls = m.getattr("Release")?;
953
954 cls.call1((d,))
955 }
956}
957
958#[cfg(feature = "python-debian")]
959impl<'py> pyo3::FromPyObject<'_, 'py> for Release {
960 type Error = pyo3::PyErr;
961
962 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
963 Ok(Release(ob.extract()?))
964 }
965}
966
967impl Release {
968 pub fn new(paragraph: deb822_lossless::Paragraph) -> Self {
970 Self(paragraph)
971 }
972
973 pub fn parse(text: &str) -> deb822_lossless::Parse<Release> {
977 let deb822_parse = deb822_lossless::Deb822::parse(text);
978 let green = deb822_parse.green().clone();
979 let mut errors = deb822_parse.errors().to_vec();
980
981 if errors.is_empty() {
983 let deb822 = deb822_parse.tree();
984 let paragraph_count = deb822.paragraphs().count();
985 if paragraph_count == 0 {
986 errors.push("No paragraphs found".to_string());
987 } else if paragraph_count > 1 {
988 errors.push("Multiple paragraphs found, expected one".to_string());
989 }
990 }
991
992 deb822_lossless::Parse::new(green, errors)
993 }
994
995 pub fn origin(&self) -> Option<String> {
997 self.0.get("Origin").map(|s| s.to_string())
998 }
999
1000 pub fn set_origin(&mut self, origin: &str) {
1002 self.0.set("Origin", origin);
1003 }
1004
1005 pub fn label(&self) -> Option<String> {
1007 self.0.get("Label").map(|s| s.to_string())
1008 }
1009
1010 pub fn set_label(&mut self, label: &str) {
1012 self.0.set("Label", label);
1013 }
1014
1015 pub fn suite(&self) -> Option<String> {
1017 self.0.get("Suite").map(|s| s.to_string())
1018 }
1019
1020 pub fn set_suite(&mut self, suite: &str) {
1022 self.0.set("Suite", suite);
1023 }
1024
1025 pub fn codename(&self) -> Option<String> {
1027 self.0.get("Codename").map(|s| s.to_string())
1028 }
1029
1030 pub fn set_codename(&mut self, codename: &str) {
1032 self.0.set("Codename", codename);
1033 }
1034
1035 pub fn changelogs(&self) -> Option<Vec<String>> {
1037 self.0.get("Changelogs").map(|s| {
1038 s.split(',')
1039 .map(|s| s.trim().to_string())
1040 .collect::<Vec<String>>()
1041 })
1042 }
1043
1044 pub fn set_changelogs(&mut self, changelogs: Vec<String>) {
1046 self.0.set("Changelogs", &changelogs.join(", "));
1047 }
1048
1049 #[cfg(feature = "chrono")]
1050 pub fn date(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
1052 self.0
1053 .get("Date")
1054 .as_ref()
1055 .map(|s| chrono::DateTime::parse_from_rfc2822(s).unwrap())
1056 }
1057
1058 #[cfg(feature = "chrono")]
1059 pub fn set_date(&mut self, date: chrono::DateTime<chrono::FixedOffset>) {
1061 self.0.set("Date", date.to_rfc2822().as_str());
1062 }
1063
1064 #[cfg(feature = "chrono")]
1065 pub fn valid_until(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
1067 self.0
1068 .get("Valid-Until")
1069 .as_ref()
1070 .map(|s| chrono::DateTime::parse_from_rfc2822(s).unwrap())
1071 }
1072
1073 #[cfg(feature = "chrono")]
1074 pub fn set_valid_until(&mut self, date: chrono::DateTime<chrono::FixedOffset>) {
1076 self.0.set("Valid-Until", date.to_rfc2822().as_str());
1077 }
1078
1079 pub fn acquire_by_hash(&self) -> bool {
1081 self.0
1082 .get("Acquire-By-Hash")
1083 .map(|s| s == "yes")
1084 .unwrap_or(false)
1085 }
1086
1087 pub fn set_acquire_by_hash(&mut self, acquire_by_hash: bool) {
1089 self.0.set(
1090 "Acquire-By-Hash",
1091 if acquire_by_hash { "yes" } else { "no" },
1092 );
1093 }
1094
1095 pub fn no_support_for_architecture_all(&self) -> bool {
1097 self.0
1098 .get("No-Support-For-Architecture-All")
1099 .map(|s| s == "yes")
1100 .unwrap_or(false)
1101 }
1102
1103 pub fn set_no_support_for_architecture_all(&mut self, no_support_for_architecture_all: bool) {
1105 self.0.set(
1106 "No-Support-For-Architecture-All",
1107 if no_support_for_architecture_all {
1108 "yes"
1109 } else {
1110 "no"
1111 },
1112 );
1113 }
1114
1115 pub fn architectures(&self) -> Option<Vec<String>> {
1117 self.0.get("Architectures").map(|s| {
1118 s.split_whitespace()
1119 .map(|s| s.trim().to_string())
1120 .collect::<Vec<String>>()
1121 })
1122 }
1123
1124 pub fn set_architectures(&mut self, architectures: Vec<String>) {
1126 self.0.set("Architectures", &architectures.join(" "));
1127 }
1128
1129 pub fn components(&self) -> Option<Vec<String>> {
1131 self.0.get("Components").map(|s| {
1132 s.split_whitespace()
1133 .map(|s| s.trim().to_string())
1134 .collect::<Vec<String>>()
1135 })
1136 }
1137
1138 pub fn set_components(&mut self, components: Vec<String>) {
1140 self.0.set("Components", &components.join(" "));
1141 }
1142
1143 pub fn description(&self) -> Option<String> {
1145 self.0.get_multiline("Description")
1146 }
1147
1148 pub fn set_description(&mut self, description: &str) {
1150 self.0.set("Description", description);
1151 }
1152
1153 pub fn checksums_md5(&self) -> Vec<Md5Checksum> {
1155 self.0
1156 .get("MD5Sum")
1157 .map(|s| {
1158 s.lines()
1159 .map(|line| line.parse().unwrap())
1160 .collect::<Vec<Md5Checksum>>()
1161 })
1162 .unwrap_or_default()
1163 }
1164
1165 pub fn set_checksums_md5(&mut self, files: Vec<Md5Checksum>) {
1167 self.0.set(
1168 "MD5Sum",
1169 &files
1170 .iter()
1171 .map(|f| f.to_string())
1172 .collect::<Vec<String>>()
1173 .join("\n"),
1174 );
1175 }
1176
1177 pub fn checksums_sha1(&self) -> Vec<Sha1Checksum> {
1179 self.0
1180 .get("SHA1")
1181 .map(|s| {
1182 s.lines()
1183 .map(|line| line.parse().unwrap())
1184 .collect::<Vec<Sha1Checksum>>()
1185 })
1186 .unwrap_or_default()
1187 }
1188
1189 pub fn set_checksums_sha1(&mut self, checksums: Vec<Sha1Checksum>) {
1191 self.0.set(
1192 "SHA1",
1193 &checksums
1194 .iter()
1195 .map(|c| c.to_string())
1196 .collect::<Vec<String>>()
1197 .join("\n"),
1198 );
1199 }
1200
1201 pub fn checksums_sha256(&self) -> Vec<Sha256Checksum> {
1203 self.0
1204 .get("SHA256")
1205 .map(|s| {
1206 s.lines()
1207 .map(|line| line.parse().unwrap())
1208 .collect::<Vec<Sha256Checksum>>()
1209 })
1210 .unwrap_or_default()
1211 }
1212
1213 pub fn set_checksums_sha256(&mut self, checksums: Vec<Sha256Checksum>) {
1215 self.0.set(
1216 "SHA256",
1217 &checksums
1218 .iter()
1219 .map(|c| c.to_string())
1220 .collect::<Vec<String>>()
1221 .join("\n"),
1222 );
1223 }
1224
1225 pub fn checksums_sha512(&self) -> Vec<Sha512Checksum> {
1227 self.0
1228 .get("SHA512")
1229 .map(|s| {
1230 s.lines()
1231 .map(|line| line.parse().unwrap())
1232 .collect::<Vec<Sha512Checksum>>()
1233 })
1234 .unwrap_or_default()
1235 }
1236
1237 pub fn set_checksums_sha512(&mut self, checksums: Vec<Sha512Checksum>) {
1239 self.0.set(
1240 "SHA512",
1241 &checksums
1242 .iter()
1243 .map(|c| c.to_string())
1244 .collect::<Vec<String>>()
1245 .join("\n"),
1246 );
1247 }
1248}
1249
1250impl std::str::FromStr for Release {
1251 type Err = deb822_lossless::ParseError;
1252
1253 fn from_str(s: &str) -> Result<Self, Self::Err> {
1254 Release::parse(s).to_result()
1255 }
1256}
1257
1258impl AstNode for Release {
1259 type Language = deb822_lossless::Lang;
1260
1261 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
1262 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
1263 }
1264
1265 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
1266 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
1267 Some(Release(para))
1268 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
1269 deb822.paragraphs().next().map(Release)
1270 } else {
1271 None
1272 }
1273 }
1274
1275 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
1276 self.0.syntax()
1277 }
1278}
1279
1280#[cfg(test)]
1281mod tests {
1282 use super::*;
1283 use crate::fields::PackageListEntry;
1284
1285 #[test]
1286 fn test_parse_package_list() {
1287 let s = "package1 binary section standard extra1=foo extra2=bar";
1288 let p: PackageListEntry = s.parse().unwrap();
1289 assert_eq!(p.package, "package1");
1290 assert_eq!(p.package_type, "binary");
1291 assert_eq!(p.section, "section");
1292 assert_eq!(p.priority, super::Priority::Standard);
1293 assert_eq!(p.extra.get("extra1"), Some(&"foo".to_string()));
1294 assert_eq!(p.extra.get("extra2"), Some(&"bar".to_string()));
1295 }
1296
1297 #[test]
1298 fn test_parse_package_list_no_extra() {
1299 let s = "package1 binary section standard";
1300 let p: PackageListEntry = s.parse().unwrap();
1301 assert_eq!(p.package, "package1");
1302 assert_eq!(p.package_type, "binary");
1303 assert_eq!(p.section, "section");
1304 assert_eq!(p.priority, super::Priority::Standard);
1305 assert!(p.extra.is_empty());
1306 }
1307
1308 #[test]
1309 fn test_files() {
1310 let s = "md5sum 1234 filename";
1311 let f: super::Md5Checksum = s.parse().unwrap();
1312 assert_eq!(f.md5sum, "md5sum");
1313 assert_eq!(f.size, 1234);
1314 assert_eq!(f.filename, "filename");
1315 }
1316
1317 #[test]
1318 fn test_sha1_checksum() {
1319 let s = "sha1 1234 filename";
1320 let f: super::Sha1Checksum = s.parse().unwrap();
1321 assert_eq!(f.sha1, "sha1");
1322 assert_eq!(f.size, 1234);
1323 assert_eq!(f.filename, "filename");
1324 }
1325
1326 #[test]
1327 fn test_sha256_checksum() {
1328 let s = "sha256 1234 filename";
1329 let f: super::Sha256Checksum = s.parse().unwrap();
1330 assert_eq!(f.sha256, "sha256");
1331 assert_eq!(f.size, 1234);
1332 assert_eq!(f.filename, "filename");
1333 }
1334
1335 #[test]
1336 fn test_sha512_checksum() {
1337 let s = "sha512 1234 filename";
1338 let f: super::Sha512Checksum = s.parse().unwrap();
1339 assert_eq!(f.sha512, "sha512");
1340 assert_eq!(f.size, 1234);
1341 assert_eq!(f.filename, "filename");
1342 }
1343
1344 #[test]
1345 fn test_source() {
1346 let s = r#"Package: foo
1347Version: 1.0
1348Maintainer: John Doe <john@example.com>
1349Uploaders: Jane Doe <jane@example.com>
1350Standards-Version: 3.9.8
1351Format: 3.0 (quilt)
1352Vcs-Browser: https://example.com/foo
1353Vcs-Git: https://example.com/foo.git
1354Build-Depends: debhelper (>= 9)
1355Build-Depends-Indep: python
1356Build-Depends-Arch: gcc
1357Build-Conflicts: bar
1358Build-Conflicts-Indep: python
1359Build-Conflicts-Arch: gcc
1360Binary: foo, bar
1361Homepage: https://example.com/foo
1362Section: devel
1363Priority: optional
1364Architecture: any
1365Directory: pool/main/f/foo
1366Files:
1367 25dcf3b4b6b3b3b3b3b3b3b3b3b3b3b3 1234 foo_1.0.tar.gz
1368Checksums-Sha1:
1369 b72b5fae3b3b3b3b3b3b3b3b3b3b3b3 1234 foo_1.0.tar.gz
1370"#;
1371 let p: super::Source = s.parse().unwrap();
1372 assert_eq!(p.package(), Some("foo".to_string()));
1373 assert_eq!(p.version(), Some("1.0".parse().unwrap()));
1374 assert_eq!(
1375 p.maintainer(),
1376 Some("John Doe <john@example.com>".to_string())
1377 );
1378 assert_eq!(
1379 p.uploaders(),
1380 Some(vec!["Jane Doe <jane@example.com>".to_string()])
1381 );
1382 assert_eq!(p.standards_version(), Some("3.9.8".to_string()));
1383 assert_eq!(p.format(), Some("3.0 (quilt)".to_string()));
1384 assert_eq!(p.vcs_browser(), Some("https://example.com/foo".to_string()));
1385 assert_eq!(p.vcs_git(), Some("https://example.com/foo.git".to_string()));
1386 assert_eq!(
1387 p.build_depends_indep().map(|x| x.to_string()),
1388 Some("python".parse().unwrap())
1389 );
1390 assert_eq!(p.build_depends(), Some("debhelper (>= 9)".parse().unwrap()));
1391 assert_eq!(p.build_depends_arch(), Some("gcc".parse().unwrap()));
1392 assert_eq!(p.build_conflicts(), Some("bar".parse().unwrap()));
1393 assert_eq!(p.build_conflicts_indep(), Some("python".parse().unwrap()));
1394 assert_eq!(p.build_conflicts_arch(), Some("gcc".parse().unwrap()));
1395 assert_eq!(p.binary(), Some("foo, bar".parse().unwrap()));
1396 assert_eq!(p.homepage(), Some("https://example.com/foo".to_string()));
1397 assert_eq!(p.section(), Some("devel".to_string()));
1398 assert_eq!(p.priority(), Some(super::Priority::Optional));
1399 assert_eq!(p.architecture(), Some("any".to_string()));
1400 assert_eq!(p.directory(), Some("pool/main/f/foo".to_string()));
1401 assert_eq!(p.files().len(), 1);
1402 assert_eq!(
1403 p.files()[0].md5sum,
1404 "25dcf3b4b6b3b3b3b3b3b3b3b3b3b3b3".to_string()
1405 );
1406 assert_eq!(p.files()[0].size, 1234);
1407 assert_eq!(p.files()[0].filename, "foo_1.0.tar.gz".to_string());
1408 assert_eq!(p.checksums_sha1().len(), 1);
1409 assert_eq!(
1410 p.checksums_sha1()[0].sha1,
1411 "b72b5fae3b3b3b3b3b3b3b3b3b3b3b3".to_string()
1412 );
1413 }
1414
1415 #[test]
1416 fn test_package() {
1417 let s = r#"Package: foo
1418Version: 1.0
1419Source: bar
1420Maintainer: John Doe <john@example.com>
1421Architecture: any
1422Depends: bar
1423Recommends: baz
1424Suggests: qux
1425Enhances: quux
1426Pre-Depends: quuz
1427Breaks: corge
1428Conflicts: grault
1429Replaces: garply
1430Provides: waldo
1431Section: devel
1432Priority: optional
1433Description: Foo is a bar
1434Homepage: https://example.com/foo
1435Description-md5: 1234
1436Tags: foo, bar
1437Filename: pool/main/f/foo/foo_1.0.deb
1438Size: 1234
1439Installed-Size: 1234
1440MD5sum: 1234
1441SHA256: 1234
1442Multi-Arch: same
1443"#;
1444 let p: super::Package = s.parse().unwrap();
1445 assert_eq!(p.name(), Some("foo".to_string()));
1446 assert_eq!(p.version(), Some("1.0".parse().unwrap()));
1447 assert_eq!(p.source(), Some("bar".to_string()));
1448 assert_eq!(
1449 p.maintainer(),
1450 Some("John Doe <john@example.com>".to_string())
1451 );
1452 assert_eq!(p.architecture(), Some("any".to_string()));
1453 assert_eq!(p.depends(), Some("bar".parse().unwrap()));
1454 assert_eq!(p.recommends(), Some("baz".parse().unwrap()));
1455 assert_eq!(p.suggests(), Some("qux".parse().unwrap()));
1456 assert_eq!(p.enhances(), Some("quux".parse().unwrap()));
1457 assert_eq!(p.pre_depends(), Some("quuz".parse().unwrap()));
1458 assert_eq!(p.breaks(), Some("corge".parse().unwrap()));
1459 assert_eq!(p.conflicts(), Some("grault".parse().unwrap()));
1460 assert_eq!(p.replaces(), Some("garply".parse().unwrap()));
1461 assert_eq!(p.provides(), Some("waldo".parse().unwrap()));
1462 assert_eq!(p.section(), Some("devel".to_string()));
1463 assert_eq!(p.priority(), Some(super::Priority::Optional));
1464 assert_eq!(p.description(), Some("Foo is a bar".to_string()));
1465 assert_eq!(
1466 p.homepage(),
1467 Some(url::Url::parse("https://example.com/foo").unwrap())
1468 );
1469 assert_eq!(p.description_md5(), Some("1234".to_string()));
1470 assert_eq!(
1471 p.tags("Tags"),
1472 Some(vec!["foo".to_string(), "bar".to_string()])
1473 );
1474 assert_eq!(
1475 p.filename(),
1476 Some("pool/main/f/foo/foo_1.0.deb".to_string())
1477 );
1478 assert_eq!(p.size(), Some(1234));
1479 assert_eq!(p.installed_size(), Some(1234));
1480 assert_eq!(p.md5sum(), Some("1234".to_string()));
1481 assert_eq!(p.sha256(), Some("1234".to_string()));
1482 assert_eq!(p.multi_arch(), Some(MultiArch::Same));
1483 }
1484
1485 #[test]
1486 fn test_release() {
1487 let s = include_str!("../testdata/Release");
1488 let release: super::Release = s.parse().unwrap();
1489
1490 assert_eq!(release.origin(), Some("Debian".to_string()));
1491 assert_eq!(release.label(), Some("Debian".to_string()));
1492 assert_eq!(release.suite(), Some("testing".to_string()));
1493 assert_eq!(
1494 release.architectures(),
1495 vec![
1496 "all".to_string(),
1497 "amd64".to_string(),
1498 "arm64".to_string(),
1499 "armel".to_string(),
1500 "armhf".to_string()
1501 ]
1502 .into()
1503 );
1504 assert_eq!(
1505 release.components(),
1506 vec![
1507 "main".to_string(),
1508 "contrib".to_string(),
1509 "non-free-firmware".to_string(),
1510 "non-free".to_string()
1511 ]
1512 .into()
1513 );
1514 assert_eq!(
1515 release.description(),
1516 Some("Debian x.y Testing distribution - Not Released".to_string())
1517 );
1518 assert_eq!(318, release.checksums_md5().len());
1519 }
1520
1521 #[test]
1522 fn test_source_vcs_arch() {
1523 let s = r#"Package: foo
1524Version: 1.0
1525Vcs-Arch: https://example.com/arch/repo
1526"#;
1527 let p: super::Source = s.parse().unwrap();
1528 assert_eq!(
1529 p.vcs_arch(),
1530 Some("https://example.com/arch/repo".to_string())
1531 );
1532 }
1533
1534 #[test]
1535 fn test_source_vcs_cvs() {
1536 let s = r#"Package: foo
1537Version: 1.0
1538Vcs-Cvs: :pserver:anoncvs@example.com:/cvs/repo
1539"#;
1540 let p: super::Source = s.parse().unwrap();
1541 assert_eq!(
1542 p.vcs_cvs(),
1543 Some(":pserver:anoncvs@example.com:/cvs/repo".to_string())
1544 );
1545 }
1546
1547 #[test]
1548 fn test_source_set_priority() {
1549 let s = r#"Package: foo
1550Version: 1.0
1551Priority: optional
1552"#;
1553 let mut p: super::Source = s.parse().unwrap();
1554 p.set_priority(super::Priority::Required);
1555 assert_eq!(p.priority(), Some(super::Priority::Required));
1556 }
1557
1558 #[test]
1559 fn test_release_set_suite() {
1560 let s = r#"Origin: Debian
1561Label: Debian
1562Suite: testing
1563"#;
1564 let mut release: super::Release = s.parse().unwrap();
1565 release.set_suite("unstable");
1566 assert_eq!(release.suite(), Some("unstable".to_string()));
1567 }
1568
1569 #[test]
1570 fn test_release_codename() {
1571 let s = r#"Origin: Debian
1572Label: Debian
1573Suite: testing
1574Codename: trixie
1575"#;
1576 let release: super::Release = s.parse().unwrap();
1577 assert_eq!(release.codename(), Some("trixie".to_string()));
1578 }
1579
1580 #[test]
1581 fn test_release_set_checksums_sha512() {
1582 let s = r#"Origin: Debian
1583Label: Debian
1584Suite: testing
1585"#;
1586 let mut release: super::Release = s.parse().unwrap();
1587 let checksums = vec![super::Sha512Checksum {
1588 sha512: "abc123".to_string(),
1589 size: 1234,
1590 filename: "test.deb".to_string(),
1591 }];
1592 release.set_checksums_sha512(checksums);
1593 assert_eq!(release.checksums_sha512().len(), 1);
1594 }
1595
1596 #[test]
1597 fn test_package_set_replaces() {
1598 let s = r#"Package: foo
1599Version: 1.0
1600"#;
1601 let mut p: super::Package = s.parse().unwrap();
1602 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1603 p.set_replaces(relations);
1604 assert_eq!(p.replaces(), Some("bar (>= 1.0.0)".parse().unwrap()));
1605 }
1606}