use crate::error::GlobError;
const MAX_EXPANSIONS: usize = 1000;
const MAX_DEPTH: usize = 10;
pub fn expand(input: &str) -> Result<Vec<String>, GlobError> {
fn expand_inner(input: &str, depth: usize) -> Result<Vec<String>, GlobError> {
if depth > MAX_DEPTH {
return Err(GlobError::BraceExpansionDepth);
}
fn find_brace(s: &str) -> Option<(usize, usize)> {
let mut depth = 0usize;
let mut start = None;
for (i, ch) in s.char_indices() {
if ch == '{' {
if depth == 0 {
start = Some(i);
}
depth += 1;
} else if ch == '}' {
if depth == 0 {
return None; }
depth -= 1;
if depth == 0 {
return start.map(|st| (st, i));
}
}
}
None }
if let Some((st, en)) = find_brace(input) {
let before = &input[..st];
let inner = &input[st + 1..en];
let after = &input[en + 1..];
let new_depth = depth + 1;
if new_depth > MAX_DEPTH {
return Err(GlobError::BraceExpansionDepth);
}
let mut items = Vec::new();
let mut buf = String::new();
let mut inner_depth = 0usize;
for ch in inner.chars() {
if ch == ',' && inner_depth == 0 {
items.push(buf.clone());
buf.clear();
} else {
if ch == '{' {
inner_depth += 1;
} else if ch == '}' {
inner_depth = inner_depth.saturating_sub(1);
}
buf.push(ch);
}
}
if !buf.is_empty() {
items.push(buf);
}
let mut expanded_items = Vec::new();
for it in items {
if let Some((a, b)) = parse_range(&it) {
for v in a..=b {
expanded_items.push(v.to_string());
}
} else {
expanded_items.push(it);
}
}
let mut out = Vec::new();
for it in expanded_items {
for mid in expand_inner(&it, depth + 1)? {
for suf in expand_inner(after, depth + 1)? {
out.push(format!("{}{}{}", before, mid, suf));
if out.len() > MAX_EXPANSIONS {
return Err(GlobError::BraceExpansionCount);
}
}
}
}
Ok(out)
} else {
Ok(vec![input.to_string()])
}
}
expand_inner(input, 0)
}
fn parse_range(s: &str) -> Option<(i64, i64)> {
let parts: Vec<&str> = s.split("..").collect();
if parts.len() == 2 {
if let (Ok(a), Ok(b)) = (parts[0].parse::<i64>(), parts[1].parse::<i64>()) {
return Some((a, b));
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_brace_expansion() {
assert_eq!(
expand("file.{txt,md}").unwrap(),
vec!["file.txt", "file.md"]
);
assert_eq!(
expand("test{1..3}").unwrap(),
vec!["test1", "test2", "test3"]
);
assert_eq!(expand("a{b,c}d").unwrap(), vec!["abd", "acd"]);
}
#[test]
fn test_brace_expansion_depth() {
let result = expand("{a,b{1,2}}");
assert!(result.is_ok());
}
#[test]
fn test_brace_expansion_too_deep() {
let deep = "{{{{{{{{{{{a,b}}}}}}}}}}}";
let result = expand(deep);
assert!(matches!(result, Err(GlobError::BraceExpansionDepth)));
}
}