use crate::dependency::Dependency;
use crate::internal::key_value_vec_map;
use crate::internal::macros::bail;
use crate::internal::serde_key_value;
use serde::{self, Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PkgInfoError {
#[error(transparent)]
Decode(#[from] serde_key_value::Error),
#[error("syntax error on line {0}: missing ' = ' in '{1}'")]
Syntax(usize, String),
}
#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct PkgInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub maintainer: Option<String>,
pub pkgname: String,
pub pkgver: String,
pub pkgdesc: String,
pub url: String,
pub arch: String,
pub license: String,
#[serde(default, alias = "depend", with = "key_value_vec_map")]
pub depends: Vec<Dependency>,
#[serde(default, with = "key_value_vec_map")]
pub conflicts: Vec<Dependency>,
#[serde(default, with = "key_value_vec_map")]
pub install_if: Vec<Dependency>,
#[serde(default, with = "key_value_vec_map")]
pub provides: Vec<Dependency>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_priority: Option<u16>,
#[serde(default, with = "key_value_vec_map")]
pub replaces: Vec<Dependency>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replaces_priority: Option<u16>,
#[serde(default)]
pub triggers: Vec<String>,
pub origin: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub commit: Option<String>,
pub builddate: i64,
pub packager: String,
pub size: usize,
pub datahash: String,
}
impl PkgInfo {
pub fn parse(s: &str) -> Result<Self, PkgInfoError> {
parse_key_value(s)
.try_fold(Vec::with_capacity(64), |mut acc, kv| {
match kv {
Ok((key @ ("install_if" | "triggers"), val)) => {
for word in val.split_ascii_whitespace() {
acc.push((key, word));
}
}
Ok(("depend", val)) => {
acc.push(if let Some(val) = val.strip_prefix('!') {
("conflicts", val)
} else {
("depends", val)
});
}
Ok(kv) => acc.push(kv),
Err(e) => bail!(e),
};
Ok(acc)
})
.and_then(|pairs| serde_key_value::from_pairs(pairs).map_err(PkgInfoError::from))
}
}
fn parse_key_value(s: &str) -> impl Iterator<Item = Result<(&str, &str), PkgInfoError>> {
s.lines().enumerate().filter_map(|(lno, line)| {
if line.is_empty() || line.starts_with('#') {
None
} else if let Some(item) = line.split_once(" = ") {
Some(Ok(item))
} else {
Some(Err(PkgInfoError::Syntax(lno + 1, line.to_string())))
}
})
}
#[cfg(test)]
#[path = "pkginfo.test.rs"]
mod test;