Skip to main content

ferroui_base/media/
string_splitter.rs

1//! Splitting of strings on separators that are not nested inside brackets.
2
3use crate::utilities::FormatError;
4
5/// Options for [`split_respecting_brackets`].
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub struct StringSplitOptions {
8    pub remove_empty_entries: bool,
9    pub trim_entries: bool,
10}
11
12impl StringSplitOptions {
13    pub const NONE: Self = Self { remove_empty_entries: false, trim_entries: false };
14    pub const REMOVE_EMPTY_ENTRIES: Self = Self { remove_empty_entries: true, trim_entries: false };
15    pub const TRIM_ENTRIES: Self = Self { remove_empty_entries: false, trim_entries: true };
16}
17
18/// Splits `s` on any of `separators`, ignoring separators nested inside
19/// brackets.
20///
21/// Panics if `opening_bracket` equals `closing_bracket`.
22pub fn split_respecting_brackets<'a>(
23    s: &'a str,
24    separators: &[char],
25    opening_bracket: char,
26    closing_bracket: char,
27    options: StringSplitOptions,
28) -> Result<Vec<&'a str>, FormatError> {
29    assert!(
30        opening_bracket != closing_bracket,
31        "Opening bracket and closing bracket cannot be the same character '{opening_bracket}'."
32    );
33
34    let mut result = Vec::new();
35    let mut depth = 0usize;
36    let mut seg_start = 0usize;
37
38    let mut process_segment = |start: usize, end: usize| {
39        let mut segment = &s[start..end];
40        if options.trim_entries {
41            segment = segment.trim();
42        }
43        if !segment.is_empty() || !options.remove_empty_entries {
44            result.push(segment);
45        }
46    };
47
48    for (i, ch) in s.char_indices() {
49        if ch == opening_bracket {
50            depth += 1;
51        } else if ch == closing_bracket {
52            if depth == 0 {
53                return Err(FormatError::from_string(format!(
54                    "Unmatched closing bracket '{closing_bracket}' at position {i}."
55                )));
56            }
57            depth -= 1;
58        } else if separators.contains(&ch) {
59            if depth != 0 {
60                continue;
61            }
62            process_segment(seg_start, i);
63            seg_start = i + ch.len_utf8();
64        }
65    }
66
67    if depth != 0 {
68        return Err(FormatError::from_string(format!(
69            "Unmatched opening bracket '{opening_bracket}' in input string."
70        )));
71    }
72
73    // last segment
74    process_segment(seg_start, s.len());
75    Ok(result)
76}