use itertools::Itertools;
use shrub_parser::word;
pub(crate) fn generate_and_combine_brace_expansions(
pieces: Vec<shrub_parser::word::BraceExpressionOrText>,
) -> impl IntoIterator<Item = String> {
let expansions: Vec<Vec<String>> = pieces
.into_iter()
.map(|piece| expand_brace_expr_or_text(piece).collect())
.collect();
expansions
.into_iter()
.multi_cartesian_product()
.map(|v| v.join(""))
}
fn expand_brace_expr_or_text(
beot: word::BraceExpressionOrText,
) -> Box<dyn Iterator<Item = String>> {
match beot {
word::BraceExpressionOrText::Expr(members) => {
Box::new(members.into_iter().flat_map(expand_brace_expr_member))
}
word::BraceExpressionOrText::Text(text) => Box::new(std::iter::once(text)),
}
}
#[expect(clippy::cast_possible_truncation)]
fn expand_brace_expr_member(bem: word::BraceExpressionMember) -> Box<dyn Iterator<Item = String>> {
match bem {
word::BraceExpressionMember::NumberSequence {
start,
end,
increment,
zero_padded_width,
} => {
let mut increment = increment.unsigned_abs() as usize;
if increment == 0 {
increment = 1;
}
let format = move |n: i64| match zero_padded_width {
Some(width) => std::format!("{n:0width$}"),
None => n.to_string(),
};
if start <= end {
Box::new((start..=end).step_by(increment).map(format))
} else {
let increment = increment as u64;
Box::new(
std::iter::successors(Some(start), move |&n| {
let next = n.checked_sub_unsigned(increment)?;
(next >= end).then_some(next)
})
.map(format),
)
}
}
word::BraceExpressionMember::CharSequence {
start,
end,
increment,
} => {
let mut increment = increment.unsigned_abs() as usize;
if increment == 0 {
increment = 1;
}
if start <= end {
Box::new((start..=end).step_by(increment).map(|c| c.to_string()))
} else {
let increment = u32::try_from(increment).unwrap_or(u32::MAX);
Box::new(
std::iter::successors(Some(start), move |&c| {
let next = char::from_u32((c as u32).checked_sub(increment)?)?;
(next >= end).then_some(next)
})
.map(|c| c.to_string()),
)
}
}
word::BraceExpressionMember::Child(elements) => {
Box::new(generate_and_combine_brace_expansions(elements).into_iter())
}
}
}