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