debian_control/lossless/
control.rs

1//! This module provides a lossless representation of a Debian control file.
2//!
3//! # Example
4//! ```rust
5//! use debian_control::lossless::Control;
6//! use debian_control::relations::VersionConstraint;
7//! let input = r###"Source: dulwich
8//! ## Comments are preserved
9//! Maintainer: Jelmer Vernooij <jelmer@jelmer.uk>
10//! Build-Depends: python3, debhelper-compat (= 12)
11//!
12//! Package: python3-dulwich
13//! Architecture: amd64
14//! Description: Pure-python git implementation
15//! "###;
16//!
17//! let mut control: Control = input.parse().unwrap();
18//!
19//! // Bump debhelper-compat
20//! let source = control.source().unwrap();
21//! let bd = source.build_depends().unwrap();
22//!
23//! // Get entry with index 1 in Build-Depends, then set the version
24//! let entry = bd.get_entry(1).unwrap();
25//! let mut debhelper = entry.relations().next().unwrap();
26//! assert_eq!(debhelper.name(), "debhelper-compat");
27//! debhelper.set_version(Some((VersionConstraint::Equal, "13".parse().unwrap())));
28//!
29//! assert_eq!(source.to_string(), r###"Source: dulwich
30//! ## Comments are preserved
31//! Maintainer: Jelmer Vernooij <jelmer@jelmer.uk>
32//! Build-Depends: python3, debhelper-compat (= 12)
33//! "###);
34//! ```
35use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph};
38
39fn format_field(name: &str, value: &str) -> String {
40    match name {
41        "Uploaders" => value
42            .split(',')
43            .map(|s| s.trim().to_string())
44            .collect::<Vec<_>>()
45            .join(",\n"),
46        "Build-Depends"
47        | "Build-Depends-Indep"
48        | "Build-Depends-Arch"
49        | "Build-Conflicts"
50        | "Build-Conflicts-Indep"
51        | "Build-Conflics-Arch"
52        | "Depends"
53        | "Recommends"
54        | "Suggests"
55        | "Enhances"
56        | "Pre-Depends"
57        | "Breaks" => {
58            let relations: Relations = value.parse().unwrap();
59            let relations = relations.wrap_and_sort();
60            relations.to_string()
61        }
62        _ => value.to_string(),
63    }
64}
65
66/// A Debian control file
67pub struct Control(Deb822);
68
69impl Control {
70    /// Create a new control file
71    pub fn new() -> Self {
72        Control(Deb822::new())
73    }
74
75    /// Return the underlying deb822 object, mutable
76    pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
77        &mut self.0
78    }
79
80    /// Return the underlying deb822 object
81    pub fn as_deb822(&self) -> &Deb822 {
82        &self.0
83    }
84
85    /// Return the source package
86    pub fn source(&self) -> Option<Source> {
87        self.0
88            .paragraphs()
89            .find(|p| p.get("Source").is_some())
90            .map(Source)
91    }
92
93    /// Iterate over all binary packages
94    pub fn binaries(&self) -> impl Iterator<Item = Binary> {
95        self.0
96            .paragraphs()
97            .filter(|p| p.get("Package").is_some())
98            .map(Binary)
99    }
100
101    /// Add a new source package
102    ///
103    /// # Arguments
104    /// * `name` - The name of the source package
105    ///
106    /// # Returns
107    /// The newly created source package
108    ///
109    /// # Example
110    /// ```rust
111    /// use debian_control::lossless::control::Control;
112    /// let mut control = Control::new();
113    /// let source = control.add_source("foo");
114    /// assert_eq!(source.name(), Some("foo".to_owned()));
115    /// ```
116    pub fn add_source(&mut self, name: &str) -> Source {
117        let mut p = self.0.add_paragraph();
118        p.set("Source", name);
119        self.source().unwrap()
120    }
121
122    /// Add new binary package
123    ///
124    /// # Arguments
125    /// * `name` - The name of the binary package
126    ///
127    /// # Returns
128    /// The newly created binary package
129    ///
130    /// # Example
131    /// ```rust
132    /// use debian_control::lossless::control::Control;
133    /// let mut control = Control::new();
134    /// let binary = control.add_binary("foo");
135    /// assert_eq!(binary.name(), Some("foo".to_owned()));
136    /// ```
137    pub fn add_binary(&mut self, name: &str) -> Binary {
138        let mut p = self.0.add_paragraph();
139        p.set("Package", name);
140        Binary(p)
141    }
142
143    /// Read a control file from a file
144    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
145        Ok(Control(Deb822::from_file(path)?))
146    }
147
148    /// Read a control file from a file, allowing syntax errors
149    pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
150        path: P,
151    ) -> Result<(Self, Vec<String>), std::io::Error> {
152        let (control, errors) = Deb822::from_file_relaxed(path)?;
153        Ok((Control(control), errors))
154    }
155
156    /// Read a control file from a reader
157    pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
158        Ok(Control(Deb822::read(&mut r)?))
159    }
160
161    /// Read a control file from a reader, allowing syntax errors
162    pub fn read_relaxed<R: std::io::Read>(
163        mut r: R,
164    ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
165        let (control, errors) = Deb822::read_relaxed(&mut r)?;
166        Ok((Self(control), errors))
167    }
168
169    /// Wrap and sort the control file
170    ///
171    /// # Arguments
172    /// * `indentation` - The indentation to use
173    /// * `immediate_empty_line` - Whether to add an empty line at the start of multi-line fields
174    /// * `max_line_length_one_liner` - The maximum line length for one-liner fields
175    pub fn wrap_and_sort(
176        &mut self,
177        indentation: deb822_lossless::Indentation,
178        immediate_empty_line: bool,
179        max_line_length_one_liner: Option<usize>,
180    ) {
181        let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
182            // Sort Source before Package
183            let a_is_source = a.get("Source").is_some();
184            let b_is_source = b.get("Source").is_some();
185
186            if a_is_source && !b_is_source {
187                return std::cmp::Ordering::Less;
188            } else if !a_is_source && b_is_source {
189                return std::cmp::Ordering::Greater;
190            } else if a_is_source && b_is_source {
191                return a.get("Source").cmp(&b.get("Source"));
192            }
193
194            a.get("Package").cmp(&b.get("Package"))
195        };
196
197        let wrap_paragraph = |p: &Paragraph| -> Paragraph {
198            // TODO: Add Source/Package specific wrapping
199            // TODO: Add support for wrapping and sorting fields
200            p.wrap_and_sort(
201                indentation,
202                immediate_empty_line,
203                max_line_length_one_liner,
204                None,
205                Some(&format_field),
206            )
207        };
208
209        self.0 = self
210            .0
211            .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
212    }
213}
214
215impl From<Control> for Deb822 {
216    fn from(c: Control) -> Self {
217        c.0
218    }
219}
220
221impl From<Deb822> for Control {
222    fn from(d: Deb822) -> Self {
223        Control(d)
224    }
225}
226
227impl Default for Control {
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233impl std::str::FromStr for Control {
234    type Err = deb822_lossless::ParseError;
235
236    fn from_str(s: &str) -> Result<Self, Self::Err> {
237        Ok(Control(s.parse()?))
238    }
239}
240
241/// A source package paragraph
242pub struct Source(Paragraph);
243
244impl From<Source> for Paragraph {
245    fn from(s: Source) -> Self {
246        s.0
247    }
248}
249
250impl From<Paragraph> for Source {
251    fn from(p: Paragraph) -> Self {
252        Source(p)
253    }
254}
255
256impl std::fmt::Display for Source {
257    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
258        self.0.fmt(f)
259    }
260}
261
262impl Source {
263    /// The name of the source package.
264    pub fn name(&self) -> Option<String> {
265        self.0.get("Source")
266    }
267
268    /// Wrap and sort the control file paragraph
269    pub fn wrap_and_sort(
270        &mut self,
271        indentation: deb822_lossless::Indentation,
272        immediate_empty_line: bool,
273        max_line_length_one_liner: Option<usize>,
274    ) {
275        self.0 = self.0.wrap_and_sort(
276            indentation,
277            immediate_empty_line,
278            max_line_length_one_liner,
279            None,
280            Some(&format_field),
281        );
282    }
283
284    /// Return the underlying deb822 paragraph, mutable
285    pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
286        &mut self.0
287    }
288
289    /// Return the underlying deb822 paragraph
290    pub fn as_deb822(&self) -> &Paragraph {
291        &self.0
292    }
293
294    /// Set the name of the source package.
295    pub fn set_name(&mut self, name: &str) {
296        self.0.set("Source", name);
297    }
298
299    /// The default section of the packages built from this source package.
300    pub fn section(&self) -> Option<String> {
301        self.0.get("Section")
302    }
303
304    /// Set the section of the source package
305    pub fn set_section(&mut self, section: Option<&str>) {
306        if let Some(section) = section {
307            self.0.set("Section", section);
308        } else {
309            self.0.remove("Section");
310        }
311    }
312
313    /// The default priority of the packages built from this source package.
314    pub fn priority(&self) -> Option<Priority> {
315        self.0.get("Priority").and_then(|v| v.parse().ok())
316    }
317
318    /// Set the priority of the source package
319    pub fn set_priority(&mut self, priority: Option<Priority>) {
320        if let Some(priority) = priority {
321            self.0.set("Priority", priority.to_string().as_str());
322        } else {
323            self.0.remove("Priority");
324        }
325    }
326
327    /// The maintainer of the package.
328    pub fn maintainer(&self) -> Option<String> {
329        self.0.get("Maintainer")
330    }
331
332    /// Set the maintainer of the package
333    pub fn set_maintainer(&mut self, maintainer: &str) {
334        self.0.set("Maintainer", maintainer);
335    }
336
337    /// The build dependencies of the package.
338    pub fn build_depends(&self) -> Option<Relations> {
339        self.0.get("Build-Depends").map(|s| s.parse().unwrap())
340    }
341
342    /// Set the Build-Depends field
343    pub fn set_build_depends(&mut self, relations: &Relations) {
344        self.0.set("Build-Depends", relations.to_string().as_str());
345    }
346
347    /// Return the Build-Depends-Indep field
348    pub fn build_depends_indep(&self) -> Option<Relations> {
349        self.0
350            .get("Build-Depends-Indep")
351            .map(|s| s.parse().unwrap())
352    }
353
354    /// Return the Build-Depends-Arch field
355    pub fn build_depends_arch(&self) -> Option<Relations> {
356        self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
357    }
358
359    /// The build conflicts of the package.
360    pub fn build_conflicts(&self) -> Option<Relations> {
361        self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
362    }
363
364    /// Return the Build-Conflicts-Indep field
365    pub fn build_conflicts_indep(&self) -> Option<Relations> {
366        self.0
367            .get("Build-Conflicts-Indep")
368            .map(|s| s.parse().unwrap())
369    }
370
371    /// Return the Build-Conflicts-Arch field
372    pub fn build_conflicts_arch(&self) -> Option<Relations> {
373        self.0
374            .get("Build-Conflicts-Arch")
375            .map(|s| s.parse().unwrap())
376    }
377
378    /// Return the standards version
379    pub fn standards_version(&self) -> Option<String> {
380        self.0.get("Standards-Version")
381    }
382
383    /// Set the Standards-Version field
384    pub fn set_standards_version(&mut self, version: &str) {
385        self.0.set("Standards-Version", version);
386    }
387
388    /// Return the upstrea mHomepage
389    pub fn homepage(&self) -> Option<url::Url> {
390        self.0.get("Homepage").and_then(|s| s.parse().ok())
391    }
392
393    /// Set the Homepage field
394    pub fn set_homepage(&mut self, homepage: &url::Url) {
395        self.0.set("Homepage", homepage.to_string().as_str());
396    }
397
398    /// Return the Vcs-Git field
399    pub fn vcs_git(&self) -> Option<String> {
400        self.0.get("Vcs-Git")
401    }
402
403    /// Set the Vcs-Git field
404    pub fn set_vcs_git(&mut self, url: &str) {
405        self.0.set("Vcs-Git", url);
406    }
407
408    /// Return the Vcs-Browser field
409    pub fn vcs_svn(&self) -> Option<String> {
410        self.0.get("Vcs-Svn").map(|s| s.to_string())
411    }
412
413    /// Set the Vcs-Svn field
414    pub fn set_vcs_svn(&mut self, url: &str) {
415        self.0.set("Vcs-Svn", url);
416    }
417
418    /// Return the Vcs-Bzr field
419    pub fn vcs_bzr(&self) -> Option<String> {
420        self.0.get("Vcs-Bzr").map(|s| s.to_string())
421    }
422
423    /// Set the Vcs-Bzr field
424    pub fn set_vcs_bzr(&mut self, url: &str) {
425        self.0.set("Vcs-Bzr", url);
426    }
427
428    /// Return the Vcs-Arch field
429    pub fn vcs_arch(&self) -> Option<String> {
430        self.0.get("Vcs-Arch").map(|s| s.to_string())
431    }
432
433    /// Set the Vcs-Arch field
434    pub fn set_vcs_arch(&mut self, url: &str) {
435        self.0.set("Vcs-Arch", url);
436    }
437
438    /// Return the Vcs-Svk field
439    pub fn vcs_svk(&self) -> Option<String> {
440        self.0.get("Vcs-Svk").map(|s| s.to_string())
441    }
442
443    /// Set the Vcs-Svk field
444    pub fn set_vcs_svk(&mut self, url: &str) {
445        self.0.set("Vcs-Svk", url);
446    }
447
448    /// Return the Vcs-Darcs field
449    pub fn vcs_darcs(&self) -> Option<String> {
450        self.0.get("Vcs-Darcs").map(|s| s.to_string())
451    }
452
453    /// Set the Vcs-Darcs field
454    pub fn set_vcs_darcs(&mut self, url: &str) {
455        self.0.set("Vcs-Darcs", url);
456    }
457
458    /// Return the Vcs-Mtn field
459    pub fn vcs_mtn(&self) -> Option<String> {
460        self.0.get("Vcs-Mtn").map(|s| s.to_string())
461    }
462
463    /// Set the Vcs-Mtn field
464    pub fn set_vcs_mtn(&mut self, url: &str) {
465        self.0.set("Vcs-Mtn", url);
466    }
467
468    /// Return the Vcs-Cvs field
469    pub fn vcs_cvs(&self) -> Option<String> {
470        self.0.get("Vcs-Cvs").map(|s| s.to_string())
471    }
472
473    /// Set the Vcs-Cvs field
474    pub fn set_vcs_cvs(&mut self, url: &str) {
475        self.0.set("Vcs-Cvs", url);
476    }
477
478    /// Return the Vcs-Hg field
479    pub fn vcs_hg(&self) -> Option<String> {
480        self.0.get("Vcs-Hg").map(|s| s.to_string())
481    }
482
483    /// Set the Vcs-Hg field
484    pub fn set_vcs_hg(&mut self, url: &str) {
485        self.0.set("Vcs-Hg", url);
486    }
487
488    /// Return the Vcs-Browser field
489    pub fn vcs_browser(&self) -> Option<String> {
490        self.0.get("Vcs-Browser")
491    }
492
493    /// Return the Vcs used by the package
494    pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
495        for (name, value) in self.0.items() {
496            if name.starts_with("Vcs-") && name != "Vcs-Browser" {
497                return crate::vcs::Vcs::from_field(&name, &value).ok();
498            }
499        }
500        None
501    }
502
503    /// Set the Vcs-Browser field
504    pub fn set_vcs_browser(&mut self, url: Option<&str>) {
505        if let Some(url) = url {
506            self.0.set("Vcs-Browser", url);
507        } else {
508            self.0.remove("Vcs-Browser");
509        }
510    }
511
512    /// Return the Uploaders field
513    pub fn uploaders(&self) -> Option<Vec<String>> {
514        self.0
515            .get("Uploaders")
516            .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
517    }
518
519    /// Set the uploaders field
520    pub fn set_uploaders(&mut self, uploaders: &[&str]) {
521        self.0.set(
522            "Uploaders",
523            uploaders
524                .iter()
525                .map(|s| s.to_string())
526                .collect::<Vec<_>>()
527                .join(", ")
528                .as_str(),
529        );
530    }
531
532    /// Return the architecture field
533    pub fn architecture(&self) -> Option<String> {
534        self.0.get("Architecture")
535    }
536
537    /// Set the architecture field
538    pub fn set_architecture(&mut self, arch: Option<&str>) {
539        if let Some(arch) = arch {
540            self.0.set("Architecture", arch);
541        } else {
542            self.0.remove("Architecture");
543        }
544    }
545
546    /// Return the Rules-Requires-Root field
547    pub fn rules_requires_root(&self) -> Option<bool> {
548        self.0
549            .get("Rules-Requires-Root")
550            .map(|s| match s.to_lowercase().as_str() {
551                "yes" => true,
552                "no" => false,
553                _ => panic!("invalid Rules-Requires-Root value"),
554            })
555    }
556
557    /// Set the Rules-Requires-Root field
558    pub fn set_rules_requires_root(&mut self, requires_root: bool) {
559        self.0.set(
560            "Rules-Requires-Root",
561            if requires_root { "yes" } else { "no" },
562        );
563    }
564
565    /// Return the Testsuite field
566    pub fn testsuite(&self) -> Option<String> {
567        self.0.get("Testsuite")
568    }
569
570    /// Set the Testsuite field
571    pub fn set_testsuite(&mut self, testsuite: &str) {
572        self.0.set("Testsuite", testsuite);
573    }
574}
575
576#[cfg(feature = "python-debian")]
577impl<'py> pyo3::IntoPyObject<'py> for Source {
578    type Target = pyo3::PyAny;
579    type Output = pyo3::Bound<'py, Self::Target>;
580    type Error = pyo3::PyErr;
581
582    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
583        self.0.into_pyobject(py)
584    }
585}
586
587#[cfg(feature = "python-debian")]
588impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
589    type Target = pyo3::PyAny;
590    type Output = pyo3::Bound<'py, Self::Target>;
591    type Error = pyo3::PyErr;
592
593    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
594        (&self.0).into_pyobject(py)
595    }
596}
597
598#[cfg(feature = "python-debian")]
599impl pyo3::FromPyObject<'_> for Source {
600    fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
601        use pyo3::prelude::*;
602        Ok(Source(ob.extract()?))
603    }
604}
605
606impl std::fmt::Display for Control {
607    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
608        self.0.fmt(f)
609    }
610}
611
612/// A binary package paragraph
613pub struct Binary(Paragraph);
614
615impl From<Binary> for Paragraph {
616    fn from(b: Binary) -> Self {
617        b.0
618    }
619}
620
621impl From<Paragraph> for Binary {
622    fn from(p: Paragraph) -> Self {
623        Binary(p)
624    }
625}
626
627#[cfg(feature = "python-debian")]
628impl<'py> pyo3::IntoPyObject<'py> for Binary {
629    type Target = pyo3::PyAny;
630    type Output = pyo3::Bound<'py, Self::Target>;
631    type Error = pyo3::PyErr;
632
633    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
634        self.0.into_pyobject(py)
635    }
636}
637
638#[cfg(feature = "python-debian")]
639impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
640    type Target = pyo3::PyAny;
641    type Output = pyo3::Bound<'py, Self::Target>;
642    type Error = pyo3::PyErr;
643
644    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
645        (&self.0).into_pyobject(py)
646    }
647}
648
649#[cfg(feature = "python-debian")]
650impl pyo3::FromPyObject<'_> for Binary {
651    fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
652        use pyo3::prelude::*;
653        Ok(Binary(ob.extract()?))
654    }
655}
656
657impl Default for Binary {
658    fn default() -> Self {
659        Self::new()
660    }
661}
662
663impl Binary {
664    /// Create a new binary package control file
665    pub fn new() -> Self {
666        Binary(Paragraph::new())
667    }
668
669    /// Return the underlying deb822 paragraph, mutable
670    pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
671        &mut self.0
672    }
673
674    /// Return the underlying deb822 paragraph
675    pub fn as_deb822(&self) -> &Paragraph {
676        &self.0
677    }
678
679    /// Wrap and sort the control file
680    pub fn wrap_and_sort(
681        &mut self,
682        indentation: deb822_lossless::Indentation,
683        immediate_empty_line: bool,
684        max_line_length_one_liner: Option<usize>,
685    ) {
686        self.0 = self.0.wrap_and_sort(
687            indentation,
688            immediate_empty_line,
689            max_line_length_one_liner,
690            None,
691            Some(&format_field),
692        );
693    }
694
695    /// The name of the package.
696    pub fn name(&self) -> Option<String> {
697        self.0.get("Package")
698    }
699
700    /// Set the name of the package
701    pub fn set_name(&mut self, name: &str) {
702        self.0.set("Package", name);
703    }
704
705    /// The section of the package.
706    pub fn section(&self) -> Option<String> {
707        self.0.get("Section")
708    }
709
710    /// Set the section
711    pub fn set_section(&mut self, section: Option<&str>) {
712        if let Some(section) = section {
713            self.0.set("Section", section);
714        } else {
715            self.0.remove("Section");
716        }
717    }
718
719    /// The priority of the package.
720    pub fn priority(&self) -> Option<Priority> {
721        self.0.get("Priority").and_then(|v| v.parse().ok())
722    }
723
724    /// Set the priority of the package
725    pub fn set_priority(&mut self, priority: Option<Priority>) {
726        if let Some(priority) = priority {
727            self.0.set("Priority", priority.to_string().as_str());
728        } else {
729            self.0.remove("Priority");
730        }
731    }
732
733    /// The architecture of the package.
734    pub fn architecture(&self) -> Option<String> {
735        self.0.get("Architecture")
736    }
737
738    /// Set the architecture of the package
739    pub fn set_architecture(&mut self, arch: Option<&str>) {
740        if let Some(arch) = arch {
741            self.0.set("Architecture", arch);
742        } else {
743            self.0.remove("Architecture");
744        }
745    }
746
747    /// The dependencies of the package.
748    pub fn depends(&self) -> Option<Relations> {
749        self.0.get("Depends").map(|s| s.parse().unwrap())
750    }
751
752    /// Set the Depends field
753    pub fn set_depends(&mut self, depends: Option<&Relations>) {
754        if let Some(depends) = depends {
755            self.0.set("Depends", depends.to_string().as_str());
756        } else {
757            self.0.remove("Depends");
758        }
759    }
760
761    /// The package that this package recommends
762    pub fn recommends(&self) -> Option<Relations> {
763        self.0.get("Recommends").map(|s| s.parse().unwrap())
764    }
765
766    /// Set the Recommends field
767    pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
768        if let Some(recommends) = recommends {
769            self.0.set("Recommends", recommends.to_string().as_str());
770        } else {
771            self.0.remove("Recommends");
772        }
773    }
774
775    /// Packages that this package suggests
776    pub fn suggests(&self) -> Option<Relations> {
777        self.0.get("Suggests").map(|s| s.parse().unwrap())
778    }
779
780    /// Set the Suggests field
781    pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
782        if let Some(suggests) = suggests {
783            self.0.set("Suggests", suggests.to_string().as_str());
784        } else {
785            self.0.remove("Suggests");
786        }
787    }
788
789    /// The package that this package enhances
790    pub fn enhances(&self) -> Option<Relations> {
791        self.0.get("Enhances").map(|s| s.parse().unwrap())
792    }
793
794    /// Set the Enhances field
795    pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
796        if let Some(enhances) = enhances {
797            self.0.set("Enhances", enhances.to_string().as_str());
798        } else {
799            self.0.remove("Enhances");
800        }
801    }
802
803    /// The package that this package pre-depends on
804    pub fn pre_depends(&self) -> Option<Relations> {
805        self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
806    }
807
808    /// Set the Pre-Depends field
809    pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
810        if let Some(pre_depends) = pre_depends {
811            self.0.set("Pre-Depends", pre_depends.to_string().as_str());
812        } else {
813            self.0.remove("Pre-Depends");
814        }
815    }
816
817    /// The package that this package breaks
818    pub fn breaks(&self) -> Option<Relations> {
819        self.0.get("Breaks").map(|s| s.parse().unwrap())
820    }
821
822    /// Set the Breaks field
823    pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
824        if let Some(breaks) = breaks {
825            self.0.set("Breaks", breaks.to_string().as_str());
826        } else {
827            self.0.remove("Breaks");
828        }
829    }
830
831    /// The package that this package conflicts with
832    pub fn conflicts(&self) -> Option<Relations> {
833        self.0.get("Conflicts").map(|s| s.parse().unwrap())
834    }
835
836    /// Set the Conflicts field
837    pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
838        if let Some(conflicts) = conflicts {
839            self.0.set("Conflicts", conflicts.to_string().as_str());
840        } else {
841            self.0.remove("Conflicts");
842        }
843    }
844
845    /// The package that this package replaces
846    pub fn replaces(&self) -> Option<Relations> {
847        self.0.get("Replaces").map(|s| s.parse().unwrap())
848    }
849
850    /// Set the Replaces field
851    pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
852        if let Some(replaces) = replaces {
853            self.0.set("Replaces", replaces.to_string().as_str());
854        } else {
855            self.0.remove("Replaces");
856        }
857    }
858
859    /// Return the Provides field
860    pub fn provides(&self) -> Option<Relations> {
861        self.0.get("Provides").map(|s| s.parse().unwrap())
862    }
863
864    /// Set the Provides field
865    pub fn set_provides(&mut self, provides: Option<&Relations>) {
866        if let Some(provides) = provides {
867            self.0.set("Provides", provides.to_string().as_str());
868        } else {
869            self.0.remove("Provides");
870        }
871    }
872
873    /// Return the Built-Using field
874    pub fn built_using(&self) -> Option<Relations> {
875        self.0.get("Built-Using").map(|s| s.parse().unwrap())
876    }
877
878    /// Set the Built-Using field
879    pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
880        if let Some(built_using) = built_using {
881            self.0.set("Built-Using", built_using.to_string().as_str());
882        } else {
883            self.0.remove("Built-Using");
884        }
885    }
886
887    /// The Multi-Arch field
888    pub fn multi_arch(&self) -> Option<MultiArch> {
889        self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
890    }
891
892    /// Set the Multi-Arch field
893    pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
894        if let Some(multi_arch) = multi_arch {
895            self.0.set("Multi-Arch", multi_arch.to_string().as_str());
896        } else {
897            self.0.remove("Multi-Arch");
898        }
899    }
900
901    /// Whether the package is essential
902    pub fn essential(&self) -> bool {
903        self.0.get("Essential").map(|s| s == "yes").unwrap_or(false)
904    }
905
906    /// Set whether the package is essential
907    pub fn set_essential(&mut self, essential: bool) {
908        if essential {
909            self.0.set("Essential", "yes");
910        } else {
911            self.0.remove("Essential");
912        }
913    }
914
915    /// Binary package description
916    pub fn description(&self) -> Option<String> {
917        self.0.get("Description")
918    }
919
920    /// Set the binary package description
921    pub fn set_description(&mut self, description: Option<&str>) {
922        if let Some(description) = description {
923            self.0.set("Description", description);
924        } else {
925            self.0.remove("Description");
926        }
927    }
928
929    /// Return the upstream homepage
930    pub fn homepage(&self) -> Option<url::Url> {
931        self.0.get("Homepage").and_then(|s| s.parse().ok())
932    }
933
934    /// Set the upstream homepage
935    pub fn set_homepage(&mut self, url: &url::Url) {
936        self.0.set("Homepage", url.as_str());
937    }
938}
939
940#[cfg(test)]
941mod tests {
942    use super::*;
943    use crate::relations::VersionConstraint;
944    #[test]
945    fn test_parse() {
946        let control: Control = r#"Source: foo
947Section: libs
948Priority: optional
949Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
950Homepage: https://example.com
951
952"#
953        .parse()
954        .unwrap();
955        let source = control.source().unwrap();
956
957        assert_eq!(source.name(), Some("foo".to_owned()));
958        assert_eq!(source.section(), Some("libs".to_owned()));
959        assert_eq!(source.priority(), Some(super::Priority::Optional));
960        assert_eq!(
961            source.homepage(),
962            Some("https://example.com".parse().unwrap())
963        );
964        let bd = source.build_depends().unwrap();
965        let entries = bd.entries().collect::<Vec<_>>();
966        assert_eq!(entries.len(), 2);
967        let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
968        assert_eq!(rel.name(), "bar");
969        assert_eq!(
970            rel.version(),
971            Some((
972                VersionConstraint::GreaterThanEqual,
973                "1.0.0".parse().unwrap()
974            ))
975        );
976        let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
977        assert_eq!(rel.name(), "baz");
978        assert_eq!(
979            rel.version(),
980            Some((
981                VersionConstraint::GreaterThanEqual,
982                "1.0.0".parse().unwrap()
983            ))
984        );
985    }
986
987    #[test]
988    fn test_description() {
989        let control: Control = r#"Source: foo
990
991Package: foo
992Description: this is the short description
993 And the longer one
994 .
995 is on the next lines
996"#
997        .parse()
998        .unwrap();
999        let binary = control.binaries().next().unwrap();
1000        assert_eq!(
1001            binary.description(),
1002            Some(
1003                "this is the short description\nAnd the longer one\n.\nis on the next lines"
1004                    .to_owned()
1005            )
1006        );
1007    }
1008
1009    #[test]
1010    fn test_as_mut_deb822() {
1011        let mut control = Control::new();
1012        let deb822 = control.as_mut_deb822();
1013        let mut p = deb822.add_paragraph();
1014        p.set("Source", "foo");
1015        assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1016    }
1017
1018    #[test]
1019    fn test_as_deb822() {
1020        let control = Control::new();
1021        let _deb822: &Deb822 = control.as_deb822();
1022    }
1023
1024    #[test]
1025    fn test_set_depends() {
1026        let mut control = Control::new();
1027        let mut binary = control.add_binary("foo");
1028        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1029        binary.set_depends(Some(&relations));
1030    }
1031
1032    #[test]
1033    fn test_wrap_and_sort() {
1034        let mut control: Control = r#"Package: blah
1035Section:     libs
1036
1037
1038
1039Package: foo
1040Description: this is a 
1041      bar
1042      blah
1043"#
1044        .parse()
1045        .unwrap();
1046        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1047        let expected = r#"Package: blah
1048Section: libs
1049
1050Package: foo
1051Description: this is a 
1052  bar
1053  blah
1054"#
1055        .to_owned();
1056        assert_eq!(control.to_string(), expected);
1057    }
1058
1059    #[test]
1060    fn test_wrap_and_sort_source() {
1061        let mut control: Control = r#"Source: blah
1062Depends: foo, bar   (<=  1.0.0)
1063
1064"#
1065        .parse()
1066        .unwrap();
1067        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1068        let expected = r#"Source: blah
1069Depends: bar (<= 1.0.0), foo
1070"#
1071        .to_owned();
1072        assert_eq!(control.to_string(), expected);
1073    }
1074}