alpm_srcinfo/source_info/v1/mod.rs
1//! Contains the second parsing and linting pass.
2//!
3//! The raw representation from the [`parser`](crate::source_info::parser) module is brought into a
4//! proper struct-based representation that fully represents the SRCINFO data (apart from comments
5//! and empty lines).
6use std::{
7 fs::File,
8 io::{BufReader, Read},
9 path::Path,
10};
11
12use alpm_pkgbuild::bridge::BridgeOutput;
13use alpm_types::Architecture;
14use serde::{Deserialize, Serialize};
15use winnow::Parser;
16use writer::{pkgbase_section, pkgname_section};
17
18pub mod merged;
19pub mod package;
20pub mod package_base;
21pub mod writer;
22
23#[cfg(doc)]
24use crate::MergedPackage;
25use crate::{
26 error::Error,
27 source_info::{
28 parser::SourceInfoContent,
29 v1::{merged::MergedPackagesIterator, package::Package, package_base::PackageBase},
30 },
31};
32
33/// The representation of SRCINFO data.
34///
35/// Provides access to a [`PackageBase`] which tracks all data in a `pkgbase` section and a list of
36/// [`Package`] instances that provide the accumulated data of all `pkgname` sections.
37///
38/// This is the entry point for parsing SRCINFO files. Once created,
39/// [`Self::packages_for_architecture`] can be used to create usable [`MergedPackage`]s.
40#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
41pub struct SourceInfoV1 {
42 /// The information of the `pkgbase` section.
43 pub base: PackageBase,
44 /// The information of the `pkgname` sections.
45 pub packages: Vec<Package>,
46}
47
48impl SourceInfoV1 {
49 /// Returns the [SRCINFO] representation.
50 ///
51 /// ```
52 /// use std::{env::var, path::PathBuf};
53 ///
54 /// use alpm_srcinfo::SourceInfoV1;
55 ///
56 /// const TEST_FILE: &str = include_str!("../../../tests/unit_test_files/normal.srcinfo");
57 ///
58 /// # fn main() -> testresult::TestResult {
59 /// // Read a .SRCINFO file and bring it into the `SourceInfoV1` representation.
60 /// let source_info = SourceInfoV1::from_string(TEST_FILE)?;
61 /// // Convert the `SourceInfoV1` back into it's alpm `.SRCINFO` format.
62 /// println!("{}", source_info.as_srcinfo());
63 /// # Ok(())
64 /// # }
65 /// ```
66 ///
67 /// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
68 pub fn as_srcinfo(&self) -> String {
69 let mut srcinfo = String::new();
70
71 pkgbase_section(&self.base, &mut srcinfo);
72 for package in &self.packages {
73 srcinfo.push('\n');
74 pkgname_section(package, &self.base.architectures, &mut srcinfo);
75 }
76
77 srcinfo
78 }
79
80 /// Reads the file at the specified path and converts it into a [`SourceInfoV1`] struct.
81 ///
82 /// # Errors
83 ///
84 /// Returns an [`Error`] if the file cannot be read or parsed.
85 pub fn from_file(path: &Path) -> Result<SourceInfoV1, Error> {
86 let mut buffer = Vec::new();
87 let file = File::open(path)
88 .map_err(|err| Error::IoPath(path.to_path_buf(), "opening file", err))?;
89 let mut buf_reader = BufReader::new(file);
90 buf_reader
91 .read_to_end(&mut buffer)
92 .map_err(|err| Error::IoPath(path.to_path_buf(), "reading file", err))?;
93
94 let content = String::from_utf8(buffer)?.to_string();
95
96 Self::from_string(&content)
97 }
98
99 /// Creates a [`SourceInfoV1`] from a [`PKGBUILD`] file.
100 ///
101 /// # Errors
102 ///
103 /// Returns an error if
104 ///
105 /// - The `PKGBUILD` cannot be read.
106 /// - a required field is not set,
107 /// - a `package` functions exists, but does not correspond to a declared [alpm-split-package],
108 /// - a `package` function without an [alpm-package-name] suffix exists in an
109 /// [alpm-split-package] setup,
110 /// - a value cannot be turned into its [`alpm_types`] equivalent,
111 /// - multiple values exist for a field that only accepts a singular value,
112 /// - an [alpm-architecture] is duplicated,
113 /// - an [alpm-architecture] is cleared in `package` function,
114 /// - or an [alpm-architecture] suffix is set on a keyword that does not support it.
115 ///
116 /// [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
117 /// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
118 /// [alpm-package-name]: https://alpm.archlinux.page/specifications/alpm-package-name.7.html
119 /// [alpm-split-package]: https://alpm.archlinux.page/specifications/alpm-split-package.7.html
120 pub fn from_pkgbuild(pkgbuild_path: &Path) -> Result<SourceInfoV1, Error> {
121 let output = BridgeOutput::from_file(pkgbuild_path)?;
122 let source_info: SourceInfoV1 = output.try_into()?;
123
124 Ok(source_info)
125 }
126
127 /// Parses a SRCINFO file's content into a [`SourceInfoV1`] struct.
128 ///
129 /// # Error
130 ///
131 /// This function returns two types of errors.
132 /// 1. An [`Error`] is returned if the input is, for example, invalid UTF-8 or if the input
133 /// SRCINFO file couldn't be parsed due to invalid syntax.
134 /// 2. An [`Error`] is returned if the parsed data is incomplete or otherwise invalid.
135 ///
136 /// ```rust
137 /// use alpm_srcinfo::SourceInfoV1;
138 /// use alpm_types::{Architecture, Name, PackageRelation};
139 ///
140 /// # fn main() -> Result<(), alpm_srcinfo::Error> {
141 /// let source_info_data = r#"
142 /// pkgbase = example
143 /// pkgver = 1.0.0
144 /// epoch = 1
145 /// pkgrel = 1
146 /// pkgdesc = A project that does something
147 /// url = https://example.org/
148 /// arch = x86_64
149 /// depends = glibc
150 /// optdepends = python: for special-python-script.py
151 /// makedepends = cmake
152 /// checkdepends = extra-test-tool
153 ///
154 /// pkgname = example
155 /// depends = glibc
156 /// depends = gcc-libs
157 /// "#;
158 ///
159 /// // Parse the file. This errors if the file cannot be parsed, is missing data or contains invalid data.
160 /// let source_info = SourceInfoV1::from_string(source_info_data)?;
161 /// # Ok(())
162 /// # }
163 /// ```
164 pub fn from_string(content: &str) -> Result<SourceInfoV1, Error> {
165 // Parse the given srcinfo content.
166 let parsed = SourceInfoContent::parser
167 .parse(content)
168 .map_err(|err| Error::ParseError(format!("{err}")))?;
169
170 // Bring it into a proper structural representation
171 let source_info = SourceInfoV1::from_raw(parsed)?;
172
173 Ok(source_info)
174 }
175
176 /// Reads raw [`SourceInfoContent`] from a first parsing step and converts it into a
177 /// [`SourceInfoV1`].
178 pub fn from_raw(content: SourceInfoContent) -> Result<SourceInfoV1, Error> {
179 let base = PackageBase::from_parsed(content.package_base)?;
180
181 let mut packages = Vec::new();
182 for package in content.packages {
183 let package = Package::from_parsed(package);
184 packages.push(package);
185 }
186
187 Ok(SourceInfoV1 { base, packages })
188 }
189
190 /// Get an iterator over all packages
191 ///
192 /// ```
193 /// use alpm_srcinfo::{MergedPackage, SourceInfoV1};
194 /// use alpm_types::{Architecture, Name, PackageDescription, PackageRelation};
195 ///
196 /// # fn main() -> Result<(), alpm_srcinfo::Error> {
197 /// let source_info_data = r#"
198 /// pkgbase = example
199 /// pkgver = 1.0.0
200 /// epoch = 1
201 /// pkgrel = 1
202 /// arch = x86_64
203 ///
204 /// pkgname = example
205 /// pkgdesc = Example split package
206 ///
207 /// pkgname = example_other
208 /// pkgdesc = The other example split package
209 /// "#;
210 /// // Parse the file. This errors if the file cannot be parsed, is missing data or contains invalid data.
211 /// let source_info = SourceInfoV1::from_string(source_info_data)?;
212 ///
213 /// /// Get all merged package representations for the x86_64 architecture.
214 /// let mut packages = source_info.packages_for_architecture(Architecture::X86_64);
215 ///
216 /// let example = packages.next().unwrap();
217 /// assert_eq!(
218 /// example.description,
219 /// Some(PackageDescription::new("Example split package"))
220 /// );
221 ///
222 /// let example_other = packages.next().unwrap();
223 /// assert_eq!(
224 /// example_other.description,
225 /// Some(PackageDescription::new("The other example split package"))
226 /// );
227 ///
228 /// # Ok(())
229 /// # }
230 /// ```
231 pub fn packages_for_architecture(
232 &self,
233 architecture: Architecture,
234 ) -> MergedPackagesIterator<'_> {
235 MergedPackagesIterator {
236 architecture,
237 source_info: self,
238 package_iterator: self.packages.iter(),
239 }
240 }
241}