use crate::{RNode, tree};
use day_spec::PieceKind;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
pub type PieceOperationDone = Box<dyn FnOnce(Result<String, String>)>;
pub type PieceOperationFn = Rc<dyn Fn(RNode, &str, PieceOperationDone)>;
type Operations = HashMap<(PieceKind, &'static str), PieceOperationFn>;
thread_local! {
static OPERATIONS: RefCell<Operations> = RefCell::new(HashMap::new());
}
pub fn register_piece_operation(kind: PieceKind, name: &'static str, handler: PieceOperationFn) {
OPERATIONS.with(|ops| ops.borrow_mut().insert((kind, name), handler));
}
pub fn piece_operation(node: RNode, name: &str, input: &str, done: PieceOperationDone) -> bool {
let Some(kind) = tree::with_tree(|t| t.node_kind(node)) else {
return false;
};
dispatch(kind, node, name, input, done)
}
fn dispatch(
kind: PieceKind,
node: RNode,
name: &str,
input: &str,
done: PieceOperationDone,
) -> bool {
let handler = OPERATIONS.with(|ops| ops.borrow().get(&(kind, name)).cloned());
let Some(handler) = handler else { return false };
handler(node, input, done);
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operations_are_scoped_by_kind_and_name_and_can_reenter() {
let node = RNode::default();
register_piece_operation(
"test.first",
"test.read",
Rc::new(|_, input, done| {
register_piece_operation(
"test.second",
"test.read",
Rc::new(|_, _, done| done(Ok("second".into()))),
);
done(Ok(input.to_uppercase()));
}),
);
register_piece_operation(
"test.first",
"test.write",
Rc::new(|_, _, done| done(Err("read-only".into()))),
);
let result = Rc::new(RefCell::new(None));
let call = |kind, name, input| {
let result = result.clone();
dispatch(
kind,
node,
name,
input,
Box::new(move |r| *result.borrow_mut() = Some(r)),
)
};
assert!(!call("test.absent", "test.read", "hello"));
assert!(!call("test.first", "test.absent", "hello"));
assert!(result.borrow().is_none());
assert!(call("test.first", "test.read", "hello"));
assert_eq!(result.borrow_mut().take(), Some(Ok("HELLO".into())));
assert!(call("test.second", "test.read", ""));
assert_eq!(result.borrow_mut().take(), Some(Ok("second".into())));
assert!(call("test.first", "test.write", ""));
assert_eq!(result.borrow_mut().take(), Some(Err("read-only".into())));
register_piece_operation(
"test.first",
"test.read",
Rc::new(|_, _, done| done(Ok("replacement".into()))),
);
assert!(call("test.first", "test.read", ""));
assert_eq!(result.borrow_mut().take(), Some(Ok("replacement".into())));
}
}