mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-15 19:28:00 +01:00
Support recording to a directory, with auto-generated filename
This commit is contained in:
93
Cargo.lock
generated
93
Cargo.lock
generated
@@ -26,6 +26,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
@@ -87,11 +102,13 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"avt",
|
"avt",
|
||||||
"axum",
|
"axum",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"config",
|
"config",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"hostname",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"nix",
|
"nix",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -292,6 +309,20 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.4.18"
|
version = "4.4.18"
|
||||||
@@ -816,6 +847,17 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hostname"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@@ -971,6 +1013,29 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -1198,6 +1263,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -2750,6 +2824,25 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ url = "2.5.0"
|
|||||||
tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-webpki-roots"] }
|
tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-webpki-roots"] }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
|
chrono = "0.4.38"
|
||||||
|
hostname = "0.4.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = { version = "4.4.7", features = ["derive"] }
|
clap = { version = "4.4.7", features = ["derive"] }
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ pub enum Commands {
|
|||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
pub filename: String,
|
/// Output path - either a file or a directory path
|
||||||
|
pub path: String,
|
||||||
|
|
||||||
/// Enable input recording
|
/// Enable input recording
|
||||||
#[arg(long, short = 'I', alias = "stdin")]
|
#[arg(long, short = 'I', alias = "stdin")]
|
||||||
@@ -73,6 +74,10 @@ pub struct Record {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub command: Option<String>,
|
pub command: Option<String>,
|
||||||
|
|
||||||
|
/// Filename template, used when recording to a directory
|
||||||
|
#[arg(long, value_name = "TEMPLATE")]
|
||||||
|
pub filename: Option<String>,
|
||||||
|
|
||||||
/// List of env vars to save [default: TERM,SHELL]
|
/// List of env vars to save [default: TERM,SHELL]
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub env: Option<String>,
|
pub env: Option<String>,
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ use cli::Format;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process;
|
||||||
|
|
||||||
impl Command for cli::Record {
|
impl Command for cli::Record {
|
||||||
fn run(self, config: &Config) -> Result<()> {
|
fn run(mut self, config: &Config) -> Result<()> {
|
||||||
locale::check_utf8_locale()?;
|
locale::check_utf8_locale()?;
|
||||||
|
|
||||||
|
self.ensure_filename(config)?;
|
||||||
let format = self.get_format();
|
let format = self.get_format();
|
||||||
let (append, overwrite) = self.get_mode()?;
|
let (append, overwrite) = self.get_mode()?;
|
||||||
let file = self.open_file(append, overwrite)?;
|
let file = self.open_file(append, overwrite)?;
|
||||||
@@ -30,7 +32,7 @@ impl Command for cli::Record {
|
|||||||
let exec_command = super::build_exec_command(command.as_ref().cloned());
|
let exec_command = super::build_exec_command(command.as_ref().cloned());
|
||||||
let exec_extra_env = super::build_exec_extra_env(&[]);
|
let exec_extra_env = super::build_exec_extra_env(&[]);
|
||||||
|
|
||||||
logger::info!("Recording session started, writing to {}", self.filename);
|
logger::info!("Recording session started, writing to {}", self.path);
|
||||||
|
|
||||||
if command.is_none() {
|
if command.is_none() {
|
||||||
logger::info!("Press <ctrl+d> or type 'exit' to end");
|
logger::info!("Press <ctrl+d> or type 'exit' to end");
|
||||||
@@ -58,10 +60,48 @@ impl Command for cli::Record {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl cli::Record {
|
impl cli::Record {
|
||||||
|
fn ensure_filename(&mut self, config: &Config) -> Result<()> {
|
||||||
|
let mut path = PathBuf::from(&self.path);
|
||||||
|
|
||||||
|
if path.exists() && fs::metadata(&path)?.is_dir() {
|
||||||
|
let mut tpl = self.filename.clone().unwrap_or(config.cmd_rec_filename());
|
||||||
|
|
||||||
|
if tpl.contains("{pid}") {
|
||||||
|
let pid = process::id().to_string();
|
||||||
|
tpl = tpl.replace("{pid}", &pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if tpl.contains("{user}") {
|
||||||
|
let user = env::var("USER").ok().unwrap_or("unknown".to_owned());
|
||||||
|
tpl = tpl.replace("{user}", &user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if tpl.contains("{hostname}") {
|
||||||
|
let hostname = hostname::get()
|
||||||
|
.ok()
|
||||||
|
.and_then(|h| h.into_string().ok())
|
||||||
|
.unwrap_or("unknown".to_owned());
|
||||||
|
|
||||||
|
tpl = tpl.replace("{hostname}", &hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
let filename = chrono::Local::now().format(&tpl).to_string();
|
||||||
|
path.push(Path::new(&filename));
|
||||||
|
|
||||||
|
if let Some(dir) = path.parent() {
|
||||||
|
fs::create_dir_all(dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.path = path.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_mode(&self) -> Result<(bool, bool)> {
|
fn get_mode(&self) -> Result<(bool, bool)> {
|
||||||
let mut overwrite = self.overwrite;
|
let mut overwrite = self.overwrite;
|
||||||
let mut append = self.append;
|
let mut append = self.append;
|
||||||
let path = Path::new(&self.filename);
|
let path = Path::new(&self.path);
|
||||||
|
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
let metadata = fs::metadata(path)?;
|
let metadata = fs::metadata(path)?;
|
||||||
@@ -88,7 +128,7 @@ impl cli::Record {
|
|||||||
.create(overwrite)
|
.create(overwrite)
|
||||||
.create_new(!overwrite && !append)
|
.create_new(!overwrite && !append)
|
||||||
.truncate(overwrite)
|
.truncate(overwrite)
|
||||||
.open(&self.filename)?;
|
.open(&self.path)?;
|
||||||
|
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
@@ -97,7 +137,7 @@ impl cli::Record {
|
|||||||
self.format.unwrap_or_else(|| {
|
self.format.unwrap_or_else(|| {
|
||||||
if self.raw {
|
if self.raw {
|
||||||
Format::Raw
|
Format::Raw
|
||||||
} else if self.filename.to_lowercase().ends_with(".txt") {
|
} else if self.path.to_lowercase().ends_with(".txt") {
|
||||||
Format::Txt
|
Format::Txt
|
||||||
} else {
|
} else {
|
||||||
Format::Asciicast
|
Format::Asciicast
|
||||||
@@ -107,7 +147,7 @@ impl cli::Record {
|
|||||||
|
|
||||||
fn get_time_offset(&self, append: bool, format: Format) -> Result<u64> {
|
fn get_time_offset(&self, append: bool, format: Format) -> Result<u64> {
|
||||||
if append && format == Format::Asciicast {
|
if append && format == Format::Asciicast {
|
||||||
asciicast::get_duration(&self.filename)
|
asciicast::get_duration(&self.path)
|
||||||
} else {
|
} else {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub struct Cmd {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct Rec {
|
pub struct Rec {
|
||||||
pub command: Option<String>,
|
pub command: Option<String>,
|
||||||
|
pub filename: String,
|
||||||
pub input: bool,
|
pub input: bool,
|
||||||
pub env: Option<String>,
|
pub env: Option<String>,
|
||||||
pub idle_time_limit: Option<f64>,
|
pub idle_time_limit: Option<f64>,
|
||||||
@@ -78,6 +79,7 @@ impl Config {
|
|||||||
let mut config = config::Config::builder()
|
let mut config = config::Config::builder()
|
||||||
.set_default("server.url", None::<Option<String>>)?
|
.set_default("server.url", None::<Option<String>>)?
|
||||||
.set_default("cmd.rec.input", false)?
|
.set_default("cmd.rec.input", false)?
|
||||||
|
.set_default("cmd.rec.filename", "%Y-%m-%d-%H-%M-%S-{pid}.cast")?
|
||||||
.set_default("cmd.play.speed", None::<Option<f64>>)?
|
.set_default("cmd.play.speed", None::<Option<f64>>)?
|
||||||
.set_default("cmd.stream.input", false)?
|
.set_default("cmd.stream.input", false)?
|
||||||
.set_default("notifications.enabled", true)?
|
.set_default("notifications.enabled", true)?
|
||||||
@@ -134,6 +136,10 @@ impl Config {
|
|||||||
self.cmd.rec.command.as_ref().cloned()
|
self.cmd.rec.command.as_ref().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cmd_rec_filename(&self) -> String {
|
||||||
|
self.cmd.rec.filename.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cmd_rec_input(&self) -> bool {
|
pub fn cmd_rec_input(&self) -> bool {
|
||||||
self.cmd.rec.input
|
self.cmd.rec.input
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user