Crate for formatting string arguments

I need to format any given message, so I can't use macros like format!(...). For this, I've the following code, which I adapted from my crate rialight_localization, which allows to do:

  • string_args! { "x" => "str" }
  • apply("Some parameter: $x", string_args! { "x" => "str" })
use std::any::Any;
use std::collections::HashMap;
use lazy_regex::regex;

/// Creates a `HashMap<String, Box<dyn Any>>` from a list of key-value pairs.
///
/// ## Example
///
/// ```
/// use apply_string_args::string_args;
/// fn main() {
///     let map = string_args!{
///         "a" => "foo",
///         "b" => "bar",
///     };
///     assert_eq!(*map[&"a".to_owned()], "foo");
///     assert_eq!(*map[&"b".to_owned()], "bar");
///     assert_eq!(map.get(&"c".to_owned()), None);
/// }
/// ```
#[macro_export]
macro_rules! string_args {
    (@single $($x:tt)*) => (());
    (@count $($rest:expr),*) => (<[()]>::len(&[$(string_args!(@single $rest)),*]));

    ($($key:expr => $value:expr,)+) => { string_args!($($key => $value),+) };
    ($($key:expr => $value:expr),*) => {
        {
            let r_cap = string_args!(@count $($key),*);
            let mut r_map = HashMap::<String, Box<dyn Any>>::with_capacity(r_cap);
            $(
                let _ = r_map.insert($key.to_string(), Box::new($value));
            )*
            r_map
        }
    };
}

pub fn apply(base: impl AsRef<str>, vars: &HashMap<String, Box<dyn Any>>, convert: fn(arg: &Box<dyn Any>) -> String) -> String {
    regex!(r"\$(\$|[A-Za-z0-9]+)").replace_all(base.as_ref(), |s: &regex::Captures<'_>| {
        let s = s.get(0).unwrap().as_str();
        if s == "$$" {
            "$".to_owned()
        } else {
            let v = vars.get(&s.to_string().replace("$", ""));
            if let Some(v) = v { convert(v) } else { "None".to_owned() }
        }
    }).as_ref().to_string()
}

Is there a crate that does the same?

1 Like

Something is off here: dyn Any does not implement PartialEq<&str>, so those assert_eq! statements shouldn't compile.


For a similar macro, take a look at phf::phf_map!.

4 Likes

This starts to sound suspiciously like template engine. Are you sure you don't want to use tera or something like that?

4 Likes

I just gave a look at that and it seems it's more suited for more complex cases. I've to format message strings that aren't located at files.

1 Like

These files are not necessary to be in physical files in disk. And there's one_of if you just need to format one string.

And I haven't said you need tera, specifically. I said something like tera. There are hundreds of template engines (and probably dozen of supported and actively used ones), unless your request are extremely special there are no need to invent your own.

5 Likes