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(",").map(|s| s.trim().to_string()).collect())
95}
96
97fn deserialize_testsuite_triggers(value: &str) -> Result<Vec<String>, String> {
98 Ok(value.split(",").map(|s| s.trim().to_string()).collect())
99}
100
101fn join_lines(components: &[String]) -> String {
102 components.join("\n")
103}
104
105fn deserialize_package_list(value: &str) -> Result<Vec<String>, String> {
106 Ok(value.split('\n').map(|s| s.to_string()).collect())
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
110pub struct Source {
112 #[deb822(field = "Directory")]
113 pub directory: String,
115
116 #[deb822(field = "Description")]
117 pub description: Option<String>,
119
120 #[deb822(field = "Version")]
121 pub version: debversion::Version,
123
124 #[deb822(field = "Package")]
125 pub package: String,
127
128 #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)]
129 pub binaries: Option<Vec<String>>,
131
132 #[deb822(field = "Maintainer")]
133 pub maintainer: Option<String>,
135
136 #[deb822(field = "Build-Depends")]
137 pub build_depends: Option<Relations>,
139
140 #[deb822(field = "Build-Depends-Indep")]
141 pub build_depends_indep: Option<Relations>,
143
144 #[deb822(field = "Build-Depends-Arch")]
145 pub build_depends_arch: Option<Relations>,
147
148 #[deb822(field = "Build-Conflicts")]
149 pub build_conflicts: Option<Relations>,
151
152 #[deb822(field = "Build-Conflicts-Indep")]
153 pub build_conflicts_indep: Option<Relations>,
155
156 #[deb822(field = "Build-Conflicts-Arch")]
157 pub build_conflicts_arch: Option<Relations>,
159
160 #[deb822(field = "Standards-Version")]
161 pub standards_version: Option<String>,
163
164 #[deb822(field = "Homepage")]
165 pub homepage: Option<String>,
167
168 #[deb822(field = "Autobuild")]
169 pub autobuild: Option<bool>,
171
172 #[deb822(field = "Testsuite")]
173 pub testsuite: Option<String>,
175
176 #[deb822(field = "Testsuite-Triggers", deserialize_with = deserialize_testsuite_triggers, serialize_with = join_whitespace)]
177 pub testsuite_triggers: Option<Vec<String>>,
179
180 #[deb822(field = "Vcs-Browser")]
181 pub vcs_browser: Option<String>,
183
184 #[deb822(field = "Vcs-Git")]
185 pub vcs_git: Option<String>,
187
188 #[deb822(field = "Vcs-Bzr")]
189 pub vcs_bzr: Option<String>,
191
192 #[deb822(field = "Vcs-Hg")]
193 pub vcs_hg: Option<String>,
195
196 #[deb822(field = "Vcs-Svn")]
197 pub vcs_svn: Option<String>,
199
200 #[deb822(field = "Vcs-Darcs")]
201 pub vcs_darcs: Option<String>,
203
204 #[deb822(field = "Vcs-Cvs")]
205 pub vcs_cvs: Option<String>,
207
208 #[deb822(field = "Vcs-Arch")]
209 pub vcs_arch: Option<String>,
211
212 #[deb822(field = "Vcs-Mtn")]
213 pub vcs_mtn: Option<String>,
215
216 #[deb822(field = "Dgit")]
217 pub dgit: Option<crate::fields::DgitInfo>,
219
220 #[deb822(field = "Priority")]
221 pub priority: Option<crate::fields::Priority>,
223
224 #[deb822(field = "Section")]
225 pub section: Option<String>,
227
228 #[deb822(field = "Format")]
229 pub format: Option<String>,
231
232 #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)]
233 pub package_list: Vec<String>,
235}
236
237impl std::str::FromStr for Source {
238 type Err = String;
239
240 fn from_str(s: &str) -> Result<Self, Self::Err> {
241 let para = s
242 .parse::<deb822_fast::Paragraph>()
243 .map_err(|e| e.to_string())?;
244
245 FromDeb822Paragraph::from_paragraph(¶)
246 }
247}
248
249impl std::fmt::Display for Source {
250 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
251 let para: deb822_fast::Paragraph = self.to_paragraph();
252 write!(f, "{}", para)
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
258pub struct Package {
259 #[deb822(field = "Package")]
261 pub name: String,
262
263 #[deb822(field = "Version")]
265 pub version: debversion::Version,
266
267 #[deb822(field = "Source")]
269 pub source: Option<SourceRelation>,
270
271 #[deb822(field = "Architecture")]
273 pub architecture: String,
274
275 #[deb822(field = "Maintainer")]
277 pub maintainer: Option<String>,
278
279 #[deb822(field = "Installed-Size")]
281 pub installed_size: Option<usize>,
282
283 #[deb822(field = "Depends")]
285 pub depends: Option<Relations>,
286
287 #[deb822(field = "Pre-Depends")]
289 pub pre_depends: Option<Relations>,
290
291 #[deb822(field = "Recommends")]
293 pub recommends: Option<Relations>,
294
295 #[deb822(field = "Suggests")]
297 pub suggests: Option<Relations>,
298
299 #[deb822(field = "Enhances")]
301 pub enhances: Option<Relations>,
302
303 #[deb822(field = "Breaks")]
305 pub breaks: Option<Relations>,
306
307 #[deb822(field = "Conflicts")]
309 pub conflicts: Option<Relations>,
310
311 #[deb822(field = "Provides")]
313 pub provides: Option<Relations>,
314
315 #[deb822(field = "Replaces")]
317 pub replaces: Option<Relations>,
318
319 #[deb822(field = "Built-Using")]
321 pub built_using: Option<Relations>,
322
323 #[deb822(field = "Static-Built-Using")]
325 pub static_built_using: Option<Relations>,
326
327 #[deb822(field = "Description")]
329 pub description: Option<String>,
330
331 #[deb822(field = "Homepage")]
333 pub homepage: Option<String>,
334
335 #[deb822(field = "Priority")]
337 pub priority: Option<crate::fields::Priority>,
338
339 #[deb822(field = "Section")]
341 pub section: Option<String>,
342
343 #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
345 pub essential: Option<bool>,
346
347 #[deb822(field = "Tag")]
349 pub tag: Option<String>,
350
351 #[deb822(field = "Size")]
353 pub size: Option<usize>,
354
355 #[deb822(field = "MD5sum")]
357 pub md5sum: Option<String>,
358
359 #[deb822(field = "SHA256")]
361 pub sha256: Option<String>,
362
363 #[deb822(field = "Description-MD5")]
365 pub description_md5: Option<String>,
366}
367
368impl std::str::FromStr for Package {
369 type Err = String;
370
371 fn from_str(s: &str) -> Result<Self, Self::Err> {
372 let para = s
373 .parse::<deb822_fast::Paragraph>()
374 .map_err(|e| e.to_string())?;
375
376 FromDeb822Paragraph::from_paragraph(¶)
377 }
378}
379
380impl std::fmt::Display for Package {
381 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
382 let para: deb822_fast::Paragraph = self.to_paragraph();
383 write!(f, "{}", para)
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390 use deb822_fast::Paragraph;
391 use deb822_fast::ToDeb822Paragraph;
392
393 #[test]
394 fn test_release() {
395 let release = Release {
396 codename: "focal".to_string(),
397 components: vec!["main".to_string(), "restricted".to_string()],
398 architectures: vec!["amd64".to_string(), "arm64".to_string()],
399 description: "Ubuntu 20.04 LTS".to_string(),
400 origin: "Ubuntu".to_string(),
401 label: "Ubuntu".to_string(),
402 suite: "focal".to_string(),
403 version: "20.04".to_string(),
404 date: "Thu, 23 Apr 2020 17:19:19 UTC".to_string(),
405 not_automatic: false,
406 but_automatic_upgrades: true,
407 acquire_by_hash: true,
408 };
409
410 let deb822 = r#"Codename: focal
411Components: main restricted
412Architectures: amd64 arm64
413Description: Ubuntu 20.04 LTS
414Origin: Ubuntu
415Label: Ubuntu
416Suite: focal
417Version: 20.04
418Date: Thu, 23 Apr 2020 17:19:19 UTC
419NotAutomatic: no
420ButAutomaticUpgrades: yes
421Acquire-By-Hash: yes
422"#;
423
424 let para = deb822.parse::<Paragraph>().unwrap();
425
426 let release: deb822_fast::Paragraph = release.to_paragraph();
427
428 assert_eq!(release, para);
429 }
430
431 #[test]
432 fn test_package() {
433 let package = r#"Package: apt
434Version: 2.1.10
435Architecture: amd64
436Maintainer: APT Development Team <apt@lists.debian.org>
437Installed-Size: 3524
438Depends: libc6 (>= 2.14), libgcc1
439Pre-Depends: dpkg (>= 1.15.6)
440Recommends: gnupg
441Suggests: apt-doc, aptitude | synaptic | wajig
442"#;
443
444 let package: Package = package.parse().unwrap();
445
446 assert_eq!(package.name, "apt");
447 assert_eq!(package.version, "2.1.10".parse().unwrap());
448 assert_eq!(package.architecture, "amd64");
449 }
450
451 #[test]
452 fn test_package_essential() {
453 let package = r#"Package: base-files
454Version: 11.1
455Architecture: amd64
456Essential: yes
457"#;
458
459 let package: Package = package.parse().unwrap();
460
461 assert_eq!(package.name, "base-files");
462 assert_eq!(package.essential, Some(true));
463 }
464
465 #[test]
466 fn test_package_essential_no() {
467 let package = r#"Package: apt
468Version: 2.1.10
469Architecture: amd64
470Essential: no
471"#;
472
473 let package: Package = package.parse().unwrap();
474
475 assert_eq!(package.name, "apt");
476 assert_eq!(package.essential, Some(false));
477 }
478
479 #[test]
480 fn test_package_with_different_source() {
481 let package = r#"Package: apt
482Source: not-apt (1.1.5)
483Version: 2.1.10
484Architecture: amd64
485Maintainer: APT Development Team <apt@lists.debian.org>
486Installed-Size: 3524
487Depends: libc6 (>= 2.14), libgcc1
488Pre-Depends: dpkg (>= 1.15.6)
489Recommends: gnupg
490Suggests: apt-doc, aptitude | synaptic | wajig
491"#;
492
493 let package: Package = package.parse().unwrap();
494
495 assert_eq!(package.name, "apt");
496 assert_eq!(package.version, "2.1.10".parse().unwrap());
497 assert_eq!(package.architecture, "amd64");
498 assert_eq!(
499 package.source,
500 Some(SourceRelation {
501 name: "not-apt".to_string(),
502 version: Some("1.1.5".parse().unwrap())
503 })
504 );
505 }
506
507 #[test]
508 fn test_source() {
509 let source = r#"Package: abinit
510Binary: abinit, abinit-doc, abinit-data
511Version: 9.10.4-3
512Maintainer: Debichem Team <debichem-devel@lists.alioth.debian.org>
513Uploaders: Andreas Tille <tille@debian.org>, Michael Banck <mbanck@debian.org>
514Build-Depends: debhelper (>= 11), gfortran, liblapack-dev, python3, graphviz, markdown, ghostscript, help2man, libfftw3-dev, libhdf5-dev, libnetcdff-dev, libssl-dev, libxc-dev, mpi-default-dev, python3-dev, python3-numpy, python3-pandas, python3-yaml, texlive-latex-extra, texlive-fonts-recommended, texlive-extra-utils, texlive-pstricks, texlive-publishers, texlive-luatex
515Architecture: any all
516Standards-Version: 3.9.8
517Format: 3.0 (quilt)
518Files:
519 843550cbd14395c0b9408158a91a239c 2464 abinit_9.10.4-3.dsc
520 a323f11fbd4a7d0f461d99c931903b5c 130747285 abinit_9.10.4.orig.tar.gz
521 27c12d3dac5cd105cebaa2af4247e807 15068 abinit_9.10.4-3.debian.tar.xz
522Vcs-Browser: https://salsa.debian.org/debichem-team/abinit
523Vcs-Git: https://salsa.debian.org/debichem-team/abinit.git
524Checksums-Sha256:
525 c3c217b14bc5705a1d8930a2e7fcef58e64beaa22abc213e2eacc7d5537ef840 2464 abinit_9.10.4-3.dsc
526 6bf3c276c333956f722761f189f2b4324e150c8a50470ecb72ee07cc1c457d48 130747285 abinit_9.10.4.orig.tar.gz
527 80c4fb7575d67f3167d7c34fd59477baf839810d0b863e19f1dd9fea1bc0b3b5 15068 abinit_9.10.4-3.debian.tar.xz
528Homepage: http://www.abinit.org/
529Package-List:
530 abinit deb science optional arch=any
531 abinit-data deb science optional arch=all
532 abinit-doc deb doc optional arch=all
533Testsuite: autopkgtest
534Testsuite-Triggers: python3, python3-numpy, python3-pandas, python3-yaml
535Directory: pool/main/a/abinit
536Priority: optional
537Section: science
538"#;
539
540 let source: Source = source.parse().unwrap();
541
542 assert_eq!(source.package, "abinit");
543 assert_eq!(source.version, "9.10.4-3".parse().unwrap());
544 assert_eq!(
545 source.binaries,
546 Some(vec![
547 "abinit".to_string(),
548 "abinit-doc".to_string(),
549 "abinit-data".to_string()
550 ])
551 );
552
553 let build_depends = source.build_depends.as_ref();
554 let build_depends: Vec<_> = build_depends.iter().collect();
555 let build_depends = build_depends[0];
556
557 let expected_build_depends = &[
558 "debhelper",
559 "gfortran",
560 "liblapack-dev",
561 "python3",
562 "graphviz",
563 "markdown",
564 "ghostscript",
565 "help2man",
566 "libfftw3-dev",
567 "libhdf5-dev",
568 "libnetcdff-dev",
569 "libssl-dev",
570 "libxc-dev",
571 "mpi-default-dev",
572 "python3-dev",
573 "python3-numpy",
574 "python3-pandas",
575 "python3-yaml",
576 "texlive-latex-extra",
577 "texlive-fonts-recommended",
578 "texlive-extra-utils",
579 "texlive-pstricks",
580 "texlive-publishers",
581 "texlive-luatex",
582 ];
583
584 assert_eq!(build_depends.len(), expected_build_depends.len());
585 assert_eq!(build_depends[0][0].name, expected_build_depends[0]);
586 assert_eq!(
587 build_depends[build_depends.len() - 1][0].name,
588 expected_build_depends[build_depends.len() - 1]
589 );
590
591 assert_eq!(
592 source.testsuite_triggers,
593 Some(
594 ["python3", "python3-numpy", "python3-pandas", "python3-yaml"]
595 .into_iter()
596 .map(String::from)
597 .collect()
598 )
599 );
600 }
601
602 #[test]
603 fn test_source_with_dgit() {
604 let source = r#"Package: test-pkg
605Version: 1.0-1
606Maintainer: Test Maintainer <test@example.com>
607Dgit: c1370424e2404d3c22bd09c828d4b28d81d897ad debian archive/debian/1.1.0 https://git.dgit.debian.org/test-pkg
608Directory: pool/main/t/test-pkg
609Package-List:
610 abinit deb science optional arch=any
611 abinit-data deb science optional arch=all
612 abinit-doc deb doc optional arch=all
613"#;
614
615 let source: Source = source.parse().unwrap();
616 assert_eq!(source.package, "test-pkg");
617 let dgit = source.dgit.as_ref().unwrap();
618 assert_eq!(dgit.commit, "c1370424e2404d3c22bd09c828d4b28d81d897ad");
619 assert_eq!(dgit.suite, "debian");
620 assert_eq!(dgit.git_ref, "archive/debian/1.1.0");
621 assert_eq!(dgit.url, "https://git.dgit.debian.org/test-pkg");
622 }
623}