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