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            // A temporary fix for <https://github.com/winnow-rs/winnow/issues/847>
118            .parse(s.replace('\t', " ").as_str())
119            .map_err(|err| Error::ParseError(format!("{err}")))?;
120
121        Ok(SourceInfoSchema::V1(SchemaVersion::new(Version::new(
122            1, 0, 0,
123        ))))
124    }
125}
126
127impl Default for SourceInfoSchema {
128    /// Returns the default [`SourceInfoSchema`] variant ([`SourceInfoSchema::V1`]).
129    fn default() -> Self {
130        Self::V1(SchemaVersion::new(Version::new(1, 0, 0)))
131    }
132}
133
134impl FromStr for SourceInfoSchema {
135    type Err = Error;
136
137    /// Creates a [`SourceInfoSchema`] from string slice `s`.
138    ///
139    /// Relies on [`SchemaVersion::from_str`] to create a corresponding [`SourceInfoSchema`] from
140    /// `s`.
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if
145    /// - no [`SchemaVersion`] can be created from `s`,
146    /// - or the conversion from [`SchemaVersion`] to [`SourceInfoSchema`] fails.
147    fn from_str(s: &str) -> Result<SourceInfoSchema, Self::Err> {
148        match SchemaVersion::from_str(s) {
149            Ok(version) => Self::try_from(version),
150            Err(_) => Err(Error::UnsupportedSchemaVersion(s.to_string())),
151        }
152    }
153}
154
155impl TryFrom<SchemaVersion> for SourceInfoSchema {
156    type Error = Error;
157
158    /// Converts a [`SchemaVersion`] to a [`SourceInfoSchema`].
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if the [`SchemaVersion`]'s inner [`Version`] does not provide a major
163    /// version that corresponds to a [`SourceInfoSchema`] variant.
164    fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
165        match value.inner().major {
166            1 => Ok(SourceInfoSchema::V1(value)),
167            _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
168        }
169    }
170}
171
172impl Display for SourceInfoSchema {
173    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
174        write!(
175            fmt,
176            "{}",
177            match self {
178                SourceInfoSchema::V1(version) => version.inner().major,
179            }
180        )
181    }
182}