rust_expect/transcript/
format.rs1use std::time::Duration;
4
5#[derive(Debug, Clone)]
7pub struct TranscriptEvent {
8 pub timestamp: Duration,
10 pub event_type: EventType,
12 pub data: Vec<u8>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum EventType {
19 Output,
21 Input,
23 Resize,
25 Marker,
27}
28
29impl TranscriptEvent {
30 #[must_use]
32 pub fn output(timestamp: Duration, data: impl Into<Vec<u8>>) -> Self {
33 Self {
34 timestamp,
35 event_type: EventType::Output,
36 data: data.into(),
37 }
38 }
39
40 #[must_use]
42 pub fn input(timestamp: Duration, data: impl Into<Vec<u8>>) -> Self {
43 Self {
44 timestamp,
45 event_type: EventType::Input,
46 data: data.into(),
47 }
48 }
49
50 #[must_use]
52 pub fn resize(timestamp: Duration, cols: u16, rows: u16) -> Self {
53 Self {
54 timestamp,
55 event_type: EventType::Resize,
56 data: format!("{cols}x{rows}").into_bytes(),
57 }
58 }
59
60 #[must_use]
62 pub fn marker(timestamp: Duration, label: &str) -> Self {
63 Self {
64 timestamp,
65 event_type: EventType::Marker,
66 data: label.as_bytes().to_vec(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Default)]
73pub struct TranscriptMetadata {
74 pub width: u16,
76 pub height: u16,
78 pub command: Option<String>,
80 pub title: Option<String>,
82 pub env: std::collections::HashMap<String, String>,
84 pub timestamp: Option<u64>,
86 pub duration: Option<Duration>,
88}
89
90impl TranscriptMetadata {
91 #[must_use]
93 pub fn new(width: u16, height: u16) -> Self {
94 Self {
95 width,
96 height,
97 ..Default::default()
98 }
99 }
100
101 #[must_use]
103 pub fn with_command(mut self, cmd: impl Into<String>) -> Self {
104 self.command = Some(cmd.into());
105 self
106 }
107
108 #[must_use]
110 pub fn with_title(mut self, title: impl Into<String>) -> Self {
111 self.title = Some(title.into());
112 self
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct Transcript {
119 pub metadata: TranscriptMetadata,
121 pub events: Vec<TranscriptEvent>,
123}
124
125impl Transcript {
126 #[must_use]
128 pub const fn new(metadata: TranscriptMetadata) -> Self {
129 Self {
130 metadata,
131 events: Vec::new(),
132 }
133 }
134
135 pub fn push(&mut self, event: TranscriptEvent) {
137 self.events.push(event);
138 }
139
140 #[must_use]
142 pub fn duration(&self) -> Duration {
143 self.events.last().map_or(Duration::ZERO, |e| e.timestamp)
144 }
145
146 #[must_use]
148 pub fn output_text(&self) -> String {
149 let output: Vec<u8> = self
150 .events
151 .iter()
152 .filter(|e| e.event_type == EventType::Output)
153 .flat_map(|e| e.data.clone())
154 .collect();
155 String::from_utf8_lossy(&output).into_owned()
156 }
157
158 #[must_use]
160 pub fn input_text(&self) -> String {
161 let input: Vec<u8> = self
162 .events
163 .iter()
164 .filter(|e| e.event_type == EventType::Input)
165 .flat_map(|e| e.data.clone())
166 .collect();
167 String::from_utf8_lossy(&input).into_owned()
168 }
169
170 #[must_use]
172 pub fn filter(&self, event_type: EventType) -> Vec<&TranscriptEvent> {
173 self.events
174 .iter()
175 .filter(|e| e.event_type == event_type)
176 .collect()
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn transcript_events() {
186 let mut transcript = Transcript::new(TranscriptMetadata::new(80, 24));
187 transcript.push(TranscriptEvent::output(
188 Duration::from_millis(100),
189 b"hello",
190 ));
191 transcript.push(TranscriptEvent::input(Duration::from_millis(200), b"world"));
192
193 assert_eq!(transcript.events.len(), 2);
194 assert_eq!(transcript.duration(), Duration::from_millis(200));
195 }
196
197 #[test]
198 fn transcript_output_text() {
199 let mut transcript = Transcript::new(TranscriptMetadata::new(80, 24));
200 transcript.push(TranscriptEvent::output(Duration::ZERO, b"hello "));
201 transcript.push(TranscriptEvent::output(
202 Duration::from_millis(100),
203 b"world",
204 ));
205
206 assert_eq!(transcript.output_text(), "hello world");
207 }
208}