debian_control/lossy/
apt.rs

1//! APT related structures
2use crate::lossy::{Relations, SourceRelation};
3use deb822_fast::{FromDeb822, FromDeb822Paragraph, ToDeb822, ToDeb822Paragraph};
4
5fn deserialize_yesno(s: &str) -> Result<bool, String> {
6    match s {
7        "yes" => Ok(true),
8        "no" => Ok(false),
9        _ => Err(format!("invalid value for yesno: {}", s)),
10    }
11}
12
13fn serialize_yesno(b: &bool) -> String {
14    if *b {
15        "yes".to_string()
16    } else {
17        "no".to_string()
18    }
19}
20
21fn deserialize_components(value: &str) -> Result<Vec<String>, String> {
22    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
23}
24
25fn join_whitespace(components: &[String]) -> String {
26    components.join(" ")
27}
28
29fn deserialize_architectures(value: &str) -> Result<Vec<String>, String> {
30    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
34/// A Release file
35pub struct Release {
36    #[deb822(field = "Codename")]
37    /// The codename of the release
38    pub codename: String,
39
40    #[deb822(
41        field = "Components",
42        deserialize_with = deserialize_components,
43        serialize_with = join_whitespace
44    )]
45    /// Components supported by the release
46    pub components: Vec<String>,
47
48    #[deb822(
49        field = "Architectures",
50        deserialize_with = deserialize_architectures,
51        serialize_with = join_whitespace
52    )]
53    /// Architectures supported by the release
54    pub architectures: Vec<String>,
55
56    #[deb822(field = "Description")]
57    /// Description of the release
58    pub description: String,
59
60    #[deb822(field = "Origin")]
61    /// Origin of the release
62    pub origin: String,
63
64    #[deb822(field = "Label")]
65    /// Label of the release
66    pub label: String,
67
68    #[deb822(field = "Suite")]
69    /// Suite of the release
70    pub suite: String,
71
72    #[deb822(field = "Version")]
73    /// Version of the release
74    pub version: String,
75
76    #[deb822(field = "Date")]
77    /// Date the release was published
78    pub date: String,
79
80    #[deb822(field = "NotAutomatic", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
81    /// Whether the release is not automatic
82    pub not_automatic: bool,
83
84    #[deb822(field = "ButAutomaticUpgrades", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
85    /// Indicates if packages retrieved from this release should be automatically upgraded
86    pub but_automatic_upgrades: bool,
87
88    #[deb822(field = "Acquire-By-Hash", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
89    /// Whether packages files can be acquired by hash
90    pub acquire_by_hash: bool,
91}
92
93fn deserialize_binaries(value: &str) -> Result<Vec<String>, String> {
94    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
95}
96
97fn join_lines(components: &[String]) -> String {
98    components.join("\n")
99}
100
101fn deserialize_package_list(value: &str) -> Result<Vec<String>, String> {
102    Ok(value.split('\n').map(|s| s.to_string()).collect())
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
106/// A source
107pub struct Source {
108    #[deb822(field = "Directory")]
109    /// The directory of the source
110    pub directory: String,
111
112    #[deb822(field = "Description")]
113    /// Description of the source
114    pub description: Option<String>,
115
116    #[deb822(field = "Version")]
117    /// Version of the source
118    pub version: debversion::Version,
119
120    #[deb822(field = "Package")]
121    /// Package of the source
122    pub package: String,
123
124    #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)]
125    /// Binaries of the source
126    pub binaries: Option<Vec<String>>,
127
128    #[deb822(field = "Maintainer")]
129    /// Maintainer of the source
130    pub maintainer: Option<String>,
131
132    #[deb822(field = "Build-Depends")]
133    /// Build dependencies of the source
134    pub build_depends: Option<String>,
135
136    #[deb822(field = "Build-Depends-Indep")]
137    /// Build dependencies independent of the architecture of the source
138    pub build_depends_indep: Option<Relations>,
139
140    #[deb822(field = "Build-Conflicts")]
141    /// Build conflicts of the source
142    pub build_conflicts: Option<Relations>,
143
144    #[deb822(field = "Build-Conflicts-Indep")]
145    /// Build conflicts independent of the architecture of the source
146    pub build_conflicts_indep: Option<Relations>,
147
148    #[deb822(field = "Standards-Version")]
149    /// Standards version of the source
150    pub standards_version: Option<String>,
151
152    #[deb822(field = "Homepage")]
153    /// Homepage of the source
154    pub homepage: Option<String>,
155
156    #[deb822(field = "Autobuild")]
157    /// Whether the source should be autobuilt
158    pub autobuild: Option<bool>,
159
160    #[deb822(field = "Testsuite")]
161    /// Testsuite of the source
162    pub testsuite: Option<String>,
163
164    #[deb822(field = "Vcs-Browser")]
165    /// VCS browser of the source
166    pub vcs_browser: Option<String>,
167
168    #[deb822(field = "Vcs-Git")]
169    /// VCS Git of the source
170    pub vcs_git: Option<String>,
171
172    #[deb822(field = "Vcs-Bzr")]
173    /// VCS Bzr of the source
174    pub vcs_bzr: Option<String>,
175
176    #[deb822(field = "Vcs-Hg")]
177    /// VCS Hg of the source
178    pub vcs_hg: Option<String>,
179
180    #[deb822(field = "Vcs-Svn")]
181    /// VCS SVN of the source
182    pub vcs_svn: Option<String>,
183
184    #[deb822(field = "Vcs-Darcs")]
185    /// VCS Darcs of the source
186    pub vcs_darcs: Option<String>,
187
188    #[deb822(field = "Vcs-Cvs")]
189    /// VCS CVS of the source
190    pub vcs_cvs: Option<String>,
191
192    #[deb822(field = "Vcs-Arch")]
193    /// VCS Arch of the source
194    pub vcs_arch: Option<String>,
195
196    #[deb822(field = "Vcs-Mtn")]
197    /// VCS Mtn of the source
198    pub vcs_mtn: Option<String>,
199
200    #[deb822(field = "Priority")]
201    /// Priority of the source
202    pub priority: Option<crate::fields::Priority>,
203
204    #[deb822(field = "Section")]
205    /// Section of the source
206    pub section: Option<String>,
207
208    #[deb822(field = "Format")]
209    /// Format of the source
210    pub format: Option<String>,
211
212    #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)]
213    /// Package list of the source
214    pub package_list: Vec<String>,
215}
216
217impl std::str::FromStr for Source {
218    type Err = String;
219
220    fn from_str(s: &str) -> Result<Self, Self::Err> {
221        let para = s
222            .parse::<deb822_fast::Paragraph>()
223            .map_err(|e| e.to_string())?;
224
225        FromDeb822Paragraph::from_paragraph(&para)
226    }
227}
228
229impl std::fmt::Display for Source {
230    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
231        let para: deb822_fast::Paragraph = self.to_paragraph();
232        write!(f, "{}", para)
233    }
234}
235
236/// A package
237#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
238pub struct Package {
239    /// The name of the package
240    #[deb822(field = "Package")]
241    pub name: String,
242
243    /// The version of the package
244    #[deb822(field = "Version")]
245    pub version: debversion::Version,
246
247    /// The name and version of the source package, if different from `name`
248    #[deb822(field = "Source")]
249    pub source: Option<SourceRelation>,
250
251    /// The architecture of the package
252    #[deb822(field = "Architecture")]
253    pub architecture: String,
254
255    /// The maintainer of the package
256    #[deb822(field = "Maintainer")]
257    pub maintainer: Option<String>,
258
259    /// The installed size of the package
260    #[deb822(field = "Installed-Size")]
261    pub installed_size: Option<usize>,
262
263    /// Dependencies
264    #[deb822(field = "Depends")]
265    pub depends: Option<Relations>,
266
267    /// Pre-Depends
268    #[deb822(field = "Pre-Depends")]
269    pub pre_depends: Option<Relations>,
270
271    /// Recommends
272    #[deb822(field = "Recommends")]
273    pub recommends: Option<Relations>,
274
275    /// Suggests
276    #[deb822(field = "Suggests")]
277    pub suggests: Option<Relations>,
278
279    /// Enhances
280    #[deb822(field = "Enhances")]
281    pub enhances: Option<Relations>,
282
283    /// Breaks
284    #[deb822(field = "Breaks")]
285    pub breaks: Option<Relations>,
286
287    /// Conflicts
288    #[deb822(field = "Conflicts")]
289    pub conflicts: Option<Relations>,
290
291    /// Provides
292    #[deb822(field = "Provides")]
293    pub provides: Option<Relations>,
294
295    /// Replaces
296    #[deb822(field = "Replaces")]
297    pub replaces: Option<Relations>,
298
299    /// Built-Using
300    #[deb822(field = "Built-Using")]
301    pub built_using: Option<Relations>,
302
303    /// Description
304    #[deb822(field = "Description")]
305    pub description: Option<String>,
306
307    /// Homepage
308    #[deb822(field = "Homepage")]
309    pub homepage: Option<String>,
310
311    /// Priority
312    #[deb822(field = "Priority")]
313    pub priority: Option<crate::fields::Priority>,
314
315    /// Section
316    #[deb822(field = "Section")]
317    pub section: Option<String>,
318
319    /// Essential
320    #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
321    pub essential: Option<bool>,
322
323    /// Tag
324    #[deb822(field = "Tag")]
325    pub tag: Option<String>,
326
327    /// Size
328    #[deb822(field = "Size")]
329    pub size: Option<usize>,
330
331    /// MD5sum
332    #[deb822(field = "MD5sum")]
333    pub md5sum: Option<String>,
334
335    /// SHA256
336    #[deb822(field = "SHA256")]
337    pub sha256: Option<String>,
338
339    /// Description (MD5)
340    #[deb822(field = "Description-MD5")]
341    pub description_md5: Option<String>,
342}
343
344impl std::str::FromStr for Package {
345    type Err = String;
346
347    fn from_str(s: &str) -> Result<Self, Self::Err> {
348        let para = s
349            .parse::<deb822_fast::Paragraph>()
350            .map_err(|e| e.to_string())?;
351
352        FromDeb822Paragraph::from_paragraph(&para)
353    }
354}
355
356impl std::fmt::Display for Package {
357    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
358        let para: deb822_fast::Paragraph = self.to_paragraph();
359        write!(f, "{}", para)
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366    use deb822_fast::Paragraph;
367    use deb822_fast::ToDeb822Paragraph;
368
369    #[test]
370    fn test_release() {
371        let release = Release {
372            codename: "focal".to_string(),
373            components: vec!["main".to_string(), "restricted".to_string()],
374            architectures: vec!["amd64".to_string(), "arm64".to_string()],
375            description: "Ubuntu 20.04 LTS".to_string(),
376            origin: "Ubuntu".to_string(),
377            label: "Ubuntu".to_string(),
378            suite: "focal".to_string(),
379            version: "20.04".to_string(),
380            date: "Thu, 23 Apr 2020 17:19:19 UTC".to_string(),
381            not_automatic: false,
382            but_automatic_upgrades: true,
383            acquire_by_hash: true,
384        };
385
386        let deb822 = r#"Codename: focal
387Components: main restricted
388Architectures: amd64 arm64
389Description: Ubuntu 20.04 LTS
390Origin: Ubuntu
391Label: Ubuntu
392Suite: focal
393Version: 20.04
394Date: Thu, 23 Apr 2020 17:19:19 UTC
395NotAutomatic: no
396ButAutomaticUpgrades: yes
397Acquire-By-Hash: yes
398"#;
399
400        let para = deb822.parse::<Paragraph>().unwrap();
401
402        let release: deb822_fast::Paragraph = release.to_paragraph();
403
404        assert_eq!(release, para);
405    }
406
407    #[test]
408    fn test_package() {
409        let package = r#"Package: apt
410Version: 2.1.10
411Architecture: amd64
412Maintainer: APT Development Team <apt@lists.debian.org>
413Installed-Size: 3524
414Depends: libc6 (>= 2.14), libgcc1
415Pre-Depends: dpkg (>= 1.15.6)
416Recommends: gnupg
417Suggests: apt-doc, aptitude | synaptic | wajig
418"#;
419
420        let package: Package = package.parse().unwrap();
421
422        assert_eq!(package.name, "apt");
423        assert_eq!(package.version, "2.1.10".parse().unwrap());
424        assert_eq!(package.architecture, "amd64");
425    }
426
427    #[test]
428    fn test_package_essential() {
429        let package = r#"Package: base-files
430Version: 11.1
431Architecture: amd64
432Essential: yes
433"#;
434
435        let package: Package = package.parse().unwrap();
436
437        assert_eq!(package.name, "base-files");
438        assert_eq!(package.essential, Some(true));
439    }
440
441    #[test]
442    fn test_package_essential_no() {
443        let package = r#"Package: apt
444Version: 2.1.10
445Architecture: amd64
446Essential: no
447"#;
448
449        let package: Package = package.parse().unwrap();
450
451        assert_eq!(package.name, "apt");
452        assert_eq!(package.essential, Some(false));
453    }
454
455    #[test]
456    fn test_package_with_different_source() {
457        let package = r#"Package: apt
458Source: not-apt (1.1.5)
459Version: 2.1.10
460Architecture: amd64
461Maintainer: APT Development Team <apt@lists.debian.org>
462Installed-Size: 3524
463Depends: libc6 (>= 2.14), libgcc1
464Pre-Depends: dpkg (>= 1.15.6)
465Recommends: gnupg
466Suggests: apt-doc, aptitude | synaptic | wajig
467"#;
468
469        let package: Package = package.parse().unwrap();
470
471        assert_eq!(package.name, "apt");
472        assert_eq!(package.version, "2.1.10".parse().unwrap());
473        assert_eq!(package.architecture, "amd64");
474        assert_eq!(
475            package.source,
476            Some(SourceRelation {
477                name: "not-apt".to_string(),
478                version: Some("1.1.5".parse().unwrap())
479            })
480        );
481    }
482}