Skip to main content

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