alpm_pkginfo/package_info/
v1.rs1use std::{
6 fmt::{Display, Formatter},
7 str::FromStr,
8};
9
10use alpm_types::{
11 Architecture,
12 Backup,
13 BuildDate,
14 FullVersion,
15 Group,
16 InstalledSize,
17 License,
18 Name,
19 OptionalDependency,
20 PackageDescription,
21 PackageRelation,
22 Packager,
23 Url,
24};
25use serde_with::{DisplayFromStr, serde_as};
26
27use crate::{Error, RelationOrSoname};
28
29macro_rules! generate_pkginfo {
31 ($(#[$meta:meta])* $name:ident { $($extra_fields:tt)* }) => {
35
36 $(#[$meta])*
37 #[serde_as]
38 #[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
39 #[serde(deny_unknown_fields)]
40 pub struct $name {
41 #[serde_as(as = "DisplayFromStr")]
42 pkgname: Name,
43
44 #[serde_as(as = "DisplayFromStr")]
45 pkgbase: Name,
46
47 #[serde_as(as = "DisplayFromStr")]
48 pkgver: FullVersion,
49
50 #[serde_as(as = "DisplayFromStr")]
51 pkgdesc: PackageDescription,
52
53 #[serde_as(as = "DisplayFromStr")]
54 url: Url,
55
56 #[serde_as(as = "DisplayFromStr")]
57 builddate: BuildDate,
58
59 #[serde_as(as = "DisplayFromStr")]
60 packager: Packager,
61
62 #[serde_as(as = "DisplayFromStr")]
63 size: InstalledSize,
64
65 #[serde_as(as = "DisplayFromStr")]
66 arch: Architecture,
67
68 #[serde_as(as = "Vec<DisplayFromStr>")]
69 #[serde(default)]
70 license: Vec<License>,
71
72 #[serde_as(as = "Vec<DisplayFromStr>")]
73 #[serde(default)]
74 replaces: Vec<PackageRelation>,
75
76 #[serde_as(as = "Vec<DisplayFromStr>")]
77 #[serde(default)]
78 group: Vec<Group>,
79
80 #[serde_as(as = "Vec<DisplayFromStr>")]
81 #[serde(default)]
82 conflict: Vec<PackageRelation>,
83
84 #[serde_as(as = "Vec<DisplayFromStr>")]
85 #[serde(default)]
86 provides: Vec<RelationOrSoname>,
87
88 #[serde_as(as = "Vec<DisplayFromStr>")]
89 #[serde(default)]
90 backup: Vec<Backup>,
91
92 #[serde_as(as = "Vec<DisplayFromStr>")]
93 #[serde(default)]
94 depend: Vec<RelationOrSoname>,
95
96 #[serde_as(as = "Vec<DisplayFromStr>")]
97 #[serde(default)]
98 optdepend: Vec<OptionalDependency>,
99
100 #[serde_as(as = "Vec<DisplayFromStr>")]
101 #[serde(default)]
102 makedepend: Vec<PackageRelation>,
103
104 #[serde_as(as = "Vec<DisplayFromStr>")]
105 #[serde(default)]
106 checkdepend: Vec<PackageRelation>,
107
108 $($extra_fields)*
109 }
110
111 impl $name {
112 pub fn pkgname(&self) -> &Name {
114 &self.pkgname
115 }
116
117 pub fn pkgbase(&self) -> &Name {
119 &self.pkgbase
120 }
121
122 pub fn pkgver(&self) -> &FullVersion {
124 &self.pkgver
125 }
126
127 pub fn pkgdesc(&self) -> &PackageDescription {
129 &self.pkgdesc
130 }
131
132 pub fn url(&self) -> &Url {
134 &self.url
135 }
136
137 pub fn builddate(&self) -> &BuildDate {
139 &self.builddate
140 }
141
142 pub fn packager(&self) -> &Packager {
144 &self.packager
145 }
146
147 pub fn size(&self) -> &InstalledSize {
149 &self.size
150 }
151
152 pub fn arch(&self) -> &Architecture {
154 &self.arch
155 }
156
157 pub fn license(&self) -> &[License] {
159 &self.license
160 }
161
162 pub fn replaces(&self) -> &[PackageRelation] {
164 &self.replaces
165 }
166
167 pub fn group(&self) -> &[Group] {
169 &self.group
170 }
171
172 pub fn conflict(&self) -> &[PackageRelation] {
174 &self.conflict
175 }
176
177 pub fn provides(&self) -> &[RelationOrSoname] {
179 &self.provides
180 }
181
182 pub fn backup(&self) -> &[Backup] {
184 &self.backup
185 }
186
187 pub fn depend(&self) -> &[RelationOrSoname] {
189 &self.depend
190 }
191
192 pub fn optdepend(&self) -> &[OptionalDependency] {
194 &self.optdepend
195 }
196
197 pub fn makedepend(&self) -> &[PackageRelation] {
199 &self.makedepend
200 }
201
202 pub fn checkdepend(&self) -> &[PackageRelation] {
204 &self.checkdepend
205 }
206 }
207 }
208}
209
210pub(crate) use generate_pkginfo;
211
212generate_pkginfo! {
213 PackageInfoV1 {}
266}
267
268impl PackageInfoV1 {
269 #[allow(clippy::too_many_arguments)]
271 pub fn new(
272 name: Name,
273 base: Name,
274 version: FullVersion,
275 desc: PackageDescription,
276 url: Url,
277 builddate: BuildDate,
278 packager: Packager,
279 size: InstalledSize,
280 arch: Architecture,
281 license: Vec<License>,
282 replaces: Vec<PackageRelation>,
283 group: Vec<Group>,
284 conflict: Vec<PackageRelation>,
285 provides: Vec<RelationOrSoname>,
286 backup: Vec<Backup>,
287 depend: Vec<RelationOrSoname>,
288 optdepend: Vec<OptionalDependency>,
289 makedepend: Vec<PackageRelation>,
290 checkdepend: Vec<PackageRelation>,
291 ) -> Self {
292 Self {
293 pkgname: name,
294 pkgbase: base,
295 pkgver: version,
296 pkgdesc: desc,
297 url,
298 builddate,
299 packager,
300 size,
301 arch,
302 license,
303 replaces,
304 group,
305 conflict,
306 provides,
307 backup,
308 depend,
309 optdepend,
310 makedepend,
311 checkdepend,
312 }
313 }
314}
315
316impl FromStr for PackageInfoV1 {
317 type Err = Error;
318 fn from_str(input: &str) -> Result<PackageInfoV1, Self::Err> {
325 let pkginfo: PackageInfoV1 = alpm_parsers::custom_ini::from_str(input)?;
326 Ok(pkginfo)
327 }
328}
329
330impl Display for PackageInfoV1 {
331 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
332 fn format_list(label: &str, items: &[impl Display]) -> String {
333 if items.is_empty() {
334 String::new()
335 } else {
336 items
337 .iter()
338 .map(|v| format!("{label} = {v}"))
339 .collect::<Vec<_>>()
340 .join("\n")
341 + "\n"
342 }
343 }
344 write!(
345 fmt,
346 "pkgname = {}\n\
347 pkgbase = {}\n\
348 pkgver = {}\n\
349 pkgdesc = {}\n\
350 url = {}\n\
351 builddate = {}\n\
352 packager = {}\n\
353 size = {}\n\
354 arch = {}\n\
355 {}\
356 {}\
357 {}\
358 {}\
359 {}\
360 {}\
361 {}\
362 {}\
363 {}\
364 {}",
365 self.pkgname(),
366 self.pkgbase(),
367 self.pkgver(),
368 self.pkgdesc(),
369 self.url(),
370 self.builddate(),
371 self.packager(),
372 self.size(),
373 self.arch(),
374 format_list("license", self.license()),
375 format_list("replaces", self.replaces()),
376 format_list("group", self.group()),
377 format_list("conflict", self.conflict()),
378 format_list("provides", self.provides()),
379 format_list("backup", self.backup()),
380 format_list("depend", self.depend()),
381 format_list("optdepend", self.optdepend()),
382 format_list("makedepend", self.makedepend()),
383 format_list("checkdepend", self.checkdepend()).trim_end_matches('\n'),
384 )
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use pretty_assertions::assert_eq;
391 use rstest::{fixture, rstest};
392 use testresult::TestResult;
393
394 use super::*;
395
396 #[fixture]
397 fn valid_pkginfov1() -> String {
398 r#"pkgname = example
399pkgbase = example
400pkgver = 1:1.0.0-1
401pkgdesc = A project that does something
402url = https://example.org/
403builddate = 1729181726
404packager = John Doe <john@example.org>
405size = 181849963
406arch = any
407license = GPL-3.0-or-later
408license = LGPL-3.0-or-later
409replaces = other-package>0.9.0-3
410group = package-group
411group = other-package-group
412conflict = conflicting-package<1.0.0
413conflict = other-conflicting-package<1.0.0
414provides = some-component
415provides = some-other-component=1:1.0.0-1
416provides = libexample.so=1-64
417provides = libunversionedexample.so=libunversionedexample.so-64
418provides = lib:libexample.so.1
419backup = etc/example/config.toml
420backup = etc/example/other-config.txt
421depend = glibc
422depend = gcc-libs
423depend = libother.so=0-64
424depend = libunversioned.so=libunversioned.so-64
425depend = lib:libother.so.0
426optdepend = python: for special-python-script.py
427optdepend = ruby: for special-ruby-script.rb
428makedepend = cmake
429makedepend = python-sphinx
430checkdepend = extra-test-tool
431checkdepend = other-extra-test-tool"#
432 .to_string()
433 }
434
435 #[rstest]
436 fn pkginfov1_from_str(valid_pkginfov1: String) -> TestResult {
437 PackageInfoV1::from_str(&valid_pkginfov1)?;
438 Ok(())
439 }
440
441 #[rstest]
442 fn pkginfov1() -> TestResult {
443 let pkg_info = PackageInfoV1::new(
444 Name::new("example")?,
445 Name::new("example")?,
446 FullVersion::from_str("1:1.0.0-1")?,
447 PackageDescription::from("A project that does something"),
448 Url::from_str("https://example.org")?,
449 BuildDate::from_str("1729181726")?,
450 Packager::from_str("John Doe <john@example.org>")?,
451 InstalledSize::from_str("181849963")?,
452 Architecture::Any,
453 vec![
454 License::from_str("GPL-3.0-or-later")?,
455 License::from_str("LGPL-3.0-or-later")?,
456 ],
457 vec![PackageRelation::from_str("other-package>0.9.0-3")?],
458 vec![
459 Group::from_str("package-group")?,
460 Group::from_str("other-package-group")?,
461 ],
462 vec![
463 PackageRelation::from_str("conflicting-package<1.0.0")?,
464 PackageRelation::from_str("other-conflicting-package<1.0.0")?,
465 ],
466 vec![
467 RelationOrSoname::from_str("some-component")?,
468 RelationOrSoname::from_str("some-other-component=1:1.0.0-1")?,
469 RelationOrSoname::from_str("libexample.so=1-64")?,
470 RelationOrSoname::from_str("libunversionedexample.so=libunversionedexample.so-64")?,
471 RelationOrSoname::from_str("lib:libexample.so.1")?,
472 ],
473 vec![
474 Backup::from_str("etc/example/config.toml")?,
475 Backup::from_str("etc/example/other-config.txt")?,
476 ],
477 vec![
478 RelationOrSoname::from_str("glibc")?,
479 RelationOrSoname::from_str("gcc-libs")?,
480 RelationOrSoname::from_str("libother.so=0-64")?,
481 RelationOrSoname::from_str("libunversioned.so=libunversioned.so-64")?,
482 RelationOrSoname::from_str("lib:libother.so.0")?,
483 ],
484 vec![
485 OptionalDependency::from_str("python: for special-python-script.py")?,
486 OptionalDependency::from_str("ruby: for special-ruby-script.rb")?,
487 ],
488 vec![
489 PackageRelation::from_str("cmake")?,
490 PackageRelation::from_str("python-sphinx")?,
491 ],
492 vec![
493 PackageRelation::from_str("extra-test-tool")?,
494 PackageRelation::from_str("other-extra-test-tool")?,
495 ],
496 );
497 assert_eq!(pkg_info.to_string(), valid_pkginfov1());
498 Ok(())
499 }
500
501 #[rstest]
502 #[case("pkgname = foo")]
503 #[case("pkgbase = foo")]
504 #[case("pkgver = 1:1.0.0-1")]
505 #[case("packager = Foobar McFooface <foobar@mcfooface.org>")]
506 #[case("pkgarch = any")]
507 fn pkginfov1_from_str_duplicate_fail(mut valid_pkginfov1: String, #[case] duplicate: &str) {
508 valid_pkginfov1.push_str(duplicate);
509 assert!(PackageInfoV1::from_str(&valid_pkginfov1).is_err());
510 }
511}