debian_control/lossless/
apt.rs

1//! APT package manager files
2use crate::fields::{
3    Md5Checksum, MultiArch, Priority, Sha1Checksum, Sha256Checksum, Sha512Checksum,
4};
5use crate::lossless::relations::Relations;
6use rowan::ast::AstNode;
7
8/// A source package in the APT package manager.
9#[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    /// Create a new source package
68    pub fn new() -> Self {
69        Self(deb822_lossless::Paragraph::new())
70    }
71
72    /// Parse source package text, returning a Parse result
73    ///
74    /// Note: This expects a single paragraph, not a full deb822 document
75    pub fn parse(text: &str) -> deb822_lossless::Parse<Source> {
76        // We need to transform Parse<Deb822> to Parse<Source>
77        // Since Source wraps a single Paragraph, we need custom logic
78        let deb822_parse = deb822_lossless::Deb822::parse(text);
79
80        // For now, we'll use the same green node but the cast will extract the first paragraph
81        let green = deb822_parse.green().clone();
82        let mut errors = deb822_parse.errors().to_vec();
83
84        // Check if there's exactly one paragraph
85        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    /// Get the source name
99    pub fn package(&self) -> Option<String> {
100        self.0.get("Package").map(|s| s.to_string())
101    }
102
103    /// Set the package name
104    pub fn set_package(&mut self, package: &str) {
105        self.0.set("Package", package);
106    }
107
108    /// Get the version of the package
109    pub fn version(&self) -> Option<debversion::Version> {
110        self.0.get("Version").map(|s| s.parse().unwrap())
111    }
112
113    /// Set the version of the package
114    pub fn set_version(&mut self, version: debversion::Version) {
115        self.0.set("Version", &version.to_string());
116    }
117
118    /// Get the maintainer of the package
119    pub fn maintainer(&self) -> Option<String> {
120        self.0.get("Maintainer").map(|s| s.to_string())
121    }
122
123    /// Set the maintainer of the package
124    pub fn set_maintainer(&mut self, maintainer: &str) {
125        self.0.set("Maintainer", maintainer);
126    }
127
128    /// Get the uploaders of the package
129    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    /// Set the uploaders of the package
138    pub fn set_uploaders(&mut self, uploaders: Vec<String>) {
139        self.0.set("Uploaders", &uploaders.join(", "));
140    }
141
142    /// Get the standards version of the package
143    pub fn standards_version(&self) -> Option<String> {
144        self.0.get("Standards-Version").map(|s| s.to_string())
145    }
146
147    /// Set the standards version of the package
148    pub fn set_standards_version(&mut self, version: &str) {
149        self.0.set("Standards-Version", version);
150    }
151
152    /// Get the source format of the package
153    pub fn format(&self) -> Option<String> {
154        self.0.get("Format").map(|s| s.to_string())
155    }
156
157    /// Set the format of the package
158    pub fn set_format(&mut self, format: &str) {
159        self.0.set("Format", format);
160    }
161
162    /// Get the Vcs-Browser field
163    pub fn vcs_browser(&self) -> Option<String> {
164        self.0.get("Vcs-Browser").map(|s| s.to_string())
165    }
166
167    /// Set the Vcs-Browser field
168    pub fn set_vcs_browser(&mut self, url: &str) {
169        self.0.set("Vcs-Browser", url);
170    }
171
172    /// Get the Vcs-Git field
173    pub fn vcs_git(&self) -> Option<String> {
174        self.0.get("Vcs-Git").map(|s| s.to_string())
175    }
176
177    /// Set the Vcs-Git field
178    pub fn set_vcs_git(&mut self, url: &str) {
179        self.0.set("Vcs-Git", url);
180    }
181
182    /// Get the Vcs-Svn field
183    pub fn vcs_svn(&self) -> Option<String> {
184        self.0.get("Vcs-Svn").map(|s| s.to_string())
185    }
186
187    /// Set the Vcs-Svn field
188    pub fn set_vcs_svn(&mut self, url: &str) {
189        self.0.set("Vcs-Svn", url);
190    }
191
192    /// Get the Vcs-Hg field
193    pub fn vcs_hg(&self) -> Option<String> {
194        self.0.get("Vcs-Hg").map(|s| s.to_string())
195    }
196
197    /// Set the Vcs-Hg field
198    pub fn set_vcs_hg(&mut self, url: &str) {
199        self.0.set("Vcs-Hg", url);
200    }
201
202    /// Get the Vcs-Bzr field
203    pub fn vcs_bzr(&self) -> Option<String> {
204        self.0.get("Vcs-Bzr").map(|s| s.to_string())
205    }
206
207    /// Set the Vcs-Bzr field
208    pub fn set_vcs_bzr(&mut self, url: &str) {
209        self.0.set("Vcs-Bzr", url);
210    }
211
212    /// Get the Vcs-Arch field
213    pub fn vcs_arch(&self) -> Option<String> {
214        self.0.get("Vcs-Arch").map(|s| s.to_string())
215    }
216
217    /// Set the Vcs-Arch field
218    pub fn set_vcs_arch(&mut self, url: &str) {
219        self.0.set("Vcs-Arch", url);
220    }
221
222    /// Get the Vcs-Svk field
223    pub fn vcs_svk(&self) -> Option<String> {
224        self.0.get("Vcs-Svk").map(|s| s.to_string())
225    }
226
227    /// Set the Svk VCS
228    pub fn set_vcs_svk(&mut self, url: &str) {
229        self.0.set("Vcs-Svk", url);
230    }
231
232    /// Get the Darcs VCS
233    pub fn vcs_darcs(&self) -> Option<String> {
234        self.0.get("Vcs-Darcs").map(|s| s.to_string())
235    }
236
237    /// Set the Darcs VCS
238    pub fn set_vcs_darcs(&mut self, url: &str) {
239        self.0.set("Vcs-Darcs", url);
240    }
241
242    /// Get the Mtn VCS
243    pub fn vcs_mtn(&self) -> Option<String> {
244        self.0.get("Vcs-Mtn").map(|s| s.to_string())
245    }
246
247    /// Set the Mtn VCS
248    pub fn set_vcs_mtn(&mut self, url: &str) {
249        self.0.set("Vcs-Mtn", url);
250    }
251
252    /// Get the Cvs VCS
253    pub fn vcs_cvs(&self) -> Option<String> {
254        self.0.get("Vcs-Cvs").map(|s| s.to_string())
255    }
256
257    /// Set the Cvs VCS
258    pub fn set_vcs_cvs(&mut self, url: &str) {
259        self.0.set("Vcs-Cvs", url);
260    }
261
262    /// Get the build depends
263    pub fn build_depends(&self) -> Option<Relations> {
264        self.0.get("Build-Depends").map(|s| s.parse().unwrap())
265    }
266
267    /// Set the build depends
268    pub fn set_build_depends(&mut self, relations: Relations) {
269        self.0.set("Build-Depends", relations.to_string().as_str());
270    }
271
272    /// Get the arch-independent build depends
273    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    /// Set the arch-independent build depends
280    pub fn set_build_depends_indep(&mut self, relations: Relations) {
281        self.0.set("Build-Depends-Indep", &relations.to_string());
282    }
283
284    /// Get the arch-dependent build depends
285    pub fn build_depends_arch(&self) -> Option<Relations> {
286        self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
287    }
288
289    /// Set the arch-dependent build depends
290    pub fn set_build_depends_arch(&mut self, relations: Relations) {
291        self.0.set("Build-Depends-Arch", &relations.to_string());
292    }
293
294    /// Get the build conflicts
295    pub fn build_conflicts(&self) -> Option<Relations> {
296        self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
297    }
298
299    /// Set the build conflicts
300    pub fn set_build_conflicts(&mut self, relations: Relations) {
301        self.0.set("Build-Conflicts", &relations.to_string());
302    }
303
304    /// Get the build conflicts indep
305    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    /// Set the build conflicts indep
312    pub fn set_build_conflicts_indep(&mut self, relations: Relations) {
313        self.0.set("Build-Conflicts-Indep", &relations.to_string());
314    }
315
316    /// Get the build conflicts arch
317    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    /// Set the build conflicts arch
324    pub fn set_build_conflicts_arch(&mut self, relations: Relations) {
325        self.0.set("Build-Conflicts-Arch", &relations.to_string());
326    }
327
328    /// Get the binary relations
329    pub fn binary(&self) -> Option<Relations> {
330        self.0.get("Binary").map(|s| s.parse().unwrap())
331    }
332
333    /// Set the binary relations
334    pub fn set_binary(&mut self, relations: Relations) {
335        self.0.set("Binary", &relations.to_string());
336    }
337
338    /// Get the homepage of the package.
339    pub fn homepage(&self) -> Option<String> {
340        self.0.get("Homepage").map(|s| s.to_string())
341    }
342
343    /// Set the homepage of the package.
344    pub fn set_homepage(&mut self, url: &str) {
345        self.0.set("Homepage", url);
346    }
347
348    /// Get the section of the package.
349    pub fn section(&self) -> Option<String> {
350        self.0.get("Section").map(|s| s.to_string())
351    }
352
353    /// Set the section of the package.
354    pub fn set_section(&mut self, section: &str) {
355        self.0.set("Section", section);
356    }
357
358    /// Get the priority of the package.
359    pub fn priority(&self) -> Option<Priority> {
360        self.0.get("Priority").and_then(|v| v.parse().ok())
361    }
362
363    /// Set the priority of the package.
364    pub fn set_priority(&mut self, priority: Priority) {
365        self.0.set("Priority", priority.to_string().as_str());
366    }
367
368    /// The architecture of the package.
369    pub fn architecture(&self) -> Option<String> {
370        self.0.get("Architecture")
371    }
372
373    /// Set the architecture of the package.
374    pub fn set_architecture(&mut self, arch: &str) {
375        self.0.set("Architecture", arch);
376    }
377
378    /// Get the directory
379    pub fn directory(&self) -> Option<String> {
380        self.0.get("Directory").map(|s| s.to_string())
381    }
382
383    /// Set the directory
384    pub fn set_directory(&mut self, dir: &str) {
385        self.0.set("Directory", dir);
386    }
387
388    /// Get the test suite
389    pub fn testsuite(&self) -> Option<String> {
390        self.0.get("Testsuite").map(|s| s.to_string())
391    }
392
393    /// Set the testsuite
394    pub fn set_testsuite(&mut self, testsuite: &str) {
395        self.0.set("Testsuite", testsuite);
396    }
397
398    /// Get the files
399    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    /// Set the files
411    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    /// Get the SHA1 checksums
423    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    /// Set the SHA1 checksums
435    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    /// Get the SHA256 checksums
447    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    /// Set the SHA256 checksums
459    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    /// Get the SHA512 checksums
471    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    /// Set the SHA512 checksums
483    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        // Source can be cast from either a Paragraph or a Deb822 (taking first paragraph)
508        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        // Try to cast as Paragraph first
513        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            // If it's a Deb822, take the first paragraph
517            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/// A package in the APT package manager.
529#[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    /// Create a new package.
576    pub fn new(paragraph: deb822_lossless::Paragraph) -> Self {
577        Self(paragraph)
578    }
579
580    /// Parse package text, returning a Parse result
581    ///
582    /// Note: This expects a single paragraph, not a full deb822 document
583    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        // Check if there's exactly one paragraph
589        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    /// Get the name of the package.
603    pub fn name(&self) -> Option<String> {
604        self.0.get("Package").map(|s| s.to_string())
605    }
606
607    /// Set the name of the package.
608    pub fn set_name(&mut self, name: &str) {
609        self.0.set("Package", name);
610    }
611
612    /// Get the version of the package.
613    pub fn version(&self) -> Option<debversion::Version> {
614        self.0.get("Version").map(|s| s.parse().unwrap())
615    }
616
617    /// Set the version of the package.
618    pub fn set_version(&mut self, version: debversion::Version) {
619        self.0.set("Version", &version.to_string());
620    }
621
622    /// Get the installed size of the package in bytes.
623    pub fn installed_size(&self) -> Option<usize> {
624        self.0.get("Installed-Size").map(|s| s.parse().unwrap())
625    }
626
627    /// Set the installed size of the package in bytes.
628    pub fn set_installed_size(&mut self, size: usize) {
629        self.0.set("Installed-Size", &size.to_string());
630    }
631
632    /// Get the maintainer of the package.
633    pub fn maintainer(&self) -> Option<String> {
634        self.0.get("Maintainer").map(|s| s.to_string())
635    }
636
637    /// Set the maintainer of the package.
638    pub fn set_maintainer(&mut self, maintainer: &str) {
639        self.0.set("Maintainer", maintainer);
640    }
641
642    /// Get the architecture of the package.
643    pub fn architecture(&self) -> Option<String> {
644        self.0.get("Architecture").map(|s| s.to_string())
645    }
646
647    /// Set the architecture of the package.
648    pub fn set_architecture(&mut self, arch: &str) {
649        self.0.set("Architecture", arch);
650    }
651
652    /// Get the packages that this package depends on.
653    pub fn depends(&self) -> Option<Relations> {
654        self.0.get("Depends").map(|s| s.parse().unwrap())
655    }
656
657    /// Set the packages that this package depends on.
658    pub fn set_depends(&mut self, relations: Relations) {
659        self.0.set("Depends", &relations.to_string());
660    }
661
662    /// Get the packages that this package suggests.
663    pub fn recommends(&self) -> Option<Relations> {
664        self.0.get("Recommends").map(|s| s.parse().unwrap())
665    }
666
667    /// Set the packages that this package recommends.
668    pub fn set_recommends(&mut self, relations: Relations) {
669        self.0.set("Recommends", &relations.to_string());
670    }
671
672    /// Get the packages that this package suggests.
673    pub fn suggests(&self) -> Option<Relations> {
674        self.0.get("Suggests").map(|s| s.parse().unwrap())
675    }
676
677    /// Set the packages that this package suggests.
678    pub fn set_suggests(&mut self, relations: Relations) {
679        self.0.set("Suggests", &relations.to_string());
680    }
681
682    /// Get the packages that this package enhances.
683    pub fn enhances(&self) -> Option<Relations> {
684        self.0.get("Enhances").map(|s| s.parse().unwrap())
685    }
686
687    /// Set the packages that this package enhances.
688    pub fn set_enhances(&mut self, relations: Relations) {
689        self.0.set("Enhances", &relations.to_string());
690    }
691
692    /// Get the relations that this package pre-depends on.
693    pub fn pre_depends(&self) -> Option<Relations> {
694        self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
695    }
696
697    /// Set the relations that this package pre-depends on.
698    pub fn set_pre_depends(&mut self, relations: Relations) {
699        self.0.set("Pre-Depends", &relations.to_string());
700    }
701
702    /// Get the relations that this package breaks.
703    pub fn breaks(&self) -> Option<Relations> {
704        self.0.get("Breaks").map(|s| s.parse().unwrap())
705    }
706
707    /// Set the relations that this package breaks.
708    pub fn set_breaks(&mut self, relations: Relations) {
709        self.0.set("Breaks", &relations.to_string());
710    }
711
712    /// Get the relations that this package conflicts with.
713    pub fn conflicts(&self) -> Option<Relations> {
714        self.0.get("Conflicts").map(|s| s.parse().unwrap())
715    }
716
717    /// Set the relations that this package conflicts with.
718    pub fn set_conflicts(&mut self, relations: Relations) {
719        self.0.set("Conflicts", &relations.to_string());
720    }
721
722    /// Get the relations that this package replaces.
723    pub fn replaces(&self) -> Option<Relations> {
724        self.0.get("Replaces").map(|s| s.parse().unwrap())
725    }
726
727    /// Set the relations that this package replaces.
728    pub fn set_replaces(&mut self, relations: Relations) {
729        self.0.set("Replaces", &relations.to_string());
730    }
731
732    /// Get the relations that this package provides.
733    pub fn provides(&self) -> Option<Relations> {
734        self.0.get("Provides").map(|s| s.parse().unwrap())
735    }
736
737    /// Set the relations that the package provides.
738    pub fn set_provides(&mut self, relations: Relations) {
739        self.0.set("Provides", &relations.to_string());
740    }
741
742    /// Get the section of the package.
743    pub fn section(&self) -> Option<String> {
744        self.0.get("Section").map(|s| s.to_string())
745    }
746
747    /// Set the section of the package.
748    pub fn set_section(&mut self, section: &str) {
749        self.0.set("Section", section);
750    }
751
752    /// Get the priority of the package.
753    pub fn priority(&self) -> Option<Priority> {
754        self.0.get("Priority").and_then(|v| v.parse().ok())
755    }
756
757    /// Set the priority of the package.
758    pub fn set_priority(&mut self, priority: Priority) {
759        self.0.set("Priority", priority.to_string().as_str());
760    }
761
762    /// Get the description of the package.
763    pub fn description(&self) -> Option<String> {
764        self.0.get("Description").map(|s| s.to_string())
765    }
766
767    /// Set the description of the package.
768    pub fn set_description(&mut self, description: &str) {
769        self.0.set("Description", description);
770    }
771
772    /// Get the upstream homepage of the package.
773    pub fn homepage(&self) -> Option<url::Url> {
774        self.0.get("Homepage").map(|s| s.parse().unwrap())
775    }
776
777    /// Set the upstream homepage of the package.
778    pub fn set_homepage(&mut self, url: &url::Url) {
779        self.0.set("Homepage", url.as_ref());
780    }
781
782    /// Get the source of the package.
783    pub fn source(&self) -> Option<String> {
784        self.0.get("Source").map(|s| s.to_string())
785    }
786
787    /// Set the source of the package.
788    pub fn set_source(&mut self, source: &str) {
789        self.0.set("Source", source);
790    }
791
792    /// Get the MD5 checksum of the description.
793    pub fn description_md5(&self) -> Option<String> {
794        self.0.get("Description-md5").map(|s| s.to_string())
795    }
796
797    /// Set the MD5 checksum of the description.
798    pub fn set_description_md5(&mut self, md5: &str) {
799        self.0.set("Description-md5", md5);
800    }
801
802    /// Get the tags of the package.
803    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    /// Set the tags of the package.
810    pub fn set_tags(&mut self, tag: &str, tags: Vec<String>) {
811        self.0.set(tag, &tags.join(", "));
812    }
813
814    /// Get the filename of the package.
815    pub fn filename(&self) -> Option<String> {
816        self.0.get("Filename").map(|s| s.to_string())
817    }
818
819    /// Set the filename of the package.
820    pub fn set_filename(&mut self, filename: &str) {
821        self.0.set("Filename", filename);
822    }
823
824    /// Get the size of the package.
825    pub fn size(&self) -> Option<usize> {
826        self.0.get("Size").map(|s| s.parse().unwrap())
827    }
828
829    /// Set the size of the package.
830    pub fn set_size(&mut self, size: usize) {
831        self.0.set("Size", &size.to_string());
832    }
833
834    /// Get the MD5 checksum.
835    pub fn md5sum(&self) -> Option<String> {
836        self.0.get("MD5sum").map(|s| s.to_string())
837    }
838
839    /// Set the MD5 checksum.
840    pub fn set_md5sum(&mut self, md5sum: &str) {
841        self.0.set("MD5sum", md5sum);
842    }
843
844    /// Get the SHA256 checksum.
845    pub fn sha256(&self) -> Option<String> {
846        self.0.get("SHA256").map(|s| s.to_string())
847    }
848
849    /// Set the SHA256 checksum.
850    pub fn set_sha256(&mut self, sha256: &str) {
851        self.0.set("SHA256", sha256);
852    }
853
854    /// Get the multi-arch field.
855    pub fn multi_arch(&self) -> Option<MultiArch> {
856        self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
857    }
858
859    /// Set the multi-arch field.
860    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
895/// A release in the APT package manager.
896pub 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    /// Create a new release
942    pub fn new(paragraph: deb822_lossless::Paragraph) -> Self {
943        Self(paragraph)
944    }
945
946    /// Parse release text, returning a Parse result
947    ///
948    /// Note: This expects a single paragraph, not a full deb822 document
949    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        // Check if there's exactly one paragraph
955        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    /// Get the origin of the release
969    pub fn origin(&self) -> Option<String> {
970        self.0.get("Origin").map(|s| s.to_string())
971    }
972
973    /// Set the origin of the release
974    pub fn set_origin(&mut self, origin: &str) {
975        self.0.set("Origin", origin);
976    }
977
978    /// Get the label of the release
979    pub fn label(&self) -> Option<String> {
980        self.0.get("Label").map(|s| s.to_string())
981    }
982
983    /// Set the label of the release
984    pub fn set_label(&mut self, label: &str) {
985        self.0.set("Label", label);
986    }
987
988    /// Get the suite of the release
989    pub fn suite(&self) -> Option<String> {
990        self.0.get("Suite").map(|s| s.to_string())
991    }
992
993    /// Set the suite of the release
994    pub fn set_suite(&mut self, suite: &str) {
995        self.0.set("Suite", suite);
996    }
997
998    /// Get the codename of the release
999    pub fn codename(&self) -> Option<String> {
1000        self.0.get("Codename").map(|s| s.to_string())
1001    }
1002
1003    /// Set the codename of the release
1004    pub fn set_codename(&mut self, codename: &str) {
1005        self.0.set("Codename", codename);
1006    }
1007
1008    /// Get the URLs at which the changelogs can be found
1009    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    /// Set the URLs at which the changelogs can be found
1018    pub fn set_changelogs(&mut self, changelogs: Vec<String>) {
1019        self.0.set("Changelogs", &changelogs.join(", "));
1020    }
1021
1022    #[cfg(feature = "chrono")]
1023    /// Get the date of the release
1024    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    /// Set the date of the release
1033    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    /// Get the date until the release is valid
1039    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    /// Set the date until the release is valid
1048    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    /// Get whether acquire by hash is enabled
1053    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    /// Set whether acquire by hash is enabled
1061    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    /// Get whether the release has no support for architecture all
1069    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    /// Set whether the release has no support for architecture all
1077    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    /// Get the architectures
1089    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    /// Set the architectures
1098    pub fn set_architectures(&mut self, architectures: Vec<String>) {
1099        self.0.set("Architectures", &architectures.join(" "));
1100    }
1101
1102    /// Get the components
1103    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    /// Set the components
1112    pub fn set_components(&mut self, components: Vec<String>) {
1113        self.0.set("Components", &components.join(" "));
1114    }
1115
1116    /// Get the description
1117    pub fn description(&self) -> Option<String> {
1118        self.0.get("Description").map(|s| s.to_string())
1119    }
1120
1121    /// Set the description
1122    pub fn set_description(&mut self, description: &str) {
1123        self.0.set("Description", description);
1124    }
1125
1126    /// Get the MD5 checksums
1127    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    /// Set the MD5 checksums
1139    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    /// Get the SHA1 checksums
1151    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    /// Set the SHA1 checksums
1163    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    /// Get the SHA256 checksums
1175    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    /// Set the SHA256 checksums
1187    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    /// Get the SHA512 checksums
1199    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    /// Set the SHA512 checksums
1211    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}