Skip to main content

alpm_srcinfo/pkgbuild_bridge/
mod.rs

1//! Convert untyped and unchecked [`BridgeOutput`] into a well-formed [`SourceInfoV1`].
2
3pub mod error;
4mod package;
5mod package_base;
6
7use std::collections::HashMap;
8
9use alpm_parsers::traits::AlpmParser;
10use alpm_pkgbuild::bridge::{BridgeOutput, Keyword, Value};
11use alpm_types::{Architectures, Name, SystemArchitecture};
12use package::handle_packages;
13use package_base::handle_package_base;
14use winnow::{
15    Parser,
16    error::{ContextError, ErrMode, ParseError, StrContext, StrContextValue},
17};
18
19use crate::{SourceInfoV1, pkgbuild_bridge::error::BridgeError};
20
21impl TryFrom<BridgeOutput> for SourceInfoV1 {
22    type Error = BridgeError;
23
24    /// Creates a [`SourceInfoV1`] from a [`BridgeOutput`].
25    ///
26    /// See errors and documentation in [`SourceInfoV1::from_pkgbuild`]
27    fn try_from(mut value: BridgeOutput) -> Result<Self, Self::Error> {
28        let mut name = None;
29        // Check if there's a `pkgbase` section, which hints that this is a split package.
30        let pkgbase_keyword = Keyword::simple("pkgbase");
31        if let Some(value) = value.package_base.remove(&pkgbase_keyword) {
32            name = Some(parse_value(&pkgbase_keyword, &value, Name::parser)?);
33        }
34
35        // Get the list of all packages that are declared.
36        let pkgname_keyword = Keyword::simple("pkgname");
37        let names = ensure_keyword_exists(&pkgname_keyword, &mut value.package_base)?;
38        let names = parse_value_array(&pkgname_keyword, &names, Name::parser)?;
39
40        // Use the `pkgbase` name by default, otherwise fallback to the first `pkgname` entry.
41        let name = match name {
42            Some(name) => name,
43            None => {
44                // The first package name is used as the name for the pkgbase section.
45                names.first().cloned().ok_or(BridgeError::NoName)?
46            }
47        };
48
49        let base = handle_package_base(name.clone(), value.package_base)?;
50
51        // Go through all declared functions and ensure that the package functions are also
52        // declared via `pkgname`. If one of them is not, this is a bug.
53        for name in value.functions {
54            let Some(name) = name.0 else { continue };
55
56            let name =
57                Name::parser
58                    .parse(&name)
59                    .map_err(|err| BridgeError::InvalidPackageName {
60                        name: name.clone(),
61                        error: err.into(),
62                    })?;
63
64            if !names.contains(&name) {
65                return Err(BridgeError::UndeclaredPackageName(name.to_string()));
66            }
67        }
68
69        let packages = handle_packages(name, names, value.packages)?;
70
71        Ok(SourceInfoV1 { base, packages })
72    }
73}
74
75/// Ensures a [`Keyword`] exists in a [`HashMap`], removes it and returns it.
76///
77/// This is a helper function to ensure expected values are set while throwing context-rich
78/// errors if they don't.
79///
80/// # Errors
81///
82/// Returns an error if `keyword` is not a key in `map`.
83fn ensure_keyword_exists(
84    keyword: &Keyword,
85    map: &mut HashMap<Keyword, Value>,
86) -> Result<Value, BridgeError> {
87    match map.remove(keyword) {
88        Some(value) => Ok(value),
89        None => Err(BridgeError::MissingRequiredKeyword {
90            keyword: keyword.clone(),
91        }),
92    }
93}
94
95/// Ensures that a combination of a [`Keyword`] and an optional [`SystemArchitecture`] does not use
96/// an [`SystemArchitecture`].
97///
98/// # Errors
99///
100/// Returns an error, if `architecture` provides an [`SystemArchitecture`].
101fn ensure_no_suffix(
102    keyword: &Keyword,
103    architecture: Option<SystemArchitecture>,
104) -> Result<(), BridgeError> {
105    if let Some(arch) = architecture {
106        return Err(BridgeError::UnexpectedArchitecture {
107            keyword: keyword.clone(),
108            suffix: arch,
109        });
110    }
111
112    Ok(())
113}
114
115/// Ensures that a combination of [`Keyword`] and [`Value`] uses a [`Value::Single`] and returns the
116/// value.
117///
118/// # Errors
119///
120/// Returns an error, if `value` is a [`Value::Array`].
121fn ensure_single_value<'a>(keyword: &Keyword, value: &'a Value) -> Result<&'a String, BridgeError> {
122    match value {
123        Value::Single(item) => Ok(item),
124        Value::Array(values) => Err(BridgeError::UnexpectedArray {
125            keyword: keyword.clone(),
126            values: values.clone(),
127        }),
128    }
129}
130
131/// Ensures that a combination of [`Keyword`] and [`Value`] uses a [`Value::Single`] and parses the
132/// value as a specific type.
133///
134/// # Errors
135///
136/// Returns a error for `keyword` if `value` is not [`Value::Single`] or cannot be parsed as the
137/// specific type.
138fn parse_value<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
139    keyword: &Keyword,
140    value: &'a Value,
141    mut parser: P,
142) -> Result<O, BridgeError> {
143    let input = ensure_single_value(keyword, value)?;
144    Ok(parser.parse(input).map_err(|err| (keyword.clone(), err))?)
145}
146
147/// Ensures a combination of [`Keyword`] and [`Value`] uses a [`Value::Single`] and parses the value
148/// as a specific, but optional type.
149///
150/// Returns [`None`] if `value` is **empty**.
151///
152/// # Errors
153///
154/// Returns a error for `keyword` if `value` is not [`Value::Single`] or cannot be parsed as the
155/// specific type.
156fn parse_optional_value<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
157    keyword: &Keyword,
158    value: &'a Value,
159    mut parser: P,
160) -> Result<Option<O>, BridgeError> {
161    let input = ensure_single_value(keyword, value)?;
162
163    if input.trim().is_empty() {
164        return Ok(None);
165    }
166
167    Ok(Some(
168        parser.parse(input).map_err(|err| (keyword.clone(), err))?,
169    ))
170}
171
172/// Parses a [`Value`] as a [`Vec`] of specific types.
173///
174/// Does not differentiate between [`Value::Single`] and [`Value::Array`] variants, as a
175/// [`PKGBUILD`] allows either for array values.
176///
177/// # Errors
178///
179/// Returns a error for `keyword` if `value` cannot be parsed.
180///
181/// [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
182fn parse_value_array<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
183    keyword: &Keyword,
184    value: &'a Value,
185    mut parser: P,
186) -> Result<Vec<O>, BridgeError> {
187    let input = value.as_vec();
188    Ok(input
189        .into_iter()
190        .map(|item| parser.parse(item).map_err(|err| (keyword.clone(), err)))
191        .collect::<Result<Vec<O>, (Keyword, ParseError<&'a str, ContextError>)>>()?)
192}
193
194/// Parses a [`Value`] as [`Architectures`].
195///
196/// # Errors
197///
198/// Returns an error for `keyword` if `value` cannot be parsed as either a stand-alone "any" or a
199/// list of [`SystemArchitecture`].
200fn parse_arch_array<'a>(keyword: &Keyword, value: &'a Value) -> Result<Architectures, BridgeError> {
201    // `arch` may be a list or a single value (for backward compatibility).
202    let input = value.as_vec();
203
204    // First check if the entry is a single "any".
205    // `arch = "any"`
206    // or
207    // `arch = ("any")`
208    if input.len() == 1 && input[0] == "any" {
209        return Ok(Architectures::Any);
210    }
211
212    let architectures = input
213        .into_iter()
214        .map(|item| {
215            SystemArchitecture::parser
216                .context(StrContext::Expected(StrContextValue::Description(
217                    "either a single 'any' or an array of one or more specific system architectures."
218                )))
219                .parse(item)
220                .map_err(|err| (keyword.clone(), err))
221        })
222        .collect::<Result<Vec<SystemArchitecture>, (Keyword, ParseError<&'a str, ContextError>)>>()?;
223
224    Ok(Architectures::Some(architectures))
225}
226
227#[cfg(test)]
228mod tests {
229    use testresult::TestResult;
230    use winnow::token::rest;
231
232    use super::*;
233
234    /// Ensure that an empty single value will return `None` when passed into
235    /// [`parse_optional_value`].
236    #[test]
237    pub fn test_empty_optional_value() -> TestResult {
238        let keyword = Keyword::simple("test");
239        let value = Value::Single("".to_string());
240
241        let value = parse_optional_value(&keyword, &value, rest)?;
242
243        assert!(value.is_none(), "Empty string values should return `None`.");
244
245        Ok(())
246    }
247}