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