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