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 RelationOrSoname,
24 Url,
25};
26use serde_with::{DisplayFromStr, serde_as};
27
28use crate::Error;
29
30#[serde_as]
83#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
84#[serde(deny_unknown_fields)]
85pub struct PackageInfoV1 {
86 #[serde_as(as = "DisplayFromStr")]
88 pub pkgname: Name,
89
90 #[serde_as(as = "DisplayFromStr")]
92 pub pkgbase: Name,
93
94 #[serde_as(as = "DisplayFromStr")]
96 pub pkgver: FullVersion,
97
98 #[serde_as(as = "DisplayFromStr")]
100 pub pkgdesc: PackageDescription,
101
102 #[serde_as(as = "DisplayFromStr")]
104 pub url: Url,
105
106 #[serde_as(as = "DisplayFromStr")]
108 pub builddate: BuildDate,
109
110 #[serde_as(as = "DisplayFromStr")]
112 pub packager: Packager,
113
114 #[serde_as(as = "DisplayFromStr")]
116 pub size: InstalledSize,
117
118 #[serde_as(as = "DisplayFromStr")]
120 pub arch: Architecture,
121
122 #[serde_as(as = "Vec<DisplayFromStr>")]
124 #[serde(default)]
125 pub license: Vec<License>,
126
127 #[serde_as(as = "Vec<DisplayFromStr>")]
129 #[serde(default)]
130 pub replaces: Vec<PackageRelation>,
131
132 #[serde_as(as = "Vec<DisplayFromStr>")]
134 #[serde(default)]
135 pub group: Vec<Group>,
136
137 #[serde_as(as = "Vec<DisplayFromStr>")]
139 #[serde(default)]
140 pub conflict: Vec<PackageRelation>,
141
142 #[serde_as(as = "Vec<DisplayFromStr>")]
144 #[serde(default)]
145 pub provides: Vec<RelationOrSoname>,
146
147 #[serde_as(as = "Vec<DisplayFromStr>")]
149 #[serde(default)]
150 pub backup: Vec<Backup>,
151
152 #[serde_as(as = "Vec<DisplayFromStr>")]
154 #[serde(default)]
155 pub depend: Vec<RelationOrSoname>,
156
157 #[serde_as(as = "Vec<DisplayFromStr>")]
159 #[serde(default)]
160 pub optdepend: Vec<OptionalDependency>,
161
162 #[serde_as(as = "Vec<DisplayFromStr>")]
164 #[serde(default)]
165 pub makedepend: Vec<PackageRelation>,
166
167 #[serde_as(as = "Vec<DisplayFromStr>")]
169 #[serde(default)]
170 pub checkdepend: Vec<PackageRelation>,
171}
172
173impl FromStr for PackageInfoV1 {
174 type Err = Error;
175 fn from_str(input: &str) -> Result<PackageInfoV1, Self::Err> {
182 let pkginfo: PackageInfoV1 = alpm_parsers::custom_ini::from_str(input)?;
183 Ok(pkginfo)
184 }
185}
186
187impl Display for PackageInfoV1 {
188 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
189 fn format_list(label: &str, items: &[impl Display]) -> String {
190 if items.is_empty() {
191 String::new()
192 } else {
193 items
194 .iter()
195 .map(|v| format!("{label} = {v}"))
196 .collect::<Vec<_>>()
197 .join("\n")
198 + "\n"
199 }
200 }
201 write!(
202 fmt,
203 "pkgname = {}\n\
204 pkgbase = {}\n\
205 pkgver = {}\n\
206 pkgdesc = {}\n\
207 url = {}\n\
208 builddate = {}\n\
209 packager = {}\n\
210 size = {}\n\
211 arch = {}\n\
212 {}\
213 {}\
214 {}\
215 {}\
216 {}\
217 {}\
218 {}\
219 {}\
220 {}\
221 {}",
222 self.pkgname,
223 self.pkgbase,
224 self.pkgver,
225 self.pkgdesc,
226 self.url,
227 self.builddate,
228 self.packager,
229 self.size,
230 self.arch,
231 format_list("license", &self.license),
232 format_list("replaces", &self.replaces),
233 format_list("group", &self.group),
234 format_list("conflict", &self.conflict),
235 format_list("provides", &self.provides),
236 format_list("backup", &self.backup),
237 format_list("depend", &self.depend),
238 format_list("optdepend", &self.optdepend),
239 format_list("makedepend", &self.makedepend),
240 format_list("checkdepend", &self.checkdepend).trim_end_matches('\n'),
241 )
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use pretty_assertions::assert_eq;
248 use rstest::{fixture, rstest};
249 use testresult::TestResult;
250
251 use super::*;
252
253 #[fixture]
254 fn valid_pkginfov1() -> String {
255 r#"pkgname = example
256pkgbase = example
257pkgver = 1:1.0.0-1
258pkgdesc = A project that does something
259url = https://example.org/
260builddate = 1729181726
261packager = John Doe <john@example.org>
262size = 181849963
263arch = any
264license = GPL-3.0-or-later
265license = LGPL-3.0-or-later
266replaces = other-package>0.9.0-3
267group = package-group
268group = other-package-group
269conflict = conflicting-package<1.0.0
270conflict = other-conflicting-package<1.0.0
271provides = some-component
272provides = some-other-component=1:1.0.0-1
273provides = libexample.so=1-64
274provides = libunversionedexample.so=libunversionedexample.so-64
275provides = lib:libexample.so.1
276backup = etc/example/config.toml
277backup = etc/example/other-config.txt
278depend = glibc
279depend = gcc-libs
280depend = libother.so=0-64
281depend = libunversioned.so=libunversioned.so-64
282depend = lib:libother.so.0
283optdepend = python: for special-python-script.py
284optdepend = ruby: for special-ruby-script.rb
285makedepend = cmake
286makedepend = python-sphinx
287checkdepend = extra-test-tool
288checkdepend = other-extra-test-tool"#
289 .to_string()
290 }
291
292 #[rstest]
293 fn pkginfov1_from_str(valid_pkginfov1: String) -> TestResult {
294 PackageInfoV1::from_str(&valid_pkginfov1)?;
295 Ok(())
296 }
297
298 #[rstest]
299 fn pkginfov1() -> TestResult {
300 let pkg_info = PackageInfoV1 {
301 pkgname: Name::new("example")?,
302 pkgbase: Name::new("example")?,
303 pkgver: FullVersion::from_str("1:1.0.0-1")?,
304 pkgdesc: PackageDescription::from("A project that does something"),
305 url: Url::from_str("https://example.org")?,
306 builddate: BuildDate::from_str("1729181726")?,
307 packager: Packager::from_str("John Doe <john@example.org>")?,
308 size: InstalledSize::from_str("181849963")?,
309 arch: Architecture::Any,
310 license: vec![
311 License::from_str("GPL-3.0-or-later")?,
312 License::from_str("LGPL-3.0-or-later")?,
313 ],
314 replaces: vec![PackageRelation::from_str("other-package>0.9.0-3")?],
315 group: vec![
316 Group::from_str("package-group")?,
317 Group::from_str("other-package-group")?,
318 ],
319 conflict: vec![
320 PackageRelation::from_str("conflicting-package<1.0.0")?,
321 PackageRelation::from_str("other-conflicting-package<1.0.0")?,
322 ],
323 provides: vec![
324 RelationOrSoname::from_str("some-component")?,
325 RelationOrSoname::from_str("some-other-component=1:1.0.0-1")?,
326 RelationOrSoname::from_str("libexample.so=1-64")?,
327 RelationOrSoname::from_str("libunversionedexample.so=libunversionedexample.so-64")?,
328 RelationOrSoname::from_str("lib:libexample.so.1")?,
329 ],
330 backup: vec![
331 Backup::from_str("etc/example/config.toml")?,
332 Backup::from_str("etc/example/other-config.txt")?,
333 ],
334 depend: vec![
335 RelationOrSoname::from_str("glibc")?,
336 RelationOrSoname::from_str("gcc-libs")?,
337 RelationOrSoname::from_str("libother.so=0-64")?,
338 RelationOrSoname::from_str("libunversioned.so=libunversioned.so-64")?,
339 RelationOrSoname::from_str("lib:libother.so.0")?,
340 ],
341 optdepend: vec![
342 OptionalDependency::from_str("python: for special-python-script.py")?,
343 OptionalDependency::from_str("ruby: for special-ruby-script.rb")?,
344 ],
345 makedepend: vec![
346 PackageRelation::from_str("cmake")?,
347 PackageRelation::from_str("python-sphinx")?,
348 ],
349 checkdepend: vec![
350 PackageRelation::from_str("extra-test-tool")?,
351 PackageRelation::from_str("other-extra-test-tool")?,
352 ],
353 };
354 assert_eq!(pkg_info.to_string(), valid_pkginfov1());
355 Ok(())
356 }
357
358 #[rstest]
359 #[case("pkgname = foo")]
360 #[case("pkgbase = foo")]
361 #[case("pkgver = 1:1.0.0-1")]
362 #[case("packager = Foobar McFooface <foobar@mcfooface.org>")]
363 #[case("pkgarch = any")]
364 fn pkginfov1_from_str_duplicate_fail(mut valid_pkginfov1: String, #[case] duplicate: &str) {
365 valid_pkginfov1.push_str(duplicate);
366 assert!(PackageInfoV1::from_str(&valid_pkginfov1).is_err());
367 }
368}