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
89/// A binary package.
90#[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)]
91pub struct Binary {
92    #[deb822(field = "Package")]
93    /// The name of the package.
94    pub name: String,
95    #[deb822(field = "Depends")]
96    /// The packages that this package depends on.
97    pub depends: Option<Relations>,
98    #[deb822(field = "Recommends")]
99    /// The packages that this package recommends.
100    pub recommends: Option<Relations>,
101    #[deb822(field = "Suggests")]
102    /// The packages that this package suggests.
103    pub suggests: Option<Relations>,
104    #[deb822(field = "Enhances")]
105    /// The packages that this package enhances.
106    pub enhances: Option<Relations>,
107    #[deb822(field = "Pre-Depends")]
108    /// The packages that this package depends on before it is installed.
109    pub pre_depends: Option<Relations>,
110    #[deb822(field = "Breaks")]
111    /// The packages that this package breaks.
112    pub breaks: Option<Relations>,
113    #[deb822(field = "Conflicts")]
114    /// The packages that this package conflicts with.
115    pub conflicts: Option<Relations>,
116    #[deb822(field = "Replaces")]
117    /// The packages that this package replaces.
118    pub replaces: Option<Relations>,
119    #[deb822(field = "Provides")]
120    /// The packages that this package provides.
121    pub provides: Option<Relations>,
122    #[deb822(field = "Built-Using")]
123    /// The packages that this package is built using.
124    pub built_using: Option<Relations>,
125    #[deb822(field = "Static-Built-Using")]
126    /// The packages that this package is statically built using.
127    pub static_built_using: Option<Relations>,
128    #[deb822(field = "Architecture")]
129    /// The architecture the package is built for.
130    pub architecture: Option<String>,
131    #[deb822(field = "Section")]
132    /// The section of the package.
133    pub section: Option<String>,
134    #[deb822(field = "Priority")]
135    /// The priority of the package.
136    pub priority: Option<Priority>,
137    #[deb822(field = "Multi-Arch")]
138    /// The multi-arch field.
139    pub multi_arch: Option<crate::fields::MultiArch>,
140    #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
141    /// Whether the package is essential.
142    pub essential: Option<bool>,
143    #[deb822(field = "Description")]
144    /// The description of the package. The first line is the short description, and the rest is the long description.
145    pub description: Option<String>,
146}
147
148impl std::fmt::Display for Binary {
149    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
150        let para: deb822_fast::Paragraph = self.to_paragraph();
151        write!(f, "{}", para)?;
152        Ok(())
153    }
154}
155
156/// A control file.
157#[derive(Clone, PartialEq)]
158pub struct Control {
159    /// The source package.
160    pub source: Source,
161    /// The binary packages.
162    pub binaries: Vec<Binary>,
163}
164
165impl std::fmt::Display for Control {
166    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
167        write!(f, "{}", self.source)?;
168        for binary in &self.binaries {
169            f.write_str("\n")?;
170            write!(f, "{}", binary)?;
171        }
172        Ok(())
173    }
174}
175
176impl std::str::FromStr for Control {
177    type Err = String;
178
179    fn from_str(s: &str) -> Result<Self, Self::Err> {
180        let deb822: deb822_fast::Deb822 = s.parse().map_err(|e| format!("parse error: {}", e))?;
181
182        let mut source: Option<Source> = None;
183        let mut binaries: Vec<Binary> = Vec::new();
184
185        for para in deb822.iter() {
186            if para.get("Package").is_some() {
187                let binary: Binary = Binary::from_paragraph(para)?;
188                binaries.push(binary);
189            } else if para.get("Source").is_some() {
190                if source.is_some() {
191                    return Err("more than one source paragraph".to_string());
192                }
193                source = Some(Source::from_paragraph(para)?);
194            } else {
195                return Err("paragraph without Source or Package field".to_string());
196            }
197        }
198
199        Ok(Control {
200            source: source.ok_or_else(|| "no source paragraph".to_string())?,
201            binaries,
202        })
203    }
204}
205
206impl Default for Control {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212impl Control {
213    /// Create a new control file.
214    pub fn new() -> Self {
215        Self {
216            source: Source::default(),
217            binaries: Vec::new(),
218        }
219    }
220
221    /// Add a new binary package to the control file.
222    pub fn add_binary(&mut self, name: &str) -> &mut Binary {
223        let binary = Binary {
224            name: name.to_string(),
225            ..Default::default()
226        };
227        self.binaries.push(binary);
228        self.binaries.last_mut().unwrap()
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use crate::relations::VersionConstraint;
236    #[test]
237    fn test_parse() {
238        let control: Control = r#"Source: foo
239Section: libs
240Priority: optional
241Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
242Homepage: https://example.com
243
244"#
245        .parse()
246        .unwrap();
247        let source = &control.source;
248
249        assert_eq!(source.name, "foo".to_owned());
250        assert_eq!(source.section, Some("libs".to_owned()));
251        assert_eq!(source.priority, Some(super::Priority::Optional));
252        assert_eq!(
253            source.homepage,
254            Some("https://example.com".parse().unwrap())
255        );
256        let bd = source.build_depends.as_ref().unwrap();
257        let mut entries = bd.iter().collect::<Vec<_>>();
258        assert_eq!(entries.len(), 2);
259        let rel = entries[0].pop().unwrap();
260        assert_eq!(rel.name, "bar");
261        assert_eq!(
262            rel.version,
263            Some((
264                VersionConstraint::GreaterThanEqual,
265                "1.0.0".parse().unwrap()
266            ))
267        );
268        let rel = entries[1].pop().unwrap();
269        assert_eq!(rel.name, "baz");
270        assert_eq!(
271            rel.version,
272            Some((
273                VersionConstraint::GreaterThanEqual,
274                "1.0.0".parse().unwrap()
275            ))
276        );
277    }
278
279    #[test]
280    fn test_description() {
281        let control: Control = r#"Source: foo
282
283Package: foo
284Description: this is the short description
285 And the longer one
286 .
287 is on the next lines
288"#
289        .parse()
290        .unwrap();
291        let binary = &control.binaries[0];
292        assert_eq!(
293            binary.description,
294            Some(
295                "this is the short description\nAnd the longer one\n.\nis on the next lines"
296                    .to_owned()
297            )
298        );
299    }
300}