use rhai::{Dynamic, Engine, ImmutableString};
use std::cell::Cell;
thread_local! {
static EXIT_REQUESTED: Cell<bool> = const { Cell::new(false) };
static EXIT_CODE: Cell<i32> = const { Cell::new(0) };
static SKIP_REQUESTED: Cell<bool> = const { Cell::new(false) };
}
pub fn exit_process(code: i64, msg: Dynamic) -> Dynamic {
let exit_code = code.clamp(0, 255) as i32;
EXIT_CODE.with(|ec| ec.set(exit_code));
if !msg.is_unit() {
if let Some(s) = msg.read_lock::<ImmutableString>() {
eprintln!("{}", s.as_str());
} else {
eprintln!("{}", msg);
}
}
EXIT_REQUESTED.with(|er| er.set(true));
#[cfg(not(test))]
{
std::process::exit(exit_code);
}
#[cfg(test)]
Dynamic::UNIT
}
pub fn is_exit_requested() -> bool {
EXIT_REQUESTED.with(|er| er.get())
}
pub fn get_exit_code() -> i32 {
EXIT_CODE.with(|ec| ec.get())
}
pub fn skip_event() -> Dynamic {
SKIP_REQUESTED.with(|skip| skip.set(true));
Dynamic::UNIT
}
pub fn take_skip_request() -> bool {
SKIP_REQUESTED.with(|skip| {
let requested = skip.get();
skip.set(false);
requested
})
}
#[cfg(test)]
pub fn is_skip_requested() -> bool {
SKIP_REQUESTED.with(|skip| skip.get())
}
pub fn clear_skip_request() {
SKIP_REQUESTED.with(|skip| skip.set(false));
}
#[cfg(test)]
pub fn reset_exit_state() {
EXIT_REQUESTED.with(|er| er.set(false));
EXIT_CODE.with(|ec| ec.set(0));
SKIP_REQUESTED.with(|skip| skip.set(false));
}
pub fn exit_process_single(code: i64) -> Dynamic {
exit_process(code, Dynamic::UNIT)
}
pub fn register_functions(engine: &mut Engine) {
engine.register_fn("exit", exit_process_single);
engine.register_fn("exit", exit_process);
engine.register_fn("skip", skip_event);
}
#[cfg(test)]
mod tests {
use super::*;
use rhai::Engine;
#[test]
fn test_exit_with_code_only() {
reset_exit_state();
let result = exit_process(42, Dynamic::UNIT);
assert!(result.is_unit());
assert!(is_exit_requested());
assert_eq!(get_exit_code(), 42);
}
#[test]
fn test_exit_with_message() {
reset_exit_state();
let msg = Dynamic::from("Test error message");
let result = exit_process(1, msg);
assert!(result.is_unit());
assert!(is_exit_requested());
assert_eq!(get_exit_code(), 1);
}
#[test]
fn test_exit_code_clamping() {
reset_exit_state();
let _ = exit_process(-5, Dynamic::UNIT);
assert_eq!(get_exit_code(), 0);
reset_exit_state();
let _ = exit_process(300, Dynamic::UNIT);
assert_eq!(get_exit_code(), 255);
}
#[test]
fn test_rhai_integration() {
reset_exit_state();
let mut engine = Engine::new();
register_functions(&mut engine);
let result = engine.eval::<Dynamic>("exit(123)");
if let Err(e) = &result {
eprintln!("Rhai error: {}", e);
}
assert!(result.is_ok());
eprintln!("Exit requested: {}", is_exit_requested());
eprintln!("Exit code: {}", get_exit_code());
assert!(is_exit_requested());
assert_eq!(get_exit_code(), 123);
reset_exit_state();
let result = engine.eval::<Dynamic>(r#"exit(1, "Error occurred")"#);
assert!(result.is_ok());
assert!(is_exit_requested());
assert_eq!(get_exit_code(), 1);
}
#[test]
fn test_skip_request_lifecycle() {
reset_exit_state();
assert!(!is_skip_requested());
let result = skip_event();
assert!(result.is_unit());
assert!(is_skip_requested());
assert!(take_skip_request());
assert!(!is_skip_requested());
}
}