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