use std::time::Duration;
#[derive(Debug, Clone)]
pub struct TranscriptEvent {
pub timestamp: Duration,
pub event_type: EventType,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventType {
Output,
Input,
Resize,
Marker,
}
impl TranscriptEvent {
#[must_use]
pub fn output(timestamp: Duration, data: impl Into<Vec<u8>>) -> Self {
Self {
timestamp,
event_type: EventType::Output,
data: data.into(),
}
}
#[must_use]
pub fn input(timestamp: Duration, data: impl Into<Vec<u8>>) -> Self {
Self {
timestamp,
event_type: EventType::Input,
data: data.into(),
}
}
#[must_use]
pub fn resize(timestamp: Duration, cols: u16, rows: u16) -> Self {
Self {
timestamp,
event_type: EventType::Resize,
data: format!("{cols}x{rows}").into_bytes(),
}
}
#[must_use]
pub fn marker(timestamp: Duration, label: &str) -> Self {
Self {
timestamp,
event_type: EventType::Marker,
data: label.as_bytes().to_vec(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TranscriptMetadata {
pub width: u16,
pub height: u16,
pub command: Option<String>,
pub title: Option<String>,
pub env: std::collections::HashMap<String, String>,
pub timestamp: Option<u64>,
pub duration: Option<Duration>,
}
impl TranscriptMetadata {
#[must_use]
pub fn new(width: u16, height: u16) -> Self {
Self {
width,
height,
..Default::default()
}
}
#[must_use]
pub fn with_command(mut self, cmd: impl Into<String>) -> Self {
self.command = Some(cmd.into());
self
}
#[must_use]
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
#[derive(Debug, Clone)]
pub struct Transcript {
pub metadata: TranscriptMetadata,
pub events: Vec<TranscriptEvent>,
}
impl Transcript {
#[must_use]
pub const fn new(metadata: TranscriptMetadata) -> Self {
Self {
metadata,
events: Vec::new(),
}
}
pub fn push(&mut self, event: TranscriptEvent) {
self.events.push(event);
}
#[must_use]
pub fn duration(&self) -> Duration {
self.events.last().map_or(Duration::ZERO, |e| e.timestamp)
}
#[must_use]
pub fn output_text(&self) -> String {
let output: Vec<u8> = self
.events
.iter()
.filter(|e| e.event_type == EventType::Output)
.flat_map(|e| e.data.clone())
.collect();
String::from_utf8_lossy(&output).into_owned()
}
#[must_use]
pub fn input_text(&self) -> String {
let input: Vec<u8> = self
.events
.iter()
.filter(|e| e.event_type == EventType::Input)
.flat_map(|e| e.data.clone())
.collect();
String::from_utf8_lossy(&input).into_owned()
}
#[must_use]
pub fn filter(&self, event_type: EventType) -> Vec<&TranscriptEvent> {
self.events
.iter()
.filter(|e| e.event_type == event_type)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transcript_events() {
let mut transcript = Transcript::new(TranscriptMetadata::new(80, 24));
transcript.push(TranscriptEvent::output(
Duration::from_millis(100),
b"hello",
));
transcript.push(TranscriptEvent::input(Duration::from_millis(200), b"world"));
assert_eq!(transcript.events.len(), 2);
assert_eq!(transcript.duration(), Duration::from_millis(200));
}
#[test]
fn transcript_output_text() {
let mut transcript = Transcript::new(TranscriptMetadata::new(80, 24));
transcript.push(TranscriptEvent::output(Duration::ZERO, b"hello "));
transcript.push(TranscriptEvent::output(
Duration::from_millis(100),
b"world",
));
assert_eq!(transcript.output_text(), "hello world");
}
}