alpm_pkginfo/package_info/mod.rs
1//! High-level PKGINFO handling.
2
3pub mod v1;
4pub mod v2;
5use std::{
6 fmt::Display,
7 fs::File,
8 path::{Path, PathBuf},
9 str::FromStr,
10};
11
12use alpm_common::{FileFormatSchema, MetadataFile};
13
14use crate::{Error, PackageInfoSchema, PackageInfoV1, PackageInfoV2};
15
16/// A representation of the [PKGINFO] file format.
17///
18/// Tracks all available versions of the file format.
19///
20/// [PKGINFO]: https://alpm.archlinux.page/specifications/PKGINFO.5.html
21#[derive(Clone, Debug, PartialEq, serde::Serialize)]
22#[serde(untagged)]
23pub enum PackageInfo {
24 /// The [PKGINFOv1] file format.
25 ///
26 /// [PKGINFOv1]: https://alpm.archlinux.page/specifications/PKGINFOv1.5.html
27 V1(PackageInfoV1),
28 /// The [PKGINFOv2] file format.
29 ///
30 /// [PKGINFOv2]: https://alpm.archlinux.page/specifications/PKGINFOv2.5.html
31 V2(PackageInfoV2),
32}
33
34impl MetadataFile<PackageInfoSchema> for PackageInfo {
35 type Err = Error;
36
37 /// Creates a [`PackageInfo`] from `file`, optionally validated using a [`PackageInfoSchema`].
38 ///
39 /// Opens the `file` and defers to [`PackageInfo::from_reader_with_schema`].
40 ///
41 /// # Note
42 ///
43 /// To automatically derive the [`PackageInfoSchema`], use [`PackageInfo::from_file`].
44 ///
45 /// # Examples
46 ///
47 /// ```
48 /// use std::{fs::File, io::Write};
49 ///
50 /// use alpm_common::{FileFormatSchema, MetadataFile};
51 /// use alpm_pkginfo::{PackageInfo, PackageInfoSchema};
52 /// use alpm_types::{SchemaVersion, semver_version::Version};
53 ///
54 /// # fn main() -> testresult::TestResult {
55 /// // Prepare a file with PKGINFO data
56 /// let (file, pkginfo_data) = {
57 /// let pkginfo_data = r#"pkgname = example
58 /// pkgbase = example
59 /// xdata = pkgtype=pkg
60 /// pkgver = 1:1.0.0-1
61 /// pkgdesc = A project that does something
62 /// url = https://example.org/
63 /// builddate = 1729181726
64 /// packager = John Doe <john@example.org>
65 /// size = 181849963
66 /// arch = any
67 /// "#;
68 /// let pkginfo_file = tempfile::NamedTempFile::new()?;
69 /// let mut output = File::create(&pkginfo_file)?;
70 /// write!(output, "{}", pkginfo_data)?;
71 /// (pkginfo_file, pkginfo_data)
72 /// };
73 ///
74 /// let pkginfo = PackageInfo::from_file_with_schema(
75 /// file.path().to_path_buf(),
76 /// Some(PackageInfoSchema::V2(SchemaVersion::new(Version::new(
77 /// 2, 0, 0,
78 /// )))),
79 /// )?;
80 /// assert_eq!(pkginfo.to_string(), pkginfo_data);
81 /// # Ok(())
82 /// # }
83 /// ```
84 ///
85 /// # Errors
86 ///
87 /// Returns an error if
88 /// - the `file` cannot be opened for reading,
89 /// - no variant of [`PackageInfo`] can be constructed from the contents of `file`,
90 /// - or `schema` is [`Some`] and the [`PackageInfoSchema`] does not match the contents of
91 /// `file`.
92 fn from_file_with_schema(
93 file: impl AsRef<Path>,
94 schema: Option<PackageInfoSchema>,
95 ) -> Result<Self, Error> {
96 let file = file.as_ref();
97 Self::from_reader_with_schema(
98 File::open(file).map_err(|source| {
99 Error::IoPathError(PathBuf::from(file), "opening the file for reading", source)
100 })?,
101 schema,
102 )
103 }
104
105 /// Creates a [`PackageInfo`] from a `reader`, optionally validated using a
106 /// [`PackageInfoSchema`].
107 ///
108 /// Reads the `reader` to string and defers to [`PackageInfo::from_str_with_schema`].
109 ///
110 /// # Note
111 ///
112 /// To automatically derive the [`PackageInfoSchema`], use [`PackageInfo::from_reader`].
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// use std::{fs::File, io::Write};
118 ///
119 /// use alpm_common::MetadataFile;
120 /// use alpm_pkginfo::{PackageInfo, PackageInfoSchema};
121 /// use alpm_types::{SchemaVersion, semver_version::Version};
122 ///
123 /// # fn main() -> testresult::TestResult {
124 /// // Prepare a reader with PKGINFO data
125 /// let (reader, pkginfo_data) = {
126 /// let pkginfo_data = r#"pkgname = example
127 /// pkgbase = example
128 /// xdata = pkgtype=pkg
129 /// pkgver = 1:1.0.0-1
130 /// pkgdesc = A project that does something
131 /// url = https://example.org/
132 /// builddate = 1729181726
133 /// packager = John Doe <john@example.org>
134 /// size = 181849963
135 /// arch = any
136 /// "#;
137 /// let pkginfo_file = tempfile::NamedTempFile::new()?;
138 /// let mut output = File::create(&pkginfo_file)?;
139 /// write!(output, "{}", pkginfo_data)?;
140 /// (File::open(&pkginfo_file.path())?, pkginfo_data)
141 /// };
142 ///
143 /// let pkginfo = PackageInfo::from_reader_with_schema(
144 /// reader,
145 /// Some(PackageInfoSchema::V2(SchemaVersion::new(Version::new(
146 /// 2, 0, 0,
147 /// )))),
148 /// )?;
149 /// assert_eq!(pkginfo.to_string(), pkginfo_data);
150 /// # Ok(())
151 /// # }
152 /// ```
153 ///
154 /// # Errors
155 ///
156 /// Returns an error if
157 /// - the `reader` cannot be read to string,
158 /// - no variant of [`PackageInfo`] can be constructed from the contents of the `reader`,
159 /// - or `schema` is [`Some`] and the [`PackageInfoSchema`] does not match the contents of the
160 /// `reader`.
161 fn from_reader_with_schema(
162 mut reader: impl std::io::Read,
163 schema: Option<PackageInfoSchema>,
164 ) -> Result<Self, Error> {
165 let mut buf = String::new();
166 reader
167 .read_to_string(&mut buf)
168 .map_err(|source| Error::IoReadError {
169 context: "reading PackageInfo data",
170 source,
171 })?;
172 Self::from_str_with_schema(&buf, schema)
173 }
174
175 /// Creates a [`PackageInfo`] from string slice, optionally validated using a
176 /// [`PackageInfoSchema`].
177 ///
178 /// If `schema` is [`None`] attempts to detect the [`PackageInfoSchema`] from `s`.
179 /// Attempts to create a [`PackageInfo`] variant that corresponds to the [`PackageInfoSchema`].
180 ///
181 /// # Note
182 ///
183 /// To automatically derive the [`PackageInfoSchema`], use [`PackageInfo::from_str`].
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use std::{fs::File, io::Write};
189 ///
190 /// use alpm_common::MetadataFile;
191 /// use alpm_pkginfo::{PackageInfo, PackageInfoSchema};
192 /// use alpm_types::{SchemaVersion, semver_version::Version};
193 ///
194 /// # fn main() -> testresult::TestResult {
195 /// let pkginfo_v2_data = r#"pkgname = example
196 /// pkgbase = example
197 /// xdata = pkgtype=pkg
198 /// pkgver = 1:1.0.0-1
199 /// pkgdesc = A project that does something
200 /// url = https://example.org/
201 /// builddate = 1729181726
202 /// packager = John Doe <john@example.org>
203 /// size = 181849963
204 /// arch = any
205 /// "#;
206 ///
207 /// let pkginfo_v2 = PackageInfo::from_str_with_schema(
208 /// pkginfo_v2_data,
209 /// Some(PackageInfoSchema::V2(SchemaVersion::new(Version::new(
210 /// 2, 0, 0,
211 /// )))),
212 /// )?;
213 /// assert_eq!(pkginfo_v2.to_string(), pkginfo_v2_data);
214 ///
215 /// let pkginfo_v1_data = r#"pkgname = example
216 /// pkgbase = example
217 /// pkgver = 1:1.0.0-1
218 /// pkgdesc = A project that does something
219 /// url = https://example.org/
220 /// builddate = 1729181726
221 /// packager = John Doe <john@example.org>
222 /// size = 181849963
223 /// arch = any
224 /// "#;
225 ///
226 /// let pkginfo_v1 = PackageInfo::from_str_with_schema(
227 /// pkginfo_v1_data,
228 /// Some(PackageInfoSchema::V1(SchemaVersion::new(Version::new(
229 /// 1, 0, 0,
230 /// )))),
231 /// )?;
232 /// assert_eq!(pkginfo_v1.to_string(), pkginfo_v1_data);
233 /// # Ok(())
234 /// # }
235 /// ```
236 ///
237 /// # Errors
238 ///
239 /// Returns an error if
240 /// - `schema` is [`Some`] and the specified variant of [`PackageInfo`] cannot be constructed
241 /// from `s`,
242 /// - `schema` is [`None`] and
243 /// - a [`PackageInfoSchema`] cannot be derived from `s`,
244 /// - or the detected variant of [`PackageInfo`] cannot be constructed from `s`.
245 fn from_str_with_schema(s: &str, schema: Option<PackageInfoSchema>) -> Result<Self, Error> {
246 let schema = match schema {
247 Some(schema) => schema,
248 None => PackageInfoSchema::derive_from_str(s)?,
249 };
250
251 match schema {
252 PackageInfoSchema::V1(_) => Ok(PackageInfo::V1(PackageInfoV1::from_str(s)?)),
253 PackageInfoSchema::V2(_) => Ok(PackageInfo::V2(PackageInfoV2::from_str(s)?)),
254 }
255 }
256}
257
258impl Display for PackageInfo {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 write!(
261 f,
262 "{}",
263 match self {
264 Self::V1(pkginfo) => pkginfo.to_string(),
265 Self::V2(pkginfo) => pkginfo.to_string(),
266 },
267 )
268 }
269}
270
271impl FromStr for PackageInfo {
272 type Err = Error;
273
274 /// Creates a [`PackageInfo`] from string slice `s`.
275 ///
276 /// Calls [`PackageInfo::from_str_with_schema`] with `schema` set to [`None`].
277 ///
278 /// # Errors
279 ///
280 /// Returns an error if
281 /// - a [`PackageInfoSchema`] cannot be derived from `s`,
282 /// - or the detected variant of [`PackageInfo`] cannot be constructed from `s`.
283 fn from_str(s: &str) -> Result<Self, Self::Err> {
284 Self::from_str_with_schema(s, None)
285 }
286}