use std::io::BufRead;
use serde::Deserialize;
use crate::{
Asciicast, Error, Reader,
versions::{
Streamable, V3,
common::{Env, ExitStatus, Resize, Theme},
},
};
pub type Recording = Asciicast<V3>;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Term {
pub cols: u16,
pub rows: u16,
#[serde(default)]
pub r#type: Option<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub theme: Option<Theme>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Header {
pub version: u8,
pub term: Term,
#[serde(default)]
pub timestamp: Option<i64>,
#[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 tags: Option<Vec<String>>,
}
#[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,
#[serde(rename = "x")]
Exit,
}
#[derive(Deserialize)]
struct RawEvent(f64, EventCode, String);
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum EventPayload {
Output(String),
Input(String),
Marker(String),
Resize(Resize),
Exit(ExitStatus),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
pub interval: f64,
pub payload: EventPayload,
}
impl TryFrom<RawEvent> for Event {
type Error = Error;
fn try_from(raw: RawEvent) -> Result<Self, Self::Error> {
let RawEvent(interval, 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)?),
EventCode::Exit => EventPayload::Exit(ExitStatus::parse(&data)?),
};
Ok(Self { interval, 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,
EventPayload::Exit(_) => EventCode::Exit,
}
}
#[must_use]
pub fn as_output(&self) -> Option<&str> {
match &self.payload {
EventPayload::Output(s) => Some(s),
EventPayload::Input(_)
| EventPayload::Marker(_)
| EventPayload::Resize(_)
| EventPayload::Exit(_) => None,
}
}
#[must_use]
pub fn as_input(&self) -> Option<&str> {
match &self.payload {
EventPayload::Input(s) => Some(s),
EventPayload::Output(_)
| EventPayload::Marker(_)
| EventPayload::Resize(_)
| EventPayload::Exit(_) => None,
}
}
#[must_use]
pub fn as_marker(&self) -> Option<&str> {
match &self.payload {
EventPayload::Marker(s) => Some(s),
EventPayload::Output(_)
| EventPayload::Input(_)
| EventPayload::Resize(_)
| EventPayload::Exit(_) => None,
}
}
#[must_use]
pub fn as_resize(&self) -> Option<Resize> {
match &self.payload {
EventPayload::Resize(r) => Some(*r),
EventPayload::Output(_)
| EventPayload::Input(_)
| EventPayload::Marker(_)
| EventPayload::Exit(_) => None,
}
}
#[must_use]
pub fn as_exit(&self) -> Option<ExitStatus> {
match &self.payload {
EventPayload::Exit(status) => Some(*status),
EventPayload::Output(_)
| EventPayload::Input(_)
| EventPayload::Marker(_)
| EventPayload::Resize(_) => None,
}
}
}
impl Streamable for V3 {
const SKIP_COMMENTS: bool = true;
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<V3, R>, Error> {
Reader::open(reader)
}
pub(crate) fn parse<R: BufRead>(reader: R) -> Result<Asciicast<V3>, Error> {
Reader::<V3, R>::open(reader)?.into_recording()
}