alpm_pkginfo/package_info/mod.rs
1//! High-level PKGINFO handling.
2
3pub mod v1;
4pub mod v2;
5use std::{
6 fmt::Display,
7 fs::File,
8 path::{Path, PathBuf},
9 str::FromStr,
10};
11
12use alpm_common::{FileFormatSchema, MetadataFile};
13
14use crate::{Error, PackageInfoSchema, PackageInfoV1, PackageInfoV2};
15
16/// A representation of the [PKGINFO] file format.
17///
18/// Tracks all available versions of the file format.
19///
20/// [PKGINFO]: https://alpm.archlinux.page/specifications/PKGINFO.5.html
21#[derive(Clone, Debug, PartialEq, serde::Serialize)]
22#[serde(untagged)]
23pub enum PackageInfo {
24 V1(PackageInfoV1),
25 V2(PackageInfoV2),
26}
27
28impl MetadataFile<PackageInfoSchema> for PackageInfo {
29 type Err = Error;
30
31 /// Creates a [`PackageInfo`] from `file`, optionally validated using a [`PackageInfoSchema`].
32 ///
33 /// Opens the `file` and defers to [`PackageInfo::from_reader_with_schema`].
34 ///
35 /// # Note
36 ///
37 /// To automatically derive the [`PackageInfoSchema`], use [`PackageInfo::from_file`].
38 ///
39 /// # Examples
40 ///
41 /// ```
42 /// use std::{fs::File, io::Write};
43 ///
44 /// use alpm_common::{FileFormatSchema, MetadataFile};
45 /// use alpm_pkginfo::{PackageInfo, PackageInfoSchema};
46 /// use alpm_types::{SchemaVersion, semver_version::Version};
47 ///
48 /// # fn main() -> testresult::TestResult {
49 /// // Prepare a file with PKGINFO data
50 /// let (file, pkginfo_data) = {
51 /// let pkginfo_data = r#"pkgname = example
52 /// pkgbase = example
53 /// xdata = pkgtype=pkg
54 /// pkgver = 1:1.0.0-1
55 /// pkgdesc = A project that does something
56 /// url = https://example.org/
57 /// builddate = 1729181726
58 /// packager = John Doe <john@example.org>
59 /// size = 181849963
60 /// arch = any
61 /// "#;
62 /// let pkginfo_file = tempfile::NamedTempFile::new()?;
63 /// let mut output = File::create(&pkginfo_file)?;
64 /// write!(output, "{}", pkginfo_data)?;
65 /// (pkginfo_file, pkginfo_data)
66 /// };
67 ///
68 /// let pkginfo = PackageInfo::from_file_with_schema(
69 /// file.path().to_path_buf(),
70 /// Some(PackageInfoSchema::V2(SchemaVersion::new(Version::new(
71 /// 2, 0, 0,
72 /// )))),
73 /// )?;
74 /// assert_eq!(pkginfo.to_string(), pkginfo_data);
75 /// # Ok(())
76 /// # }
77 /// ```
78 ///
79 /// # Errors
80 ///
81 /// Returns an error if
82 /// - the `file` cannot be opened for reading,
83 /// - no variant of [`PackageInfo`] can be constructed from the contents of `file`,
84 /// - or `schema` is [`Some`] and the [`PackageInfoSchema`] does not match the contents of
85 /// `file`.
86 fn from_file_with_schema(
87 file: impl AsRef<Path>,
88 schema: Option<PackageInfoSchema>,
89 ) -> Result<Self, Error> {
90 let file = file.as_ref();
91 Self::from_reader_with_schema(
92 File::open(file).map_err(|source| {
93 Error::IoPathError(PathBuf::from(file), "opening the file for reading", source)
94 })?,
95 schema,
96 )
97 }
98
99 /// Creates a [`PackageInfo`] from a `reader`, optionally validated using a
100 /// [`PackageInfoSchema`].
101 ///
102 /// Reads the `reader` to string and defers to [`PackageInfo::from_str_with_schema`].
103 ///
104 /// # Note
105 ///
106 /// To automatically derive the [`PackageInfoSchema`], use [`PackageInfo::from_reader`].
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use std::{fs::File, io::Write};
112 ///
113 /// use alpm_common::MetadataFile;
114 /// use alpm_pkginfo::{PackageInfo, PackageInfoSchema};
115 /// use alpm_types::{SchemaVersion, semver_version::Version};
116 ///
117 /// # fn main() -> testresult::TestResult {
118 /// // Prepare a reader with PKGINFO data
119 /// let (reader, pkginfo_data) = {
120 /// let pkginfo_data = r#"pkgname = example
121 /// pkgbase = example
122 /// xdata = pkgtype=pkg
123 /// pkgver = 1:1.0.0-1
124 /// pkgdesc = A project that does something
125 /// url = https://example.org/
126 /// builddate = 1729181726
127 /// packager = John Doe <john@example.org>
128 /// size = 181849963
129 /// arch = any
130 /// "#;
131 /// let pkginfo_file = tempfile::NamedTempFile::new()?;
132 /// let mut output = File::create(&pkginfo_file)?;
133 /// write!(output, "{}", pkginfo_data)?;
134 /// (File::open(&pkginfo_file.path())?, pkginfo_data)
135 /// };
136 ///
137 /// let pkginfo = PackageInfo::from_reader_with_schema(
138 /// reader,
139 /// Some(PackageInfoSchema::V2(SchemaVersion::new(Version::new(
140 /// 2, 0, 0,
141 /// )))),
142 /// )?;
143 /// assert_eq!(pkginfo.to_string(), pkginfo_data);
144 /// # Ok(())
145 /// # }
146 /// ```
147 ///
148 /// # Errors
149 ///
150 /// Returns an error if
151 /// - the `reader` cannot be read to string,
152 /// - no variant of [`PackageInfo`] can be constructed from the contents of the `reader`,
153 /// - or `schema` is [`Some`] and the [`PackageInfoSchema`] does not match the contents of the
154 /// `reader`.
155 fn from_reader_with_schema(
156 mut reader: impl std::io::Read,
157 schema: Option<PackageInfoSchema>,
158 ) -> Result<Self, Error> {
159 let mut buf = String::new();
160 reader
161 .read_to_string(&mut buf)
162 .map_err(|source| Error::IoReadError {
163 context: "reading PackageInfo data",
164 source,
165 })?;
166 Self::from_str_with_schema(&buf, schema)
167 }
168
169 /// Creates a [`PackageInfo`] from string slice, optionally validated using a
170 /// [`PackageInfoSchema`].
171 ///
172 /// If `schema` is [`None`] attempts to detect the [`PackageInfoSchema`] from `s`.
173 /// Attempts to create a [`PackageInfo`] variant that corresponds to the [`PackageInfoSchema`].
174 ///
175 /// # Note
176 ///
177 /// To automatically derive the [`PackageInfoSchema`], use [`PackageInfo::from_str`].
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use std::{fs::File, io::Write};
183 ///
184 /// use alpm_common::MetadataFile;
185 /// use alpm_pkginfo::{PackageInfo, PackageInfoSchema};
186 /// use alpm_types::{SchemaVersion, semver_version::Version};
187 ///
188 /// # fn main() -> testresult::TestResult {
189 /// let pkginfo_v2_data = r#"pkgname = example
190 /// pkgbase = example
191 /// xdata = pkgtype=pkg
192 /// pkgver = 1:1.0.0-1
193 /// pkgdesc = A project that does something
194 /// url = https://example.org/
195 /// builddate = 1729181726
196 /// packager = John Doe <john@example.org>
197 /// size = 181849963
198 /// arch = any
199 /// "#;
200 ///
201 /// let pkginfo_v2 = PackageInfo::from_str_with_schema(
202 /// pkginfo_v2_data,
203 /// Some(PackageInfoSchema::V2(SchemaVersion::new(Version::new(
204 /// 2, 0, 0,
205 /// )))),
206 /// )?;
207 /// assert_eq!(pkginfo_v2.to_string(), pkginfo_v2_data);
208 ///
209 /// let pkginfo_v1_data = r#"pkgname = example
210 /// pkgbase = example
211 /// pkgver = 1:1.0.0-1
212 /// pkgdesc = A project that does something
213 /// url = https://example.org/
214 /// builddate = 1729181726
215 /// packager = John Doe <john@example.org>
216 /// size = 181849963
217 /// arch = any
218 /// "#;
219 ///
220 /// let pkginfo_v1 = PackageInfo::from_str_with_schema(
221 /// pkginfo_v1_data,
222 /// Some(PackageInfoSchema::V1(SchemaVersion::new(Version::new(
223 /// 1, 0, 0,
224 /// )))),
225 /// )?;
226 /// assert_eq!(pkginfo_v1.to_string(), pkginfo_v1_data);
227 /// # Ok(())
228 /// # }
229 /// ```
230 ///
231 /// # Errors
232 ///
233 /// Returns an error if
234 /// - `schema` is [`Some`] and the specified variant of [`PackageInfo`] cannot be constructed
235 /// from `s`,
236 /// - `schema` is [`None`] and
237 /// - a [`PackageInfoSchema`] cannot be derived from `s`,
238 /// - or the detected variant of [`PackageInfo`] cannot be constructed from `s`.
239 fn from_str_with_schema(s: &str, schema: Option<PackageInfoSchema>) -> Result<Self, Error> {
240 let schema = match schema {
241 Some(schema) => schema,
242 None => PackageInfoSchema::derive_from_str(s)?,
243 };
244
245 match schema {
246 PackageInfoSchema::V1(_) => Ok(PackageInfo::V1(PackageInfoV1::from_str(s)?)),
247 PackageInfoSchema::V2(_) => Ok(PackageInfo::V2(PackageInfoV2::from_str(s)?)),
248 }
249 }
250}
251
252impl Display for PackageInfo {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 write!(
255 f,
256 "{}",
257 match self {
258 Self::V1(pkginfo) => pkginfo.to_string(),
259 Self::V2(pkginfo) => pkginfo.to_string(),
260 },
261 )
262 }
263}
264
265impl FromStr for PackageInfo {
266 type Err = Error;
267
268 /// Creates a [`PackageInfo`] from string slice `s`.
269 ///
270 /// Calls [`PackageInfo::from_str_with_schema`] with `schema` set to [`None`].
271 ///
272 /// # Errors
273 ///
274 /// Returns an error if
275 /// - a [`PackageInfoSchema`] cannot be derived from `s`,
276 /// - or the detected variant of [`PackageInfo`] cannot be constructed from `s`.
277 fn from_str(s: &str) -> Result<Self, Self::Err> {
278 Self::from_str_with_schema(s, None)
279 }
280}