use regex::Regex;
use std::collections::HashMap;
#[cfg(feature = "python")]
mod python;
pub const SRCINFO_STRINGS: [&str; 10] = [
"pkgbase", "pkgdesc", "pkgver", "pkgrel", "epoch", "url", "preinst", "postinst", "prerm",
"postrm",
];
pub const SRCINFO_ARRAYS: [&str; 19] = [
"pkgname",
"arch",
"license",
"depends",
"makedepends",
"checkdepends",
"optdepends",
"conflicts",
"provides",
"replaces",
"source",
"control_fields",
"md5sums",
"sha1sums",
"sha224sums",
"sha256sums",
"sha384sums",
"sha512sums",
"b2sums",
];
pub const SRCINFO_EXTENDED: [&str; 20] = [
"preinst",
"postinst",
"prerm",
"postrm",
"depends",
"makedepends",
"checkdepends",
"optdepends",
"conflicts",
"provides",
"replaces",
"source",
"control_fields",
"md5sums",
"sha1sums",
"sha224sums",
"sha256sums",
"sha384sums",
"sha512sums",
"b2sums",
];
pub const SRCINFO_REQUIRED: [&str; 5] = ["pkgbase", "pkgname", "pkgver", "pkgrel", "arch"];
#[derive(Debug)]
pub struct ParserError {
pub msg: String,
pub line_num: Option<usize>,
}
type ParseMap = HashMap<String, Vec<String>>;
#[derive(Debug)]
pub struct SrcInfo {
map: ParseMap,
}
impl SrcInfo {
pub fn new(content: &str) -> Result<Self, ParserError> {
let mut map: ParseMap = HashMap::new();
for (_index, _line) in content.lines().enumerate() {
let mut line = _line.to_owned();
let index = _index + 1;
if line.starts_with('#') {
continue;
}
if line.is_empty() {
continue;
}
line = line.replace('\t', "");
let _parts = line.split(" = ");
if _parts.clone().count() < 2 {
return Err(ParserError {
msg: "No ' = ' delimiter found.".to_string(),
line_num: Some(index),
});
}
let parts: Vec<&str> = _parts.collect();
let key = parts[0].to_string();
let value = parts[1..].join(" = ");
if let Some(values) = map.get_mut(&key) {
values.push(value);
} else {
map.insert(key, vec![value]);
}
}
for item in SRCINFO_REQUIRED {
if !map.contains_key(&item.to_owned()) {
return Err(ParserError {
msg: format!("Required key '{}' not found.", item),
line_num: None,
});
}
}
for item in SRCINFO_STRINGS {
if let Some(values) = map.get(&item.to_owned()) {
if values.len() > 1 {
return Err(ParserError {
msg: format!(
"Key '{}' is present more than once when it is not allowed to.",
item
),
line_num: None,
});
}
}
}
Ok(Self { map })
}
fn get_base_key(item: &str) -> &str {
let mut keys = SRCINFO_STRINGS.to_vec();
keys.append(&mut SRCINFO_ARRAYS.to_vec());
if keys.contains(&item) {
return item;
}
for key in keys {
let re_key = format!("^{0}_|_{0}_|_{0}$", key);
let re = Regex::new(&re_key).unwrap();
if re.is_match(item) {
return key;
}
}
""
}
pub fn get_string(&self, key: &str) -> Option<&String> {
if !SRCINFO_STRINGS.contains(&SrcInfo::get_base_key(key)) {
return None;
}
if let Some(values) = self.map.get(&key.to_owned()) {
Some(&values[0])
} else {
None
}
}
pub fn get_array(&self, key: &str) -> Option<&Vec<String>> {
if !SRCINFO_ARRAYS.contains(&SrcInfo::get_base_key(key)) {
return None;
}
self.map.get(&key.to_owned())
}
pub fn get_extended_values(&self, key: &str) -> Option<Vec<String>> {
if !SRCINFO_EXTENDED.contains(&key) {
return None;
}
let mut matches: Vec<String> = Vec::new();
let re = Regex::new(&format!(".*_{0}$|.*_{0}_.*|^{0}.*|^{0}$", key)).unwrap();
for item in self.map.keys() {
if re.is_match(item) {
matches.push(item.clone());
}
}
if matches.is_empty() {
None
} else {
Some(matches)
}
}
}
pub struct SplitPackage {
pub pkgname: String,
pub operator: Option<String>,
pub version: Option<String>,
}
impl SplitPackage {
pub fn new(pkg_string: &str) -> Self {
let pkg = pkg_string.to_owned();
for operator in ["<=", ">=", "=", "<", ">"] {
if pkg.contains(operator) {
let (pkgname, version) = pkg.split_once(operator).unwrap();
return Self {
pkgname: pkgname.to_owned(),
operator: Some(operator.to_owned()),
version: Some(version.to_owned()),
};
}
}
Self {
pkgname: pkg_string.to_owned(),
operator: None,
version: None,
}
}
}
pub struct SplitDependency {
pub(crate) deps: Vec<SplitPackage>,
}
impl SplitDependency {
pub fn new(dep_string: &str) -> Self {
let mut deps = vec![];
for dep in dep_string.split('|') {
deps.push(SplitPackage::new(dep));
}
Self { deps }
}
pub(crate) fn internal_as_control(deps: &Vec<SplitPackage>) -> String {
let mut segments = vec![];
for dep in deps {
if dep.operator.is_some() && dep.version.is_some() {
let mut operator = dep.operator.as_ref().unwrap().clone();
let version = dep.version.as_ref().unwrap();
if ["<", ">"].contains(&operator.as_str()) {
operator = operator.clone() + &operator;
}
segments.push(format!("{} ({} {})", dep.pkgname, operator, version));
} else {
segments.push(dep.pkgname.clone());
}
}
segments.join(" | ")
}
pub fn as_control(&self) -> String {
Self::internal_as_control(&self.deps)
}
}