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}