use crate::tools::permissions::PermissionManager;
pub(super) const COMPOUND_KEYWORDS: &[&str] = &[
"do", "done", "then", "else", "elif", "fi", "case", "esac", "{", "}", "(",
];
pub(super) const COMPOUND_HEADERS_WITH_BODY: &[&str] = &["while", "until", "if"];
pub(super) const COMPOUND_HEADERS_NO_BODY: &[&str] = &["for"];
pub(super) fn is_env_assignment(tok: &str) -> bool {
let Some((key, _)) = tok.split_once('=') else {
return false;
};
let mut chars = key.chars();
let Some(first) = chars.next() else {
return false;
};
(first.is_ascii_alphabetic() || first == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
impl PermissionManager {
pub(super) fn extract_base_command(command: &str) -> &str {
let command = command.trim();
let without_prefix = if let Some(stripped) = command.strip_prefix("Bash(") {
if let Some(end) = stripped.find(')') {
&stripped[..end]
} else {
stripped
}
} else {
command
};
without_prefix
.split_whitespace()
.find(|tok| !is_env_assignment(tok))
.or_else(|| without_prefix.split_whitespace().next())
.unwrap_or(without_prefix)
}
pub(super) fn split_compound_command(command: &str) -> Vec<String> {
let mut segments: Vec<String> = Vec::new();
let mut current = String::new();
let mut chars = command.chars().peekable();
let mut quote: Option<char> = None;
while let Some(c) = chars.next() {
if let Some(q) = quote {
current.push(c);
if c == q {
quote = None;
}
continue;
}
match c {
'\'' | '"' => {
quote = Some(c);
current.push(c);
}
';' | '\n' => Self::push_segment(&mut segments, &mut current),
'|' => {
if chars.peek() == Some(&'|') {
chars.next();
}
Self::push_segment(&mut segments, &mut current);
}
'&' if chars.peek() == Some(&'&') => {
chars.next();
Self::push_segment(&mut segments, &mut current);
}
_ => current.push(c),
}
}
Self::push_segment(&mut segments, &mut current);
segments
}
fn push_segment(segments: &mut Vec<String>, current: &mut String) {
let trimmed = current.trim();
if !trimmed.is_empty() {
segments.push(trimmed.to_string());
}
current.clear();
}
pub(super) fn extract_segment_base_with_args(segment: &str) -> Option<(&str, Vec<&str>)> {
let mut tokens = segment.split_whitespace().peekable();
while let Some(&tok) = tokens.peek() {
if COMPOUND_HEADERS_NO_BODY.contains(&tok) {
return None;
}
if is_env_assignment(tok)
|| COMPOUND_KEYWORDS.contains(&tok)
|| COMPOUND_HEADERS_WITH_BODY.contains(&tok)
{
tokens.next();
continue;
}
break;
}
let base = tokens.next()?;
if base.starts_with('#') {
return None;
}
let args: Vec<&str> = tokens.collect();
Some((base, args))
}
pub(super) fn enumerate_compound_bases(command: &str) -> Vec<String> {
Self::split_compound_command(command)
.iter()
.filter_map(|seg| {
Self::extract_segment_base_with_args(seg).map(|(base, _)| base.to_string())
})
.collect()
}
}