mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-15 19:28:00 +01:00
Implement --append and --overwrite for rec
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
## 3.0.0 (wip)
|
||||
|
||||
* Full rewrite in Rust
|
||||
* Use of `--append` and `--overwrite` together gives returns error now
|
||||
* rec: `--append` can be used with `--raw` now
|
||||
* rec: use of `--append` and `--overwrite` together returns error now
|
||||
|
||||
## 2.4.0 (2023-10-23)
|
||||
|
||||
|
||||
62
Cargo.lock
generated
62
Cargo.lock
generated
@@ -66,7 +66,6 @@ dependencies = [
|
||||
"nix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"termion",
|
||||
]
|
||||
|
||||
@@ -134,22 +133,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -168,12 +151,6 @@ version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
@@ -236,35 +213,13 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"redox_syscall 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -321,19 +276,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "2.0.1"
|
||||
@@ -342,7 +284,7 @@ checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall 0.2.16",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
|
||||
@@ -18,6 +18,3 @@ termion = "2.0.1"
|
||||
serde = { version = "1.0.189", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8.0"
|
||||
|
||||
101
src/asciicast.rs
101
src/asciicast.rs
@@ -7,8 +7,8 @@ use std::io::BufRead;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct Writer {
|
||||
file: fs::File,
|
||||
pub struct Writer<W: Write> {
|
||||
writer: W,
|
||||
time_offset: f64,
|
||||
}
|
||||
|
||||
@@ -99,46 +99,25 @@ pub fn get_duration<S: AsRef<Path>>(path: S) -> anyhow::Result<f64> {
|
||||
Ok(time)
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
pub fn new<S: AsRef<Path>>(path: S, append: bool) -> anyhow::Result<Self> {
|
||||
if append {
|
||||
Self::append(path)
|
||||
} else {
|
||||
Self::create(path)
|
||||
impl<W> Writer<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
pub fn new(writer: W, time_offset: f64) -> Self {
|
||||
Self {
|
||||
writer,
|
||||
time_offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create<S: AsRef<Path>>(path: S) -> anyhow::Result<Self> {
|
||||
let file = fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(path)?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
time_offset: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn append<S: AsRef<Path>>(path: S) -> anyhow::Result<Self> {
|
||||
let time_offset = get_duration(&path)?;
|
||||
let file = fs::OpenOptions::new().append(true).open(path)?;
|
||||
|
||||
Ok(Self { file, time_offset })
|
||||
}
|
||||
|
||||
pub fn write_header(&mut self, header: &Header) -> io::Result<()> {
|
||||
if self.time_offset == 0.0 {
|
||||
write_header(&mut self.file, header)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
write_header(&mut self.writer, header)
|
||||
}
|
||||
|
||||
pub fn write_event(&mut self, mut event: Event) -> io::Result<()> {
|
||||
event.time += self.time_offset;
|
||||
|
||||
write_event(&mut self.file, &event)
|
||||
write_event(&mut self.writer, &event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,9 +194,8 @@ impl From<V2Header> for Header {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Event, EventCode, Header, Writer};
|
||||
use std::fs::{self, File};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn open() {
|
||||
@@ -246,38 +224,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn writer() {
|
||||
let tmp_dir = tempdir().unwrap();
|
||||
let tmp_path = tmp_dir.path().join("test.cast");
|
||||
let mut data = Vec::new();
|
||||
|
||||
{
|
||||
let header = Header {
|
||||
terminal_size: (80, 24),
|
||||
idle_time_limit: None,
|
||||
};
|
||||
let cursor = io::Cursor::new(&mut data);
|
||||
let mut fw = Writer::new(cursor, 0.0);
|
||||
|
||||
let mut fw = Writer::create(&tmp_path).unwrap();
|
||||
let header = Header {
|
||||
terminal_size: (80, 24),
|
||||
idle_time_limit: None,
|
||||
};
|
||||
|
||||
fw.write_header(&header).unwrap();
|
||||
fw.write_header(&header).unwrap();
|
||||
|
||||
fw.write_event(Event {
|
||||
time: 1.0,
|
||||
code: EventCode::Output,
|
||||
data: "hello\r\n".to_owned(),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fw.write_event(Event {
|
||||
time: 1.0,
|
||||
code: EventCode::Output,
|
||||
data: "hello\r\n".to_owned(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let mut fw = Writer::append(&tmp_path).unwrap();
|
||||
let data_len = data.len() as u64;
|
||||
let mut cursor = io::Cursor::new(&mut data);
|
||||
cursor.set_position(data_len);
|
||||
let mut fw = Writer::new(cursor, 1.0);
|
||||
|
||||
fw.write_event(Event {
|
||||
time: 1.0,
|
||||
code: EventCode::Output,
|
||||
data: "world".to_owned(),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fw.write_event(Event {
|
||||
time: 1.0,
|
||||
code: EventCode::Output,
|
||||
data: "world".to_owned(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(fs::read_to_string(tmp_path).unwrap(), "{\"version\":2,\"width\":80,\"height\":24}\n[1.0,\"o\",\"hello\\r\\n\"]\n[2.0,\"o\",\"world\"]\n");
|
||||
let asciicast = String::from_utf8(data).unwrap();
|
||||
|
||||
assert_eq!(asciicast, "{\"version\":2,\"width\":80,\"height\":24}\n[1.0,\"o\",\"hello\\r\\n\"]\n[2.0,\"o\",\"world\"]\n");
|
||||
}
|
||||
}
|
||||
|
||||
9
src/format.rs
Normal file
9
src/format.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod asciicast;
|
||||
pub mod raw;
|
||||
use std::io;
|
||||
|
||||
pub trait Writer {
|
||||
fn header(&mut self, size: (u16, u16)) -> io::Result<()>;
|
||||
fn output(&mut self, time: f64, data: &[u8]) -> io::Result<()>;
|
||||
fn input(&mut self, time: f64, data: &[u8]) -> io::Result<()>;
|
||||
}
|
||||
39
src/format/asciicast.rs
Normal file
39
src/format/asciicast.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::asciicast;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct Writer<W: Write> {
|
||||
inner: asciicast::Writer<W>,
|
||||
}
|
||||
|
||||
impl<W> Writer<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
pub fn new(writer: W, time_offset: f64) -> Self {
|
||||
Writer {
|
||||
inner: asciicast::Writer::new(writer, time_offset),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> super::Writer for Writer<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn header(&mut self, size: (u16, u16)) -> io::Result<()> {
|
||||
let header = asciicast::Header {
|
||||
terminal_size: (size.0 as usize, size.1 as usize),
|
||||
idle_time_limit: None,
|
||||
};
|
||||
|
||||
self.inner.write_header(&header)
|
||||
}
|
||||
|
||||
fn output(&mut self, time: f64, data: &[u8]) -> io::Result<()> {
|
||||
self.inner.write_event(asciicast::Event::output(time, data))
|
||||
}
|
||||
|
||||
fn input(&mut self, time: f64, data: &[u8]) -> io::Result<()> {
|
||||
self.inner.write_event(asciicast::Event::input(time, data))
|
||||
}
|
||||
}
|
||||
25
src/format/raw.rs
Normal file
25
src/format/raw.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct Writer<W> {
|
||||
writer: W,
|
||||
}
|
||||
|
||||
impl<W> Writer<W> {
|
||||
pub fn new(writer: W) -> Self {
|
||||
Writer { writer }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> super::Writer for Writer<W> {
|
||||
fn header(&mut self, size: (u16, u16)) -> io::Result<()> {
|
||||
write!(self.writer, "\x1b[8;{};{}t", size.1, size.0)
|
||||
}
|
||||
|
||||
fn output(&mut self, _time: f64, data: &[u8]) -> io::Result<()> {
|
||||
self.writer.write_all(data)
|
||||
}
|
||||
|
||||
fn input(&mut self, _time: f64, _data: &[u8]) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
33
src/main.rs
33
src/main.rs
@@ -1,9 +1,12 @@
|
||||
mod asciicast;
|
||||
mod format;
|
||||
mod pty;
|
||||
mod recorder;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(author, version, about)]
|
||||
@@ -109,7 +112,7 @@ fn main() -> Result<()> {
|
||||
Commands::Record {
|
||||
filename,
|
||||
stdin,
|
||||
append,
|
||||
mut append,
|
||||
raw,
|
||||
overwrite,
|
||||
command,
|
||||
@@ -120,13 +123,33 @@ fn main() -> Result<()> {
|
||||
rows,
|
||||
quiet,
|
||||
} => {
|
||||
let format = if raw {
|
||||
recorder::Format::Raw
|
||||
let exists = path::Path::new(&filename).exists();
|
||||
append = append && exists;
|
||||
let mut opts = fs::OpenOptions::new();
|
||||
|
||||
opts.write(true)
|
||||
.append(append)
|
||||
.create_new(!overwrite && !append)
|
||||
.truncate(overwrite);
|
||||
|
||||
let writer: Box<dyn format::Writer> = if raw {
|
||||
let file = opts.open(&filename)?;
|
||||
|
||||
Box::new(format::raw::Writer::new(file))
|
||||
} else {
|
||||
recorder::Format::Asciicast
|
||||
let writer = if append {
|
||||
let time_offset = asciicast::get_duration(&filename)?;
|
||||
let file = opts.open(&filename)?;
|
||||
format::asciicast::Writer::new(file, time_offset)
|
||||
} else {
|
||||
let file = opts.open(&filename)?;
|
||||
format::asciicast::Writer::new(file, 0.0)
|
||||
};
|
||||
|
||||
Box::new(writer)
|
||||
};
|
||||
|
||||
let mut recorder = recorder::new(filename, format, append, stdin)?;
|
||||
let mut recorder = recorder::Recorder::new(writer, append, stdin);
|
||||
|
||||
let command = if command == "$SHELL" {
|
||||
env::var("SHELL").ok().unwrap_or("/bin/sh".to_owned())
|
||||
|
||||
113
src/recorder.rs
113
src/recorder.rs
@@ -1,52 +1,25 @@
|
||||
use crate::asciicast;
|
||||
use crate::format;
|
||||
use crate::pty;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use std::io;
|
||||
use std::time;
|
||||
|
||||
pub struct Recorder {
|
||||
writer: Box<dyn FileWriter>,
|
||||
writer: Box<dyn format::Writer>,
|
||||
append: bool,
|
||||
record_input: bool,
|
||||
start_time: time::Instant,
|
||||
}
|
||||
|
||||
trait FileWriter {
|
||||
fn header(&mut self, size: (u16, u16)) -> io::Result<()>;
|
||||
fn output(&mut self, time: f64, data: &[u8]) -> io::Result<()>;
|
||||
fn input(&mut self, time: f64, data: &[u8]) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub enum Format {
|
||||
Asciicast,
|
||||
Raw,
|
||||
}
|
||||
|
||||
struct RawWriter {
|
||||
file: File,
|
||||
append: bool,
|
||||
}
|
||||
|
||||
pub fn new<S: Into<String>>(
|
||||
path: S,
|
||||
format: Format,
|
||||
append: bool,
|
||||
record_input: bool,
|
||||
) -> anyhow::Result<Recorder> {
|
||||
let path = path.into();
|
||||
|
||||
let writer: Box<dyn FileWriter> = match format {
|
||||
Format::Asciicast => Box::new(asciicast::Writer::new(path, append)?),
|
||||
Format::Raw => Box::new(RawWriter::new(path, append)?),
|
||||
};
|
||||
|
||||
Ok(Recorder {
|
||||
writer,
|
||||
record_input,
|
||||
start_time: time::Instant::now(),
|
||||
})
|
||||
}
|
||||
|
||||
impl Recorder {
|
||||
pub fn new(writer: Box<dyn format::Writer>, append: bool, record_input: bool) -> Self {
|
||||
Recorder {
|
||||
writer,
|
||||
append,
|
||||
record_input,
|
||||
start_time: time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn elapsed_time(&self) -> f64 {
|
||||
self.start_time.elapsed().as_secs_f64()
|
||||
}
|
||||
@@ -55,7 +28,12 @@ impl Recorder {
|
||||
impl pty::Recorder for Recorder {
|
||||
fn start(&mut self, size: (u16, u16)) -> io::Result<()> {
|
||||
self.start_time = time::Instant::now();
|
||||
self.writer.header(size)
|
||||
|
||||
if !self.append {
|
||||
self.writer.header(size)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn output(&mut self, data: &[u8]) {
|
||||
@@ -70,56 +48,3 @@ impl pty::Recorder for Recorder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileWriter for asciicast::Writer {
|
||||
fn header(&mut self, size: (u16, u16)) -> io::Result<()> {
|
||||
let header = asciicast::Header {
|
||||
terminal_size: (size.0 as usize, size.1 as usize),
|
||||
idle_time_limit: None,
|
||||
};
|
||||
|
||||
self.write_header(&header)
|
||||
}
|
||||
|
||||
fn output(&mut self, time: f64, data: &[u8]) -> io::Result<()> {
|
||||
self.write_event(asciicast::Event::output(time, data))
|
||||
}
|
||||
|
||||
fn input(&mut self, time: f64, data: &[u8]) -> io::Result<()> {
|
||||
self.write_event(asciicast::Event::input(time, data))
|
||||
}
|
||||
}
|
||||
|
||||
impl RawWriter {
|
||||
fn new(path: String, append: bool) -> io::Result<Self> {
|
||||
let mut opts = fs::OpenOptions::new();
|
||||
|
||||
if append {
|
||||
opts.append(true);
|
||||
} else {
|
||||
opts.create_new(true).write(true);
|
||||
}
|
||||
|
||||
let file = opts.open(path)?;
|
||||
|
||||
Ok(Self { file, append })
|
||||
}
|
||||
}
|
||||
|
||||
impl FileWriter for RawWriter {
|
||||
fn header(&mut self, size: (u16, u16)) -> io::Result<()> {
|
||||
if self.append {
|
||||
Ok(())
|
||||
} else {
|
||||
write!(self.file, "\x1b[8;{};{}t", size.1, size.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn output(&mut self, _time: f64, data: &[u8]) -> io::Result<()> {
|
||||
self.file.write_all(data)
|
||||
}
|
||||
|
||||
fn input(&mut self, _time: f64, _data: &[u8]) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user