fn strip_ansi(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' && chars.peek() == Some(&'[') {
chars.next(); while let Some(&ch) = chars.peek() {
chars.next();
if ch.is_ascii_alphabetic() {
break;
}
}
} else {
result.push(c);
}
}
result
}
fn skip_ansi_sequence(chars: &mut std::iter::Peekable<std::str::Chars>, current_line: &mut String) {
current_line.push('\x1b');
current_line.push(chars.next().unwrap()); while let Some(&ch) = chars.peek() {
current_line.push(chars.next().unwrap());
if ch.is_ascii_alphabetic() {
break;
}
}
}
fn flush_line_if_needed(output: &mut String, current_line: &mut String) {
let stripped = strip_ansi(current_line);
if !stripped.trim().is_empty() {
if let Some(pos) = current_line.trim_end().rfind(',') {
current_line.truncate(pos);
}
output.push_str(current_line);
output.push('\n');
}
}
fn get_indent(indent_stack: &[usize]) -> String {
" ".repeat(indent_stack.last().copied().unwrap_or(0))
}
pub fn pretty_braces(expr: &str) -> String {
let mut output = String::new();
let mut indent_stack: Vec<usize> = Vec::new();
let mut current_line = String::new();
let mut chars = expr.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' && chars.peek() == Some(&'[') {
skip_ansi_sequence(&mut chars, &mut current_line);
continue;
}
match c {
'{' => {
current_line.push('{');
output.push_str(¤t_line);
output.push('\n');
indent_stack.push(strip_ansi(¤t_line).len());
current_line = get_indent(&indent_stack);
}
'}' => {
flush_line_if_needed(&mut output, &mut current_line);
let indent = indent_stack.pop().unwrap_or(0).saturating_sub(1);
output.push_str(&" ".repeat(indent));
output.push('}');
output.push('\n');
current_line = get_indent(&indent_stack);
}
',' => {
current_line.push(',');
output.push_str(¤t_line);
output.push('\n');
current_line = get_indent(&indent_stack);
}
_ => {
current_line.push(c);
}
}
}
if !strip_ansi(¤t_line).trim().is_empty() {
output.push_str(¤t_line);
} else if output.ends_with('\n') {
output.pop();
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
#[test]
fn test_simple_braces() {
let input = "{a,b,c}";
assert_snapshot!(pretty_braces(input), @r"
{
a,
b,
c
}
");
}
#[test]
fn test_nested_braces() {
let input = "foo/{bar,baz/{qux,quux}}";
assert_snapshot!(pretty_braces(input), @r"
foo/{
bar,
baz/{
qux,
quux
}
}
");
}
#[test]
fn test_empty_braces() {
let input = "a/{}";
assert_snapshot!(pretty_braces(input), @r"
a/{
}
");
}
#[test]
fn test_multiple_levels() {
let input = "{a/{b,c},d/{e/{f,g},h}}";
assert_snapshot!(pretty_braces(input), @r"
{
a/{
b,
c
}
,
d/{
e/{
f,
g
}
,
h
}
}
");
}
#[test]
fn test_no_braces() {
let input = "simple/path/file.txt";
assert_snapshot!(pretty_braces(input), @"simple/path/file.txt");
}
}