1use 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)]
34pub struct Release {
36 #[deb822(field = "Codename")]
37 pub codename: String,
39
40 #[deb822(
41 field = "Components",
42 deserialize_with = deserialize_components,
43 serialize_with = join_whitespace
44 )]
45 pub components: Vec<String>,
47
48 #[deb822(
49 field = "Architectures",
50 deserialize_with = deserialize_architectures,
51 serialize_with = join_whitespace
52 )]
53 pub architectures: Vec<String>,
55
56 #[deb822(field = "Description")]
57 pub description: String,
59
60 #[deb822(field = "Origin")]
61 pub origin: String,
63
64 #[deb822(field = "Label")]
65 pub label: String,
67
68 #[deb822(field = "Suite")]
69 pub suite: String,
71
72 #[deb822(field = "Version")]
73 pub version: String,
75
76 #[deb822(field = "Date")]
77 pub date: String,
79
80 #[deb822(field = "NotAutomatic", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
81 pub not_automatic: bool,
83
84 #[deb822(field = "ButAutomaticUpgrades", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
85 pub but_automatic_upgrades: bool,
87
88 #[deb822(field = "Acquire-By-Hash", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
89 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)]
106pub struct Source {
108 #[deb822(field = "Directory")]
109 pub directory: String,
111
112 #[deb822(field = "Description")]
113 pub description: Option<String>,
115
116 #[deb822(field = "Version")]
117 pub version: debversion::Version,
119
120 #[deb822(field = "Package")]
121 pub package: String,
123
124 #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)]
125 pub binaries: Option<Vec<String>>,
127
128 #[deb822(field = "Maintainer")]
129 pub maintainer: Option<String>,
131
132 #[deb822(field = "Build-Depends")]
133 pub build_depends: Option<String>,
135
136 #[deb822(field = "Build-Depends-Indep")]
137 pub build_depends_indep: Option<Relations>,
139
140 #[deb822(field = "Build-Conflicts")]
141 pub build_conflicts: Option<Relations>,
143
144 #[deb822(field = "Build-Conflicts-Indep")]
145 pub build_conflicts_indep: Option<Relations>,
147
148 #[deb822(field = "Standards-Version")]
149 pub standards_version: Option<String>,
151
152 #[deb822(field = "Homepage")]
153 pub homepage: Option<String>,
155
156 #[deb822(field = "Autobuild")]
157 pub autobuild: Option<bool>,
159
160 #[deb822(field = "Testsuite")]
161 pub testsuite: Option<String>,
163
164 #[deb822(field = "Vcs-Browser")]
165 pub vcs_browser: Option<String>,
167
168 #[deb822(field = "Vcs-Git")]
169 pub vcs_git: Option<String>,
171
172 #[deb822(field = "Vcs-Bzr")]
173 pub vcs_bzr: Option<String>,
175
176 #[deb822(field = "Vcs-Hg")]
177 pub vcs_hg: Option<String>,
179
180 #[deb822(field = "Vcs-Svn")]
181 pub vcs_svn: Option<String>,
183
184 #[deb822(field = "Vcs-Darcs")]
185 pub vcs_darcs: Option<String>,
187
188 #[deb822(field = "Vcs-Cvs")]
189 pub vcs_cvs: Option<String>,
191
192 #[deb822(field = "Vcs-Arch")]
193 pub vcs_arch: Option<String>,
195
196 #[deb822(field = "Vcs-Mtn")]
197 pub vcs_mtn: Option<String>,
199
200 #[deb822(field = "Priority")]
201 pub priority: Option<crate::fields::Priority>,
203
204 #[deb822(field = "Section")]
205 pub section: Option<String>,
207
208 #[deb822(field = "Format")]
209 pub format: Option<String>,
211
212 #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)]
213 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(¶)
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#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
238pub struct Package {
239 #[deb822(field = "Package")]
241 pub name: String,
242
243 #[deb822(field = "Version")]
245 pub version: debversion::Version,
246
247 #[deb822(field = "Source")]
249 pub source: Option<SourceRelation>,
250
251 #[deb822(field = "Architecture")]
253 pub architecture: String,
254
255 #[deb822(field = "Maintainer")]
257 pub maintainer: Option<String>,
258
259 #[deb822(field = "Installed-Size")]
261 pub installed_size: Option<usize>,
262
263 #[deb822(field = "Depends")]
265 pub depends: Option<Relations>,
266
267 #[deb822(field = "Pre-Depends")]
269 pub pre_depends: Option<Relations>,
270
271 #[deb822(field = "Recommends")]
273 pub recommends: Option<Relations>,
274
275 #[deb822(field = "Suggests")]
277 pub suggests: Option<Relations>,
278
279 #[deb822(field = "Enhances")]
281 pub enhances: Option<Relations>,
282
283 #[deb822(field = "Breaks")]
285 pub breaks: Option<Relations>,
286
287 #[deb822(field = "Conflicts")]
289 pub conflicts: Option<Relations>,
290
291 #[deb822(field = "Provides")]
293 pub provides: Option<Relations>,
294
295 #[deb822(field = "Replaces")]
297 pub replaces: Option<Relations>,
298
299 #[deb822(field = "Built-Using")]
301 pub built_using: Option<Relations>,
302
303 #[deb822(field = "Static-Built-Using")]
305 pub static_built_using: Option<Relations>,
306
307 #[deb822(field = "Description")]
309 pub description: Option<String>,
310
311 #[deb822(field = "Homepage")]
313 pub homepage: Option<String>,
314
315 #[deb822(field = "Priority")]
317 pub priority: Option<crate::fields::Priority>,
318
319 #[deb822(field = "Section")]
321 pub section: Option<String>,
322
323 #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
325 pub essential: Option<bool>,
326
327 #[deb822(field = "Tag")]
329 pub tag: Option<String>,
330
331 #[deb822(field = "Size")]
333 pub size: Option<usize>,
334
335 #[deb822(field = "MD5sum")]
337 pub md5sum: Option<String>,
338
339 #[deb822(field = "SHA256")]
341 pub sha256: Option<String>,
342
343 #[deb822(field = "Description-MD5")]
345 pub description_md5: Option<String>,
346}
347
348impl std::str::FromStr for Package {
349 type Err = String;
350
351 fn from_str(s: &str) -> Result<Self, Self::Err> {
352 let para = s
353 .parse::<deb822_fast::Paragraph>()
354 .map_err(|e| e.to_string())?;
355
356 FromDeb822Paragraph::from_paragraph(¶)
357 }
358}
359
360impl std::fmt::Display for Package {
361 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
362 let para: deb822_fast::Paragraph = self.to_paragraph();
363 write!(f, "{}", para)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use deb822_fast::Paragraph;
371 use deb822_fast::ToDeb822Paragraph;
372
373 #[test]
374 fn test_release() {
375 let release = Release {
376 codename: "focal".to_string(),
377 components: vec!["main".to_string(), "restricted".to_string()],
378 architectures: vec!["amd64".to_string(), "arm64".to_string()],
379 description: "Ubuntu 20.04 LTS".to_string(),
380 origin: "Ubuntu".to_string(),
381 label: "Ubuntu".to_string(),
382 suite: "focal".to_string(),
383 version: "20.04".to_string(),
384 date: "Thu, 23 Apr 2020 17:19:19 UTC".to_string(),
385 not_automatic: false,
386 but_automatic_upgrades: true,
387 acquire_by_hash: true,
388 };
389
390 let deb822 = r#"Codename: focal
391Components: main restricted
392Architectures: amd64 arm64
393Description: Ubuntu 20.04 LTS
394Origin: Ubuntu
395Label: Ubuntu
396Suite: focal
397Version: 20.04
398Date: Thu, 23 Apr 2020 17:19:19 UTC
399NotAutomatic: no
400ButAutomaticUpgrades: yes
401Acquire-By-Hash: yes
402"#;
403
404 let para = deb822.parse::<Paragraph>().unwrap();
405
406 let release: deb822_fast::Paragraph = release.to_paragraph();
407
408 assert_eq!(release, para);
409 }
410
411 #[test]
412 fn test_package() {
413 let package = r#"Package: apt
414Version: 2.1.10
415Architecture: amd64
416Maintainer: APT Development Team <apt@lists.debian.org>
417Installed-Size: 3524
418Depends: libc6 (>= 2.14), libgcc1
419Pre-Depends: dpkg (>= 1.15.6)
420Recommends: gnupg
421Suggests: apt-doc, aptitude | synaptic | wajig
422"#;
423
424 let package: Package = package.parse().unwrap();
425
426 assert_eq!(package.name, "apt");
427 assert_eq!(package.version, "2.1.10".parse().unwrap());
428 assert_eq!(package.architecture, "amd64");
429 }
430
431 #[test]
432 fn test_package_essential() {
433 let package = r#"Package: base-files
434Version: 11.1
435Architecture: amd64
436Essential: yes
437"#;
438
439 let package: Package = package.parse().unwrap();
440
441 assert_eq!(package.name, "base-files");
442 assert_eq!(package.essential, Some(true));
443 }
444
445 #[test]
446 fn test_package_essential_no() {
447 let package = r#"Package: apt
448Version: 2.1.10
449Architecture: amd64
450Essential: no
451"#;
452
453 let package: Package = package.parse().unwrap();
454
455 assert_eq!(package.name, "apt");
456 assert_eq!(package.essential, Some(false));
457 }
458
459 #[test]
460 fn test_package_with_different_source() {
461 let package = r#"Package: apt
462Source: not-apt (1.1.5)
463Version: 2.1.10
464Architecture: amd64
465Maintainer: APT Development Team <apt@lists.debian.org>
466Installed-Size: 3524
467Depends: libc6 (>= 2.14), libgcc1
468Pre-Depends: dpkg (>= 1.15.6)
469Recommends: gnupg
470Suggests: apt-doc, aptitude | synaptic | wajig
471"#;
472
473 let package: Package = package.parse().unwrap();
474
475 assert_eq!(package.name, "apt");
476 assert_eq!(package.version, "2.1.10".parse().unwrap());
477 assert_eq!(package.architecture, "amd64");
478 assert_eq!(
479 package.source,
480 Some(SourceRelation {
481 name: "not-apt".to_string(),
482 version: Some("1.1.5".parse().unwrap())
483 })
484 );
485 }
486}