mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 19:58:03 +01:00
Make cat use first file's asciicast version
This commit is contained in:
108
src/asciicast.rs
108
src/asciicast.rs
@@ -4,6 +4,7 @@ mod v2;
|
|||||||
mod v3;
|
mod v3;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, BufRead};
|
use std::io::{self, BufRead};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -15,10 +16,18 @@ pub use v2::V2Encoder;
|
|||||||
pub use v3::V3Encoder;
|
pub use v3::V3Encoder;
|
||||||
|
|
||||||
pub struct Asciicast<'a> {
|
pub struct Asciicast<'a> {
|
||||||
|
pub version: Version,
|
||||||
pub header: Header,
|
pub header: Header,
|
||||||
pub events: Box<dyn Iterator<Item = Result<Event>> + 'a>,
|
pub events: Box<dyn Iterator<Item = Result<Event>> + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub enum Version {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
pub term_cols: u16,
|
pub term_cols: u16,
|
||||||
pub term_rows: u16,
|
pub term_rows: u16,
|
||||||
@@ -45,6 +54,30 @@ pub enum EventData {
|
|||||||
Other(char, String),
|
Other(char, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Encoder {
|
||||||
|
fn header(&mut self, header: &Header) -> Vec<u8>;
|
||||||
|
fn event(&mut self, event: &Event) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<u8> for Version {
|
||||||
|
fn eq(&self, other: &u8) -> bool {
|
||||||
|
matches!(
|
||||||
|
(self, other),
|
||||||
|
(Version::One, 1) | (Version::Two, 2) | (Version::Three, 3)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Version {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Version::One => write!(f, "1"),
|
||||||
|
Version::Two => write!(f, "2"),
|
||||||
|
Version::Three => write!(f, "3"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Header {
|
impl Default for Header {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -62,6 +95,26 @@ impl Default for Header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Encoder for V2Encoder {
|
||||||
|
fn header(&mut self, header: &Header) -> Vec<u8> {
|
||||||
|
self.header(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, event: &Event) -> Vec<u8> {
|
||||||
|
self.event(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder for V3Encoder {
|
||||||
|
fn header(&mut self, header: &Header) -> Vec<u8> {
|
||||||
|
self.header(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, event: &Event) -> Vec<u8> {
|
||||||
|
self.event(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_from_path<S: AsRef<Path>>(path: S) -> Result<Asciicast<'static>> {
|
pub fn open_from_path<S: AsRef<Path>>(path: S) -> Result<Asciicast<'static>> {
|
||||||
fs::File::open(path)
|
fs::File::open(path)
|
||||||
.map(io::BufReader::new)
|
.map(io::BufReader::new)
|
||||||
@@ -161,6 +214,14 @@ pub fn accelerate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn encoder(version: Version) -> Option<Box<dyn Encoder>> {
|
||||||
|
match version {
|
||||||
|
Version::One => None,
|
||||||
|
Version::Two => Some(Box::new(V2Encoder::new(0))),
|
||||||
|
Version::Three => Some(Box::new(V3Encoder::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Asciicast, Event, EventData, Header, V2Encoder};
|
use super::{Asciicast, Event, EventData, Header, V2Encoder};
|
||||||
@@ -171,11 +232,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_v1_minimal() {
|
fn open_v1_minimal() {
|
||||||
let Asciicast { header, events } =
|
let Asciicast {
|
||||||
super::open_from_path("tests/casts/minimal.json").unwrap();
|
version,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
} = super::open_from_path("tests/casts/minimal.json").unwrap();
|
||||||
|
|
||||||
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, 1);
|
||||||
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
||||||
assert!(header.term_theme.is_none());
|
assert!(header.term_theme.is_none());
|
||||||
|
|
||||||
@@ -185,9 +250,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_v1_full() {
|
fn open_v1_full() {
|
||||||
let Asciicast { header, events } = super::open_from_path("tests/casts/full.json").unwrap();
|
let Asciicast {
|
||||||
|
version,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
} = super::open_from_path("tests/casts/full.json").unwrap();
|
||||||
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, 1);
|
||||||
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
||||||
|
|
||||||
assert_eq!(events[0].time, 1);
|
assert_eq!(events[0].time, 1);
|
||||||
@@ -202,11 +272,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_v2_minimal() {
|
fn open_v2_minimal() {
|
||||||
let Asciicast { header, events } =
|
let Asciicast {
|
||||||
super::open_from_path("tests/casts/minimal-v2.cast").unwrap();
|
version,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
} = super::open_from_path("tests/casts/minimal-v2.cast").unwrap();
|
||||||
|
|
||||||
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, 2);
|
||||||
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
||||||
assert!(header.term_theme.is_none());
|
assert!(header.term_theme.is_none());
|
||||||
|
|
||||||
@@ -216,11 +290,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_v2_full() {
|
fn open_v2_full() {
|
||||||
let Asciicast { header, events } =
|
let Asciicast {
|
||||||
super::open_from_path("tests/casts/full-v2.cast").unwrap();
|
version,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
} = super::open_from_path("tests/casts/full-v2.cast").unwrap();
|
||||||
let events = events.take(5).collect::<Result<Vec<Event>>>().unwrap();
|
let events = events.take(5).collect::<Result<Vec<Event>>>().unwrap();
|
||||||
let theme = header.term_theme.unwrap();
|
let theme = header.term_theme.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, 2);
|
||||||
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
||||||
assert_eq!(header.timestamp, Some(1509091818));
|
assert_eq!(header.timestamp, Some(1509091818));
|
||||||
assert_eq!(theme.fg, RGB8::new(0, 0, 0));
|
assert_eq!(theme.fg, RGB8::new(0, 0, 0));
|
||||||
@@ -248,11 +326,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_v3_minimal() {
|
fn open_v3_minimal() {
|
||||||
let Asciicast { header, events } =
|
let Asciicast {
|
||||||
super::open_from_path("tests/casts/minimal-v3.cast").unwrap();
|
version,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
} = super::open_from_path("tests/casts/minimal-v3.cast").unwrap();
|
||||||
|
|
||||||
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
let events = events.collect::<Result<Vec<Event>>>().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, 3);
|
||||||
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
||||||
assert!(header.term_theme.is_none());
|
assert!(header.term_theme.is_none());
|
||||||
|
|
||||||
@@ -262,11 +344,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_v3_full() {
|
fn open_v3_full() {
|
||||||
let Asciicast { header, events } =
|
let Asciicast {
|
||||||
super::open_from_path("tests/casts/full-v3.cast").unwrap();
|
version,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
} = super::open_from_path("tests/casts/full-v3.cast").unwrap();
|
||||||
let events = events.take(5).collect::<Result<Vec<Event>>>().unwrap();
|
let events = events.take(5).collect::<Result<Vec<Event>>>().unwrap();
|
||||||
let theme = header.term_theme.unwrap();
|
let theme = header.term_theme.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, 3);
|
||||||
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
assert_eq!((header.term_cols, header.term_rows), (100, 50));
|
||||||
assert_eq!(header.timestamp, Some(1509091818));
|
assert_eq!(header.timestamp, Some(1509091818));
|
||||||
assert_eq!(theme.fg, RGB8::new(0, 0, 0));
|
assert_eq!(theme.fg, RGB8::new(0, 0, 0));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::{Asciicast, Event, Header};
|
use super::{Asciicast, Event, Header, Version};
|
||||||
use crate::asciicast::util::deserialize_time;
|
use crate::asciicast::util::deserialize_time;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -57,5 +57,9 @@ pub fn load(json: String) -> Result<Asciicast<'static>> {
|
|||||||
Some(Ok(Event::output(time, event.data)))
|
Some(Ok(Event::output(time, event.data)))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(Asciicast { header, events })
|
Ok(Asciicast {
|
||||||
|
version: Version::One,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::io;
|
|||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
use super::{util, Asciicast, Event, EventData, Header};
|
use super::{util, Asciicast, Event, EventData, Header, Version};
|
||||||
use crate::tty::TtyTheme;
|
use crate::tty::TtyTheme;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -87,7 +87,11 @@ impl Parser {
|
|||||||
|
|
||||||
let events = Box::new(lines.filter_map(parse_line));
|
let events = Box::new(lines.filter_map(parse_line));
|
||||||
|
|
||||||
Asciicast { header, events }
|
Asciicast {
|
||||||
|
version: Version::Two,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::io;
|
|||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
use super::{util, Asciicast, Event, EventData, Header};
|
use super::{util, Asciicast, Event, EventData, Header, Version};
|
||||||
use crate::tty::TtyTheme;
|
use crate::tty::TtyTheme;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -103,7 +103,11 @@ impl Parser {
|
|||||||
|
|
||||||
let events = Box::new(lines.filter_map(move |line| self.parse_line(line)));
|
let events = Box::new(lines.filter_map(move |line| self.parse_line(line)));
|
||||||
|
|
||||||
Asciicast { header, events }
|
Asciicast {
|
||||||
|
version: Version::Three,
|
||||||
|
header,
|
||||||
|
events,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_line(&mut self, line: io::Result<String>) -> Option<Result<Event>> {
|
fn parse_line(&mut self, line: io::Result<String>) -> Option<Result<Event>> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use crate::asciicast;
|
use crate::asciicast;
|
||||||
use crate::cli;
|
use crate::cli;
|
||||||
@@ -9,11 +9,21 @@ use crate::config::Config;
|
|||||||
|
|
||||||
impl cli::Cat {
|
impl cli::Cat {
|
||||||
pub fn run(self, _config: &Config) -> Result<()> {
|
pub fn run(self, _config: &Config) -> Result<()> {
|
||||||
let mut encoder = asciicast::V2Encoder::new(0);
|
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
let mut time_offset: u64 = 0;
|
let mut time_offset: u64 = 0;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
|
|
||||||
|
let casts = self
|
||||||
|
.filename
|
||||||
|
.iter()
|
||||||
|
.map(asciicast::open_from_path)
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let version = casts[0].version;
|
||||||
|
|
||||||
|
let mut encoder = asciicast::encoder(version)
|
||||||
|
.ok_or(anyhow!("asciicast v{version} files can't be concatenated"))?;
|
||||||
|
|
||||||
for path in self.filename.iter() {
|
for path in self.filename.iter() {
|
||||||
let recording = asciicast::open_from_path(path)?;
|
let recording = asciicast::open_from_path(path)?;
|
||||||
let mut time = time_offset;
|
let mut time = time_offset;
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ use std::io::Write;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::asciicast::Event;
|
use crate::asciicast::{Event, Header};
|
||||||
use crate::asciicast::Header;
|
|
||||||
pub use asciicast::{AsciicastV2Encoder, AsciicastV3Encoder};
|
pub use asciicast::{AsciicastV2Encoder, AsciicastV3Encoder};
|
||||||
pub use raw::RawEncoder;
|
pub use raw::RawEncoder;
|
||||||
pub use txt::TextEncoder;
|
pub use txt::TextEncoder;
|
||||||
|
|||||||
Reference in New Issue
Block a user