Skip to main content

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