use crate::types::DynErrResult;
use pest::Parser;
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "format_str/grammar.pest"]
struct StrFormatParser;
fn rename_rules(rule: &Rule) -> String {
match rule {
Rule::literal => "literal".to_string(),
Rule::format_param => "%s".to_string(),
Rule::EOI => "EOI".to_string(),
__other__ => unreachable!("Unexpected rule {:?}", __other__),
}
}
pub fn format_string<S: AsRef<str>>(fmt_string: S, vars: &[&str]) -> DynErrResult<String> {
let tokens = StrFormatParser::parse(Rule::all, fmt_string.as_ref());
let tokens = match tokens {
Ok(mut tokens) => tokens.next().unwrap().into_inner(),
Err(e) => {
return Err(format!("Invalid format string:\n{}", e.renamed_rules(rename_rules)).into())
}
};
let mut result = String::new();
let mut i = 0;
for token in tokens {
match token.as_rule() {
Rule::literal => {
for literal in token.into_inner() {
match literal.as_rule() {
Rule::escaped_val => result.push('%'),
Rule::literal_content => result.push_str(literal.as_str()),
_ => {
unreachable!("Unexpected token {}", literal.as_str());
}
}
}
}
Rule::format_param => match vars.get(i) {
None => {
return Err("Not enough variables".into());
}
Some(val) => {
result.push_str(val.as_ref());
i += 1;
}
},
Rule::EOI => {
break;
}
_ => {
unreachable!("Unexpected token {}", token.as_str());
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_string() {
let fmt_string = "Hello %s %s %s %%s";
let vars = vec!["world", "!", "?"];
let result = format_string(fmt_string, &vars).unwrap();
assert_eq!(result, "Hello world ! ? %s");
let fmt_string = "";
let vars = vec!["world", "!", "?"];
let result = format_string(fmt_string, &vars).unwrap();
assert_eq!(result, "");
let fmt_string = " ";
let vars = vec!["world", "!", "?"];
let result = format_string(fmt_string, &vars).unwrap();
assert_eq!(result, " ");
let fmt_string = " %%";
let vars = vec!["world", "!", "?"];
let result = format_string(fmt_string, &vars).unwrap();
assert_eq!(result, " %");
}
#[test]
fn test_format_string_errors() {
let fmt_string = " %";
let vars = vec!["world", "!", "?"];
let result = format_string(fmt_string, &vars).unwrap_err().to_string();
let expected_result = r#"Invalid format string:
--> 1:2
|
1 | %
| ^---
|
= expected EOI, literal, or %s"#;
assert_eq!(result, expected_result);
let fmt_string = "Hello %s %s %s";
let vars = vec!["world", "extra"];
let result = format_string(fmt_string, &vars).unwrap_err().to_string();
let expected_result = r#"Not enough variables"#;
assert_eq!(result, expected_result);
}
}