alpm_buildinfo/
build_info.rs

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