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}