mod event;
mod header;
use super::{util, ErrorType};
use event::Event;
use header::Header;
use serde_json::ser::to_writer;
use std::{collections::HashMap, io::Write};
pub struct AsciiCast<'a, T>
where
T: Write + ?Sized,
{
header: Option<Header>,
writer: &'a mut T,
}
impl<'a, T> AsciiCast<'a, T>
where
T: Write + ?Sized,
{
pub fn new(writer: &'a mut T) -> Self {
Self {
header: Some(Header::new()),
writer,
}
}
pub fn width(&mut self, width: u16) -> Result<&mut Self, ErrorType> {
self.get_header_mut()?.width = width;
Ok(self)
}
pub fn height(&mut self, height: u16) -> Result<&mut Self, ErrorType> {
self.get_header_mut()?.height = height;
Ok(self)
}
pub fn timestamp(&mut self, timestamp: u64) -> Result<&mut Self, ErrorType> {
self.get_header_mut()?.timestamp = Some(timestamp);
Ok(self)
}
pub fn idle_time_limit(&mut self, idle_time_limit: f64) -> Result<&mut Self, ErrorType> {
self.get_header_mut()?.idle_time_limit = Some(idle_time_limit);
Ok(self)
}
pub fn title(&mut self, title: String) -> Result<&mut Self, ErrorType> {
self.get_header_mut()?.title = Some(title);
Ok(self)
}
pub fn capture(&mut self, env_vars: HashMap<String, String>) -> Result<&mut Self, ErrorType> {
self.get_header_mut()?.env = if env_vars.is_empty() {
None
} else {
Some(env_vars)
};
Ok(self)
}
pub fn write_header(&mut self) -> Result<&mut Self, ErrorType> {
let header = self.header.take().ok_or(ErrorType::HeaderAlreadyWritten)?;
to_writer(&mut self.writer, &header)?;
writeln!(&mut self.writer)?;
Ok(self)
}
fn try_write_header(&mut self) -> Result<(), ErrorType> {
if self.header.is_some() {
self.write_header()?;
}
Ok(())
}
fn get_header_mut(&mut self) -> Result<&mut Header, ErrorType> {
self.header.as_mut().ok_or(ErrorType::HeaderAlreadyWritten)
}
pub fn output(&mut self, time: u128, data: &str) -> Result<&mut Self, ErrorType> {
self.try_write_header()?;
self.event(&Event::output(time, data))?;
Ok(self)
}
pub fn input(&mut self, time: u128, data: &str) -> Result<&mut Self, ErrorType> {
self.try_write_header()?;
self.event(&Event::input(time, data))?;
Ok(self)
}
pub fn marker(&mut self, time: u128, name: &str) -> Result<&mut Self, ErrorType> {
self.try_write_header()?;
self.event(&Event::marker(time, name))?;
Ok(self)
}
pub fn resize(&mut self, time: u128, columns: u16, rows: u16) -> Result<&mut Self, ErrorType> {
self.try_write_header()?;
self.event(&Event::resize(time, &format!("{columns}x{rows}")))?;
Ok(self)
}
fn event(&mut self, event: &Event) -> Result<(), ErrorType> {
event.write(&mut self.writer)?;
writeln!(&mut self.writer)?;
Ok(())
}
pub fn finish(&mut self) -> Result<(), ErrorType> {
self.try_write_header()?;
self.writer.flush()?;
Ok(())
}
}
impl<'a, T> From<&'a mut T> for AsciiCast<'a, T>
where
T: Write + ?Sized,
{
fn from(writer: &'a mut T) -> Self {
Self::new(writer)
}
}
impl<T> Drop for AsciiCast<'_, T>
where
T: Write + ?Sized,
{
fn drop(&mut self) {
if let Err(err) = self.finish() {
eprintln!("Error while calling `finish` on AsciiCast: {err}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_write_header() -> Result<(), ErrorType> {
let mut writer = Vec::new();
AsciiCast::new(&mut writer)
.width(80)?
.height(24)?
.timestamp(1_000_000)?
.idle_time_limit(2.5)?
.title("Test".to_string())?
.capture(HashMap::new())?
.write_header()?
.output(0, "Hello, world!")?
.input(100, "echo Hello, world!")?
.marker(200, "marker")?
.resize(300, 80, 25)?;
let expected = r#"{"version":2,"width":80,"height":24,"timestamp":1000000,"idle_time_limit":2.5,"title":"Test"}
[0.000000,"o","Hello, world!"]
[0.000100,"i","echo Hello, world!"]
[0.000200,"m","marker"]
[0.000300,"r","80x25"]
"#;
assert_eq!(String::from_utf8(writer).unwrap(), expected);
Ok(())
}
#[test]
fn implicit_write_header() -> Result<(), ErrorType> {
let mut writer = Vec::new();
AsciiCast::new(&mut writer)
.width(80)?
.height(24)?
.timestamp(1_000_000)?
.idle_time_limit(2.5)?
.title("Test".to_string())?
.capture(HashMap::new())?
.output(0, "Hello, world!")?
.input(100, "echo Hello, world!")?
.marker(200, "marker")?
.resize(300, 80, 25)?;
let expected = r#"{"version":2,"width":80,"height":24,"timestamp":1000000,"idle_time_limit":2.5,"title":"Test"}
[0.000000,"o","Hello, world!"]
[0.000100,"i","echo Hello, world!"]
[0.000200,"m","marker"]
[0.000300,"r","80x25"]
"#;
assert_eq!(String::from_utf8(writer).unwrap(), expected);
Ok(())
}
#[test]
fn explicit_header_already_written() -> Result<(), ErrorType> {
let mut writer = std::io::sink();
let mut asciicast = AsciiCast::new(&mut writer);
asciicast.width(80)?;
asciicast.write_header()?;
match asciicast.width(80) {
Ok(_) => panic!("Expected error"),
Err(err) => assert_eq!(err, ErrorType::HeaderAlreadyWritten),
};
Ok(())
}
#[test]
fn implicit_header_already_written() -> Result<(), ErrorType> {
let mut writer = std::io::sink();
let mut asciicast = AsciiCast::new(&mut writer);
asciicast.width(80)?;
asciicast.output(0, "Hello, world!")?;
match asciicast.width(80) {
Ok(_) => panic!("Expected error"),
Err(err) => assert_eq!(err, ErrorType::HeaderAlreadyWritten),
};
Ok(())
}
}