use crate::parser::ast::Arg;
pub struct ChainParts {
pub name: String,
pub primary: Option<String>,
pub args: Vec<Arg>,
}
#[allow(clippy::result_unit_err)]
pub fn parse_builder_chain(bare: &str) -> Result<ChainParts, ()> {
let s = bare.trim_start_matches('/');
let first_open = s.find('(');
let first_dot = find_dot_at_depth_zero(s);
if let Some(paren_pos) = first_open {
if first_dot.is_none() || paren_pos < first_dot.unwrap() {
let cmd_name = &s[..paren_pos];
let rest = &s[paren_pos..];
let (primary_val, remainder) = extract_balanced_parens(rest)?;
let primary = if primary_val.is_empty() {
None
} else {
Some(primary_val.to_string())
};
let args = if let Some(after_dot) = remainder.strip_prefix('.') {
parse_chain(after_dot)?
} else if remainder.is_empty() {
vec![]
} else {
return Err(());
};
return Ok(ChainParts {
name: cmd_name.to_string(),
primary,
args,
});
}
}
match s.split_once('.') {
Some((cmd_raw, chain)) => Ok(ChainParts {
name: cmd_raw.to_string(),
primary: None,
args: parse_chain(chain)?,
}),
None => Ok(ChainParts {
name: s.to_string(),
primary: None,
args: vec![],
}),
}
}
fn find_dot_at_depth_zero(s: &str) -> Option<usize> {
let mut depth: usize = 0;
for (i, ch) in s.char_indices() {
match ch {
'(' => depth += 1,
')' => {
if depth == 0 {
return None;
}
depth -= 1;
}
'.' if depth == 0 => return Some(i),
_ => {}
}
}
None
}
fn extract_balanced_parens(s: &str) -> Result<(&str, &str), ()> {
debug_assert!(s.starts_with('('));
let mut depth: usize = 0;
for (i, ch) in s.char_indices() {
match ch {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
let inner = &s[1..i];
let remainder = &s[i + 1..];
return Ok((inner, remainder));
}
}
_ => {}
}
}
Err(())
}
fn parse_chain(chain: &str) -> Result<Vec<Arg>, ()> {
split_segments(chain)?
.into_iter()
.filter(|s| !s.is_empty())
.map(parse_arg)
.collect()
}
fn split_segments(chain: &str) -> Result<Vec<&str>, ()> {
let mut segments = Vec::new();
let mut depth: usize = 0;
let mut start = 0;
for (i, ch) in chain.char_indices() {
match ch {
'(' => depth += 1,
')' => {
if depth == 0 {
return Err(());
}
depth -= 1;
}
'.' if depth == 0 => {
segments.push(&chain[start..i]);
start = i + 1;
}
_ => {}
}
}
if depth != 0 {
return Err(());
}
segments.push(&chain[start..]);
Ok(segments)
}
fn parse_arg(segment: &str) -> Result<Arg, ()> {
if let Some((name, rest)) = segment.split_once('(') {
let value = rest.strip_suffix(')').ok_or(())?;
Ok(Arg {
name: name.to_string(),
value: if value.is_empty() {
None
} else {
Some(value.to_string())
},
})
} else {
Ok(Arg {
name: segment.to_string(),
value: None,
})
}
}