Make Encoder return bytes instead of directly writing to io::Write impl

This commit is contained in:
Marcin Kulik
2024-10-17 17:31:59 +02:00
parent 75e275082a
commit 4ad1c22a29
14 changed files with 239 additions and 293 deletions

5
Cargo.lock generated
View File

@@ -179,12 +179,11 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "avt"
version = "0.11.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cb3c38905502a1f46c37ae2d3373c36a3aaf3cd9f7fde871984ef870f039bd6"
checksum = "b485f400d02970694eed10e7080f994ad82eaf56a867d6671af5d5e184ed8ee6"
dependencies = [
"rgb",
"serde",
"unicode-width",
]

View File

@@ -28,7 +28,7 @@ config = { version = "0.14.0", default-features = false, features = ["toml", "in
which = "6.0.0"
tempfile = "3.9.0"
scraper = { version = "0.19.0", default-features = false }
avt = "0.11.0"
avt = "0.14.0"
axum = { version = "0.7.4", default-features = false, features = ["http1", "ws"] }
tokio = { version = "1.35.1", features = ["full"] }
futures-util = "0.3.30"

View File

@@ -7,7 +7,7 @@ use std::collections::HashMap;
use std::fs;
use std::io::{self, BufRead};
use std::path::Path;
pub use v2::Writer;
pub use v2::Encoder;
pub struct Asciicast<'a> {
pub header: Header,
@@ -138,12 +138,11 @@ pub fn accelerate(
#[cfg(test)]
mod tests {
use super::{Asciicast, Event, EventData, Header, Writer};
use super::{Asciicast, Encoder, Event, EventData, Header};
use crate::tty;
use anyhow::Result;
use rgb::RGB8;
use std::collections::HashMap;
use std::io;
#[test]
fn open_v1_minimal() {
@@ -221,44 +220,30 @@ mod tests {
}
#[test]
fn writer() {
fn encoder() {
let mut data = Vec::new();
{
let mut fw = Writer::new(&mut data, 0);
let header = Header {
version: 2,
cols: 80,
rows: 24,
timestamp: None,
idle_time_limit: None,
command: None,
title: None,
env: Default::default(),
theme: None,
};
let header = Header {
version: 2,
cols: 80,
rows: 24,
timestamp: None,
idle_time_limit: None,
command: None,
title: None,
env: Default::default(),
theme: None,
};
let mut enc = Encoder::new(0);
data.extend(enc.header(&header));
data.extend(enc.event(&Event::output(1000001, "hello\r\n".to_owned())));
fw.write_header(&header).unwrap();
fw.write_event(&Event::output(1000001, "hello\r\n".to_owned()))
.unwrap();
}
{
let mut fw = Writer::new(&mut data, 1000001);
fw.write_event(&Event::output(1000001, "world".to_owned()))
.unwrap();
fw.write_event(&Event::input(2000002, " ".to_owned()))
.unwrap();
fw.write_event(&Event::resize(3000003, (100, 40))).unwrap();
fw.write_event(&Event::output(4000004, "żółć".to_owned()))
.unwrap();
}
let mut enc = Encoder::new(1000001);
data.extend(enc.event(&Event::output(1000001, "world".to_owned())));
data.extend(enc.event(&Event::input(2000002, " ".to_owned())));
data.extend(enc.event(&Event::resize(3000003, (100, 40))));
data.extend(enc.event(&Event::output(4000004, "żółć".to_owned())));
let lines = parse(data);
@@ -284,53 +269,48 @@ mod tests {
}
#[test]
fn write_header() {
let mut data = Vec::new();
fn header_encoding() {
let mut enc = Encoder::new(0);
let mut env = HashMap::new();
env.insert("SHELL".to_owned(), "/usr/bin/fish".to_owned());
env.insert("TERM".to_owned(), "xterm256-color".to_owned());
{
let mut fw = Writer::new(io::Cursor::new(&mut data), 0);
let mut env = HashMap::new();
env.insert("SHELL".to_owned(), "/usr/bin/fish".to_owned());
env.insert("TERM".to_owned(), "xterm256-color".to_owned());
let theme = tty::Theme {
fg: RGB8::new(0, 1, 2),
bg: RGB8::new(0, 100, 200),
palette: vec![
RGB8::new(0, 0, 0),
RGB8::new(10, 11, 12),
RGB8::new(20, 21, 22),
RGB8::new(30, 31, 32),
RGB8::new(40, 41, 42),
RGB8::new(50, 51, 52),
RGB8::new(60, 61, 62),
RGB8::new(70, 71, 72),
RGB8::new(80, 81, 82),
RGB8::new(90, 91, 92),
RGB8::new(100, 101, 102),
RGB8::new(110, 111, 112),
RGB8::new(120, 121, 122),
RGB8::new(130, 131, 132),
RGB8::new(140, 141, 142),
RGB8::new(150, 151, 152),
],
};
let theme = tty::Theme {
fg: RGB8::new(0, 1, 2),
bg: RGB8::new(0, 100, 200),
palette: vec![
RGB8::new(0, 0, 0),
RGB8::new(10, 11, 12),
RGB8::new(20, 21, 22),
RGB8::new(30, 31, 32),
RGB8::new(40, 41, 42),
RGB8::new(50, 51, 52),
RGB8::new(60, 61, 62),
RGB8::new(70, 71, 72),
RGB8::new(80, 81, 82),
RGB8::new(90, 91, 92),
RGB8::new(100, 101, 102),
RGB8::new(110, 111, 112),
RGB8::new(120, 121, 122),
RGB8::new(130, 131, 132),
RGB8::new(140, 141, 142),
RGB8::new(150, 151, 152),
],
};
let header = Header {
version: 2,
cols: 80,
rows: 24,
timestamp: Some(1704719152),
idle_time_limit: Some(1.5),
command: Some("/bin/bash".to_owned()),
title: Some("Demo".to_owned()),
env: Some(env),
theme: Some(theme),
};
fw.write_header(&header).unwrap();
}
let header = Header {
version: 2,
cols: 80,
rows: 24,
timestamp: Some(1704719152),
idle_time_limit: Some(1.5),
command: Some("/bin/bash".to_owned()),
title: Some("Demo".to_owned()),
env: Some(env),
theme: Some(theme),
};
let data = enc.header(&header);
let lines = parse(data);
assert_eq!(lines[0]["version"], 2);

View File

@@ -4,7 +4,7 @@ use anyhow::{anyhow, bail, Result};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::io::{self, Write};
use std::io;
#[derive(Deserialize)]
struct V2Header {
@@ -156,30 +156,28 @@ where
}
}
pub struct Writer<W: Write> {
writer: io::LineWriter<W>,
pub struct Encoder {
time_offset: u64,
}
impl<W> Writer<W>
where
W: Write,
{
pub fn new(writer: W, time_offset: u64) -> Self {
Self {
writer: io::LineWriter::new(writer),
time_offset,
}
impl Encoder {
pub fn new(time_offset: u64) -> Self {
Self { time_offset }
}
pub fn write_header(&mut self, header: &Header) -> io::Result<()> {
pub fn header(&mut self, header: &Header) -> Vec<u8> {
let header: V2Header = header.into();
let mut data = serde_json::to_string(&header).unwrap().into_bytes();
data.push(b'\n');
writeln!(self.writer, "{}", serde_json::to_string(&header)?)
data
}
pub fn write_event(&mut self, event: &Event) -> io::Result<()> {
writeln!(self.writer, "{}", self.serialize_event(event)?)
pub fn event(&mut self, event: &Event) -> Vec<u8> {
let mut data = self.serialize_event(event).unwrap().into_bytes();
data.push(b'\n');
data
}
fn serialize_event(&self, event: &Event) -> Result<String, serde_json::Error> {

View File

@@ -4,10 +4,12 @@ use crate::cli;
use crate::config::Config;
use anyhow::Result;
use std::io;
use std::io::Write;
impl Command for cli::Cat {
fn run(self, _config: &Config) -> Result<()> {
let mut writer = asciicast::Writer::new(io::stdout(), 0);
let mut encoder = asciicast::Encoder::new(0);
let mut stdout = io::stdout();
let mut time_offset: u64 = 0;
let mut first = true;
@@ -16,7 +18,7 @@ impl Command for cli::Cat {
let mut time = time_offset;
if first {
writer.write_header(&recording.header)?;
stdout.write_all(&encoder.header(&recording.header))?;
first = false;
}
@@ -24,7 +26,7 @@ impl Command for cli::Cat {
let mut event = event?;
time = time_offset + event.time;
event.time = time;
writer.write_event(&event)?;
stdout.write_all(&encoder.event(&event))?;
}
time_offset = time;

View File

@@ -2,7 +2,7 @@ use super::Command;
use crate::asciicast::{self, Header};
use crate::cli::{self, Format};
use crate::config::Config;
use crate::encoder::{self, EncoderExt};
use crate::encoder::{self, AsciicastEncoder, EncoderExt, RawEncoder, TextEncoder};
use crate::util;
use anyhow::{bail, Result};
use std::fs;
@@ -11,17 +11,16 @@ use std::path::Path;
impl Command for cli::Convert {
fn run(self, _config: &Config) -> Result<()> {
let path = util::get_local_path(&self.input_filename)?;
let input = asciicast::open_from_path(&*path)?;
let mut output = self.get_output(&input.header)?;
let cast = asciicast::open_from_path(&*path)?;
let mut encoder = self.get_encoder(&cast.header);
let mut file = self.open_file()?;
output.encode(input)
encoder.encode_to_file(cast, &mut file)
}
}
impl cli::Convert {
fn get_output(&self, header: &Header) -> Result<Box<dyn encoder::Encoder>> {
let file = self.open_file()?;
fn get_encoder(&self, header: &Header) -> Box<dyn encoder::Encoder> {
let format = self.format.unwrap_or_else(|| {
if self.output_filename.to_lowercase().ends_with(".txt") {
Format::Txt
@@ -31,15 +30,9 @@ impl cli::Convert {
});
match format {
Format::Asciicast => Ok(Box::new(encoder::AsciicastEncoder::new(
file,
false,
0,
header.into(),
))),
Format::Raw => Ok(Box::new(encoder::RawEncoder::new(file, false))),
Format::Txt => Ok(Box::new(encoder::TextEncoder::new(file))),
Format::Asciicast => Box::new(AsciicastEncoder::new(false, 0, header.into())),
Format::Raw => Box::new(RawEncoder::new(false)),
Format::Txt => Box::new(TextEncoder::new()),
}
}

View File

@@ -2,10 +2,11 @@ use super::Command;
use crate::asciicast;
use crate::cli;
use crate::config::Config;
use crate::encoder;
use crate::encoder::{AsciicastEncoder, Encoder, Metadata, RawEncoder, TextEncoder};
use crate::locale;
use crate::logger;
use crate::pty;
use crate::recorder::Output;
use crate::recorder::{self, KeyBindings};
use crate::tty::{self, FixedSizeTty, Tty};
use anyhow::{bail, Result};
@@ -13,8 +14,10 @@ use cli::Format;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use std::time::{SystemTime, UNIX_EPOCH};
impl Command for cli::Record {
fn run(mut self, config: &Config) -> Result<()> {
@@ -173,17 +176,14 @@ impl cli::Record {
match format {
Format::Asciicast => {
let metadata = self.build_asciicast_metadata(theme, config);
let file = io::LineWriter::new(file);
let encoder = AsciicastEncoder::new(append, time_offset, metadata);
Box::new(encoder::AsciicastEncoder::new(
file,
append,
time_offset,
metadata,
))
Box::new(FileOutput(file, encoder))
}
Format::Raw => Box::new(encoder::RawEncoder::new(file, append)),
Format::Txt => Box::new(encoder::TextEncoder::new(file)),
Format::Raw => Box::new(FileOutput(file, RawEncoder::new(append))),
Format::Txt => Box::new(FileOutput(file, TextEncoder::new())),
}
}
@@ -191,11 +191,7 @@ impl cli::Record {
self.command.as_ref().cloned().or(config.cmd_rec_command())
}
fn build_asciicast_metadata(
&self,
theme: Option<tty::Theme>,
config: &Config,
) -> encoder::Metadata {
fn build_asciicast_metadata(&self, theme: Option<tty::Theme>, config: &Config) -> Metadata {
let idle_time_limit = self.idle_time_limit.or(config.cmd_rec_idle_time_limit());
let command = self.get_command(config);
@@ -206,7 +202,7 @@ impl cli::Record {
.or(config.cmd_rec_env())
.unwrap_or(String::from("TERM,SHELL"));
encoder::Metadata {
Metadata {
idle_time_limit,
command,
title: self.title.clone(),
@@ -216,6 +212,23 @@ impl cli::Record {
}
}
struct FileOutput<W: Write, E: Encoder>(W, E);
impl<W: Write, E: Encoder> Output for FileOutput<W, E> {
fn header(&mut self, time: SystemTime, tty_size: tty::TtySize) -> io::Result<()> {
let timestamp = time.duration_since(UNIX_EPOCH).unwrap().as_secs();
self.0.write_all(&self.1.start(Some(timestamp), tty_size))
}
fn event(&mut self, event: asciicast::Event) -> io::Result<()> {
self.0.write_all(&self.1.event(event))
}
fn flush(&mut self) -> io::Result<()> {
self.0.write_all(&self.1.finish())
}
}
fn get_key_bindings(config: &Config) -> Result<KeyBindings> {
let mut keys = KeyBindings::default();

View File

@@ -1,10 +1,9 @@
use crate::asciicast::{Event, Header, Writer};
use crate::asciicast::{Encoder, Event, Header};
use crate::tty;
use std::collections::HashMap;
use std::io::{self, Write};
pub struct AsciicastEncoder<W: Write> {
writer: Writer<W>,
pub struct AsciicastEncoder {
inner: Encoder,
append: bool,
metadata: Metadata,
}
@@ -17,13 +16,12 @@ pub struct Metadata {
pub theme: Option<tty::Theme>,
}
impl<W> AsciicastEncoder<W>
where
W: Write,
{
pub fn new(writer: W, append: bool, time_offset: u64, metadata: Metadata) -> Self {
impl AsciicastEncoder {
pub fn new(append: bool, time_offset: u64, metadata: Metadata) -> Self {
let inner = Encoder::new(time_offset);
Self {
writer: Writer::new(writer, time_offset),
inner,
append,
metadata,
}
@@ -44,22 +42,21 @@ where
}
}
impl<W> super::Encoder for AsciicastEncoder<W>
where
W: Write,
{
fn start(&mut self, timestamp: Option<u64>, tty_size: &tty::TtySize) -> io::Result<()> {
impl super::Encoder for AsciicastEncoder {
fn start(&mut self, timestamp: Option<u64>, tty_size: tty::TtySize) -> Vec<u8> {
if self.append {
Ok(())
Vec::new()
} else {
let header = self.build_header(timestamp, tty_size);
self.writer.write_header(&header)
self.inner.header(&self.build_header(timestamp, &tty_size))
}
}
fn event(&mut self, event: &Event) -> io::Result<()> {
self.writer.write_event(event)
fn event(&mut self, event: Event) -> Vec<u8> {
self.inner.event(&event)
}
fn finish(&mut self) -> Vec<u8> {
Vec::new()
}
}

View File

@@ -8,67 +8,32 @@ pub use raw::RawEncoder;
pub use txt::TextEncoder;
use crate::asciicast::Event;
use crate::recorder;
use crate::tty;
use anyhow::Result;
use std::io;
use std::time::{SystemTime, UNIX_EPOCH};
use std::fs::File;
use std::io::Write;
pub trait Encoder {
fn start(&mut self, timestamp: Option<u64>, tty_size: &tty::TtySize) -> io::Result<()>;
fn event(&mut self, event: &Event) -> io::Result<()>;
fn finish(&mut self) -> io::Result<()> {
Ok(())
}
fn start(&mut self, timestamp: Option<u64>, tty_size: tty::TtySize) -> Vec<u8>;
fn event(&mut self, event: Event) -> Vec<u8>;
fn finish(&mut self) -> Vec<u8>;
}
pub trait EncoderExt {
fn encode(&mut self, recording: crate::asciicast::Asciicast) -> Result<()>;
fn encode_to_file(&mut self, cast: crate::asciicast::Asciicast, file: &mut File) -> Result<()>;
}
impl<E: Encoder + ?Sized> EncoderExt for E {
fn encode(&mut self, recording: crate::asciicast::Asciicast) -> Result<()> {
let tty_size = tty::TtySize(recording.header.cols, recording.header.rows);
self.start(recording.header.timestamp, &tty_size)?;
fn encode_to_file(&mut self, cast: crate::asciicast::Asciicast, file: &mut File) -> Result<()> {
let tty_size = tty::TtySize(cast.header.cols, cast.header.rows);
file.write_all(&self.start(cast.header.timestamp, tty_size))?;
for event in recording.events {
self.event(&event?)?;
for event in cast.events {
file.write_all(&self.event(event?))?;
}
self.finish()?;
file.write_all(&self.finish())?;
Ok(())
}
}
impl<E: Encoder> recorder::Output for E {
fn start(&mut self, tty_size: &tty::TtySize) -> io::Result<()> {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
self.start(Some(timestamp), tty_size)
}
fn output(&mut self, time: u64, text: String) -> io::Result<()> {
self.event(&Event::output(time, text))
}
fn input(&mut self, time: u64, text: String) -> io::Result<()> {
self.event(&Event::input(time, text))
}
fn resize(&mut self, time: u64, size: (u16, u16)) -> io::Result<()> {
self.event(&Event::resize(time, size))
}
fn marker(&mut self, time: u64) -> io::Result<()> {
self.event(&Event::marker(time, "".to_owned()))
}
fn finish(&mut self) -> io::Result<()> {
self.finish()
}
}

View File

@@ -1,34 +1,36 @@
use crate::asciicast::{Event, EventData};
use crate::tty;
use std::io::{self, Write};
pub struct RawEncoder<W> {
writer: W,
pub struct RawEncoder {
append: bool,
}
impl<W> RawEncoder<W> {
pub fn new(writer: W, append: bool) -> Self {
RawEncoder { writer, append }
impl RawEncoder {
pub fn new(append: bool) -> Self {
RawEncoder { append }
}
}
impl<W: Write> super::Encoder for RawEncoder<W> {
fn start(&mut self, _timestamp: Option<u64>, tty_size: &tty::TtySize) -> io::Result<()> {
impl super::Encoder for RawEncoder {
fn start(&mut self, _timestamp: Option<u64>, tty_size: tty::TtySize) -> Vec<u8> {
if self.append {
Ok(())
Vec::new()
} else {
write!(self.writer, "\x1b[8;{};{}t", tty_size.1, tty_size.0)
format!("\x1b[8;{};{}t", tty_size.1, tty_size.0).into_bytes()
}
}
fn event(&mut self, event: &Event) -> io::Result<()> {
if let EventData::Output(data) = &event.data {
self.writer.write_all(data.as_bytes())
fn event(&mut self, event: Event) -> Vec<u8> {
if let EventData::Output(data) = event.data {
data.into_bytes()
} else {
Ok(())
Vec::new()
}
}
fn finish(&mut self) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
@@ -39,20 +41,27 @@ mod tests {
use crate::tty::TtySize;
#[test]
fn encoder_impl() -> anyhow::Result<()> {
let mut out: Vec<u8> = Vec::new();
let mut enc = RawEncoder::new(&mut out, false);
fn encoder() {
let mut enc = RawEncoder::new(false);
enc.start(None, &TtySize(100, 50))?;
enc.event(&Event::output(0, "he\x1b[1mllo\r\n".to_owned()))?;
enc.event(&Event::output(1, "world\r\n".to_owned()))?;
enc.event(&Event::input(2, ".".to_owned()))?;
enc.event(&Event::resize(3, (80, 24)))?;
enc.event(&Event::marker(4, ".".to_owned()))?;
enc.finish()?;
assert_eq!(
enc.start(None, TtySize(100, 50)),
"\x1b[8;50;100t".as_bytes()
);
assert_eq!(out, b"\x1b[8;50;100the\x1b[1mllo\r\nworld\r\n");
assert_eq!(
enc.event(Event::output(0, "he\x1b[1mllo\r\n".to_owned())),
"he\x1b[1mllo\r\n".as_bytes()
);
Ok(())
assert_eq!(
enc.event(Event::output(1, "world\r\n".to_owned())),
"world\r\n".as_bytes()
);
assert!(enc.event(Event::input(2, ".".to_owned())).is_empty());
assert!(enc.event(Event::resize(3, (80, 24))).is_empty());
assert!(enc.event(Event::marker(4, ".".to_owned())).is_empty());
assert!(enc.finish().is_empty());
}
}

View File

@@ -1,62 +1,56 @@
use crate::asciicast::{Event, EventData};
use crate::tty;
use avt::util::{TextCollector, TextCollectorOutput};
use std::io::{self, Write};
use avt::util::TextCollector;
pub struct TextEncoder<W: Write> {
writer: Option<W>,
collector: Option<TextCollector<TextWriter<W>>>,
pub struct TextEncoder {
collector: Option<TextCollector>,
}
impl<W: Write> TextEncoder<W> {
pub fn new(writer: W) -> Self {
TextEncoder {
writer: Some(writer),
collector: None,
}
impl TextEncoder {
pub fn new() -> Self {
TextEncoder { collector: None }
}
}
impl<W: Write> super::Encoder for TextEncoder<W> {
fn start(&mut self, _timestamp: Option<u64>, tty_size: &tty::TtySize) -> io::Result<()> {
impl super::Encoder for TextEncoder {
fn start(&mut self, _timestamp: Option<u64>, tty_size: tty::TtySize) -> Vec<u8> {
let vt = avt::Vt::builder()
.size(tty_size.0 as usize, tty_size.1 as usize)
.resizable(true)
.scrollback_limit(100)
.build();
self.collector = Some(TextCollector::new(
vt,
TextWriter(self.writer.take().unwrap()),
));
self.collector = Some(TextCollector::new(vt));
Ok(())
Vec::new()
}
fn event(&mut self, event: &Event) -> io::Result<()> {
fn event(&mut self, event: Event) -> Vec<u8> {
use EventData::*;
match &event.data {
Output(data) => self.collector.as_mut().unwrap().feed_str(data),
Resize(cols, rows) => self.collector.as_mut().unwrap().resize(*cols, *rows),
_ => Ok(()),
Output(data) => text_lines_to_bytes(self.collector.as_mut().unwrap().feed_str(data)),
Resize(cols, rows) => {
text_lines_to_bytes(self.collector.as_mut().unwrap().resize(*cols, *rows))
}
_ => Vec::new(),
}
}
fn finish(&mut self) -> io::Result<()> {
self.collector.as_mut().unwrap().flush()
fn finish(&mut self) -> Vec<u8> {
text_lines_to_bytes(self.collector.take().unwrap().flush().iter())
}
}
struct TextWriter<W: Write>(W);
fn text_lines_to_bytes<S: AsRef<str>>(lines: impl Iterator<Item = S>) -> Vec<u8> {
lines.fold(Vec::new(), |mut bytes, line| {
bytes.extend_from_slice(line.as_ref().as_bytes());
bytes.push(b'\n');
impl<W: Write> TextCollectorOutput for TextWriter<W> {
type Error = io::Error;
fn push(&mut self, line: String) -> Result<(), Self::Error> {
self.0.write_all(line.as_bytes())?;
self.0.write_all(b"\n")
}
bytes
})
}
#[cfg(test)]
@@ -67,17 +61,19 @@ mod tests {
use crate::tty::TtySize;
#[test]
fn encoder_impl() -> anyhow::Result<()> {
let mut out: Vec<u8> = Vec::new();
let mut enc = TextEncoder::new(&mut out);
fn encoder() {
let mut enc = TextEncoder::new();
enc.start(None, &TtySize(3, 1))?;
enc.event(&Event::output(0, "he\x1b[1mllo\r\n".to_owned()))?;
enc.event(&Event::output(1, "world\r\n".to_owned()))?;
enc.finish()?;
assert!(enc.start(None, TtySize(3, 1)).is_empty());
assert_eq!(out, b"hello\nworld\n");
assert!(enc
.event(Event::output(0, "he\x1b[1mllo\r\n".to_owned()))
.is_empty());
Ok(())
assert!(enc
.event(Event::output(1, "world\r\n".to_owned()))
.is_empty());
assert_eq!(enc.finish(), "hello\nworld\n".as_bytes());
}
}

View File

@@ -23,7 +23,7 @@ use std::time::{Duration, Instant};
type ExtraEnv = HashMap<String, String>;
pub trait Handler {
fn start(&mut self, epoch: Instant, tty_size: TtySize);
fn start(&mut self, tty_size: TtySize);
fn output(&mut self, time: Duration, data: &[u8]) -> bool;
fn input(&mut self, time: Duration, data: &[u8]) -> bool;
fn resize(&mut self, time: Duration, tty_size: TtySize) -> bool;
@@ -37,7 +37,7 @@ pub fn exec<S: AsRef<str>, T: Tty + ?Sized, H: Handler>(
) -> Result<i32> {
let winsize = tty.get_size();
let epoch = Instant::now();
handler.start(epoch, winsize.into());
handler.start(winsize.into());
let result = unsafe { pty::forkpty(Some(&winsize), None) }?;
match result.fork_result {
@@ -378,7 +378,7 @@ mod tests {
use super::Handler;
use crate::pty::ExtraEnv;
use crate::tty::{FixedSizeTty, NullTty, TtySize};
use std::time::{Duration, Instant};
use std::time::Duration;
#[derive(Default)]
struct TestHandler {
@@ -387,7 +387,7 @@ mod tests {
}
impl Handler for TestHandler {
fn start(&mut self, _epoch: Instant, tty_size: TtySize) {
fn start(&mut self, tty_size: TtySize) {
self.tty_size = Some(tty_size);
}

View File

@@ -1,3 +1,4 @@
use crate::asciicast::Event;
use crate::config::Key;
use crate::notifier::Notifier;
use crate::pty;
@@ -6,7 +7,7 @@ use crate::util;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use std::time::{Duration, SystemTime};
pub struct Recorder {
output: Option<Box<dyn Output + Send>>,
@@ -22,15 +23,9 @@ pub struct Recorder {
}
pub trait Output {
fn start(&mut self, tty_size: &tty::TtySize) -> io::Result<()>;
fn output(&mut self, time: u64, text: String) -> io::Result<()>;
fn input(&mut self, time: u64, text: String) -> io::Result<()>;
fn resize(&mut self, time: u64, size: (u16, u16)) -> io::Result<()>;
fn marker(&mut self, time: u64) -> io::Result<()>;
fn finish(&mut self) -> io::Result<()> {
Ok(())
}
fn header(&mut self, time: SystemTime, tty_size: tty::TtySize) -> io::Result<()>;
fn event(&mut self, event: Event) -> io::Result<()>;
fn flush(&mut self) -> io::Result<()>;
}
enum Message {
@@ -82,9 +77,9 @@ impl Recorder {
}
impl pty::Handler for Recorder {
fn start(&mut self, _epoch: Instant, tty_size: tty::TtySize) {
fn start(&mut self, tty_size: tty::TtySize) {
let mut output = self.output.take().unwrap();
let _ = output.start(&tty_size);
let _ = output.header(SystemTime::now(), tty_size);
let receiver = self.receiver.take().unwrap();
let mut notifier = self.notifier.take().unwrap();
@@ -100,7 +95,7 @@ impl pty::Handler for Recorder {
let text = output_decoder.feed(&data);
if !text.is_empty() {
let _ = output.output(time, text);
let _ = output.event(Event::output(time, text));
}
}
@@ -108,19 +103,19 @@ impl pty::Handler for Recorder {
let text = input_decoder.feed(&data);
if !text.is_empty() {
let _ = output.input(time, text);
let _ = output.event(Event::input(time, text));
}
}
Resize(time, new_tty_size) => {
if new_tty_size != last_tty_size {
let _ = output.resize(time, new_tty_size.into());
let _ = output.event(Event::resize(time, new_tty_size.into()));
last_tty_size = new_tty_size;
}
}
Marker(time) => {
let _ = output.marker(time);
let _ = output.event(Event::marker(time, String::new()));
}
Notification(text) => {
@@ -129,7 +124,7 @@ impl pty::Handler for Recorder {
}
}
let _ = output.finish();
let _ = output.flush();
});
self.handle = Some(util::JoinHandle::new(handle));

View File

@@ -10,7 +10,6 @@ use crate::util;
use std::net;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use tokio::sync::mpsc;
use tracing::info;
@@ -83,7 +82,7 @@ impl Streamer {
}
impl pty::Handler for Streamer {
fn start(&mut self, _epoch: Instant, tty_size: tty::TtySize) {
fn start(&mut self, tty_size: tty::TtySize) {
let pty_rx = self.pty_rx.take().unwrap();
let (clients_tx, mut clients_rx) = mpsc::channel(1);
let shutdown_token = tokio_util::sync::CancellationToken::new();