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}