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<'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.get("Build-Depends").map(|s| s.parse().unwrap())
266 }
267
268 pub fn set_build_depends(&mut self, relations: Relations) {
270 self.0.set("Build-Depends", relations.to_string().as_str());
271 }
272
273 pub fn build_depends_indep(&self) -> Option<Relations> {
275 self.0
276 .get("Build-Depends-Indep")
277 .map(|s| s.parse().unwrap())
278 }
279
280 pub fn set_build_depends_indep(&mut self, relations: Relations) {
282 self.0.set("Build-Depends-Indep", &relations.to_string());
283 }
284
285 pub fn build_depends_arch(&self) -> Option<Relations> {
287 self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
288 }
289
290 pub fn set_build_depends_arch(&mut self, relations: Relations) {
292 self.0.set("Build-Depends-Arch", &relations.to_string());
293 }
294
295 pub fn build_conflicts(&self) -> Option<Relations> {
297 self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
298 }
299
300 pub fn set_build_conflicts(&mut self, relations: Relations) {
302 self.0.set("Build-Conflicts", &relations.to_string());
303 }
304
305 pub fn build_conflicts_indep(&self) -> Option<Relations> {
307 self.0
308 .get("Build-Conflicts-Indep")
309 .map(|s| s.parse().unwrap())
310 }
311
312 pub fn set_build_conflicts_indep(&mut self, relations: Relations) {
314 self.0.set("Build-Conflicts-Indep", &relations.to_string());
315 }
316
317 pub fn build_conflicts_arch(&self) -> Option<Relations> {
319 self.0
320 .get("Build-Conflicts-Arch")
321 .map(|s| s.parse().unwrap())
322 }
323
324 pub fn set_build_conflicts_arch(&mut self, relations: Relations) {
326 self.0.set("Build-Conflicts-Arch", &relations.to_string());
327 }
328
329 pub fn binary(&self) -> Option<Relations> {
331 self.0.get("Binary").map(|s| s.parse().unwrap())
332 }
333
334 pub fn set_binary(&mut self, relations: Relations) {
336 self.0.set("Binary", &relations.to_string());
337 }
338
339 pub fn homepage(&self) -> Option<String> {
341 self.0.get("Homepage").map(|s| s.to_string())
342 }
343
344 pub fn set_homepage(&mut self, url: &str) {
346 self.0.set("Homepage", url);
347 }
348
349 pub fn section(&self) -> Option<String> {
351 self.0.get("Section").map(|s| s.to_string())
352 }
353
354 pub fn set_section(&mut self, section: &str) {
356 self.0.set("Section", section);
357 }
358
359 pub fn priority(&self) -> Option<Priority> {
361 self.0.get("Priority").and_then(|v| v.parse().ok())
362 }
363
364 pub fn set_priority(&mut self, priority: Priority) {
366 self.0.set("Priority", priority.to_string().as_str());
367 }
368
369 pub fn architecture(&self) -> Option<String> {
371 self.0.get("Architecture")
372 }
373
374 pub fn set_architecture(&mut self, arch: &str) {
376 self.0.set("Architecture", arch);
377 }
378
379 pub fn directory(&self) -> Option<String> {
381 self.0.get("Directory").map(|s| s.to_string())
382 }
383
384 pub fn set_directory(&mut self, dir: &str) {
386 self.0.set("Directory", dir);
387 }
388
389 pub fn testsuite(&self) -> Option<String> {
391 self.0.get("Testsuite").map(|s| s.to_string())
392 }
393
394 pub fn set_testsuite(&mut self, testsuite: &str) {
396 self.0.set("Testsuite", testsuite);
397 }
398
399 pub fn files(&self) -> Vec<Md5Checksum> {
401 self.0
402 .get("Files")
403 .map(|s| {
404 s.lines()
405 .map(|line| line.parse().unwrap())
406 .collect::<Vec<Md5Checksum>>()
407 })
408 .unwrap_or_default()
409 }
410
411 pub fn set_files(&mut self, files: Vec<Md5Checksum>) {
413 self.0.set(
414 "Files",
415 &files
416 .iter()
417 .map(|f| f.to_string())
418 .collect::<Vec<String>>()
419 .join("\n"),
420 );
421 }
422
423 pub fn checksums_sha1(&self) -> Vec<Sha1Checksum> {
425 self.0
426 .get("Checksums-Sha1")
427 .map(|s| {
428 s.lines()
429 .map(|line| line.parse().unwrap())
430 .collect::<Vec<Sha1Checksum>>()
431 })
432 .unwrap_or_default()
433 }
434
435 pub fn set_checksums_sha1(&mut self, checksums: Vec<Sha1Checksum>) {
437 self.0.set(
438 "Checksums-Sha1",
439 &checksums
440 .iter()
441 .map(|c| c.to_string())
442 .collect::<Vec<String>>()
443 .join("\n"),
444 );
445 }
446
447 pub fn checksums_sha256(&self) -> Vec<Sha256Checksum> {
449 self.0
450 .get("Checksums-Sha256")
451 .map(|s| {
452 s.lines()
453 .map(|line| line.parse().unwrap())
454 .collect::<Vec<Sha256Checksum>>()
455 })
456 .unwrap_or_default()
457 }
458
459 pub fn set_checksums_sha256(&mut self, checksums: Vec<Sha256Checksum>) {
461 self.0.set(
462 "Checksums-Sha256",
463 &checksums
464 .iter()
465 .map(|c| c.to_string())
466 .collect::<Vec<String>>()
467 .join("\n"),
468 );
469 }
470
471 pub fn checksums_sha512(&self) -> Vec<Sha512Checksum> {
473 self.0
474 .get("Checksums-Sha512")
475 .map(|s| {
476 s.lines()
477 .map(|line| line.parse().unwrap())
478 .collect::<Vec<Sha512Checksum>>()
479 })
480 .unwrap_or_default()
481 }
482
483 pub fn set_checksums_sha512(&mut self, checksums: Vec<Sha512Checksum>) {
485 self.0.set(
486 "Checksums-Sha512",
487 &checksums
488 .iter()
489 .map(|c| c.to_string())
490 .collect::<Vec<String>>()
491 .join("\n"),
492 );
493 }
494}
495
496impl std::str::FromStr for Source {
497 type Err = deb822_lossless::ParseError;
498
499 fn from_str(s: &str) -> Result<Self, Self::Err> {
500 Source::parse(s).to_result()
501 }
502}
503
504impl AstNode for Source {
505 type Language = deb822_lossless::Lang;
506
507 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
508 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
510 }
511
512 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
513 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
515 Some(Source(para))
516 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
517 deb822.paragraphs().next().map(Source)
519 } else {
520 None
521 }
522 }
523
524 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
525 self.0.syntax()
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq)]
531pub struct Package(deb822_lossless::Paragraph);
532
533#[cfg(feature = "python-debian")]
534impl<'py> pyo3::IntoPyObject<'py> for Package {
535 type Target = pyo3::PyAny;
536 type Output = pyo3::Bound<'py, Self::Target>;
537 type Error = pyo3::PyErr;
538
539 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
540 use pyo3::prelude::*;
541 let d = self.0.into_pyobject(py)?;
542
543 let m = py.import("debian.deb822")?;
544 let cls = m.getattr("Packages")?;
545
546 cls.call1((d,))
547 }
548}
549
550#[cfg(feature = "python-debian")]
551impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Package {
552 type Target = pyo3::PyAny;
553 type Output = pyo3::Bound<'py, Self::Target>;
554 type Error = pyo3::PyErr;
555
556 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
557 use pyo3::prelude::*;
558 let d = (&self.0).into_pyobject(py)?;
559
560 let m = py.import("debian.deb822")?;
561 let cls = m.getattr("Packages")?;
562
563 cls.call1((d,))
564 }
565}
566
567#[cfg(feature = "python-debian")]
568impl<'py> pyo3::FromPyObject<'_, 'py> for Package {
569 type Error = pyo3::PyErr;
570
571 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
572 Ok(Package(ob.extract()?))
573 }
574}
575
576impl Package {
577 pub fn new(paragraph: deb822_lossless::Paragraph) -> Self {
579 Self(paragraph)
580 }
581
582 pub fn parse(text: &str) -> deb822_lossless::Parse<Package> {
586 let deb822_parse = deb822_lossless::Deb822::parse(text);
587 let green = deb822_parse.green().clone();
588 let mut errors = deb822_parse.errors().to_vec();
589
590 if errors.is_empty() {
592 let deb822 = deb822_parse.tree();
593 let paragraph_count = deb822.paragraphs().count();
594 if paragraph_count == 0 {
595 errors.push("No paragraphs found".to_string());
596 } else if paragraph_count > 1 {
597 errors.push("Multiple paragraphs found, expected one".to_string());
598 }
599 }
600
601 deb822_lossless::Parse::new(green, errors)
602 }
603
604 pub fn name(&self) -> Option<String> {
606 self.0.get("Package").map(|s| s.to_string())
607 }
608
609 pub fn set_name(&mut self, name: &str) {
611 self.0.set("Package", name);
612 }
613
614 pub fn version(&self) -> Option<debversion::Version> {
616 self.0.get("Version").map(|s| s.parse().unwrap())
617 }
618
619 pub fn set_version(&mut self, version: debversion::Version) {
621 self.0.set("Version", &version.to_string());
622 }
623
624 pub fn installed_size(&self) -> Option<usize> {
626 self.0.get("Installed-Size").map(|s| s.parse().unwrap())
627 }
628
629 pub fn set_installed_size(&mut self, size: usize) {
631 self.0.set("Installed-Size", &size.to_string());
632 }
633
634 pub fn maintainer(&self) -> Option<String> {
636 self.0.get("Maintainer").map(|s| s.to_string())
637 }
638
639 pub fn set_maintainer(&mut self, maintainer: &str) {
641 self.0.set("Maintainer", maintainer);
642 }
643
644 pub fn architecture(&self) -> Option<String> {
646 self.0.get("Architecture").map(|s| s.to_string())
647 }
648
649 pub fn set_architecture(&mut self, arch: &str) {
651 self.0.set("Architecture", arch);
652 }
653
654 pub fn depends(&self) -> Option<Relations> {
656 self.0.get("Depends").map(|s| s.parse().unwrap())
657 }
658
659 pub fn set_depends(&mut self, relations: Relations) {
661 self.0.set("Depends", &relations.to_string());
662 }
663
664 pub fn recommends(&self) -> Option<Relations> {
666 self.0.get("Recommends").map(|s| s.parse().unwrap())
667 }
668
669 pub fn set_recommends(&mut self, relations: Relations) {
671 self.0.set("Recommends", &relations.to_string());
672 }
673
674 pub fn suggests(&self) -> Option<Relations> {
676 self.0.get("Suggests").map(|s| s.parse().unwrap())
677 }
678
679 pub fn set_suggests(&mut self, relations: Relations) {
681 self.0.set("Suggests", &relations.to_string());
682 }
683
684 pub fn enhances(&self) -> Option<Relations> {
686 self.0.get("Enhances").map(|s| s.parse().unwrap())
687 }
688
689 pub fn set_enhances(&mut self, relations: Relations) {
691 self.0.set("Enhances", &relations.to_string());
692 }
693
694 pub fn pre_depends(&self) -> Option<Relations> {
696 self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
697 }
698
699 pub fn set_pre_depends(&mut self, relations: Relations) {
701 self.0.set("Pre-Depends", &relations.to_string());
702 }
703
704 pub fn breaks(&self) -> Option<Relations> {
706 self.0.get("Breaks").map(|s| s.parse().unwrap())
707 }
708
709 pub fn set_breaks(&mut self, relations: Relations) {
711 self.0.set("Breaks", &relations.to_string());
712 }
713
714 pub fn conflicts(&self) -> Option<Relations> {
716 self.0.get("Conflicts").map(|s| s.parse().unwrap())
717 }
718
719 pub fn set_conflicts(&mut self, relations: Relations) {
721 self.0.set("Conflicts", &relations.to_string());
722 }
723
724 pub fn replaces(&self) -> Option<Relations> {
726 self.0.get("Replaces").map(|s| s.parse().unwrap())
727 }
728
729 pub fn set_replaces(&mut self, relations: Relations) {
731 self.0.set("Replaces", &relations.to_string());
732 }
733
734 pub fn provides(&self) -> Option<Relations> {
736 self.0.get("Provides").map(|s| s.parse().unwrap())
737 }
738
739 pub fn set_provides(&mut self, relations: Relations) {
741 self.0.set("Provides", &relations.to_string());
742 }
743
744 pub fn section(&self) -> Option<String> {
746 self.0.get("Section").map(|s| s.to_string())
747 }
748
749 pub fn set_section(&mut self, section: &str) {
751 self.0.set("Section", section);
752 }
753
754 pub fn priority(&self) -> Option<Priority> {
756 self.0.get("Priority").and_then(|v| v.parse().ok())
757 }
758
759 pub fn set_priority(&mut self, priority: Priority) {
761 self.0.set("Priority", priority.to_string().as_str());
762 }
763
764 pub fn description(&self) -> Option<String> {
766 self.0.get("Description").map(|s| s.to_string())
767 }
768
769 pub fn set_description(&mut self, description: &str) {
771 self.0.set("Description", description);
772 }
773
774 pub fn homepage(&self) -> Option<url::Url> {
776 self.0.get("Homepage").map(|s| s.parse().unwrap())
777 }
778
779 pub fn set_homepage(&mut self, url: &url::Url) {
781 self.0.set("Homepage", url.as_ref());
782 }
783
784 pub fn source(&self) -> Option<String> {
786 self.0.get("Source").map(|s| s.to_string())
787 }
788
789 pub fn set_source(&mut self, source: &str) {
791 self.0.set("Source", source);
792 }
793
794 pub fn description_md5(&self) -> Option<String> {
796 self.0.get("Description-md5").map(|s| s.to_string())
797 }
798
799 pub fn set_description_md5(&mut self, md5: &str) {
801 self.0.set("Description-md5", md5);
802 }
803
804 pub fn tags(&self, tag: &str) -> Option<Vec<String>> {
806 self.0
807 .get(tag)
808 .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
809 }
810
811 pub fn set_tags(&mut self, tag: &str, tags: Vec<String>) {
813 self.0.set(tag, &tags.join(", "));
814 }
815
816 pub fn filename(&self) -> Option<String> {
818 self.0.get("Filename").map(|s| s.to_string())
819 }
820
821 pub fn set_filename(&mut self, filename: &str) {
823 self.0.set("Filename", filename);
824 }
825
826 pub fn size(&self) -> Option<usize> {
828 self.0.get("Size").map(|s| s.parse().unwrap())
829 }
830
831 pub fn set_size(&mut self, size: usize) {
833 self.0.set("Size", &size.to_string());
834 }
835
836 pub fn md5sum(&self) -> Option<String> {
838 self.0.get("MD5sum").map(|s| s.to_string())
839 }
840
841 pub fn set_md5sum(&mut self, md5sum: &str) {
843 self.0.set("MD5sum", md5sum);
844 }
845
846 pub fn sha256(&self) -> Option<String> {
848 self.0.get("SHA256").map(|s| s.to_string())
849 }
850
851 pub fn set_sha256(&mut self, sha256: &str) {
853 self.0.set("SHA256", sha256);
854 }
855
856 pub fn multi_arch(&self) -> Option<MultiArch> {
858 self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
859 }
860
861 pub fn set_multi_arch(&mut self, arch: MultiArch) {
863 self.0.set("Multi-Arch", arch.to_string().as_str());
864 }
865}
866
867impl std::str::FromStr for Package {
868 type Err = deb822_lossless::ParseError;
869
870 fn from_str(s: &str) -> Result<Self, Self::Err> {
871 Package::parse(s).to_result()
872 }
873}
874
875impl AstNode for Package {
876 type Language = deb822_lossless::Lang;
877
878 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
879 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
880 }
881
882 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
883 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
884 Some(Package(para))
885 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
886 deb822.paragraphs().next().map(Package)
887 } else {
888 None
889 }
890 }
891
892 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
893 self.0.syntax()
894 }
895}
896
897pub struct Release(deb822_lossless::Paragraph);
899
900#[cfg(feature = "python-debian")]
901impl<'py> pyo3::IntoPyObject<'py> for Release {
902 type Target = pyo3::PyAny;
903 type Output = pyo3::Bound<'py, Self::Target>;
904 type Error = pyo3::PyErr;
905
906 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
907 use pyo3::prelude::*;
908 let d = self.0.into_pyobject(py)?;
909
910 let m = py.import("debian.deb822")?;
911 let cls = m.getattr("Release")?;
912
913 cls.call1((d,))
914 }
915}
916
917#[cfg(feature = "python-debian")]
918impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Release {
919 type Target = pyo3::PyAny;
920 type Output = pyo3::Bound<'py, Self::Target>;
921 type Error = pyo3::PyErr;
922
923 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
924 use pyo3::prelude::*;
925 let d = (&self.0).into_pyobject(py)?;
926
927 let m = py.import("debian.deb822")?;
928 let cls = m.getattr("Release")?;
929
930 cls.call1((d,))
931 }
932}
933
934#[cfg(feature = "python-debian")]
935impl<'py> pyo3::FromPyObject<'_, 'py> for Release {
936 type Error = pyo3::PyErr;
937
938 fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
939 Ok(Release(ob.extract()?))
940 }
941}
942
943impl Release {
944 pub fn new(paragraph: deb822_lossless::Paragraph) -> Self {
946 Self(paragraph)
947 }
948
949 pub fn parse(text: &str) -> deb822_lossless::Parse<Release> {
953 let deb822_parse = deb822_lossless::Deb822::parse(text);
954 let green = deb822_parse.green().clone();
955 let mut errors = deb822_parse.errors().to_vec();
956
957 if errors.is_empty() {
959 let deb822 = deb822_parse.tree();
960 let paragraph_count = deb822.paragraphs().count();
961 if paragraph_count == 0 {
962 errors.push("No paragraphs found".to_string());
963 } else if paragraph_count > 1 {
964 errors.push("Multiple paragraphs found, expected one".to_string());
965 }
966 }
967
968 deb822_lossless::Parse::new(green, errors)
969 }
970
971 pub fn origin(&self) -> Option<String> {
973 self.0.get("Origin").map(|s| s.to_string())
974 }
975
976 pub fn set_origin(&mut self, origin: &str) {
978 self.0.set("Origin", origin);
979 }
980
981 pub fn label(&self) -> Option<String> {
983 self.0.get("Label").map(|s| s.to_string())
984 }
985
986 pub fn set_label(&mut self, label: &str) {
988 self.0.set("Label", label);
989 }
990
991 pub fn suite(&self) -> Option<String> {
993 self.0.get("Suite").map(|s| s.to_string())
994 }
995
996 pub fn set_suite(&mut self, suite: &str) {
998 self.0.set("Suite", suite);
999 }
1000
1001 pub fn codename(&self) -> Option<String> {
1003 self.0.get("Codename").map(|s| s.to_string())
1004 }
1005
1006 pub fn set_codename(&mut self, codename: &str) {
1008 self.0.set("Codename", codename);
1009 }
1010
1011 pub fn changelogs(&self) -> Option<Vec<String>> {
1013 self.0.get("Changelogs").map(|s| {
1014 s.split(',')
1015 .map(|s| s.trim().to_string())
1016 .collect::<Vec<String>>()
1017 })
1018 }
1019
1020 pub fn set_changelogs(&mut self, changelogs: Vec<String>) {
1022 self.0.set("Changelogs", &changelogs.join(", "));
1023 }
1024
1025 #[cfg(feature = "chrono")]
1026 pub fn date(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
1028 self.0
1029 .get("Date")
1030 .as_ref()
1031 .map(|s| chrono::DateTime::parse_from_rfc2822(s).unwrap())
1032 }
1033
1034 #[cfg(feature = "chrono")]
1035 pub fn set_date(&mut self, date: chrono::DateTime<chrono::FixedOffset>) {
1037 self.0.set("Date", date.to_rfc2822().as_str());
1038 }
1039
1040 #[cfg(feature = "chrono")]
1041 pub fn valid_until(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
1043 self.0
1044 .get("Valid-Until")
1045 .as_ref()
1046 .map(|s| chrono::DateTime::parse_from_rfc2822(s).unwrap())
1047 }
1048
1049 #[cfg(feature = "chrono")]
1050 pub fn set_valid_until(&mut self, date: chrono::DateTime<chrono::FixedOffset>) {
1052 self.0.set("Valid-Until", date.to_rfc2822().as_str());
1053 }
1054
1055 pub fn acquire_by_hash(&self) -> bool {
1057 self.0
1058 .get("Acquire-By-Hash")
1059 .map(|s| s == "yes")
1060 .unwrap_or(false)
1061 }
1062
1063 pub fn set_acquire_by_hash(&mut self, acquire_by_hash: bool) {
1065 self.0.set(
1066 "Acquire-By-Hash",
1067 if acquire_by_hash { "yes" } else { "no" },
1068 );
1069 }
1070
1071 pub fn no_support_for_architecture_all(&self) -> bool {
1073 self.0
1074 .get("No-Support-For-Architecture-All")
1075 .map(|s| s == "yes")
1076 .unwrap_or(false)
1077 }
1078
1079 pub fn set_no_support_for_architecture_all(&mut self, no_support_for_architecture_all: bool) {
1081 self.0.set(
1082 "No-Support-For-Architecture-All",
1083 if no_support_for_architecture_all {
1084 "yes"
1085 } else {
1086 "no"
1087 },
1088 );
1089 }
1090
1091 pub fn architectures(&self) -> Option<Vec<String>> {
1093 self.0.get("Architectures").map(|s| {
1094 s.split_whitespace()
1095 .map(|s| s.trim().to_string())
1096 .collect::<Vec<String>>()
1097 })
1098 }
1099
1100 pub fn set_architectures(&mut self, architectures: Vec<String>) {
1102 self.0.set("Architectures", &architectures.join(" "));
1103 }
1104
1105 pub fn components(&self) -> Option<Vec<String>> {
1107 self.0.get("Components").map(|s| {
1108 s.split_whitespace()
1109 .map(|s| s.trim().to_string())
1110 .collect::<Vec<String>>()
1111 })
1112 }
1113
1114 pub fn set_components(&mut self, components: Vec<String>) {
1116 self.0.set("Components", &components.join(" "));
1117 }
1118
1119 pub fn description(&self) -> Option<String> {
1121 self.0.get("Description").map(|s| s.to_string())
1122 }
1123
1124 pub fn set_description(&mut self, description: &str) {
1126 self.0.set("Description", description);
1127 }
1128
1129 pub fn checksums_md5(&self) -> Vec<Md5Checksum> {
1131 self.0
1132 .get("MD5Sum")
1133 .map(|s| {
1134 s.lines()
1135 .map(|line| line.parse().unwrap())
1136 .collect::<Vec<Md5Checksum>>()
1137 })
1138 .unwrap_or_default()
1139 }
1140
1141 pub fn set_checksums_md5(&mut self, files: Vec<Md5Checksum>) {
1143 self.0.set(
1144 "MD5Sum",
1145 &files
1146 .iter()
1147 .map(|f| f.to_string())
1148 .collect::<Vec<String>>()
1149 .join("\n"),
1150 );
1151 }
1152
1153 pub fn checksums_sha1(&self) -> Vec<Sha1Checksum> {
1155 self.0
1156 .get("SHA1")
1157 .map(|s| {
1158 s.lines()
1159 .map(|line| line.parse().unwrap())
1160 .collect::<Vec<Sha1Checksum>>()
1161 })
1162 .unwrap_or_default()
1163 }
1164
1165 pub fn set_checksums_sha1(&mut self, checksums: Vec<Sha1Checksum>) {
1167 self.0.set(
1168 "SHA1",
1169 &checksums
1170 .iter()
1171 .map(|c| c.to_string())
1172 .collect::<Vec<String>>()
1173 .join("\n"),
1174 );
1175 }
1176
1177 pub fn checksums_sha256(&self) -> Vec<Sha256Checksum> {
1179 self.0
1180 .get("SHA256")
1181 .map(|s| {
1182 s.lines()
1183 .map(|line| line.parse().unwrap())
1184 .collect::<Vec<Sha256Checksum>>()
1185 })
1186 .unwrap_or_default()
1187 }
1188
1189 pub fn set_checksums_sha256(&mut self, checksums: Vec<Sha256Checksum>) {
1191 self.0.set(
1192 "SHA256",
1193 &checksums
1194 .iter()
1195 .map(|c| c.to_string())
1196 .collect::<Vec<String>>()
1197 .join("\n"),
1198 );
1199 }
1200
1201 pub fn checksums_sha512(&self) -> Vec<Sha512Checksum> {
1203 self.0
1204 .get("SHA512")
1205 .map(|s| {
1206 s.lines()
1207 .map(|line| line.parse().unwrap())
1208 .collect::<Vec<Sha512Checksum>>()
1209 })
1210 .unwrap_or_default()
1211 }
1212
1213 pub fn set_checksums_sha512(&mut self, checksums: Vec<Sha512Checksum>) {
1215 self.0.set(
1216 "SHA512",
1217 &checksums
1218 .iter()
1219 .map(|c| c.to_string())
1220 .collect::<Vec<String>>()
1221 .join("\n"),
1222 );
1223 }
1224}
1225
1226impl std::str::FromStr for Release {
1227 type Err = deb822_lossless::ParseError;
1228
1229 fn from_str(s: &str) -> Result<Self, Self::Err> {
1230 Release::parse(s).to_result()
1231 }
1232}
1233
1234impl AstNode for Release {
1235 type Language = deb822_lossless::Lang;
1236
1237 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
1238 deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind)
1239 }
1240
1241 fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
1242 if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) {
1243 Some(Release(para))
1244 } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) {
1245 deb822.paragraphs().next().map(Release)
1246 } else {
1247 None
1248 }
1249 }
1250
1251 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
1252 self.0.syntax()
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use super::*;
1259 use crate::fields::PackageListEntry;
1260
1261 #[test]
1262 fn test_parse_package_list() {
1263 let s = "package1 binary section standard extra1=foo extra2=bar";
1264 let p: PackageListEntry = s.parse().unwrap();
1265 assert_eq!(p.package, "package1");
1266 assert_eq!(p.package_type, "binary");
1267 assert_eq!(p.section, "section");
1268 assert_eq!(p.priority, super::Priority::Standard);
1269 assert_eq!(p.extra.get("extra1"), Some(&"foo".to_string()));
1270 assert_eq!(p.extra.get("extra2"), Some(&"bar".to_string()));
1271 }
1272
1273 #[test]
1274 fn test_parse_package_list_no_extra() {
1275 let s = "package1 binary section standard";
1276 let p: PackageListEntry = s.parse().unwrap();
1277 assert_eq!(p.package, "package1");
1278 assert_eq!(p.package_type, "binary");
1279 assert_eq!(p.section, "section");
1280 assert_eq!(p.priority, super::Priority::Standard);
1281 assert!(p.extra.is_empty());
1282 }
1283
1284 #[test]
1285 fn test_files() {
1286 let s = "md5sum 1234 filename";
1287 let f: super::Md5Checksum = s.parse().unwrap();
1288 assert_eq!(f.md5sum, "md5sum");
1289 assert_eq!(f.size, 1234);
1290 assert_eq!(f.filename, "filename");
1291 }
1292
1293 #[test]
1294 fn test_sha1_checksum() {
1295 let s = "sha1 1234 filename";
1296 let f: super::Sha1Checksum = s.parse().unwrap();
1297 assert_eq!(f.sha1, "sha1");
1298 assert_eq!(f.size, 1234);
1299 assert_eq!(f.filename, "filename");
1300 }
1301
1302 #[test]
1303 fn test_sha256_checksum() {
1304 let s = "sha256 1234 filename";
1305 let f: super::Sha256Checksum = s.parse().unwrap();
1306 assert_eq!(f.sha256, "sha256");
1307 assert_eq!(f.size, 1234);
1308 assert_eq!(f.filename, "filename");
1309 }
1310
1311 #[test]
1312 fn test_sha512_checksum() {
1313 let s = "sha512 1234 filename";
1314 let f: super::Sha512Checksum = s.parse().unwrap();
1315 assert_eq!(f.sha512, "sha512");
1316 assert_eq!(f.size, 1234);
1317 assert_eq!(f.filename, "filename");
1318 }
1319
1320 #[test]
1321 fn test_source() {
1322 let s = r#"Package: foo
1323Version: 1.0
1324Maintainer: John Doe <john@example.com>
1325Uploaders: Jane Doe <jane@example.com>
1326Standards-Version: 3.9.8
1327Format: 3.0 (quilt)
1328Vcs-Browser: https://example.com/foo
1329Vcs-Git: https://example.com/foo.git
1330Build-Depends: debhelper (>= 9)
1331Build-Depends-Indep: python
1332Build-Depends-Arch: gcc
1333Build-Conflicts: bar
1334Build-Conflicts-Indep: python
1335Build-Conflicts-Arch: gcc
1336Binary: foo, bar
1337Homepage: https://example.com/foo
1338Section: devel
1339Priority: optional
1340Architecture: any
1341Directory: pool/main/f/foo
1342Files:
1343 25dcf3b4b6b3b3b3b3b3b3b3b3b3b3b3 1234 foo_1.0.tar.gz
1344Checksums-Sha1:
1345 b72b5fae3b3b3b3b3b3b3b3b3b3b3b3 1234 foo_1.0.tar.gz
1346"#;
1347 let p: super::Source = s.parse().unwrap();
1348 assert_eq!(p.package(), Some("foo".to_string()));
1349 assert_eq!(p.version(), Some("1.0".parse().unwrap()));
1350 assert_eq!(
1351 p.maintainer(),
1352 Some("John Doe <john@example.com>".to_string())
1353 );
1354 assert_eq!(
1355 p.uploaders(),
1356 Some(vec!["Jane Doe <jane@example.com>".to_string()])
1357 );
1358 assert_eq!(p.standards_version(), Some("3.9.8".to_string()));
1359 assert_eq!(p.format(), Some("3.0 (quilt)".to_string()));
1360 assert_eq!(p.vcs_browser(), Some("https://example.com/foo".to_string()));
1361 assert_eq!(p.vcs_git(), Some("https://example.com/foo.git".to_string()));
1362 assert_eq!(
1363 p.build_depends_indep().map(|x| x.to_string()),
1364 Some("python".parse().unwrap())
1365 );
1366 assert_eq!(p.build_depends(), Some("debhelper (>= 9)".parse().unwrap()));
1367 assert_eq!(p.build_depends_arch(), Some("gcc".parse().unwrap()));
1368 assert_eq!(p.build_conflicts(), Some("bar".parse().unwrap()));
1369 assert_eq!(p.build_conflicts_indep(), Some("python".parse().unwrap()));
1370 assert_eq!(p.build_conflicts_arch(), Some("gcc".parse().unwrap()));
1371 assert_eq!(p.binary(), Some("foo, bar".parse().unwrap()));
1372 assert_eq!(p.homepage(), Some("https://example.com/foo".to_string()));
1373 assert_eq!(p.section(), Some("devel".to_string()));
1374 assert_eq!(p.priority(), Some(super::Priority::Optional));
1375 assert_eq!(p.architecture(), Some("any".to_string()));
1376 assert_eq!(p.directory(), Some("pool/main/f/foo".to_string()));
1377 assert_eq!(p.files().len(), 1);
1378 assert_eq!(
1379 p.files()[0].md5sum,
1380 "25dcf3b4b6b3b3b3b3b3b3b3b3b3b3b3".to_string()
1381 );
1382 assert_eq!(p.files()[0].size, 1234);
1383 assert_eq!(p.files()[0].filename, "foo_1.0.tar.gz".to_string());
1384 assert_eq!(p.checksums_sha1().len(), 1);
1385 assert_eq!(
1386 p.checksums_sha1()[0].sha1,
1387 "b72b5fae3b3b3b3b3b3b3b3b3b3b3b3".to_string()
1388 );
1389 }
1390
1391 #[test]
1392 fn test_package() {
1393 let s = r#"Package: foo
1394Version: 1.0
1395Source: bar
1396Maintainer: John Doe <john@example.com>
1397Architecture: any
1398Depends: bar
1399Recommends: baz
1400Suggests: qux
1401Enhances: quux
1402Pre-Depends: quuz
1403Breaks: corge
1404Conflicts: grault
1405Replaces: garply
1406Provides: waldo
1407Section: devel
1408Priority: optional
1409Description: Foo is a bar
1410Homepage: https://example.com/foo
1411Description-md5: 1234
1412Tags: foo, bar
1413Filename: pool/main/f/foo/foo_1.0.deb
1414Size: 1234
1415Installed-Size: 1234
1416MD5sum: 1234
1417SHA256: 1234
1418Multi-Arch: same
1419"#;
1420 let p: super::Package = s.parse().unwrap();
1421 assert_eq!(p.name(), Some("foo".to_string()));
1422 assert_eq!(p.version(), Some("1.0".parse().unwrap()));
1423 assert_eq!(p.source(), Some("bar".to_string()));
1424 assert_eq!(
1425 p.maintainer(),
1426 Some("John Doe <john@example.com>".to_string())
1427 );
1428 assert_eq!(p.architecture(), Some("any".to_string()));
1429 assert_eq!(p.depends(), Some("bar".parse().unwrap()));
1430 assert_eq!(p.recommends(), Some("baz".parse().unwrap()));
1431 assert_eq!(p.suggests(), Some("qux".parse().unwrap()));
1432 assert_eq!(p.enhances(), Some("quux".parse().unwrap()));
1433 assert_eq!(p.pre_depends(), Some("quuz".parse().unwrap()));
1434 assert_eq!(p.breaks(), Some("corge".parse().unwrap()));
1435 assert_eq!(p.conflicts(), Some("grault".parse().unwrap()));
1436 assert_eq!(p.replaces(), Some("garply".parse().unwrap()));
1437 assert_eq!(p.provides(), Some("waldo".parse().unwrap()));
1438 assert_eq!(p.section(), Some("devel".to_string()));
1439 assert_eq!(p.priority(), Some(super::Priority::Optional));
1440 assert_eq!(p.description(), Some("Foo is a bar".to_string()));
1441 assert_eq!(
1442 p.homepage(),
1443 Some(url::Url::parse("https://example.com/foo").unwrap())
1444 );
1445 assert_eq!(p.description_md5(), Some("1234".to_string()));
1446 assert_eq!(
1447 p.tags("Tags"),
1448 Some(vec!["foo".to_string(), "bar".to_string()])
1449 );
1450 assert_eq!(
1451 p.filename(),
1452 Some("pool/main/f/foo/foo_1.0.deb".to_string())
1453 );
1454 assert_eq!(p.size(), Some(1234));
1455 assert_eq!(p.installed_size(), Some(1234));
1456 assert_eq!(p.md5sum(), Some("1234".to_string()));
1457 assert_eq!(p.sha256(), Some("1234".to_string()));
1458 assert_eq!(p.multi_arch(), Some(MultiArch::Same));
1459 }
1460
1461 #[test]
1462 fn test_release() {
1463 let s = include_str!("../testdata/Release");
1464 let release: super::Release = s.parse().unwrap();
1465
1466 assert_eq!(release.origin(), Some("Debian".to_string()));
1467 assert_eq!(release.label(), Some("Debian".to_string()));
1468 assert_eq!(release.suite(), Some("testing".to_string()));
1469 assert_eq!(
1470 release.architectures(),
1471 vec![
1472 "all".to_string(),
1473 "amd64".to_string(),
1474 "arm64".to_string(),
1475 "armel".to_string(),
1476 "armhf".to_string()
1477 ]
1478 .into()
1479 );
1480 assert_eq!(
1481 release.components(),
1482 vec![
1483 "main".to_string(),
1484 "contrib".to_string(),
1485 "non-free-firmware".to_string(),
1486 "non-free".to_string()
1487 ]
1488 .into()
1489 );
1490 assert_eq!(
1491 release.description(),
1492 Some("Debian x.y Testing distribution - Not Released".to_string())
1493 );
1494 assert_eq!(318, release.checksums_md5().len());
1495 }
1496
1497 #[test]
1498 fn test_source_vcs_arch() {
1499 let s = r#"Package: foo
1500Version: 1.0
1501Vcs-Arch: https://example.com/arch/repo
1502"#;
1503 let p: super::Source = s.parse().unwrap();
1504 assert_eq!(
1505 p.vcs_arch(),
1506 Some("https://example.com/arch/repo".to_string())
1507 );
1508 }
1509
1510 #[test]
1511 fn test_source_vcs_cvs() {
1512 let s = r#"Package: foo
1513Version: 1.0
1514Vcs-Cvs: :pserver:anoncvs@example.com:/cvs/repo
1515"#;
1516 let p: super::Source = s.parse().unwrap();
1517 assert_eq!(
1518 p.vcs_cvs(),
1519 Some(":pserver:anoncvs@example.com:/cvs/repo".to_string())
1520 );
1521 }
1522
1523 #[test]
1524 fn test_source_set_priority() {
1525 let s = r#"Package: foo
1526Version: 1.0
1527Priority: optional
1528"#;
1529 let mut p: super::Source = s.parse().unwrap();
1530 p.set_priority(super::Priority::Required);
1531 assert_eq!(p.priority(), Some(super::Priority::Required));
1532 }
1533
1534 #[test]
1535 fn test_release_set_suite() {
1536 let s = r#"Origin: Debian
1537Label: Debian
1538Suite: testing
1539"#;
1540 let mut release: super::Release = s.parse().unwrap();
1541 release.set_suite("unstable");
1542 assert_eq!(release.suite(), Some("unstable".to_string()));
1543 }
1544
1545 #[test]
1546 fn test_release_codename() {
1547 let s = r#"Origin: Debian
1548Label: Debian
1549Suite: testing
1550Codename: trixie
1551"#;
1552 let release: super::Release = s.parse().unwrap();
1553 assert_eq!(release.codename(), Some("trixie".to_string()));
1554 }
1555
1556 #[test]
1557 fn test_release_set_checksums_sha512() {
1558 let s = r#"Origin: Debian
1559Label: Debian
1560Suite: testing
1561"#;
1562 let mut release: super::Release = s.parse().unwrap();
1563 let checksums = vec![super::Sha512Checksum {
1564 sha512: "abc123".to_string(),
1565 size: 1234,
1566 filename: "test.deb".to_string(),
1567 }];
1568 release.set_checksums_sha512(checksums);
1569 assert_eq!(release.checksums_sha512().len(), 1);
1570 }
1571
1572 #[test]
1573 fn test_package_set_replaces() {
1574 let s = r#"Package: foo
1575Version: 1.0
1576"#;
1577 let mut p: super::Package = s.parse().unwrap();
1578 let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1579 p.set_replaces(relations);
1580 assert_eq!(p.replaces(), Some("bar (>= 1.0.0)".parse().unwrap()));
1581 }
1582}