use std::io::BufRead;
use serde::Deserialize;
use crate::{
Asciicast, Error, Reader,
versions::{
Streamable, V2,
common::{Env, Resize, Theme},
},
};
pub type Recording = Asciicast<V2>;
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Header {
pub version: u8,
pub width: u16,
pub height: u16,
#[serde(default)]
pub timestamp: Option<i64>,
#[serde(default)]
pub duration: Option<f64>,
#[serde(default)]
pub idle_time_limit: Option<f64>,
#[serde(default)]
pub command: Option<String>,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub env: Option<Env>,
#[serde(default)]
pub theme: Option<Theme>,
}
#[cfg(feature = "chrono")]
impl Header {
#[must_use]
pub fn timestamp_datetime(&self) -> Option<chrono::DateTime<chrono::Utc>> {
self.timestamp
.and_then(|seconds| chrono::DateTime::from_timestamp(seconds, 0))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[non_exhaustive]
pub enum EventCode {
#[serde(rename = "o")]
Output,
#[serde(rename = "i")]
Input,
#[serde(rename = "m")]
Marker,
#[serde(rename = "r")]
Resize,
}
#[derive(Deserialize)]
struct RawEvent(f64, EventCode, String);
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum EventPayload {
Output(String),
Input(String),
Marker(String),
Resize(Resize),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
pub time: f64,
pub payload: EventPayload,
}
impl TryFrom<RawEvent> for Event {
type Error = Error;
fn try_from(raw: RawEvent) -> Result<Self, Self::Error> {
let RawEvent(time, code, data) = raw;
let payload = match code {
EventCode::Output => EventPayload::Output(data),
EventCode::Input => EventPayload::Input(data),
EventCode::Marker => EventPayload::Marker(data),
EventCode::Resize => EventPayload::Resize(Resize::parse(&data)?),
};
Ok(Self { time, payload })
}
}
impl Event {
#[must_use]
pub fn code(&self) -> EventCode {
match self.payload {
EventPayload::Output(_) => EventCode::Output,
EventPayload::Input(_) => EventCode::Input,
EventPayload::Marker(_) => EventCode::Marker,
EventPayload::Resize(_) => EventCode::Resize,
}
}
#[must_use]
pub fn as_output(&self) -> Option<&str> {
match &self.payload {
EventPayload::Output(s) => Some(s),
EventPayload::Input(_) | EventPayload::Marker(_) | EventPayload::Resize(_) => None,
}
}
#[must_use]
pub fn as_input(&self) -> Option<&str> {
match &self.payload {
EventPayload::Input(s) => Some(s),
EventPayload::Output(_) | EventPayload::Marker(_) | EventPayload::Resize(_) => None,
}
}
#[must_use]
pub fn as_marker(&self) -> Option<&str> {
match &self.payload {
EventPayload::Marker(s) => Some(s),
EventPayload::Output(_) | EventPayload::Input(_) | EventPayload::Resize(_) => None,
}
}
#[must_use]
pub fn as_resize(&self) -> Option<Resize> {
match &self.payload {
EventPayload::Resize(r) => Some(*r),
EventPayload::Output(_) | EventPayload::Input(_) | EventPayload::Marker(_) => None,
}
}
}
impl Streamable for V2 {
const SKIP_COMMENTS: bool = false;
fn header_version(header: &Header) -> u8 {
header.version
}
fn parse_event(line: &str) -> Result<Event, Error> {
Event::try_from(serde_json::from_str::<RawEvent>(line)?)
}
}
pub fn stream<R: BufRead>(reader: R) -> Result<Reader<V2, R>, Error> {
Reader::open(reader)
}
pub(crate) fn parse<R: BufRead>(reader: R) -> Result<Asciicast<V2>, Error> {
Reader::<V2, R>::open(reader)?.into_recording()
}