alpm_srcinfo/pkgbuild_bridge/
mod.rs1pub mod error;
4mod package;
5mod package_base;
6
7use std::collections::HashMap;
8
9use alpm_parsers::prelude::*;
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::error::{ErrMode, ParseError};
15
16use crate::{SourceInfoV1, pkgbuild_bridge::error::BridgeError};
17
18impl TryFrom<BridgeOutput> for SourceInfoV1 {
19 type Error = BridgeError;
20
21 fn try_from(mut value: BridgeOutput) -> Result<Self, Self::Error> {
25 let mut name = None;
26 let pkgbase_keyword = Keyword::simple("pkgbase");
28 if let Some(value) = value.package_base.remove(&pkgbase_keyword) {
29 name = Some(parse_value(&pkgbase_keyword, &value, Name::parser)?);
30 }
31
32 let pkgname_keyword = Keyword::simple("pkgname");
34 let names = ensure_keyword_exists(&pkgname_keyword, &mut value.package_base)?;
35 let names = parse_value_array(&pkgname_keyword, &names, Name::parser)?;
36
37 let name = match name {
39 Some(name) => name,
40 None => {
41 names.first().cloned().ok_or(BridgeError::NoName)?
43 }
44 };
45
46 let base = handle_package_base(name.clone(), value.package_base)?;
47
48 for name in value.functions {
51 let Some(name) = name.0 else { continue };
52
53 let name = Name::parser.parse(Input::new(&name)).map_err(|err| {
54 BridgeError::InvalidPackageName {
55 name: name.clone(),
56 error: err.into(),
57 }
58 })?;
59
60 if !names.contains(&name) {
61 return Err(BridgeError::UndeclaredPackageName(name.to_string()));
62 }
63 }
64
65 let packages = handle_packages(name, names, value.packages)?;
66
67 Ok(SourceInfoV1 { base, packages })
68 }
69}
70
71fn ensure_keyword_exists(
80 keyword: &Keyword,
81 map: &mut HashMap<Keyword, Value>,
82) -> Result<Value, BridgeError> {
83 match map.remove(keyword) {
84 Some(value) => Ok(value),
85 None => Err(BridgeError::MissingRequiredKeyword {
86 keyword: keyword.clone(),
87 }),
88 }
89}
90
91fn ensure_no_suffix(
98 keyword: &Keyword,
99 architecture: Option<SystemArchitecture>,
100) -> Result<(), BridgeError> {
101 if let Some(arch) = architecture {
102 return Err(BridgeError::UnexpectedArchitecture {
103 keyword: keyword.clone(),
104 suffix: arch,
105 });
106 }
107
108 Ok(())
109}
110
111fn ensure_single_value<'a>(keyword: &Keyword, value: &'a Value) -> Result<&'a String, BridgeError> {
118 match value {
119 Value::Single(item) => Ok(item),
120 Value::Array(values) => Err(BridgeError::UnexpectedArray {
121 keyword: keyword.clone(),
122 values: values.clone(),
123 }),
124 }
125}
126
127fn parse_value<'a, O, P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>>(
135 keyword: &Keyword,
136 value: &'a Value,
137 mut parser: P,
138) -> Result<O, BridgeError> {
139 let input = ensure_single_value(keyword, value)?;
140 Ok(parser
141 .parse(Input::new(input))
142 .map_err(|err| (keyword.clone(), err))?)
143}
144
145fn parse_optional_value<'a, O, P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>>(
155 keyword: &Keyword,
156 value: &'a Value,
157 mut parser: P,
158) -> Result<Option<O>, BridgeError> {
159 let input = ensure_single_value(keyword, value)?;
160
161 if input.trim().is_empty() {
162 return Ok(None);
163 }
164
165 Ok(Some(
166 parser
167 .parse(Input::new(input))
168 .map_err(|err| (keyword.clone(), err))?,
169 ))
170}
171
172#[expect(clippy::result_large_err)]
184fn parse_value_array<'a, O, P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>>(
185 keyword: &Keyword,
186 value: &'a Value,
187 mut parser: P,
188) -> Result<Vec<O>, BridgeError> {
189 let input = value.as_vec();
190 Ok(input
191 .into_iter()
192 .map(|item| {
193 parser
194 .parse(Input::new(item))
195 .map_err(|err| (keyword.clone(), err))
196 })
197 .collect::<Result<Vec<O>, (Keyword, ParseError<Input<'a>, ParseStack<'a>>)>>()?)
198}
199
200#[expect(clippy::result_large_err, clippy::indexing_slicing)]
209fn parse_arch_array<'a>(keyword: &Keyword, value: &'a Value) -> Result<Architectures, BridgeError> {
210 let input = value.as_vec();
212
213 if input.len() == 1 && input[0] == "any" {
218 return Ok(Architectures::Any);
219 }
220
221 let architectures = input
222 .into_iter()
223 .map(|item| {
224 SystemArchitecture::parser
225 .expected_strings([
226 "any", "an array of one or more specific system architectures."
227 ])
228 .parse(Input::new(item))
229 .map_err(|err| (keyword.clone(), err))
230 })
231 .collect::<Result<Vec<SystemArchitecture>, (Keyword, ParseError<Input<'a>, ParseStack<'a>>)>>()?;
232
233 Ok(Architectures::Some(architectures))
234}
235
236#[cfg(test)]
237mod tests {
238 use testresult::TestResult;
239 use winnow::token::rest;
240
241 use super::*;
242
243 #[test]
246 pub fn test_empty_optional_value() -> TestResult {
247 let keyword = Keyword::simple("test");
248 let value = Value::Single("".to_string());
249
250 let value = parse_optional_value(&keyword, &value, rest)?;
251
252 assert!(value.is_none(), "Empty string values should return `None`.");
253
254 Ok(())
255 }
256}