use std::sync::Arc;
pub type InputHook = Arc<dyn Fn(&[u8]) -> Vec<u8> + Send + Sync>;
pub type OutputHook = Arc<dyn Fn(&[u8]) -> Vec<u8> + Send + Sync>;
pub type EventHook = Arc<dyn Fn(InteractionEvent) + Send + Sync>;
#[derive(Debug, Clone)]
pub enum InteractionEvent {
Started,
Ended,
Input(Vec<u8>),
Output(Vec<u8>),
ExitRequested,
EscapeSequence(Vec<u8>),
Resize {
cols: u16,
rows: u16,
},
}
#[derive(Default)]
pub struct HookManager {
inputs: Vec<InputHook>,
outputs: Vec<OutputHook>,
events: Vec<EventHook>,
}
impl HookManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_input_hook<F>(&mut self, hook: F)
where
F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static,
{
self.inputs.push(Arc::new(hook));
}
pub fn add_output_hook<F>(&mut self, hook: F)
where
F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static,
{
self.outputs.push(Arc::new(hook));
}
pub fn add_event_hook<F>(&mut self, hook: F)
where
F: Fn(InteractionEvent) + Send + Sync + 'static,
{
self.events.push(Arc::new(hook));
}
#[must_use]
pub fn process_input(&self, mut data: Vec<u8>) -> Vec<u8> {
for hook in &self.inputs {
data = hook(&data);
}
data
}
#[must_use]
pub fn process_output(&self, mut data: Vec<u8>) -> Vec<u8> {
for hook in &self.outputs {
data = hook(&data);
}
data
}
pub fn notify(&self, event: &InteractionEvent) {
for hook in &self.events {
hook(event.clone());
}
}
pub fn clear(&mut self) {
self.inputs.clear();
self.outputs.clear();
self.events.clear();
}
}
impl std::fmt::Debug for HookManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HookManager")
.field("inputs", &self.inputs.len())
.field("outputs", &self.outputs.len())
.field("events", &self.events.len())
.finish()
}
}
#[derive(Default)]
pub struct HookBuilder {
manager: HookManager,
}
impl HookBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_crlf(mut self) -> Self {
self.manager.add_input_hook(|data| {
let mut result = Vec::with_capacity(data.len() * 2);
for &b in data {
if b == b'\n' {
result.push(b'\r');
result.push(b'\n');
} else {
result.push(b);
}
}
result
});
self
}
#[must_use]
pub fn with_echo(mut self) -> Self {
self.manager.add_input_hook(|data| {
let _ = std::io::Write::write_all(&mut std::io::stdout(), data);
let _ = std::io::Write::flush(&mut std::io::stdout());
data.to_vec()
});
self
}
#[must_use]
pub fn with_logging(mut self) -> Self {
self.manager.add_event_hook(|event| match event {
InteractionEvent::Started => eprintln!("[interact] Session started"),
InteractionEvent::Ended => eprintln!("[interact] Session ended"),
InteractionEvent::ExitRequested => eprintln!("[interact] Exit requested"),
_ => {}
});
self
}
#[must_use]
pub fn build(self) -> HookManager {
self.manager
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hook_manager_process_input() {
let mut manager = HookManager::new();
manager.add_input_hook(|data| data.iter().map(u8::to_ascii_uppercase).collect());
let result = manager.process_input(b"hello".to_vec());
assert_eq!(result, b"HELLO");
}
#[test]
fn hook_chain() {
let mut manager = HookManager::new();
manager.add_input_hook(|data| {
let mut v = data.to_vec();
v.push(b'1');
v
});
manager.add_input_hook(|data| {
let mut v = data.to_vec();
v.push(b'2');
v
});
let result = manager.process_input(b"x".to_vec());
assert_eq!(result, b"x12");
}
#[test]
fn hook_builder() {
let manager = HookBuilder::new().with_crlf().build();
let result = manager.process_input(b"a\nb".to_vec());
assert_eq!(result, b"a\r\nb");
}
}