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}