kelora 1.5.0

A command-line log analysis tool with embedded Rhai scripting
Documentation
//! Global state management for Rhai scripts
//!
//! Provides a mutable `state` map in sequential mode for tracking information
//! across events. In parallel mode, accessing state returns an error.

use rhai::{Dynamic, Engine, EvalAltResult, Map, Position};
use std::fmt;
use std::sync::{Arc, RwLock};

/// Wrapper for shared mutable map (behaves like a regular Rhai map with interior mutability)
#[derive(Debug, Clone)]
pub struct StateMap {
    inner: Arc<RwLock<Map>>,
}

impl StateMap {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(RwLock::new(Map::new())),
        }
    }
}

impl Default for StateMap {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for StateMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let map = self.inner.read().unwrap();
        write!(f, "{:?}", *map)
    }
}

/// Dummy type used in parallel mode that returns errors on any access
#[derive(Debug, Clone)]
pub struct StateNotAvailable;

impl fmt::Display for StateNotAvailable {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "state (unavailable in parallel mode)")
    }
}

/// Register state-related functions with the Rhai engine
#[allow(dependency_on_unit_never_type_fallback)]
pub fn register(engine: &mut Engine) {
    // Register StateMap type
    engine.register_type::<StateMap>();

    // Register indexers (most important - makes state["key"] work)
    engine
        .register_indexer_get(|state: &mut StateMap, key: &str| -> Dynamic {
            let map = state.inner.read().unwrap();
            map.get(key).cloned().unwrap_or(Dynamic::UNIT)
        })
        .register_indexer_set(|state: &mut StateMap, key: &str, value: Dynamic| {
            let mut map = state.inner.write().unwrap();
            map.insert(key.into(), value);
        });

    // Register all standard map methods
    engine
        .register_fn("contains", |state: &mut StateMap, key: &str| -> bool {
            let map = state.inner.read().unwrap();
            map.contains_key(key)
        })
        .register_fn("get", |state: &mut StateMap, key: &str| -> Dynamic {
            let map = state.inner.read().unwrap();
            map.get(key).cloned().unwrap_or(Dynamic::UNIT)
        })
        .register_fn("set", |state: &mut StateMap, key: &str, value: Dynamic| {
            let mut map = state.inner.write().unwrap();
            map.insert(key.into(), value);
        })
        .register_fn("len", |state: &mut StateMap| -> i64 {
            let map = state.inner.read().unwrap();
            map.len() as i64
        })
        .register_fn("is_empty", |state: &mut StateMap| -> bool {
            let map = state.inner.read().unwrap();
            map.is_empty()
        })
        .register_fn("keys", |state: &mut StateMap| -> Vec<Dynamic> {
            let map = state.inner.read().unwrap();
            map.keys().map(|k| Dynamic::from(k.to_string())).collect()
        })
        .register_fn("values", |state: &mut StateMap| -> Vec<Dynamic> {
            let map = state.inner.read().unwrap();
            map.values().cloned().collect()
        })
        .register_fn("clear", |state: &mut StateMap| {
            let mut map = state.inner.write().unwrap();
            map.clear()
        })
        .register_fn("remove", |state: &mut StateMap, key: &str| -> Dynamic {
            let mut map = state.inner.write().unwrap();
            map.remove(key).unwrap_or(Dynamic::UNIT)
        })
        .register_fn("mixin", |state: &mut StateMap, other: Map| {
            let mut map = state.inner.write().unwrap();
            for (k, v) in other {
                map.insert(k, v);
            }
        })
        .register_fn("+", |state: StateMap, other: Map| -> StateMap {
            let new_state = StateMap::new();
            {
                let src = state.inner.read().unwrap();
                let mut dst = new_state.inner.write().unwrap();
                for (k, v) in src.iter() {
                    dst.insert(k.clone(), v.clone());
                }
                for (k, v) in other {
                    dst.insert(k, v);
                }
            }
            new_state
        })
        .register_fn("+=", |state: &mut StateMap, other: Map| {
            let mut map = state.inner.write().unwrap();
            for (k, v) in other {
                map.insert(k, v);
            }
        })
        .register_fn("fill_with", |state: &mut StateMap, other: Map| {
            let mut map = state.inner.write().unwrap();
            *map = other;
        });

    // Register conversion to regular Map (for compatibility with functions that accept Map)
    // Users can call state.to_map().to_logfmt(), state.to_map().to_kv(), etc.
    engine.register_fn("to_map", |state: &mut StateMap| -> Map {
        let map = state.inner.read().unwrap();
        map.clone()
    });

    // Register StateNotAvailable type
    engine.register_type::<StateNotAvailable>();

    // Register error-returning operations for StateNotAvailable
    engine
        .register_indexer_get(
            |_state: &mut StateNotAvailable, _key: &str| -> Result<Dynamic, Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_indexer_set(
            |_state: &mut StateNotAvailable,
             _key: &str,
             _value: Dynamic|
             -> Result<(), Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "contains",
            |_state: &mut StateNotAvailable, _key: &str| -> Result<bool, Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "len",
            |_state: &mut StateNotAvailable| -> Result<i64, Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "is_empty",
            |_state: &mut StateNotAvailable| -> Result<bool, Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "keys",
            |_state: &mut StateNotAvailable| -> Result<Vec<Dynamic>, Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "clear",
            |_state: &mut StateNotAvailable| -> Result<(), Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "get",
            |_state: &mut StateNotAvailable, _key: &str| -> Result<Dynamic, Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        )
        .register_fn(
            "insert",
            |_state: &mut StateNotAvailable,
             _key: &str,
             _value: Dynamic|
             -> Result<(), Box<EvalAltResult>> {
                Err(EvalAltResult::ErrorRuntime(
                    "'state' is not available in --parallel mode. Rerun without --parallel if you need shared mutable state; use track_* helpers for parallel-safe aggregation."
                        .into(),
                    Position::NONE,
                )
                .into())
            },
        );
}