alpm_buildinfo/
build_info.rs

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