Skip to main content

debian_control/lossy/
control.rs

1use crate::fields::Priority;
2use crate::lossy::relations::Relations;
3use deb822_fast::{FromDeb822, ToDeb822};
4use deb822_fast::{FromDeb822Paragraph, ToDeb822Paragraph};
5
6fn deserialize_yesno(s: &str) -> Result<bool, String> {
7    match s {
8        "yes" => Ok(true),
9        "no" => Ok(false),
10        _ => Err(format!("invalid value for yesno: {}", s)),
11    }
12}
13
14fn serialize_yesno(b: &bool) -> String {
15    if *b {
16        "yes".to_string()
17    } else {
18        "no".to_string()
19    }
20}
21
22/// The source package.
23#[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)]
24pub struct Source {
25    #[deb822(field = "Source")]
26    /// The name of the source package.
27    pub name: String,
28    #[deb822(field = "Build-Depends")]
29    /// The packages that this package depends on during build.
30    pub build_depends: Option<Relations>,
31    #[deb822(field = "Build-Depends-Indep")]
32    /// The packages that this package depends on during build.
33    pub build_depends_indep: Option<Relations>,
34    #[deb822(field = "Build-Depends-Arch")]
35    /// The packages that this package depends on during build.
36    pub build_depends_arch: Option<Relations>,
37    #[deb822(field = "Build-Conflicts")]
38    /// The packages that this package conflicts with during build.
39    pub build_conflicts: Option<Relations>,
40    #[deb822(field = "Build-Conflicts-Indep")]
41    /// The packages that this package conflicts with during build.
42    pub build_conflicts_indep: Option<Relations>,
43    #[deb822(field = "Build-Conflicts-Arch")]
44    /// The packages that this package conflicts with during build.
45    pub build_conflicts_arch: Option<Relations>,
46    #[deb822(field = "Standards-Version")]
47    /// The version of the Debian Policy Manual that the package complies with.
48    pub standards_version: Option<String>,
49    #[deb822(field = "Homepage")]
50    /// The homepage of the package.
51    pub homepage: Option<url::Url>,
52    #[deb822(field = "Section")]
53    /// The section of the package.
54    pub section: Option<String>,
55    #[deb822(field = "Priority")]
56    /// The priority of the package.
57    pub priority: Option<Priority>,
58    #[deb822(field = "Maintainer")]
59    /// The maintainer of the package.
60    pub maintainer: Option<String>,
61    #[deb822(field = "Uploaders")]
62    /// The uploaders of the package.
63    pub uploaders: Option<String>,
64    #[deb822(field = "Architecture")]
65    /// The architecture the package is built for.
66    pub architecture: Option<String>,
67    #[deb822(field = "Rules-Requires-Root", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
68    /// Whether the package's build rules require root.
69    pub rules_requires_root: Option<bool>,
70    #[deb822(field = "Testsuite")]
71    /// The name of the test suite.
72    pub testsuite: Option<String>,
73    #[deb822(field = "Vcs-Git")]
74    /// The URL of the Git repository.
75    pub vcs_git: Option<crate::vcs::ParsedVcs>,
76    #[deb822(field = "Vcs-Browser")]
77    /// The URL to the web interface of the VCS.
78    pub vcs_browser: Option<url::Url>,
79}
80
81impl std::fmt::Display for Source {
82    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
83        let para: deb822_fast::Paragraph = self.to_paragraph();
84        write!(f, "{}", para)?;
85        Ok(())
86    }
87}
88
89impl Source {
90    /// Return whether this package is maintained by the Debian QA team.
91    ///
92    /// Orphaned packages have their `Maintainer` field set to
93    /// `Debian QA Group <packages@qa.debian.org>`.
94    pub fn is_qa_package(&self) -> bool {
95        self.maintainer
96            .as_deref()
97            .and_then(|m| crate::parse_identity(m).ok())
98            .map(|(_, email)| email.eq_ignore_ascii_case("packages@qa.debian.org"))
99            .unwrap_or(false)
100    }
101}
102
103/// A binary package.
104#[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)]
105pub struct Binary {
106    #[deb822(field = "Package")]
107    /// The name of the package.
108    pub name: String,
109    #[deb822(field = "Depends")]
110    /// The packages that this package depends on.
111    pub depends: Option<Relations>,
112    #[deb822(field = "Recommends")]
113    /// The packages that this package recommends.
114    pub recommends: Option<Relations>,
115    #[deb822(field = "Suggests")]
116    /// The packages that this package suggests.
117    pub suggests: Option<Relations>,
118    #[deb822(field = "Enhances")]
119    /// The packages that this package enhances.
120    pub enhances: Option<Relations>,
121    #[deb822(field = "Pre-Depends")]
122    /// The packages that this package depends on before it is installed.
123    pub pre_depends: Option<Relations>,
124    #[deb822(field = "Breaks")]
125    /// The packages that this package breaks.
126    pub breaks: Option<Relations>,
127    #[deb822(field = "Conflicts")]
128    /// The packages that this package conflicts with.
129    pub conflicts: Option<Relations>,
130    #[deb822(field = "Replaces")]
131    /// The packages that this package replaces.
132    pub replaces: Option<Relations>,
133    #[deb822(field = "Provides")]
134    /// The packages that this package provides.
135    pub provides: Option<Relations>,
136    #[deb822(field = "Built-Using")]
137    /// The packages that this package is built using.
138    pub built_using: Option<Relations>,
139    #[deb822(field = "Static-Built-Using")]
140    /// The packages that this package is statically built using.
141    pub static_built_using: Option<Relations>,
142    #[deb822(field = "Architecture")]
143    /// The architecture the package is built for.
144    pub architecture: Option<String>,
145    #[deb822(field = "Section")]
146    /// The section of the package.
147    pub section: Option<String>,
148    #[deb822(field = "Priority")]
149    /// The priority of the package.
150    pub priority: Option<Priority>,
151    #[deb822(field = "Multi-Arch")]
152    /// The multi-arch field.
153    pub multi_arch: Option<crate::fields::MultiArch>,
154    #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
155    /// Whether the package is essential.
156    pub essential: Option<bool>,
157    #[deb822(field = "Description")]
158    /// The description of the package. The first line is the short description, and the rest is the long description.
159    pub description: Option<String>,
160}
161
162impl std::fmt::Display for Binary {
163    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
164        let para: deb822_fast::Paragraph = self.to_paragraph();
165        write!(f, "{}", para)?;
166        Ok(())
167    }
168}
169
170/// A control file.
171#[derive(Clone, PartialEq)]
172pub struct Control {
173    /// The source package.
174    pub source: Source,
175    /// The binary packages.
176    pub binaries: Vec<Binary>,
177}
178
179impl std::fmt::Display for Control {
180    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
181        write!(f, "{}", self.source)?;
182        for binary in &self.binaries {
183            f.write_str("\n")?;
184            write!(f, "{}", binary)?;
185        }
186        Ok(())
187    }
188}
189
190impl std::str::FromStr for Control {
191    type Err = String;
192
193    fn from_str(s: &str) -> Result<Self, Self::Err> {
194        let deb822: deb822_fast::Deb822 = s.parse().map_err(|e| format!("parse error: {}", e))?;
195
196        let mut source: Option<Source> = None;
197        let mut binaries: Vec<Binary> = Vec::new();
198
199        for para in deb822.iter() {
200            if para.get("Package").is_some() {
201                let binary: Binary = Binary::from_paragraph(para)?;
202                binaries.push(binary);
203            } else if para.get("Source").is_some() {
204                if source.is_some() {
205                    return Err("more than one source paragraph".to_string());
206                }
207                source = Some(Source::from_paragraph(para)?);
208            } else {
209                return Err("paragraph without Source or Package field".to_string());
210            }
211        }
212
213        Ok(Control {
214            source: source.ok_or_else(|| "no source paragraph".to_string())?,
215            binaries,
216        })
217    }
218}
219
220impl Default for Control {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226impl Control {
227    /// Create a new control file.
228    pub fn new() -> Self {
229        Self {
230            source: Source::default(),
231            binaries: Vec::new(),
232        }
233    }
234
235    /// Add a new binary package to the control file.
236    pub fn add_binary(&mut self, name: &str) -> &mut Binary {
237        let binary = Binary {
238            name: name.to_string(),
239            ..Default::default()
240        };
241        self.binaries.push(binary);
242        self.binaries.last_mut().unwrap()
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::relations::VersionConstraint;
250    #[test]
251    fn test_parse() {
252        let control: Control = r#"Source: foo
253Section: libs
254Priority: optional
255Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
256Homepage: https://example.com
257
258"#
259        .parse()
260        .unwrap();
261        let source = &control.source;
262
263        assert_eq!(source.name, "foo".to_owned());
264        assert_eq!(source.section, Some("libs".to_owned()));
265        assert_eq!(source.priority, Some(super::Priority::Optional));
266        assert_eq!(
267            source.homepage,
268            Some("https://example.com".parse().unwrap())
269        );
270        let bd = source.build_depends.as_ref().unwrap();
271        let mut entries = bd.iter().collect::<Vec<_>>();
272        assert_eq!(entries.len(), 2);
273        let rel = entries[0].pop().unwrap();
274        assert_eq!(rel.name, "bar");
275        assert_eq!(
276            rel.version,
277            Some((
278                VersionConstraint::GreaterThanEqual,
279                "1.0.0".parse().unwrap()
280            ))
281        );
282        let rel = entries[1].pop().unwrap();
283        assert_eq!(rel.name, "baz");
284        assert_eq!(
285            rel.version,
286            Some((
287                VersionConstraint::GreaterThanEqual,
288                "1.0.0".parse().unwrap()
289            ))
290        );
291    }
292
293    #[test]
294    fn test_description() {
295        let control: Control = r#"Source: foo
296
297Package: foo
298Description: this is the short description
299 And the longer one
300 .
301 is on the next lines
302"#
303        .parse()
304        .unwrap();
305        let binary = &control.binaries[0];
306        assert_eq!(
307            binary.description,
308            Some(
309                "this is the short description\nAnd the longer one\n.\nis on the next lines"
310                    .to_owned()
311            )
312        );
313    }
314
315    #[test]
316    fn test_is_qa_package() {
317        let control: Control = r#"Source: foo
318Maintainer: Debian QA Group <packages@qa.debian.org>
319"#
320        .parse()
321        .unwrap();
322        assert!(control.source.is_qa_package());
323
324        let control: Control = r#"Source: foo
325Maintainer: Jane Packager <jane@example.com>
326"#
327        .parse()
328        .unwrap();
329        assert!(!control.source.is_qa_package());
330
331        let control: Control = r#"Source: foo
332"#
333        .parse()
334        .unwrap();
335        assert!(!control.source.is_qa_package());
336    }
337}