1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Part<'a> {
9 Text(&'a str),
11 Token(&'a str),
13 Unclosed(&'a str),
15}
16
17pub fn split_tokens(text: &str) -> impl Iterator<Item = Part<'_>> {
19 let mut rest = text;
20 core::iter::from_fn(move || {
21 if rest.is_empty() {
22 return None;
23 }
24 let part = match rest.split_once('{') {
25 None => Part::Text(rest),
26 Some(("", after)) => match after.split_once('}') {
27 Some((token, after)) => {
28 rest = after;
29 return Some(Part::Token(token));
30 }
31 None => Part::Unclosed(rest),
32 },
33 Some((before, _)) => {
34 rest = &rest[before.len()..];
35 return Some(Part::Text(before));
36 }
37 };
38 rest = "";
39 Some(part)
40 })
41}
42
43#[cfg(test)]
44mod tests {
45 use alloc::vec::Vec;
46
47 use super::*;
48
49 #[test]
50 fn splits_tokens() {
51 let parts: Vec<_> = split_tokens("a{b}c{d").collect();
52 assert_eq!(
53 parts,
54 [
55 Part::Text("a"),
56 Part::Token("b"),
57 Part::Text("c"),
58 Part::Unclosed("{d")
59 ]
60 );
61 assert_eq!(split_tokens("").count(), 0);
62 assert_eq!(split_tokens("{}").collect::<Vec<_>>(), [Part::Token("")]);
63 }
64
65 #[test]
66 fn text_after_the_last_token_is_kept() {
67 let parts: Vec<_> = split_tokens("{a}{b} end").collect();
68 assert_eq!(
69 parts,
70 [Part::Token("a"), Part::Token("b"), Part::Text(" end")]
71 );
72 assert_eq!(
73 split_tokens("plain").collect::<Vec<_>>(),
74 [Part::Text("plain")]
75 );
76 assert_eq!(
77 split_tokens("{open").collect::<Vec<_>>(),
78 [Part::Unclosed("{open")]
79 );
80 }
81}