#![doc(html_root_url = "https://docs.rs/brace-expand/0.1.0")]
#[cfg(test)]
mod tests;
use std::str::Chars;
#[derive(Debug, Clone, Copy, PartialEq)]
enum Token {
OpenBrace,
CloseBrace,
Comma,
Char(char),
}
struct TokenIter<'a> {
stream: Chars<'a>,
}
impl<'a> TokenIter<'a> {
fn new(buffer: &'a str) -> Self {
Self {
stream: buffer.chars(),
}
}
}
impl<'a> Iterator for TokenIter<'a> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
self.stream.next().and_then(|ch| match ch {
'\\' => self.stream.next().map(Token::Char),
'{' => Some(Token::OpenBrace),
'}' => Some(Token::CloseBrace),
',' => Some(Token::Comma),
_ => Some(Token::Char(ch)),
})
}
}
fn convert_to_string(tokens: &[Token]) -> String {
tokens
.iter()
.filter_map(|token| match token {
Token::Char(ch) => Some(ch),
_ => None,
})
.collect()
}
enum Expansion {
Partial(Vec<Vec<Token>>),
Complete(String),
}
fn expand_one_level(to_expand: Vec<Token>) -> Expansion {
let mut level = 0;
let mut list_start_pos = 0;
let mut list_end_pos = 0;
let mut term_start_pos = 0;
let mut terms = Vec::new();
for (pos, token) in to_expand.iter().enumerate() {
match token {
Token::OpenBrace => {
level += 1;
if level == 1 {
list_start_pos = pos;
term_start_pos = pos + 1;
}
}
Token::CloseBrace => {
level -= 1;
if level == 0 {
list_end_pos = pos + 1;
terms.push(&to_expand[term_start_pos..pos]);
break; }
}
Token::Comma => {
if level == 1 {
terms.push(&to_expand[term_start_pos..pos]);
term_start_pos = pos + 1;
}
}
_ => (),
}
}
if !terms.is_empty() {
let prefix = &to_expand[..list_start_pos];
let suffix = &to_expand[list_end_pos..];
let results: Vec<Vec<Token>> = terms
.iter()
.map(|term| [prefix, term, suffix].concat())
.collect();
Expansion::Partial(results)
} else {
Expansion::Complete(convert_to_string(&to_expand))
}
}
pub fn brace_expand(input: &str) -> Vec<String> {
let mut work_queue: Vec<Vec<_>> = vec![TokenIter::new(input).collect()];
let mut results = Vec::new();
while let Some(to_expand) = work_queue.pop() {
match expand_one_level(to_expand) {
Expansion::Partial(mut new_work) => work_queue.append(&mut new_work),
Expansion::Complete(fully_expanded) => results.push(fully_expanded),
}
}
results.reverse();
results
}