1use yog_sync::RwLock;
12
13const LINK: &str = "#2f5fbf";
15const KEY_OPEN: &str = "{key|";
17const CLOSE: char = '}';
18
19pub type KeyNamer = fn(&str) -> Option<String>;
22
23static KEY_NAMER: RwLock<Option<KeyNamer>> = RwLock::new(None);
24
25pub fn set_key_namer(f: KeyNamer) {
27 *KEY_NAMER.write() = Some(f);
28}
29
30fn keys(text: &str) -> String {
32 let mut out = String::with_capacity(text.len());
33 let mut rest = text;
34 while let Some(at) = rest.find(KEY_OPEN) {
35 let after = &rest[at + KEY_OPEN.len()..];
36 let Some(end) = after.find(CLOSE) else { break };
37 let binding = &after[..end];
38 let name = (*KEY_NAMER.read())
39 .and_then(|f| f(binding))
40 .unwrap_or_else(|| binding.to_owned());
41 out.push_str(&rest[..at]);
42 out.push_str(&name.replace('{', "{{").replace('}', "}}"));
43 rest = &after[end + 1..];
44 }
45 out.push_str(rest);
46 out
47}
48
49pub fn resolve(text: &str, accent: &str) -> String {
51 yog_ui::rich::map_attrs(&keys(text), |w| match w {
52 "accent" => Some(accent.to_string()),
53 w if w.starts_with("link=") => Some(format!("u {LINK}")),
54 _ => None,
55 })
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn resolves_the_books_words() {
64 let r = resolve(
65 "{accent|Media} is {b|{link=hexcasting:staff|staves}} {{x}}",
66 "#8d6acc",
67 );
68 assert_eq!(r, "{#8d6acc|Media} is {b|{u #2f5fbf|staves}} {{x}}");
69 let runs = yog_ui::rich::runs(&r);
70 assert_eq!(runs[0].1.color, Some(0x8d6acc));
71 assert!(
72 runs.iter()
73 .any(|(t, s)| t == "staves" && s.bold && s.underline)
74 );
75 }
76
77 #[test]
78 fn a_key_is_named_as_the_game_binds_it() {
79 assert_eq!(resolve("hold {key|sprint}.", "#000000"), "hold sprint.");
80 set_key_namer(|b| (b == "sprint").then(|| "Left Control".to_owned()));
81 assert_eq!(
82 resolve("hold {key|sprint}.", "#000000"),
83 "hold Left Control."
84 );
85 }
86}