const MAX_ALTERNATIVES: usize = 64;
pub(super) fn alternatives(pattern: &str) -> Vec<String> {
let mut out = Vec::new();
if expand(pattern, &mut out) {
out
} else {
vec![pattern.to_owned()]
}
}
fn expand(pattern: &str, out: &mut Vec<String>) -> bool {
let Some((open, close, options)) = first_group(pattern) else {
out.push(pattern.to_owned());
return true;
};
let (head, tail) = (&pattern[..open], &pattern[close + 1..]);
options.iter().all(|option| {
expand(&format!("{head}{option}{tail}"), out) && out.len() <= MAX_ALTERNATIVES
})
}
fn first_group(pattern: &str) -> Option<(usize, usize, Vec<&str>)> {
let (mut depth, mut open, mut start) = (0usize, None, 0);
let mut options = Vec::new();
let mut chars = pattern.char_indices();
while let Some((i, c)) = chars.next() {
match c {
'\\' => {
chars.next();
}
'{' => {
if depth == 0 {
open = Some(i);
start = i + 1;
}
depth += 1;
}
',' if depth == 1 => {
options.push(&pattern[start..i]);
start = i + 1;
}
'}' if depth > 0 => {
depth -= 1;
if depth == 0 {
options.push(&pattern[start..i]);
return Some((open?, i, options));
}
}
_ => {}
}
}
None
}