use super::{Expr, ExprEvaluator};
use crate::err::*;
use crate::eval::*;
use crate::value::*;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct FuncCallExpr {
pub namespace: Vec<String>,
pub name: String,
pub args: Vec<Index>,
}
impl Expr for FuncCallExpr {
fn resolve(
&self,
index: Index,
evaluation: &mut dyn ExprEvaluator,
) -> Result<ValueOrReference, ErrorKind> {
evaluation.ensure_resolved(self.args.to_vec())?;
let namespace_ref: Vec<&str> = self.namespace.iter().map(|s| s.as_str()).collect();
let values = self
.args
.iter()
.map(|arg| evaluation.get_value(*arg))
.collect::<Result<Vec<Value>, ErrorKind>>()?;
let value: Value = match (
namespace_ref.as_slice(),
self.name.as_str(),
values.as_slice(),
) {
(["str"], "empty", [Value::String(value)]) => Ok(Value::from(value.is_empty())),
(["str"], "len", [Value::String(value)]) => Ok(Value::from(value.len())),
(["str"], "trim", [Value::String(value)]) => Ok(value.trim().into()),
(["str"], "trim_start", [Value::String(value)]) => Ok(value.trim_start().into()),
(["str"], "trim_end", [Value::String(value)]) => Ok(value.trim_end().into()),
(["str"], "contains", [Value::String(value), Value::String(search)]) => {
Ok(value.contains(search).into())
}
(["str"], "starts_with", [Value::String(value), Value::String(prefix)]) => {
Ok(value.starts_with(prefix).into())
}
(["str"], "ends_with", [Value::String(value), Value::String(suffix)]) => {
Ok(value.ends_with(suffix).into())
}
(["str"], "to_upper", [Value::String(value)]) => Ok(value.to_uppercase().into()),
(["str"], "to_lower", [Value::String(value)]) => Ok(value.to_lowercase().into()),
(["str"], "join", [values @ .., Value::String(sep)]) => {
list_of_str(values, |list| list.join(sep))
}
(["str"], "concat", values) => list_of_str(values, |list| list.join("")),
(["str"], "split", [Value::String(value), Value::String(delimiter)]) => {
Ok(Value::List(
value
.split(delimiter)
.map(|s| Value::String(s.to_string()))
.collect(),
))
}
(
["str"],
"replace",
[Value::String(value), Value::String(old), Value::String(new)],
) => Ok(value.replace(old, new).into()),
(["str"], "reverse", [Value::String(value)]) => {
Ok(value.chars().rev().collect::<String>().into())
}
(["path"], "basename" | "file_name", [Value::String(value)]) => Ok(Path::new(value)
.file_name()
.map(|path| String::from_utf8_lossy(path.as_encoded_bytes()).to_string())
.unwrap_or_default()
.into()),
(["path"], "file_stem", [Value::String(value)]) => Ok(Path::new(value)
.file_stem()
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_default()
.into()),
(["path"], "file_ext", [Value::String(value)]) => Ok(Path::new(value)
.extension()
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_default()
.into()),
(["path"], "dirname", [Value::String(value)]) => Ok(Path::new(value)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default()
.into()),
(["path"], "join", values) => list_of_str(values, |list| {
list.iter()
.fold(std::path::PathBuf::new(), |mut path, segment| {
path.push(segment);
path
})
.to_string_lossy()
.to_string()
}),
(["list"], "empty", [Value::List(value)]) => Ok(value.is_empty().into()),
(["list"], "len", [Value::List(value)]) => Ok(value.len().into()),
(["list"], "all", values) => list_of_bool(values, |list| list.iter().all(|b| *b)),
(["list"], "any", values) => list_of_bool(values, |list| list.iter().any(|b| *b)),
(["list"], "first", [Value::List(list)]) => list
.first()
.cloned()
.ok_or_else(|| anyhow::anyhow!("list is empty")),
(["list"], "last", [Value::List(list)]) => list
.last()
.cloned()
.ok_or_else(|| anyhow::anyhow!("list is empty")),
(["list"], "reverse", [Value::List(list)]) => {
let mut reversed = list.clone();
reversed.reverse();
Ok(Value::List(reversed))
}
(["list"], "contains", [Value::List(list), value]) => Ok(list.contains(value).into()),
(["map"], "empty", [Value::Map(value)]) => Ok(value.is_empty().into()),
(["map"], "len", [Value::Map(value)]) => Ok(value.len().into()),
(["map"], "keys", [Value::Map(map)]) => Ok(Value::List(
map.keys().map(|k| Value::String(k.clone())).collect(),
)),
(["map"], "values", [Value::Map(map)]) => {
Ok(Value::List(map.values().cloned().collect()))
}
(["map"], "has_key", [Value::Map(map), Value::String(key)]) => {
Ok(map.contains_key(key).into())
}
(["map"], "merge", values) => list_of_maps(values, |maps| {
let mut merged = indexmap::IndexMap::new();
for map in maps {
for (k, v) in map.iter() {
merged.insert(k.clone(), v.clone());
}
}
merged
}),
(["int"], "abs", &[Value::Integer(num)]) => Ok(num.abs().into()),
(["int"], "sum", values) => list_of_int(values, |list| list.iter().sum::<i64>()),
(["int"], "min", values) => {
list_of_int(values, |list| *list.iter().min().unwrap_or(&0i64))
}
(["int"], "max", values) => {
list_of_int(values, |list| *list.iter().max().unwrap_or(&0i64))
}
(
["int"],
"clamp",
[
Value::Integer(num),
Value::Integer(min),
Value::Integer(max),
],
) => Ok(Value::Integer(*num.clamp(min, max))),
(["time"], "minutes", [Value::Integer(num)]) => Ok(Value::Integer(num * 60)),
(["time"], "hours", [Value::Integer(num)]) => Ok(Value::Integer(num * 60 * 60)),
(["time"], "days", [Value::Integer(num)]) => Ok(Value::Integer(num * 60 * 60 * 24)),
(["time"], "weeks", [Value::Integer(num)]) => {
Ok(Value::Integer(num * 60 * 60 * 24 * 7))
}
_ => {
return Err(ErrorKind::UnknownFunction {
item: index,
name: self.name.clone(),
namespace: self.namespace.clone(),
arg_kinds: values.iter().map(Into::into).collect(),
})?;
}
}
.map_err(|inner| ErrorKind::Other(std::sync::Arc::new(inner)))?;
Ok(ValueOrReference::Value(value))
}
fn traverse(
&self,
_evaluation: &dyn ExprEvaluator,
expression: Index,
_name: &Indexer,
) -> Result<Option<ValueOrReference>, ErrorKind> {
Err(ErrorKind::DependenciesNotSatisfied {
indices: vec![expression],
})
}
}
pub fn list_of_str<R>(values: &[Value], map: impl FnOnce(Vec<&str>) -> R) -> anyhow::Result<Value>
where
R: Into<Value>,
{
let values = if let [Value::List(values)] = values {
values
} else {
values
};
values
.iter()
.map(|s| s.as_str())
.collect::<Option<Vec<&str>>>()
.ok_or_else(|| anyhow::anyhow!("only strings are allowed"))
.map(map)
.map(Into::into)
}
pub fn list_of_bool<R>(values: &[Value], map: impl FnOnce(Vec<bool>) -> R) -> anyhow::Result<Value>
where
R: Into<Value>,
{
let values = if let [Value::List(values)] = values {
values
} else {
values
};
values
.iter()
.map(|s| s.as_boolean())
.collect::<Option<Vec<bool>>>()
.ok_or_else(|| anyhow::anyhow!("only booleans are allowed"))
.map(map)
.map(Into::into)
}
pub fn list_of_int<R>(values: &[Value], map: impl FnOnce(Vec<i64>) -> R) -> anyhow::Result<Value>
where
R: Into<Value>,
{
let values = if let [Value::List(values)] = values {
values
} else {
values
};
values
.iter()
.map(|s| s.as_integer())
.collect::<Option<Vec<i64>>>()
.ok_or_else(|| anyhow::anyhow!("only integers are allowed"))
.map(map)
.map(Into::into)
}
pub fn list_of_maps<R>(
values: &[Value],
map: impl FnOnce(Vec<&indexmap::IndexMap<String, Value>>) -> R,
) -> anyhow::Result<Value>
where
R: Into<Value>,
{
let values = if let [Value::List(values)] = values {
values
} else {
values
};
values
.iter()
.map(|s| s.as_map())
.collect::<Option<Vec<_>>>()
.ok_or_else(|| anyhow::anyhow!("only integers are allowed"))
.map(map)
.map(Into::into)
}