rust_expect/transcript/
recorder.rs1use std::sync::{Arc, Mutex};
4use std::time::{Duration, Instant};
5
6use super::format::{Transcript, TranscriptEvent, TranscriptMetadata};
7
8#[derive(Debug)]
10pub struct Recorder {
11 start: Instant,
13 transcript: Arc<Mutex<Transcript>>,
15 recording: bool,
17 max_duration: Option<Duration>,
19 max_events: Option<usize>,
21}
22
23impl Recorder {
24 #[must_use]
26 pub fn new(width: u16, height: u16) -> Self {
27 Self {
28 start: Instant::now(),
29 transcript: Arc::new(Mutex::new(Transcript::new(TranscriptMetadata::new(
30 width, height,
31 )))),
32 recording: true,
33 max_duration: None,
34 max_events: None,
35 }
36 }
37
38 #[must_use]
40 pub const fn with_max_duration(mut self, duration: Duration) -> Self {
41 self.max_duration = Some(duration);
42 self
43 }
44
45 #[must_use]
47 pub const fn with_max_events(mut self, count: usize) -> Self {
48 self.max_events = Some(count);
49 self
50 }
51
52 #[must_use]
54 pub fn elapsed(&self) -> Duration {
55 self.start.elapsed()
56 }
57
58 #[must_use]
60 pub const fn is_recording(&self) -> bool {
61 self.recording
62 }
63
64 pub fn stop(&mut self) {
66 self.recording = false;
67 if let Ok(mut t) = self.transcript.lock() {
68 t.metadata.duration = Some(self.elapsed());
69 }
70 }
71
72 pub fn record_output(&self, data: &[u8]) {
74 if !self.should_record() {
75 return;
76 }
77 self.push_event(TranscriptEvent::output(self.elapsed(), data));
78 }
79
80 pub fn record_input(&self, data: &[u8]) {
82 if !self.should_record() {
83 return;
84 }
85 self.push_event(TranscriptEvent::input(self.elapsed(), data));
86 }
87
88 pub fn record_resize(&self, cols: u16, rows: u16) {
90 if !self.should_record() {
91 return;
92 }
93 self.push_event(TranscriptEvent::resize(self.elapsed(), cols, rows));
94 }
95
96 pub fn add_marker(&self, label: &str) {
98 if !self.should_record() {
99 return;
100 }
101 self.push_event(TranscriptEvent::marker(self.elapsed(), label));
102 }
103
104 fn should_record(&self) -> bool {
106 if !self.recording {
107 return false;
108 }
109
110 if let Some(max_dur) = self.max_duration
111 && self.elapsed() > max_dur
112 {
113 return false;
114 }
115
116 if let Some(max_events) = self.max_events
117 && let Ok(t) = self.transcript.lock()
118 && t.events.len() >= max_events
119 {
120 return false;
121 }
122
123 true
124 }
125
126 fn push_event(&self, event: TranscriptEvent) {
128 if let Ok(mut t) = self.transcript.lock() {
129 t.push(event);
130 }
131 }
132
133 #[must_use]
135 pub fn transcript(&self) -> Arc<Mutex<Transcript>> {
136 Arc::clone(&self.transcript)
137 }
138
139 #[must_use]
141 pub fn into_transcript(self) -> Transcript {
142 Arc::try_unwrap(self.transcript)
143 .ok()
144 .and_then(|m| m.into_inner().ok())
145 .unwrap_or_else(|| Transcript::new(TranscriptMetadata::new(80, 24)))
146 }
147
148 #[must_use]
150 pub fn event_count(&self) -> usize {
151 self.transcript.lock().map(|t| t.events.len()).unwrap_or(0)
152 }
153}
154
155#[derive(Debug, Default)]
157pub struct RecorderBuilder {
158 width: u16,
159 height: u16,
160 command: Option<String>,
161 title: Option<String>,
162 max_duration: Option<Duration>,
163 max_events: Option<usize>,
164}
165
166impl RecorderBuilder {
167 #[must_use]
169 pub fn new() -> Self {
170 Self {
171 width: 80,
172 height: 24,
173 ..Default::default()
174 }
175 }
176
177 #[must_use]
179 pub const fn size(mut self, width: u16, height: u16) -> Self {
180 self.width = width;
181 self.height = height;
182 self
183 }
184
185 #[must_use]
187 pub fn command(mut self, cmd: impl Into<String>) -> Self {
188 self.command = Some(cmd.into());
189 self
190 }
191
192 #[must_use]
194 pub fn title(mut self, title: impl Into<String>) -> Self {
195 self.title = Some(title.into());
196 self
197 }
198
199 #[must_use]
201 pub const fn max_duration(mut self, duration: Duration) -> Self {
202 self.max_duration = Some(duration);
203 self
204 }
205
206 #[must_use]
208 pub const fn max_events(mut self, count: usize) -> Self {
209 self.max_events = Some(count);
210 self
211 }
212
213 #[must_use]
215 pub fn build(self) -> Recorder {
216 let mut recorder = Recorder::new(self.width, self.height);
217
218 if let Some(duration) = self.max_duration {
219 recorder.max_duration = Some(duration);
220 }
221 if let Some(events) = self.max_events {
222 recorder.max_events = Some(events);
223 }
224
225 if let Ok(mut t) = recorder.transcript.lock() {
226 t.metadata.command = self.command;
227 t.metadata.title = self.title;
228 }
229
230 recorder
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn recorder_basic() {
240 let recorder = Recorder::new(80, 24);
241 recorder.record_output(b"hello");
242 recorder.record_input(b"world");
243
244 assert_eq!(recorder.event_count(), 2);
245 }
246
247 #[test]
248 fn recorder_stop() {
249 let mut recorder = Recorder::new(80, 24);
250 recorder.record_output(b"before");
251 recorder.stop();
252 recorder.record_output(b"after");
253
254 assert_eq!(recorder.event_count(), 1);
255 }
256
257 #[test]
258 fn recorder_builder() {
259 let recorder = RecorderBuilder::new()
260 .size(120, 40)
261 .title("Test")
262 .max_events(10)
263 .build();
264
265 assert!(recorder.is_recording());
266 }
267}