mumu-process 0.1.0

Proces tools plugin for the Lava language
Documentation
// src/lib.rs (final, warnings removed)

// If your code also has a "spawn" bridging or other tasks, it might store them
// in `PROCESS_MANAGER`. For illustration, we keep it, but it's not strictly
// necessary if you only want `process:info`.

use mumu::{
    parser::interpreter::Interpreter,
    parser::types::{Value, FunctionValue},
    parser::interpreter::apply_one_function_value,
};
use std::ffi::c_void;
use std::{
    sync::{
        Arc,
        Mutex,
        mpsc::{channel, Receiver, Sender},
        atomic::{AtomicUsize, Ordering},
    },
    collections::HashMap,
};
use lazy_static::lazy_static;
use indexmap::IndexMap;
use whoami;

// A global counter so the test harness or others can track active tasks
pub static ACTIVE_TASKS: AtomicUsize = AtomicUsize::new(0);

// If you’re on Unix, we can get the actual UID; otherwise fallback to 0
#[cfg(unix)]
fn get_uid() -> i32 {
    use nix::unistd::getuid;
    getuid().as_raw() as i32
}
#[cfg(not(unix))]
fn get_uid() -> i32 {
    0
}

// We allow dead_code here because we don't construct these variants yet
#[allow(dead_code)]
enum ProcessMessage {
    Ok(usize, Value),
    Err(usize, String),
}

struct ProcessTask {
    callback: Box<FunctionValue>,
    done: bool,
}

struct ProcessManager {
    // Renamed to underscore to avoid warnings about unread fields
    _next_id: usize,
    tasks: HashMap<usize, ProcessTask>,
    _tx: Sender<ProcessMessage>,
    rx: Receiver<ProcessMessage>,
    active_count: AtomicUsize,
}

impl ProcessManager {
    fn new() -> Self {
        let (tx, rx) = channel();
        Self {
            _next_id: 0,
            tasks: HashMap::new(),
            _tx: tx,
            rx,
            active_count: AtomicUsize::new(0),
        }
    }

    fn poll_events(&mut self, interp: &mut Interpreter) {
        while let Ok(msg) = self.rx.try_recv() {
            match msg {
                ProcessMessage::Ok(id, data_val) => {
                    if let Some(t) = self.tasks.get_mut(&id) {
                        if !t.done {
                            t.done = true;
                            let _ = apply_one_function_value(interp, t.callback.clone(), data_val);
                        }
                    }
                }
                ProcessMessage::Err(id, err_str) => {
                    if let Some(t) = self.tasks.get_mut(&id) {
                        if !t.done {
                            t.done = true;
                            let mut map = IndexMap::new();
                            map.insert("error".to_string(), Value::SingleString(err_str));
                            let final_val = Value::KeyedArray(map);
                            let _ = apply_one_function_value(interp, t.callback.clone(), final_val);
                        }
                    }
                }
            }
        }

        let before = self.tasks.len();
        self.tasks.retain(|_, t| !t.done);
        let removed = before.saturating_sub(self.tasks.len());
        if removed > 0 {
            self.active_count.fetch_sub(removed, Ordering::SeqCst);
        }
    }

    fn count_tasks(&self) -> usize {
        self.active_count.load(Ordering::SeqCst)
    }
}

// Not currently called, so mark it unused to avoid warnings
#[allow(dead_code)]
fn process_spawn_bridge(
    _interp: &mut Interpreter,
    _args: Vec<Value>
) -> Result<Value, String> {
    // If you eventually need to spawn tasks, this is where you'd do it
    Ok(Value::Bool(true))
}

/// process:info => gather some system data in a callback
fn process_info_bridge(
    interp: &mut Interpreter,
    mut args: Vec<Value>
) -> Result<Value, String> {
    if args.len() != 1 {
        return Err(format!("process:info => expected 1 argument => callback, got {}", args.len()));
    }
    let callback_val = args.remove(0);
    let callback_func = match callback_val {
        Value::Function(fb) => fb,
        other => return Err(format!("process:info => first arg must be function, got {:?}", other)),
    };

    // Suppose we gather info synchronously here:
    let mut map = IndexMap::new();
    let pid_u32 = std::process::id();
    map.insert("pid".to_string(), Value::Int(pid_u32 as i32));
    map.insert("uid".to_string(), Value::Int(get_uid()));
    map.insert("username".to_string(), Value::SingleString(whoami::username()));
    map.insert("binary_name".to_string(), Value::SingleString("mumu".into()));
    map.insert("event_loop_len".to_string(), Value::Int(42));

    let info_val = Value::KeyedArray(map);

    // Call the user’s callback => callback(info_val)
    let _ = apply_one_function_value(interp, callback_func, info_val)?;

    Ok(Value::Bool(true))
}

/// process:check_tasks => poll manager, return how many tasks remain
fn process_check_tasks_bridge(
    interp: &mut Interpreter,
    _args: Vec<Value>
) -> Result<Value, String> {
    let mut mgr = PROCESS_MANAGER.lock().unwrap();
    mgr.poll_events(interp);
    let count = mgr.count_tasks();
    Ok(Value::Int(count as i32))
}

lazy_static! {
    static ref PROCESS_MANAGER: Mutex<ProcessManager> = Mutex::new(ProcessManager::new());
}

#[export_name = "Cargo_lock"]
pub unsafe extern "C" fn cargo_lock(
    interp_ptr: *mut c_void,
    _extra_str: *const c_void,
) -> i32 {
    if interp_ptr.is_null() {
        return 1;
    }
    let interp_ref = &mut *(interp_ptr as *mut Interpreter);

    // Register process:info bridging:
    let info_fn = Arc::new(Mutex::new(process_info_bridge));
    interp_ref.register_dynamic_function("process:info", info_fn);
    interp_ref.set_variable(
        "process:info",
        Value::Function(Box::new(FunctionValue::Named("process:info".to_string())))
    );

    // Register process:check_tasks bridging:
    let check_fn = Arc::new(Mutex::new(process_check_tasks_bridge));
    interp_ref.register_dynamic_function("process:check_tasks", check_fn);
    interp_ref.set_variable(
        "process:check_tasks",
        Value::Function(Box::new(FunctionValue::Named("process:check_tasks".to_string())))
    );

    // If you wanted process:spawn bridging, you’d do similarly, e.g.:
    // let spawn_fn = Arc::new(Mutex::new(process_spawn_bridge));
    // interp_ref.register_dynamic_function("process:spawn", spawn_fn);
    // interp_ref.set_variable(
    //     "process:spawn",
    //     Value::Function(Box::new(FunctionValue::Named("process:spawn".to_string())))
    // );

    // Add poller for background tasks if you have any
    let poller = Arc::new(Mutex::new(move |interp: &mut Interpreter| {
        let mut mgr = PROCESS_MANAGER.lock().unwrap();
        mgr.poll_events(interp);
        mgr.count_tasks()
    }));
    interp_ref.add_poller(poller);

    0
}