Skip to main content

pfx_load/scene/
bind.rs

1use std::collections::BTreeMap;
2use std::sync::{LazyLock, PoisonError, RwLock};
3
4static BOUND: LazyLock<RwLock<BTreeMap<String, String>>> = LazyLock::new(RwLock::default);
5
6pub fn bind(key: &str, value: &str) {
7    BOUND
8        .write()
9        .unwrap_or_else(PoisonError::into_inner)
10        .insert(key.to_string(), value.to_string());
11}
12
13pub fn bound(key: &str) -> Option<String> {
14    BOUND
15        .read()
16        .unwrap_or_else(PoisonError::into_inner)
17        .get(key)
18        .cloned()
19}
20
21fn is_key(key: &str) -> bool {
22    !key.is_empty()
23        && key
24            .chars()
25            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
26}
27
28pub fn filled(text: &str) -> String {
29    let mut out = String::with_capacity(text.len());
30    let mut rest = text;
31    while let Some(open) = rest.find('{') {
32        out.push_str(&rest[..open]);
33        let after = &rest[open + 1..];
34        let value = after
35            .find('}')
36            .map(|close| &after[..close])
37            .filter(|key| is_key(key))
38            .and_then(|key| bound(key).map(|value| (key.len(), value)));
39        match value {
40            Some((len, value)) => {
41                out.push_str(&value);
42                rest = &after[len + 1..];
43            }
44            None => {
45                out.push('{');
46                rest = after;
47            }
48        }
49    }
50    out.push_str(rest);
51    out
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn a_bound_key_fills_its_token_and_anything_else_stays_as_written() {
60        bind("bind-test-version", "1.4.2");
61        bind("bind-test-name", "sample");
62        assert_eq!(filled("v{bind-test-version}"), "v1.4.2");
63        assert_eq!(
64            filled("{bind-test-name} {bind-test-version}!"),
65            "sample 1.4.2!"
66        );
67        assert_eq!(filled("{bind-test-unbound}"), "{bind-test-unbound}");
68        assert_eq!(filled("{ bind-test-name }"), "{ bind-test-name }");
69        assert_eq!(filled("{{bind-test-name}}"), "{sample}");
70        assert_eq!(filled("{bind-test-name"), "{bind-test-name");
71        assert_eq!(filled("}{}{"), "}{}{");
72        assert_eq!(filled("plain"), "plain");
73        bind("bind-test-name", "other");
74        assert_eq!(filled("{bind-test-name}"), "other");
75    }
76}