alpm_srcinfo/
schema.rs

1//! Schemas for SRCINFO data.
2
3use std::{
4    fmt::{Display, Formatter},
5    fs::File,
6    path::Path,
7    str::FromStr,
8};
9
10use alpm_common::FileFormatSchema;
11use alpm_types::{SchemaVersion, semver_version::Version};
12use fluent_i18n::t;
13use winnow::Parser;
14
15use crate::{Error, source_info::parser::SourceInfoContent};
16
17/// An enum tracking all available [SRCINFO] schemas.
18///
19/// The schema of a SRCINFO refers to the minimum required sections and keywords, as well as the
20/// complete set of available keywords in a specific version.
21///
22/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub enum SourceInfoSchema {
25    /// Schema for the [SRCINFO] file format.
26    ///
27    /// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
28    V1(SchemaVersion),
29}
30
31impl FileFormatSchema for SourceInfoSchema {
32    type Err = Error;
33
34    /// Returns a reference to the inner [`SchemaVersion`].
35    fn inner(&self) -> &SchemaVersion {
36        match self {
37            SourceInfoSchema::V1(v) => v,
38        }
39    }
40
41    /// Derives a [`SourceInfoSchema`] from a SRCINFO file.
42    ///
43    /// Opens the `file` and defers to [`SourceInfoSchema::derive_from_reader`].
44    ///
45    /// # Errors
46    ///
47    /// Returns an error if
48    /// - opening `file` for reading fails
49    /// - or deriving a [`SourceInfoSchema`] from the contents of `file` fails.
50    fn derive_from_file(file: impl AsRef<Path>) -> Result<Self, Error>
51    where
52        Self: Sized,
53    {
54        let file = file.as_ref();
55        Self::derive_from_reader(File::open(file).map_err(|source| Error::IoPath {
56            path: file.to_path_buf(),
57            context: t!("error-io-deriving-schema-from-srcinfo-file"),
58            source,
59        })?)
60    }
61
62    /// Derives a [`SourceInfoSchema`] from SRCINFO data in a `reader`.
63    ///
64    /// Reads the `reader` to string and defers to [`SourceInfoSchema::derive_from_str`].
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if
69    /// - reading a [`String`] from `reader` fails
70    /// - or deriving a [`SourceInfoSchema`] from the contents of `reader` fails.
71    fn derive_from_reader(reader: impl std::io::Read) -> Result<Self, Error>
72    where
73        Self: Sized,
74    {
75        let mut buf = String::new();
76        let mut reader = reader;
77        reader
78            .read_to_string(&mut buf)
79            .map_err(|source| Error::Io {
80                context: t!("error-io-deriving-schema-from-srcinfo-data"),
81                source,
82            })?;
83        Self::derive_from_str(&buf)
84    }
85
86    /// Derives a [`SourceInfoSchema`] from a string slice containing SRCINFO data.
87    ///
88    /// Since the SRCINFO format is only covered by a single version and it not carrying any
89    /// version information, this function checks whether `s` contains at least the sections
90    /// `pkgbase` and `pkgname` and the keywords `pkgver` and `pkgrel`.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use alpm_common::FileFormatSchema;
96    /// use alpm_srcinfo::SourceInfoSchema;
97    /// use alpm_types::{SchemaVersion, semver_version::Version};
98    ///
99    /// # fn main() -> Result<(), alpm_srcinfo::Error> {
100    /// let srcinfo_data = r#"
101    /// pkgbase = example
102    ///     pkgdesc = An example
103    ///     pkgver = 0.1.0
104    ///     pkgrel = 1
105    ///
106    /// pkgname = example
107    /// "#;
108    /// # Ok(())
109    /// # }
110    /// ```
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if `s` cannot be parsed.
115    fn derive_from_str(s: &str) -> Result<SourceInfoSchema, Error> {
116        let _parsed = SourceInfoContent::parser
117            .parse(s)
118            .map_err(|err| Error::ParseError(format!("{err}")))?;
119
120        Ok(SourceInfoSchema::V1(SchemaVersion::new(Version::new(
121            1, 0, 0,
122        ))))
123    }
124}
125
126impl Default for SourceInfoSchema {
127    /// Returns the default [`SourceInfoSchema`] variant ([`SourceInfoSchema::V1`]).
128    fn default() -> Self {
129        Self::V1(SchemaVersion::new(Version::new(1, 0, 0)))
130    }
131}
132
133impl FromStr for SourceInfoSchema {
134    type Err = Error;
135
136    /// Creates a [`SourceInfoSchema`] from string slice `s`.
137    ///
138    /// Relies on [`SchemaVersion::from_str`] to create a corresponding [`SourceInfoSchema`] from
139    /// `s`.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if
144    /// - no [`SchemaVersion`] can be created from `s`,
145    /// - or the conversion from [`SchemaVersion`] to [`SourceInfoSchema`] fails.
146    fn from_str(s: &str) -> Result<SourceInfoSchema, Self::Err> {
147        match SchemaVersion::from_str(s) {
148            Ok(version) => Self::try_from(version),
149            Err(_) => Err(Error::UnsupportedSchemaVersion(s.to_string())),
150        }
151    }
152}
153
154impl TryFrom<SchemaVersion> for SourceInfoSchema {
155    type Error = Error;
156
157    /// Converts a [`SchemaVersion`] to a [`SourceInfoSchema`].
158    ///
159    /// # Errors
160    ///
161    /// Returns an error if the [`SchemaVersion`]'s inner [`Version`] does not provide a major
162    /// version that corresponds to a [`SourceInfoSchema`] variant.
163    fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
164        match value.inner().major {
165            1 => Ok(SourceInfoSchema::V1(value)),
166            _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
167        }
168    }
169}
170
171impl Display for SourceInfoSchema {
172    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
173        write!(
174            fmt,
175            "{}",
176            match self {
177                SourceInfoSchema::V1(version) => version.inner().major,
178            }
179        )
180    }
181}