anathema-value-resolver 0.2.11

Anathema value resolver
Documentation
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

use crate::ValueKind;

pub(super) fn to_upper<'bp>(args: &[ValueKind<'bp>]) -> ValueKind<'bp> {
    if args.len() != 1 {
        return ValueKind::Null;
    }

    let mut buffer = String::new();
    args[0].strings(|s| {
        buffer.push_str(&s.to_uppercase());
        true
    });

    ValueKind::Str(buffer.into())
}

pub(super) fn to_lower<'bp>(args: &[ValueKind<'bp>]) -> ValueKind<'bp> {
    if args.len() != 1 {
        return ValueKind::Null;
    }

    let mut buffer = String::new();
    args[0].strings(|s| {
        buffer.push_str(&s.to_lowercase());
        true
    });

    ValueKind::Str(buffer.into())
}

pub(super) fn to_str<'bp>(args: &[ValueKind<'bp>]) -> ValueKind<'bp> {
    if args.len() != 1 {
        return ValueKind::Null;
    }
    let mut buffer = String::new();
    args[0].strings(|s| {
        buffer.push_str(s);
        true
    });
    ValueKind::Str(buffer.into())
}

pub(super) fn truncate<'bp>(args: &[ValueKind<'bp>]) -> ValueKind<'bp> {
    if args.len() != 2 {
        return ValueKind::Null;
    }

    let Some(mut remainder) = args[1].as_int() else { return ValueKind::Null };

    let mut buffer = String::new();
    args[0].strings(|s| {
        let width = s.width() as i64;

        if width < remainder {
            remainder = remainder.saturating_sub(width);
            buffer.push_str(s);
        } else {
            let mut chars = s.chars();
            while remainder > 0 {
                let Some(c) = chars.next() else { break };
                let width = c.width().unwrap_or(0) as i64;
                if width > remainder {
                    break;
                };
                buffer.push(c);
                remainder = remainder.saturating_sub(width);
            }
        }
        true
    });
    ValueKind::Str(buffer.into())
}

pub(super) fn pad<'bp>(args: &[ValueKind<'bp>]) -> ValueKind<'bp> {
    if args.len() != 2 {
        return ValueKind::Null;
    }

    let Some(width) = args[1].as_int() else { return ValueKind::Null };
    if width < 0 {
        return ValueKind::Null;
    }
    let width = width as usize;

    let mut buffer = String::new();
    args[0].strings(|s| {
        buffer.push_str(s);
        true
    });

    while width > buffer.width() {
        buffer.push(' ');
    }

    ValueKind::Str(buffer.into())
}

pub(super) fn width<'bp>(args: &[ValueKind<'bp>]) -> ValueKind<'bp> {
    if args.len() != 1 {
        return ValueKind::Null;
    }

    let mut width = 0;

    args[0].strings(|s| {
        width += s.width();
        true
    });

    ValueKind::Int(width as i64)
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::functions::test::{list, value};

    fn to_string(value: ValueKind<'_>) -> String {
        let ValueKind::Str(s) = to_str(&[value]) else { panic!() };
        s.to_string()
    }

    #[test]
    fn string_to_upper() {
        let val = value("hello");
        let val = to_upper(&[val]);
        let val = to_string(val);
        assert_eq!(val, "HELLO");
    }

    #[test]
    fn string_to_lower() {
        let val = value("HELLO");
        let val = to_lower(&[val]);
        let val = to_string(val);
        assert_eq!(val, "hello");
    }

    #[test]
    fn int_to_string() {
        let val = value(123);
        let val = to_str(&[val]);
        assert_eq!(val, value("123"));
    }

    #[test]
    fn bool_to_string() {
        let val = value(true);
        let val = to_str(&[val]);
        assert_eq!(val, value("true"));
    }

    #[test]
    fn list_to_string() {
        let val = list([1, 2, 3]);
        let val = to_str(&[val]);
        assert_eq!(val, value("123"));
    }

    #[test]
    fn truncate_long_string() {
        let val = value("this is a longer string");
        let val = truncate(&[val, value(4)]);
        assert_eq!(val, value("this"));
    }

    #[test]
    fn truncate_short_string() {
        let val = value("hi");
        let val = truncate(&[val, value(4)]);
        assert_eq!(val, value("hi"));
    }

    #[test]
    fn truncate_wide_cells() {
        let val = value("🐇🐇");
        let val = truncate(&[val, value(3)]);
        assert_eq!(val, value("🐇"));
    }

    #[test]
    fn pad_string() {
        let val = value("hi");
        let val = pad(&[val, value(3)]);
        assert_eq!(val, value("hi "));

        let val = value("bye");
        let val = pad(&[val, value(3)]);
        assert_eq!(val, value("bye"));

        let val = value("hello");
        let val = pad(&[val, value(3)]);
        assert_eq!(val, value("hello"));
    }

    #[test]
    fn pad_non_string() {
        let val = value(1);
        let val = pad(&[val, value(3)]);
        assert_eq!(val, value("1  "));
    }

    #[test]
    fn width_of_string() {
        let val = value("one");
        let val = width(&[val]);
        assert_eq!(val, value(3));
    }

    #[test]
    fn width_of_int() {
        let val = value(1);
        let val = width(&[val]);
        assert_eq!(val, value(1));
    }
}