Krolyxon
Tag v0.1.1

Branches

Tags

Refs →

remagesrcuitemplate.rs

90 lines · 2.9 KB · Rust

1//! `{placeholder}` templates for the status bar and window title.
2//!
3//! A template is split into segments at runs of two or more spaces. A segment whose
4//! placeholders are all known but one of them is empty drops out with its separator, so
5//! `{format} {width}×{height} {index}/{count}` shows just `012/240` in the grid. Unknown
6//! names stay as written.
7
8/// Expand `tpl`, looking placeholders up with `var` (`None` = not a placeholder).
9pub fn expand(tpl: &str, var: &dyn Fn(&str) -> Option<String>) -> String {
10 let mut out: Vec<String> = Vec::new();
11 for seg in segments(tpl) {
12 if let Some(s) = expand_segment(seg, var) {
13 out.push(s);
14 }
15 }
16 out.join(" ")
17}
18
19/// Split at runs of two or more spaces.
20fn segments(tpl: &str) -> Vec<&str> {
21 let mut out = Vec::new();
22 let mut rest = tpl.trim();
23 while let Some(i) = rest.find(" ") {
24 out.push(&rest[..i]);
25 rest = rest[i..].trim_start();
26 }
27 if !rest.is_empty() {
28 out.push(rest);
29 }
30 out
31}
32
33/// One segment, or `None` if a placeholder in it expanded to nothing.
34fn expand_segment(seg: &str, var: &dyn Fn(&str) -> Option<String>) -> Option<String> {
35 let mut out = String::with_capacity(seg.len());
36 let mut rest = seg;
37 while let Some(open) = rest.find('{') {
38 out.push_str(&rest[..open]);
39 let after = &rest[open + 1..];
40 match after.find('}') {
41 Some(close) => {
42 let name = &after[..close];
43 match var(name) {
44 Some(v) if v.is_empty() => return None,
45 Some(v) => out.push_str(&v),
46 None => {
47 out.push('{');
48 out.push_str(name);
49 out.push('}');
50 }
51 }
52 rest = &after[close + 1..];
53 }
54 None => {
55 out.push_str(&rest[open..]);
56 rest = "";
57 }
58 }
59 }
60 out.push_str(rest);
61 (!out.is_empty()).then_some(out)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 fn vars(name: &str) -> Option<String> {
69 match name {
70 "name" => Some("a.png".into()),
71 "format" | "width" | "height" => Some(String::new()),
72 "index" => Some("012".into()),
73 "count" => Some("240".into()),
74 _ => None,
75 }
76 }
77
78 #[test]
79 fn empty_segments_drop_out() {
80 assert_eq!(
81 expand("{format} {width}×{height} {index}/{count}", &vars),
82 "012/240"
83 );
84 assert_eq!(expand("{name} — remage", &vars), "a.png — remage");
85 // Unknown placeholders survive; single spaces don't split.
86 assert_eq!(expand("{name} {nope} x", &vars), "a.png {nope} x");
87 assert_eq!(expand("open {", &vars), "open {");
88 assert_eq!(expand("", &vars), "");
89 }
90}