rust_expect/interact/
hooks.rs1use std::sync::Arc;
4
5pub type InputHook = Arc<dyn Fn(&[u8]) -> Vec<u8> + Send + Sync>;
7
8pub type OutputHook = Arc<dyn Fn(&[u8]) -> Vec<u8> + Send + Sync>;
10
11pub type EventHook = Arc<dyn Fn(InteractionEvent) + Send + Sync>;
13
14#[derive(Debug, Clone)]
16pub enum InteractionEvent {
17 Started,
19 Ended,
21 Input(Vec<u8>),
23 Output(Vec<u8>),
25 ExitRequested,
27 EscapeSequence(Vec<u8>),
29 Resize {
31 cols: u16,
33 rows: u16,
35 },
36}
37
38#[derive(Default)]
40pub struct HookManager {
41 inputs: Vec<InputHook>,
43 outputs: Vec<OutputHook>,
45 events: Vec<EventHook>,
47}
48
49impl HookManager {
50 #[must_use]
52 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn add_input_hook<F>(&mut self, hook: F)
58 where
59 F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static,
60 {
61 self.inputs.push(Arc::new(hook));
62 }
63
64 pub fn add_output_hook<F>(&mut self, hook: F)
66 where
67 F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static,
68 {
69 self.outputs.push(Arc::new(hook));
70 }
71
72 pub fn add_event_hook<F>(&mut self, hook: F)
74 where
75 F: Fn(InteractionEvent) + Send + Sync + 'static,
76 {
77 self.events.push(Arc::new(hook));
78 }
79
80 #[must_use]
82 pub fn process_input(&self, mut data: Vec<u8>) -> Vec<u8> {
83 for hook in &self.inputs {
84 data = hook(&data);
85 }
86 data
87 }
88
89 #[must_use]
91 pub fn process_output(&self, mut data: Vec<u8>) -> Vec<u8> {
92 for hook in &self.outputs {
93 data = hook(&data);
94 }
95 data
96 }
97
98 pub fn notify(&self, event: &InteractionEvent) {
100 for hook in &self.events {
101 hook(event.clone());
102 }
103 }
104
105 pub fn clear(&mut self) {
107 self.inputs.clear();
108 self.outputs.clear();
109 self.events.clear();
110 }
111}
112
113impl std::fmt::Debug for HookManager {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct("HookManager")
116 .field("inputs", &self.inputs.len())
117 .field("outputs", &self.outputs.len())
118 .field("events", &self.events.len())
119 .finish()
120 }
121}
122
123#[derive(Default)]
125pub struct HookBuilder {
126 manager: HookManager,
127}
128
129impl HookBuilder {
130 #[must_use]
132 pub fn new() -> Self {
133 Self::default()
134 }
135
136 #[must_use]
138 pub fn with_crlf(mut self) -> Self {
139 self.manager.add_input_hook(|data| {
140 let mut result = Vec::with_capacity(data.len() * 2);
141 for &b in data {
142 if b == b'\n' {
143 result.push(b'\r');
144 result.push(b'\n');
145 } else {
146 result.push(b);
147 }
148 }
149 result
150 });
151 self
152 }
153
154 #[must_use]
156 pub fn with_echo(mut self) -> Self {
157 self.manager.add_input_hook(|data| {
158 let _ = std::io::Write::write_all(&mut std::io::stdout(), data);
160 let _ = std::io::Write::flush(&mut std::io::stdout());
161 data.to_vec()
162 });
163 self
164 }
165
166 #[must_use]
168 pub fn with_logging(mut self) -> Self {
169 self.manager.add_event_hook(|event| match event {
170 InteractionEvent::Started => eprintln!("[interact] Session started"),
171 InteractionEvent::Ended => eprintln!("[interact] Session ended"),
172 InteractionEvent::ExitRequested => eprintln!("[interact] Exit requested"),
173 _ => {}
174 });
175 self
176 }
177
178 #[must_use]
180 pub fn build(self) -> HookManager {
181 self.manager
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn hook_manager_process_input() {
191 let mut manager = HookManager::new();
192 manager.add_input_hook(|data| data.iter().map(u8::to_ascii_uppercase).collect());
193
194 let result = manager.process_input(b"hello".to_vec());
195 assert_eq!(result, b"HELLO");
196 }
197
198 #[test]
199 fn hook_chain() {
200 let mut manager = HookManager::new();
201 manager.add_input_hook(|data| {
202 let mut v = data.to_vec();
203 v.push(b'1');
204 v
205 });
206 manager.add_input_hook(|data| {
207 let mut v = data.to_vec();
208 v.push(b'2');
209 v
210 });
211
212 let result = manager.process_input(b"x".to_vec());
213 assert_eq!(result, b"x12");
214 }
215
216 #[test]
217 fn hook_builder() {
218 let manager = HookBuilder::new().with_crlf().build();
219
220 let result = manager.process_input(b"a\nb".to_vec());
221 assert_eq!(result, b"a\r\nb");
222 }
223}