#[allow(clippy::case_sensitive_file_extension_comparisons)]
pub(super) fn parse_srcinfo_deps(
srcinfo: &str,
) -> (Vec<String>, Vec<String>, Vec<String>, Vec<String>) {
let mut depends = Vec::new();
let mut makedepends = Vec::new();
let mut checkdepends = Vec::new();
let mut optdepends = Vec::new();
for line in srcinfo.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim();
let value_lower = value.to_lowercase();
if value_lower.ends_with(".so")
|| value_lower.contains(".so.")
|| value_lower.contains(".so=")
{
continue;
}
match key {
"depends" => depends.push(value.to_string()),
"makedepends" => makedepends.push(value.to_string()),
"checkdepends" => checkdepends.push(value.to_string()),
"optdepends" => optdepends.push(value.to_string()),
_ => {}
}
}
}
(depends, makedepends, checkdepends, optdepends)
}
pub fn parse_pkgbuild_deps(pkgbuild: &str) -> (Vec<String>, Vec<String>, Vec<String>, Vec<String>) {
tracing::debug!(
"parse_pkgbuild_deps: Starting parse, PKGBUILD length={}, first 500 chars: {:?}",
pkgbuild.len(),
pkgbuild.chars().take(500).collect::<String>()
);
let mut depends = Vec::new();
let mut makedepends = Vec::new();
let mut checkdepends = Vec::new();
let mut optdepends = Vec::new();
let lines: Vec<&str> = pkgbuild.lines().collect();
tracing::debug!(
"parse_pkgbuild_deps: Total lines in PKGBUILD: {}",
lines.len()
);
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
i += 1;
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim();
let base_key = key.strip_suffix('+').map_or(key, |stripped| stripped);
if !matches!(
base_key,
"depends" | "makedepends" | "checkdepends" | "optdepends"
) {
continue;
}
tracing::debug!(
"parse_pkgbuild_deps: Found key-value pair: key='{}', base_key='{}', value='{}'",
key,
base_key,
value.chars().take(100).collect::<String>()
);
if value.starts_with('(') {
tracing::debug!(
"parse_pkgbuild_deps: Detected array declaration for key='{}'",
key
);
let deps = find_matching_closing_paren(value).map_or_else(
|| {
tracing::debug!("Parsing multi-line {} array", key);
let mut array_lines = Vec::new();
while i < lines.len() {
let next_line = lines[i].trim();
i += 1;
if next_line.is_empty() || next_line.starts_with('#') {
continue;
}
if next_line == ")" {
break;
}
if let Some(paren_pos) = next_line.find(')') {
let content_before_paren = &next_line[..paren_pos].trim();
if !content_before_paren.is_empty() {
array_lines.push((*content_before_paren).to_string());
}
break;
}
array_lines.push(next_line.to_string());
}
let array_content = array_lines
.iter()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ");
tracing::debug!(
"Collected {} lines for multi-line {} array: {}",
array_lines.len(),
key,
array_content
);
let parsed = parse_array_content(&array_content);
tracing::debug!("Parsed array content: {:?}", parsed);
parsed
},
|closing_paren_pos| {
let array_content = &value[1..closing_paren_pos];
tracing::debug!("Parsing single-line {} array: {}", key, array_content);
let parsed = parse_array_content(array_content);
tracing::debug!("Parsed array content: {:?}", parsed);
parsed
},
);
let filtered_deps: Vec<String> = deps
.into_iter()
.filter_map(|dep| {
let dep_trimmed = dep.trim();
if dep_trimmed.is_empty() {
return None;
}
let dep_lower = dep_trimmed.to_lowercase();
if std::path::Path::new(&dep_lower)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("so"))
|| dep_lower.contains(".so.")
|| dep_lower.contains(".so=")
{
return None;
}
if dep_trimmed.ends_with(')') {
if dep_trimmed.contains(">=")
|| dep_trimmed.contains("<=")
|| dep_trimmed.contains("==")
{
return None;
}
return None;
}
let first_char = dep_trimmed.chars().next().unwrap_or(' ');
if !first_char.is_alphanumeric() && first_char != '_' {
return None;
}
if dep_trimmed.len() < 2 {
return None;
}
let has_valid_chars = dep_trimmed
.chars()
.any(|c| c.is_alphanumeric() || c == '-' || c == '_');
if !has_valid_chars {
return None;
}
Some(dep_trimmed.to_string())
})
.collect();
match base_key {
"depends" => depends.extend(filtered_deps),
"makedepends" => makedepends.extend(filtered_deps),
"checkdepends" => checkdepends.extend(filtered_deps),
"optdepends" => optdepends.extend(filtered_deps),
_ => {}
}
}
}
}
(depends, makedepends, checkdepends, optdepends)
}
fn find_matching_closing_paren(s: &str) -> Option<usize> {
let mut depth = 0;
let mut in_quotes = false;
let mut quote_char = '\0';
for (pos, ch) in s.char_indices() {
match ch {
'\'' | '"' => {
if !in_quotes {
in_quotes = true;
quote_char = ch;
} else if ch == quote_char {
in_quotes = false;
quote_char = '\0';
}
}
'(' if !in_quotes => {
depth += 1;
}
')' if !in_quotes => {
depth -= 1;
if depth == 0 {
return Some(pos);
}
}
_ => {}
}
}
None
}
fn parse_array_content(content: &str) -> Vec<String> {
let mut deps = Vec::new();
let mut in_quotes = false;
let mut quote_char = '\0';
let mut current = String::new();
for ch in content.chars() {
match ch {
'\'' | '"' => {
if !in_quotes {
in_quotes = true;
quote_char = ch;
} else if ch == quote_char {
if !current.is_empty() {
deps.push(current.clone());
current.clear();
}
in_quotes = false;
quote_char = '\0';
} else {
current.push(ch);
}
}
_ if in_quotes => {
current.push(ch);
}
ch if ch.is_whitespace() => {
if !current.is_empty() {
deps.push(current.clone());
current.clear();
}
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
deps.push(current);
}
deps
}
pub fn parse_pkgbuild_conflicts(pkgbuild: &str) -> Vec<String> {
tracing::debug!(
"parse_pkgbuild_conflicts: Starting parse, PKGBUILD length={}",
pkgbuild.len()
);
let mut conflicts = Vec::new();
let lines: Vec<&str> = pkgbuild.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
i += 1;
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim();
let base_key = key.strip_suffix('+').map_or(key, |stripped| stripped);
if base_key != "conflicts" {
continue;
}
tracing::debug!(
"parse_pkgbuild_conflicts: Found key-value pair: key='{}', base_key='{}', value='{}'",
key,
base_key,
value.chars().take(100).collect::<String>()
);
if value.starts_with('(') {
tracing::debug!(
"parse_pkgbuild_conflicts: Detected array declaration for key='{}'",
key
);
let conflict_deps = find_matching_closing_paren(value).map_or_else(
|| {
tracing::debug!("Parsing multi-line {} array", key);
let mut array_lines = Vec::new();
while i < lines.len() {
let next_line = lines[i].trim();
i += 1;
if next_line.is_empty() || next_line.starts_with('#') {
continue;
}
if next_line == ")" {
break;
}
if let Some(paren_pos) = next_line.find(')') {
let content_before_paren = &next_line[..paren_pos].trim();
if !content_before_paren.is_empty() {
array_lines.push((*content_before_paren).to_string());
}
break;
}
array_lines.push(next_line.to_string());
}
let array_content = array_lines
.iter()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ");
tracing::debug!(
"Collected {} lines for multi-line {} array: {}",
array_lines.len(),
key,
array_content
);
let parsed = parse_array_content(&array_content);
tracing::debug!("Parsed array content: {:?}", parsed);
parsed
},
|closing_paren_pos| {
let array_content = &value[1..closing_paren_pos];
tracing::debug!("Parsing single-line {} array: {}", key, array_content);
let parsed = parse_array_content(array_content);
tracing::debug!("Parsed array content: {:?}", parsed);
parsed
},
);
let filtered_conflicts: Vec<String> = conflict_deps
.into_iter()
.filter_map(|conflict| {
let conflict_trimmed = conflict.trim();
if conflict_trimmed.is_empty() {
return None;
}
let conflict_lower = conflict_trimmed.to_lowercase();
if std::path::Path::new(&conflict_lower)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("so"))
|| conflict_lower.contains(".so.")
|| conflict_lower.contains(".so=")
{
return None;
}
if conflict_trimmed.ends_with(')') {
return None;
}
let first_char = conflict_trimmed.chars().next().unwrap_or(' ');
if !first_char.is_alphanumeric() && first_char != '_' {
return None;
}
if conflict_trimmed.len() < 2 {
return None;
}
let has_valid_chars = conflict_trimmed
.chars()
.any(|c| c.is_alphanumeric() || c == '-' || c == '_');
if !has_valid_chars {
return None;
}
let pkg_name = conflict_trimmed.find(['>', '<', '=']).map_or_else(
|| conflict_trimmed.to_string(),
|pos| conflict_trimmed[..pos].trim().to_string(),
);
if pkg_name.is_empty() {
None
} else {
Some(pkg_name)
}
})
.collect();
conflicts.extend(filtered_conflicts);
}
}
}
conflicts
}