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}