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