use std::collections::HashMap;
use std::ops::Index;
use lazy_static::lazy_static;
use crate::format_str::format_string;
use crate::types::DynErrResult;
pub enum FunVal<'a> {
String(&'a str),
Vec(&'a Vec<String>),
}
#[derive(PartialEq, Eq, Debug)]
pub enum FunResult {
String(String),
Vec(Vec<String>),
}
impl FunResult {
pub(crate) fn as_val(&self) -> FunVal {
match self {
FunResult::String(val) => FunVal::String(val),
FunResult::Vec(val) => FunVal::Vec(val),
}
}
pub fn is_empty(&self) -> bool {
match self {
FunResult::String(val) => val.is_empty(),
FunResult::Vec(val) => val.is_empty(),
}
}
}
fn validate_arguments_length(
fn_name: &str,
args: &Vec<FunVal>,
min: usize,
max: usize,
) -> DynErrResult<()> {
let len = args.len();
if len < min {
return if min == max {
Err(format!(
"{} requires {} arguments, but {} were given",
fn_name, min, len
)
.into())
} else {
Err(format!(
"{} requires at least {} arguments, but {} were given",
fn_name, min, len
)
.into())
};
} else if len > max {
return if max == min {
Err(format!(
"{} requires {} arguments, but {} were given",
fn_name, max, len
)
.into())
} else {
Err(format!(
"{} requires at most {} arguments, but {} were given",
fn_name, max, len
)
.into())
};
}
Ok(())
}
fn validate_string<'a>(fn_name: &str, args: &'a [FunVal], index: usize) -> DynErrResult<&'a str> {
match args[index] {
FunVal::String(s) => Ok(s),
FunVal::Vec(_) => Err(format!(
"{} requires a string argument at index {}, but a list was given",
fn_name, index
)
.into()),
}
}
type Function = fn(&Vec<FunVal>) -> DynErrResult<FunResult>;
pub struct FunctionRegistry {
pub(crate) functions: HashMap<String, Function>,
}
fn map_format_string(fmt_string: &str, val: &str) -> DynErrResult<String> {
match format_string(fmt_string, &[val]) {
Ok(val) => Ok(val),
Err(e) => Err(format!("Error formatting the string:\n{e}").into()),
}
}
fn map(args: &Vec<FunVal>) -> DynErrResult<FunResult> {
let fn_name = "map";
validate_arguments_length(fn_name, args, 2, 2)?;
let fmt_string = validate_string(fn_name, args, 0)?;
return match args.index(1) {
FunVal::String(s) => {
let result = map_format_string(fmt_string, s)?;
Ok(FunResult::String(result))
}
FunVal::Vec(l) => {
let mut result = Vec::with_capacity(l.capacity());
for s in *l {
result.push(map_format_string(fmt_string, s)?);
}
Ok(FunResult::Vec(result))
}
};
}
fn jmap(args: &Vec<FunVal>) -> DynErrResult<FunResult> {
let fn_name = "jmap";
validate_arguments_length(fn_name, args, 2, 2)?;
let fmt_string = validate_string(fn_name, args, 0)?;
return match args.index(1) {
FunVal::String(s) => {
let result = map_format_string(fmt_string, s)?;
Ok(FunResult::String(result))
}
FunVal::Vec(values) => {
let mut result = String::with_capacity(values.capacity() * 5);
for s in *values {
result.push_str(&map_format_string(fmt_string, s)?);
}
Ok(FunResult::String(result))
}
};
}
fn join(args: &Vec<FunVal>) -> DynErrResult<FunResult> {
let fn_name = "join";
validate_arguments_length(fn_name, args, 2, 2)?;
let join_val = validate_string(fn_name, args, 0)?;
match args.index(1) {
FunVal::String(s) => Ok(FunResult::String(s.to_string())),
FunVal::Vec(values) => {
if values.is_empty() {
Ok(FunResult::String(String::new()))
} else if values.len() == 1 {
Ok(FunResult::String(values.first().unwrap().clone()))
} else {
let mut result = String::with_capacity(values.capacity() * 5);
for val in &values[0..values.len() - 1] {
result.push_str(val);
result.push_str(join_val);
}
result.push_str(&values[values.len() - 1]);
Ok(FunResult::String(result))
}
}
}
}
fn fmt(args: &Vec<FunVal>) -> DynErrResult<FunResult> {
let fn_name = "fmt";
validate_arguments_length(fn_name, args, 2, usize::MAX)?;
let fmt_string = validate_string(fn_name, args, 0)?;
let mut values: Vec<&str> = Vec::with_capacity(args.len() - 1);
let mut i = 1;
while i < args.len() {
let arg = validate_string(fn_name, args, i)?;
values.push(arg);
i += 1;
}
Ok(FunResult::String(format_string(fmt_string, &values)?))
}
fn split(args: &Vec<FunVal>) -> DynErrResult<FunResult> {
let fn_name = "split";
validate_arguments_length(fn_name, args, 2, 2)?;
let split_val = validate_string(fn_name, args, 0)?;
let split_string = validate_string(fn_name, args, 1)?;
Ok(FunResult::Vec(
split_string
.split(split_val)
.map(|s| s.to_string())
.collect(),
))
}
fn trim(args: &Vec<FunVal>) -> DynErrResult<FunResult> {
let fn_name = "trim";
validate_arguments_length(fn_name, args, 1, 1)?;
match args.index(0) {
FunVal::String(s) => Ok(FunResult::String(s.trim().to_string())),
FunVal::Vec(values) => {
let mut result = Vec::with_capacity(values.capacity());
for s in *values {
result.push(s.trim().to_string());
}
Ok(FunResult::Vec(result))
}
}
}
fn load_default_functions() -> FunctionRegistry {
let mut functions: HashMap<String, Function> = HashMap::new();
functions.insert(String::from("map"), map);
functions.insert(String::from("jmap"), jmap);
functions.insert(String::from("join"), join);
functions.insert(String::from("fmt"), fmt);
functions.insert(String::from("split"), split);
functions.insert(String::from("trim"), trim);
FunctionRegistry { functions }
}
lazy_static! {
pub static ref DEFAULT_FUNCTIONS: FunctionRegistry = load_default_functions();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_arguments_length() {
let fn_name = "test";
let args = vec![FunVal::String("test")];
let result = validate_arguments_length(fn_name, &args, 1, 1);
assert!(result.is_ok());
let result = validate_arguments_length(fn_name, &args, 2, 2);
assert!(result.is_err());
let result = validate_arguments_length(fn_name, &args, 1, 2);
assert!(result.is_ok());
let result = validate_arguments_length(fn_name, &args, 0, 0);
assert!(result.is_err());
let result = validate_arguments_length(fn_name, &args, 2, 2);
assert!(result.is_err());
let result = validate_arguments_length(fn_name, &args, 2, 3);
assert!(result.is_err());
let args = vec![FunVal::String("test"), FunVal::String("test")];
let result = validate_arguments_length(fn_name, &args, 0, 1);
assert!(result.is_err());
let result = validate_arguments_length(fn_name, &args, 1, 1);
assert!(result.is_err());
}
#[test]
fn test_validate_string() {
let fn_name = "test";
let vec = vec!["test".to_string(), "test".to_string()];
let args = vec![FunVal::String("test"), FunVal::Vec(&vec)];
let result = validate_string(fn_name, &args, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "test");
let result = validate_string(fn_name, &args, 1);
assert!(result.is_err());
}
#[test]
fn test_map() {
let vars = vec![FunVal::String("Hello %s ! ? %%s"), FunVal::String("world")];
let result = map(&vars).unwrap();
let expected = FunResult::String(String::from("Hello world ! ? %s"));
assert_eq!(result, expected);
let values = vec!["world".to_string(), "people".to_string()];
let vars = vec![FunVal::String("Hello %s ! ? %%s"), FunVal::Vec(&values)];
let result = map(&vars).unwrap();
let expected = FunResult::Vec(vec![
"Hello world ! ? %s".to_string(),
"Hello people ! ? %s".to_string(),
]);
assert_eq!(result, expected);
let values = vec!["world".to_string(), "people".to_string()];
let vars = vec![FunVal::String("Hello % ! ? %s"), FunVal::Vec(&values)];
let result = map(&vars).unwrap_err().to_string();
let expected_result = r#"Error formatting the string:
Invalid format string:
--> 1:7
|
1 | Hello % ! ? %s
| ^---
|
= expected EOI, literal, or %s"#;
assert_eq!(result, expected_result);
}
#[test]
fn test_jmap() {
let vars = vec![FunVal::String("Hello %s ! ? %%s"), FunVal::String("world")];
let result = jmap(&vars).unwrap();
let expected = FunResult::String(String::from("Hello world ! ? %s"));
assert_eq!(result, expected);
let values = vec!["world".to_string(), "people".to_string()];
let vars = vec![FunVal::String("Hello %s, "), FunVal::Vec(&values)];
let result = jmap(&vars).unwrap();
let expected = FunResult::String(String::from("Hello world, Hello people, "));
assert_eq!(result, expected);
let values = vec!["world".to_string(), "people".to_string()];
let vars = vec![FunVal::String("Hello % ! ? %%"), FunVal::Vec(&values)];
let result = map(&vars).unwrap_err().to_string();
let expected_result = r#"Error formatting the string:
Invalid format string:
--> 1:7
|
1 | Hello % ! ? %%
| ^---
|
= expected EOI, literal, or %s"#;
assert_eq!(result, expected_result);
}
#[test]
fn test_join() {
let values = vec!["world".to_string(), "people".to_string()];
let vars = vec![FunVal::String(", "), FunVal::Vec(&values)];
let result = join(&vars).unwrap();
let expected = FunResult::String(String::from("world, people"));
assert_eq!(result, expected);
let vars = vec![FunVal::String(","), FunVal::String("world")];
let result = join(&vars).unwrap();
let expected = FunResult::String(String::from("world"));
assert_eq!(result, expected);
let values: Vec<String> = vec![];
let vars = vec![FunVal::String(","), FunVal::Vec(&values)];
let result = join(&vars).unwrap();
let expected = FunResult::String(String::from(""));
assert_eq!(result, expected);
let values: Vec<String> = vec![String::from("world")];
let vars = vec![FunVal::String(","), FunVal::Vec(&values)];
let result = join(&vars).unwrap();
let expected = FunResult::String(String::from("world"));
assert_eq!(result, expected);
}
#[test]
fn test_fmt() {
let vars = vec![
FunVal::String("Hello %s and %s"),
FunVal::String("world"),
FunVal::String("people"),
];
let result = fmt(&vars).unwrap();
let expected = FunResult::String(String::from("Hello world and people"));
assert_eq!(result, expected);
}
#[test]
fn test_split() {
let vars = vec![FunVal::String(","), FunVal::String("world,people")];
let result = split(&vars).unwrap();
let expected = FunResult::Vec(vec!["world".to_string(), "people".to_string()]);
assert_eq!(result, expected);
}
#[test]
fn test_trim() {
let vars = vec![FunVal::String(" world ")];
let result = trim(&vars).unwrap();
let expected = FunResult::String(String::from("world"));
assert_eq!(result, expected);
let values = vec![" world ".to_string(), " people ".to_string()];
let vars = vec![FunVal::Vec(&values)];
let result = trim(&vars).unwrap();
let expected = FunResult::Vec(vec!["world".to_string(), "people".to_string()]);
assert_eq!(result, expected);
}
}