late_format/lib.rs
1use std::collections::HashMap;
2use lazy_regex::*;
3
4/// The `LateFormat` trait allows substituting string parameters
5/// of arbitrary name that is computed at runtime.
6///
7/// `LateFormat` is implemented for the `String` and `&str` types by default.
8///
9/// The substitution syntax accepts curly braces forms:
10///
11/// ```plain
12/// {param_name} # parameter to replace
13/// {"escaped"} # escaped sequence
14/// ```
15///
16/// Syntax description:
17///
18/// - Whitespace is allowed around the parameter name or escaped form, such as
19/// `{ "foo" }` versus `{"foo"}`.
20/// - `{param_name}` expands to either an argument given in the map (whose key string is `param_name`) or
21/// the string `None` if not present. The parameter name may contain any of the following characters:
22/// ```plain
23/// A-Z a-z 0-9 . - _ $
24/// ```
25/// - `{"escaped"}` expands to the string `escaped`. It is often
26/// used for escaping the curly braces.
27///
28/// # Example
29///
30/// ```
31/// use late_format::LateFormat;
32/// use maplit::hashmap;
33/// let user_string: String = "some user string: {id}".into();
34/// assert_eq!("some user string: x", user_string.late_format(hashmap!{"id".into() => "x".into()}));
35///
36/// // if a string contains curly braces, they must be escaped.
37/// let escaped: String = r#"{"{"}"#.into();
38/// ```
39///
40pub trait LateFormat {
41 fn late_format(&self, arguments: HashMap<String, String>) -> String;
42}
43
44impl LateFormat for &str {
45 fn late_format(&self, arguments: HashMap<String, String>) -> String {
46 regex_replace_all!(
47 r#"(?x)
48 \{\s*(
49 ([a-zA-Z_0-9\-\.\$]+) | # parameter
50 ("([^\u{22}])*") # escaped
51 )\s*\}
52 "#,
53 self,
54 |_, s: &str, _, _, _| {
55 if s.starts_with('"') {
56 return s[1..s.len() - 1].to_owned().clone();
57 }
58 arguments.get(s).map_or("None".to_owned(), |v| v.clone())
59 }
60 ).into_owned()
61 }
62}
63
64impl LateFormat for String {
65 fn late_format(&self, arguments: HashMap<String, String>) -> String {
66 self.as_str().late_format(arguments)
67 }
68}
69
70#[cfg(test)]
71mod test {
72 use super::*;
73 use maplit::hashmap;
74
75 #[test]
76 fn formatting() {
77 let user_string: String = "some user string: {id}".into();
78 assert_eq!("some user string: x", user_string.late_format(hashmap!{"id".into() => "x".into()}));
79 let user_string: String = r#"some user string: {"id"}"#.into();
80 assert_eq!("some user string: id", user_string.late_format(hashmap!{"id".into() => "x".into()}));
81 let user_string: String = r#"some user string: { "id" }"#.into();
82 assert_eq!("some user string: id", user_string.late_format(hashmap!{"id".into() => "x".into()}));
83 let user_string: String = "some user string: {id}".into();
84 assert_eq!("some user string: None", user_string.late_format(hashmap!{}));
85 }
86}