Skip to main content

shuyi_core/
text.rs

1//! `{token}` text: a game's text names what is filled in when it is shown
2//! between braces (`Hello, {name}.`). [`split_tokens`] cuts such text into
3//! its plain parts and its tokens; what a token stands for is the game's.
4
5/// A piece of text: plain text, a `{token}`, or a `{` with no `}` after it
6/// (with the rest of the text).
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Part<'a> {
9    /// Text outside braces.
10    Text(&'a str),
11    /// The inside of a `{...}`.
12    Token(&'a str),
13    /// A `{` with no closing `}`, and everything after it.
14    Unclosed(&'a str),
15}
16
17/// Splits `text` into plain text and `{tokens}`.
18pub 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}