script-format 2.1.1

A simple DSL to format data via rhai scripting
Documentation
use std::any::TypeId;
use std::time::Instant;

use rhai::{Array, CustomType, Dynamic, ImmutableString, Map, TypeBuilder, FLOAT, INT};

use crate::engine::ScriptResult;

#[allow(clippy::needless_pass_by_value)]
pub fn script_is_some<T>(opt: Option<T>) -> bool {
    opt.is_some()
}

pub fn script_unwrap<T>(opt: Option<T>) -> T {
    opt.unwrap()
}

pub fn script_unwrap_or<T>(opt: Option<T>, default: T) -> T {
    opt.unwrap_or(default)
}

pub fn script_join(v: &[String], sep: &str) -> String {
    v.join(sep)
}

pub fn script_split(s: &str, pattern: &str) -> Vec<Dynamic> {
    s.split(pattern)
        .map(|s| Dynamic::from(s.to_string()))
        .collect()
}

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn script_splitn(s: &str, n: INT, pattern: &str) -> Vec<Dynamic> {
    s.splitn(n as usize, pattern)
        .map(|s| Dynamic::from(s.to_string()))
        .collect()
}

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn script_rsplitn(s: &str, n: INT, pattern: &str) -> Vec<Dynamic> {
    s.rsplitn(n as usize, pattern)
        .map(|s| Dynamic::from(s.to_string()))
        .collect()
}

pub fn script_string_is_empty(s: &str) -> bool {
    s.is_empty()
}

pub fn script_array_is_empty(s: &Array) -> bool {
    s.is_empty()
}

pub fn script_starts_with(s: &str, pat: &str) -> bool {
    s.starts_with(pat)
}

pub fn script_ends_with(s: &str, pat: &str) -> bool {
    s.ends_with(pat)
}

pub fn script_trim(s: &str) -> &str {
    s.trim()
}

pub fn script_is_no_string(_: Dynamic) -> bool {
    false
}

pub fn script_is_string(_: &str) -> bool {
    true
}

fn is_true(b: bool) -> bool {
    b
}

pub fn script_any_void(arr: &Vec<Dynamic>) -> bool {
    arr.iter()
        .filter(|a| a.is_bool())
        .filter(|a| a.as_bool().unwrap())
        .count()
        > 0
}

pub fn script_any_type<T: PartialEq>(arr: &Vec<T>, v: T) -> bool {
    arr.iter().map(|a| a == &v).any(is_true)
}

pub fn script_any(arr: &Vec<Dynamic>, v: rhai::Dynamic) -> ScriptResult<bool> {
    let values: ScriptResult<Vec<bool>> = arr
        .iter()
        .map(|a| script_value_equals(a.clone(), v.clone()))
        .collect();
    Ok(values?.into_iter().any(is_true))
}

pub fn script_all_void(arr: &Vec<Dynamic>) -> bool {
    let expected = arr.len();
    arr.iter()
        .filter(|a| a.is_bool())
        .filter(|a| a.as_bool().unwrap())
        .count()
        == expected
}

pub fn script_all_type<T: PartialEq>(arr: &Vec<T>, v: T) -> bool {
    arr.iter().map(|a| a == &v).all(is_true)
}

pub fn script_all(arr: &Vec<Dynamic>, v: rhai::Dynamic) -> ScriptResult<bool> {
    let values: ScriptResult<Vec<bool>> = arr
        .iter()
        .map(|a| script_value_equals(a.clone(), v.clone()))
        .collect();
    Ok(values?.into_iter().all(is_true))
}

pub fn script_none_void(arr: &Vec<Dynamic>) -> bool {
    arr.iter()
        .filter(|a| a.is_bool())
        .filter(|a| a.as_bool().unwrap())
        .count()
        == 0
}

pub fn script_none_type<T: PartialEq>(arr: &Vec<T>, v: T) -> bool {
    !arr.iter().map(|a| a == &v).all(is_true)
}

pub fn script_none(arr: &Vec<Dynamic>, v: rhai::Dynamic) -> ScriptResult<bool> {
    let values: ScriptResult<Vec<bool>> = arr
        .iter()
        .map(|a| script_value_equals(a.clone(), v.clone()))
        .collect();
    Ok(!values?.into_iter().all(is_true))
}

#[derive(Clone, CustomType)]
pub struct ToBe {
    count: INT,
    value: Dynamic,
}

pub fn script_to_be(count: INT, value: Dynamic) -> ToBe {
    ToBe { count, value }
}

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn script_require(arr: Array, tb: ToBe) -> ScriptResult<bool> {
    let values: ScriptResult<Vec<_>> = arr
        .iter()
        .map(|a| script_value_equals(a.clone(), tb.value.clone()))
        .collect();
    let count = values?.into_iter().filter(|b: &bool| is_true(*b)).count();
    if count == tb.count as usize {
        Ok(true)
    } else {
        Err(format!(
            "Value \"{}\" expected {} times but got only {count}!",
            tb.value, tb.count
        )
        .into())
    }
}

pub fn script_map_equals(m1: &Map, m2: &Map) -> ScriptResult<bool> {
    if m1.len() != m2.len() {
        return Ok(false);
    }
    for (key, value) in m1 {
        if let Some(value2) = m2.get(key) {
            if !script_value_equals(value.clone(), value2.clone())? {
                return Ok(false);
            }
        } else {
            return Ok(false);
        }
    }
    Ok(true)
}

pub fn script_string_contains(s: &str, v: &str) -> bool {
    s.contains(v)
}

pub fn script_map_contains(m: &Map, name: &str) -> bool {
    m.get(name).is_some()
}

pub fn script_value_equals(v1: Dynamic, v2: Dynamic) -> ScriptResult<bool> {
    let t1 = v1.type_id();
    let t2 = v2.type_id();
    if t1 != t2 {
        Ok(false)
    } else if t1 == TypeId::of::<()>() {
        Ok(true)
    } else if t1 == TypeId::of::<bool>() {
        Ok(v1.as_bool() == v2.as_bool())
    } else if t1 == TypeId::of::<ImmutableString>() {
        Ok(v1.into_immutable_string() == v2.into_immutable_string())
    } else if t1 == TypeId::of::<char>() {
        Ok(v1.as_char() == v2.as_char())
    } else if t1 == TypeId::of::<INT>() {
        Ok(v1.as_int() == v2.as_int())
    } else if t1 == TypeId::of::<FLOAT>() {
        Ok(v1.as_float() == v2.as_float())
    } else if t1 == TypeId::of::<Array>() {
        Ok(script_array_equals(
            &v1.cast::<Array>(),
            &v2.cast::<Array>(),
        ))
    } else if t1 == TypeId::of::<Map>() {
        script_map_equals(&v1.cast::<Map>(), &v2.cast::<Map>())
    } else if t1 == TypeId::of::<Instant>() {
        Ok(v1.cast::<Instant>() == v2.cast::<Instant>())
    } else {
        Err("unsupported type".into())
    }
}

pub fn script_array_equals(arr: &Array, arr2: &Array) -> bool {
    if arr.len() != arr2.len() {
        return false;
    }
    let result = arr
        .iter()
        .zip(arr2.iter())
        .all(|(e1, e2)| script_value_equals(e1.clone(), e2.clone()).unwrap_or_default());
    result
}

pub fn script_array_contains(arr: Array, v: &Dynamic) -> bool {
    arr.into_iter()
        .any(|ele| script_value_equals(ele, v.clone()).unwrap_or_default())
}