kelora 1.5.0

A command-line log analysis tool with embedded Rhai scripting
Documentation
use rhai::{Array, Dynamic, Engine};
use std::convert::TryFrom;

fn edit_distance_impl(lhs: &str, rhs: &str) -> i64 {
    if lhs == rhs {
        return 0;
    }

    let lhs_chars: Vec<char> = lhs.chars().collect();
    let rhs_chars: Vec<char> = rhs.chars().collect();
    let len_lhs = lhs_chars.len();
    let len_rhs = rhs_chars.len();

    if len_lhs == 0 {
        return i64::try_from(len_rhs).unwrap_or(i64::MAX);
    }
    if len_rhs == 0 {
        return i64::try_from(len_lhs).unwrap_or(i64::MAX);
    }

    let mut prev: Vec<usize> = (0..=len_rhs).collect();
    let mut curr: Vec<usize> = vec![0; len_rhs + 1];

    for (i, &lhs_ch) in lhs_chars.iter().enumerate() {
        curr[0] = i + 1;

        for (j, &rhs_ch) in rhs_chars.iter().enumerate() {
            let cost = usize::from(lhs_ch != rhs_ch);
            let insertion = curr[j] + 1;
            let deletion = prev[j + 1] + 1;
            let substitution = prev[j] + cost;
            curr[j + 1] = insertion.min(deletion).min(substitution);
        }

        std::mem::swap(&mut prev, &mut curr);
    }

    i64::try_from(prev[len_rhs]).unwrap_or(i64::MAX)
}

pub fn register_functions(engine: &mut Engine) {
    engine.register_fn("contains", |text: &str, pattern: &str| {
        text.contains(pattern)
    });

    engine.register_fn("to_int", |text: &str| -> rhai::Dynamic {
        text.parse::<i64>()
            .map(Dynamic::from)
            .unwrap_or(Dynamic::UNIT)
    });

    engine.register_fn("to_float", |text: &str| -> rhai::Dynamic {
        text.parse::<f64>()
            .map(Dynamic::from)
            .unwrap_or(Dynamic::UNIT)
    });

    engine.register_fn("or_empty", |text: &str| -> rhai::Dynamic {
        if text.is_empty() {
            Dynamic::UNIT
        } else {
            Dynamic::from(text.to_string())
        }
    });

    engine.register_fn("or_empty", |_unit: ()| -> rhai::Dynamic { Dynamic::UNIT });

    engine.register_fn("or_empty", |arr: rhai::Array| -> rhai::Dynamic {
        if arr.is_empty() {
            Dynamic::UNIT
        } else {
            Dynamic::from(arr)
        }
    });

    engine.register_fn("or_empty", |map: rhai::Map| -> rhai::Dynamic {
        if map.is_empty() {
            Dynamic::UNIT
        } else {
            Dynamic::from(map)
        }
    });

    engine.register_fn("lower", |text: &str| -> String { text.to_lowercase() });

    engine.register_fn("upper", |text: &str| -> String { text.to_uppercase() });

    engine.register_fn("is_digit", |text: &str| -> bool {
        !text.is_empty() && text.chars().all(|c| c.is_ascii_digit())
    });

    engine.register_fn("count", |text: &str, pattern: &str| -> i64 {
        if pattern.is_empty() {
            return 0;
        }
        text.matches(pattern).count() as i64
    });

    engine.register_fn("edit_distance", edit_distance_impl);

    engine.register_fn("join", |separator: &str, items: Array| -> String {
        items
            .into_iter()
            .filter_map(|item| item.into_string().ok())
            .collect::<Vec<String>>()
            .join(separator)
    });

    engine.register_fn("join", |items: Array, separator: &str| -> String {
        items
            .into_iter()
            .filter_map(|item| item.into_string().ok())
            .collect::<Vec<String>>()
            .join(separator)
    });
}