mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 11:48:13 +01:00
Collect full CLI interface in cli module
This commit is contained in:
234
src/cli.rs
Normal file
234
src/cli.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
use clap::{Args, ValueEnum};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::net::SocketAddr;
|
||||
use std::num::ParseIntError;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:8080";
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(author, version, about)]
|
||||
#[command(name = "asciinema")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
|
||||
/// asciinema server URL
|
||||
#[arg(long, global = true)]
|
||||
pub server_url: Option<String>,
|
||||
|
||||
/// Quiet mode, i.e. suppress diagnostic messages
|
||||
#[clap(short, long, global = true)]
|
||||
pub quiet: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Record a terminal session
|
||||
Rec(Record),
|
||||
|
||||
/// Replay a terminal session
|
||||
Play(Play),
|
||||
|
||||
/// Stream a terminal session
|
||||
Stream(Stream),
|
||||
|
||||
/// Concatenate multiple recordings
|
||||
Cat(Cat),
|
||||
|
||||
/// Convert a recording into another format
|
||||
Convert(Convert),
|
||||
|
||||
/// Upload a recording to an asciinema server
|
||||
Upload(Upload),
|
||||
|
||||
/// Authenticate this CLI with an asciinema server account
|
||||
Auth(Auth),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Record {
|
||||
pub filename: String,
|
||||
|
||||
/// Enable input recording
|
||||
#[arg(long, short = 'I', alias = "stdin")]
|
||||
pub input: bool,
|
||||
|
||||
/// Append to an existing recording file
|
||||
#[arg(short, long)]
|
||||
pub append: bool,
|
||||
|
||||
/// Recording file format [default: asciicast]
|
||||
#[arg(short, long, value_enum)]
|
||||
pub format: Option<Format>,
|
||||
|
||||
#[arg(long, hide = true)]
|
||||
pub raw: bool,
|
||||
|
||||
/// Overwrite target file if it already exists
|
||||
#[arg(long, conflicts_with = "append")]
|
||||
pub overwrite: bool,
|
||||
|
||||
/// Command to record [default: $SHELL]
|
||||
#[arg(short, long)]
|
||||
pub command: Option<String>,
|
||||
|
||||
/// List of env vars to save [default: TERM,SHELL]
|
||||
#[arg(long)]
|
||||
pub env: Option<String>,
|
||||
|
||||
/// Title of the recording
|
||||
#[arg(short, long)]
|
||||
pub title: Option<String>,
|
||||
|
||||
/// Limit idle time to a given number of seconds
|
||||
#[arg(short, long, value_name = "SECS")]
|
||||
pub idle_time_limit: Option<f64>,
|
||||
|
||||
/// Override terminal size for the recorded command
|
||||
#[arg(long, value_name = "COLSxROWS", value_parser = parse_tty_size)]
|
||||
pub tty_size: Option<(Option<u16>, Option<u16>)>,
|
||||
// pub tty_size: Option<TtySizeOverride>,
|
||||
#[arg(long, hide = true)]
|
||||
cols: Option<u16>,
|
||||
|
||||
#[arg(long, hide = true)]
|
||||
rows: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Play {
|
||||
#[arg(value_name = "FILENAME_OR_URL")]
|
||||
pub filename: String,
|
||||
|
||||
/// Limit idle time to a given number of seconds
|
||||
#[arg(short, long, value_name = "SECS")]
|
||||
pub idle_time_limit: Option<f64>,
|
||||
|
||||
/// Set playback speed
|
||||
#[arg(short, long)]
|
||||
pub speed: Option<f64>,
|
||||
|
||||
/// Loop loop loop loop
|
||||
#[arg(short, long, name = "loop")]
|
||||
pub loop_: bool,
|
||||
|
||||
/// Automatically pause on markers
|
||||
#[arg(short = 'm', long)]
|
||||
pub pause_on_markers: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Stream {
|
||||
/// Enable input capture
|
||||
#[arg(long, short = 'I', alias = "stdin")]
|
||||
pub input: bool,
|
||||
|
||||
/// Command to stream [default: $SHELL]
|
||||
#[arg(short, long)]
|
||||
pub command: Option<String>,
|
||||
|
||||
/// Serve the stream with the built-in HTTP server
|
||||
#[arg(short, long, value_name = "IP:PORT", default_missing_value = DEFAULT_LISTEN_ADDR, num_args = 0..=1)]
|
||||
pub serve: Option<SocketAddr>,
|
||||
|
||||
/// Relay the stream via an asciinema server
|
||||
#[arg(short, long, value_name = "STREAM-ID|WS-URL", default_missing_value = "", num_args = 0..=1, value_parser = validate_forward_target)]
|
||||
pub relay: Option<RelayTarget>,
|
||||
|
||||
/// Override terminal size for the session
|
||||
#[arg(long, value_name = "COLSxROWS", value_parser = parse_tty_size)]
|
||||
pub tty_size: Option<(Option<u16>, Option<u16>)>,
|
||||
// pub tty_size: Option<TtySizeOverride>,
|
||||
/// Log file path
|
||||
#[arg(long)]
|
||||
pub log_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cat {
|
||||
#[arg(required = true)]
|
||||
pub filename: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Convert {
|
||||
#[arg(value_name = "INPUT_FILENAME_OR_URL")]
|
||||
pub input_filename: String,
|
||||
|
||||
pub output_filename: String,
|
||||
|
||||
/// Output file format [default: asciicast]
|
||||
#[arg(short, long, value_enum)]
|
||||
pub format: Option<Format>,
|
||||
|
||||
/// Overwrite target file if it already exists
|
||||
#[arg(long)]
|
||||
pub overwrite: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Upload {
|
||||
/// Filename/path of asciicast to upload
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Auth {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, ValueEnum)]
|
||||
pub enum Format {
|
||||
Asciicast,
|
||||
Raw,
|
||||
Txt,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RelayTarget {
|
||||
StreamId(String),
|
||||
WsProducerUrl(url::Url),
|
||||
}
|
||||
|
||||
fn parse_tty_size(s: &str) -> Result<(Option<u16>, Option<u16>), String> {
|
||||
match s.split_once('x') {
|
||||
Some((cols, "")) => {
|
||||
let cols: u16 = cols.parse().map_err(|e: ParseIntError| e.to_string())?;
|
||||
|
||||
Ok((Some(cols), None))
|
||||
}
|
||||
|
||||
Some(("", rows)) => {
|
||||
let rows: u16 = rows.parse().map_err(|e: ParseIntError| e.to_string())?;
|
||||
|
||||
Ok((None, Some(rows)))
|
||||
}
|
||||
|
||||
Some((cols, rows)) => {
|
||||
let cols: u16 = cols.parse().map_err(|e: ParseIntError| e.to_string())?;
|
||||
let rows: u16 = rows.parse().map_err(|e: ParseIntError| e.to_string())?;
|
||||
|
||||
Ok((Some(cols), Some(rows)))
|
||||
}
|
||||
|
||||
None => Err(s.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_forward_target(s: &str) -> Result<RelayTarget, String> {
|
||||
let s = s.trim();
|
||||
|
||||
match url::Url::parse(s) {
|
||||
Ok(url) => {
|
||||
let scheme = url.scheme();
|
||||
|
||||
if scheme == "ws" || scheme == "wss" {
|
||||
Ok(RelayTarget::WsProducerUrl(url))
|
||||
} else {
|
||||
Err("must be a WebSocket URL (ws:// or wss://)".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
Err(url::ParseError::RelativeUrlWithoutBase) => Ok(RelayTarget::StreamId(s.to_owned())),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
use super::Command;
|
||||
use crate::api;
|
||||
use crate::cli;
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self, config: &Config) -> Result<()> {
|
||||
impl Command for cli::Auth {
|
||||
fn run(self, config: &Config) -> Result<()> {
|
||||
let server_url = config.get_server_url()?;
|
||||
let server_hostname = server_url.host().unwrap();
|
||||
let auth_url = api::get_auth_url(config)?;
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
use super::Command;
|
||||
use crate::asciicast;
|
||||
use crate::cli;
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
#[arg(required = true)]
|
||||
filename: Vec<String>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self) -> Result<()> {
|
||||
impl Command for cli::Cat {
|
||||
fn run(self, _config: &Config) -> Result<()> {
|
||||
let mut writer = asciicast::Writer::new(io::stdout(), 0);
|
||||
let mut time_offset: u64 = 0;
|
||||
let mut first = true;
|
||||
|
||||
@@ -1,45 +1,24 @@
|
||||
use super::Command;
|
||||
use crate::asciicast::{self, Header};
|
||||
use crate::encoder;
|
||||
use crate::cli::{self, Format};
|
||||
use crate::config::Config;
|
||||
use crate::encoder::{self, EncoderExt};
|
||||
use crate::util;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{Args, ValueEnum};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
#[arg(value_name = "INPUT_FILENAME_OR_URL")]
|
||||
input_filename: String,
|
||||
|
||||
output_filename: String,
|
||||
|
||||
/// Output file format [default: asciicast]
|
||||
#[arg(short, long, value_enum)]
|
||||
format: Option<Format>,
|
||||
|
||||
/// Overwrite target file if it already exists
|
||||
#[arg(long)]
|
||||
overwrite: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, ValueEnum)]
|
||||
enum Format {
|
||||
Asciicast,
|
||||
Raw,
|
||||
Txt,
|
||||
}
|
||||
|
||||
use crate::encoder::EncoderExt;
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self) -> Result<()> {
|
||||
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)?;
|
||||
|
||||
output.encode(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl cli::Convert {
|
||||
fn get_output(&self, header: &Header) -> Result<Box<dyn encoder::Encoder>> {
|
||||
let file = self.open_file()?;
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ use crate::notifier;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
|
||||
pub trait Command {
|
||||
fn run(self, config: &Config) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
fn get_notifier(config: &Config) -> Box<dyn notifier::Notifier> {
|
||||
if config.notifications.enabled {
|
||||
notifier::get_notifier(config.notifications.command.clone())
|
||||
|
||||
@@ -1,36 +1,15 @@
|
||||
use super::Command;
|
||||
use crate::asciicast;
|
||||
use crate::cli;
|
||||
use crate::config::Config;
|
||||
use crate::logger;
|
||||
use crate::player::{self, KeyBindings};
|
||||
use crate::tty;
|
||||
use crate::util;
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
#[arg(value_name = "FILENAME_OR_URL")]
|
||||
filename: String,
|
||||
|
||||
/// Limit idle time to a given number of seconds
|
||||
#[arg(short, long, value_name = "SECS")]
|
||||
idle_time_limit: Option<f64>,
|
||||
|
||||
/// Set playback speed
|
||||
#[arg(short, long)]
|
||||
speed: Option<f64>,
|
||||
|
||||
/// Loop loop loop loop
|
||||
#[arg(short, long, name = "loop")]
|
||||
loop_: bool,
|
||||
|
||||
/// Automatically pause on markers
|
||||
#[arg(short = 'm', long)]
|
||||
pause_on_markers: bool,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self, config: &Config) -> Result<()> {
|
||||
impl Command for cli::Play {
|
||||
fn run(self, config: &Config) -> Result<()> {
|
||||
let speed = self.speed.or(config.cmd_play_speed()).unwrap_or(1.0);
|
||||
let idle_time_limit = self.idle_time_limit.or(config.cmd_play_idle_time_limit());
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::Command;
|
||||
use crate::asciicast;
|
||||
use crate::cli;
|
||||
use crate::config::Config;
|
||||
use crate::encoder;
|
||||
use crate::locale;
|
||||
@@ -7,71 +9,14 @@ use crate::pty;
|
||||
use crate::recorder::{self, KeyBindings};
|
||||
use crate::tty;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{Args, ValueEnum};
|
||||
use cli::Format;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
filename: String,
|
||||
|
||||
/// Enable input recording
|
||||
#[arg(long, short = 'I', alias = "stdin")]
|
||||
input: bool,
|
||||
|
||||
/// Append to an existing recording file
|
||||
#[arg(short, long)]
|
||||
append: bool,
|
||||
|
||||
/// Recording file format [default: asciicast]
|
||||
#[arg(short, long, value_enum)]
|
||||
format: Option<Format>,
|
||||
|
||||
#[arg(long, hide = true)]
|
||||
raw: bool,
|
||||
|
||||
/// Overwrite target file if it already exists
|
||||
#[arg(long, conflicts_with = "append")]
|
||||
overwrite: bool,
|
||||
|
||||
/// Command to record [default: $SHELL]
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
/// List of env vars to save [default: TERM,SHELL]
|
||||
#[arg(long)]
|
||||
env: Option<String>,
|
||||
|
||||
/// Title of the recording
|
||||
#[arg(short, long)]
|
||||
title: Option<String>,
|
||||
|
||||
/// Limit idle time to a given number of seconds
|
||||
#[arg(short, long, value_name = "SECS")]
|
||||
idle_time_limit: Option<f64>,
|
||||
|
||||
/// Override terminal size for the recorded command
|
||||
#[arg(long, value_name = "COLSxROWS")]
|
||||
tty_size: Option<pty::WinsizeOverride>,
|
||||
|
||||
#[arg(long, hide = true)]
|
||||
cols: Option<u16>,
|
||||
|
||||
#[arg(long, hide = true)]
|
||||
rows: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, ValueEnum)]
|
||||
enum Format {
|
||||
Asciicast,
|
||||
Raw,
|
||||
Txt,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self, config: &Config) -> Result<()> {
|
||||
impl Command for cli::Record {
|
||||
fn run(self, config: &Config) -> Result<()> {
|
||||
locale::check_utf8_locale()?;
|
||||
|
||||
let format = self.get_format();
|
||||
@@ -116,7 +61,9 @@ impl Cli {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl cli::Record {
|
||||
fn get_mode(&self) -> Result<(bool, bool)> {
|
||||
let mut overwrite = self.overwrite;
|
||||
let mut append = self.append;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::Command;
|
||||
use crate::api;
|
||||
use crate::cli;
|
||||
use crate::config::Config;
|
||||
use crate::locale;
|
||||
use crate::logger;
|
||||
@@ -8,80 +10,24 @@ use crate::tty;
|
||||
use crate::util;
|
||||
use anyhow::bail;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Args;
|
||||
use cli::{RelayTarget, DEFAULT_LISTEN_ADDR};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpListener;
|
||||
use std::path::PathBuf;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:8080";
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
/// Enable input capture
|
||||
#[arg(long, short = 'I', alias = "stdin")]
|
||||
input: bool,
|
||||
|
||||
/// Command to stream [default: $SHELL]
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
/// Serve the stream with the built-in HTTP server
|
||||
#[clap(short, long, value_name = "IP:PORT", default_missing_value = DEFAULT_LISTEN_ADDR, num_args = 0..=1)]
|
||||
serve: Option<SocketAddr>,
|
||||
|
||||
/// Relay the stream via an asciinema server
|
||||
#[clap(short, long, value_name = "STREAM-ID|WS-URL", default_missing_value = "", num_args = 0..=1, value_parser = validate_forward_target)]
|
||||
relay: Option<RelayTarget>,
|
||||
|
||||
/// Override terminal size for the session
|
||||
#[arg(long, value_name = "COLSxROWS")]
|
||||
tty_size: Option<pty::WinsizeOverride>,
|
||||
|
||||
/// Log file path
|
||||
#[arg(long)]
|
||||
log_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum RelayTarget {
|
||||
StreamId(String),
|
||||
WsProducerUrl(url::Url),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Relay {
|
||||
ws_producer_url: Url,
|
||||
url: Option<Url>,
|
||||
}
|
||||
|
||||
fn validate_forward_target(s: &str) -> Result<RelayTarget, String> {
|
||||
let s = s.trim();
|
||||
|
||||
match url::Url::parse(s) {
|
||||
Ok(url) => {
|
||||
let scheme = url.scheme();
|
||||
|
||||
if scheme == "ws" || scheme == "wss" {
|
||||
Ok(RelayTarget::WsProducerUrl(url))
|
||||
} else {
|
||||
Err("must be a WebSocket URL (ws:// or wss://)".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
Err(url::ParseError::RelativeUrlWithoutBase) => Ok(RelayTarget::StreamId(s.to_owned())),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(mut self, config: &Config) -> Result<()> {
|
||||
impl Command for cli::Stream {
|
||||
fn run(mut self, config: &Config) -> Result<()> {
|
||||
locale::check_utf8_locale()?;
|
||||
|
||||
if self.serve.is_none() && self.relay.is_none() {
|
||||
@@ -157,7 +103,9 @@ impl Cli {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl cli::Stream {
|
||||
fn get_command(&self, config: &Config) -> Option<String> {
|
||||
self.command
|
||||
.as_ref()
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use super::Command;
|
||||
use crate::api;
|
||||
use crate::asciicast;
|
||||
use crate::cli;
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
/// Filename/path of asciicast to upload
|
||||
filename: String,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self, config: &Config) -> Result<()> {
|
||||
impl Command for cli::Upload {
|
||||
fn run(self, config: &Config) -> Result<()> {
|
||||
let _ = asciicast::open_from_path(&self.filename)?;
|
||||
let response = api::upload_asciicast(&self.filename, config)?;
|
||||
println!("{}", response.message.unwrap_or(response.url));
|
||||
|
||||
52
src/main.rs
52
src/main.rs
@@ -1,5 +1,6 @@
|
||||
mod api;
|
||||
mod asciicast;
|
||||
mod cli;
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod encoder;
|
||||
@@ -13,51 +14,12 @@ mod recorder;
|
||||
mod streamer;
|
||||
mod tty;
|
||||
mod util;
|
||||
use crate::cli::{Cli, Commands};
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::Parser;
|
||||
use cmd::Command;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(author, version, about)]
|
||||
#[command(name = "asciinema")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
|
||||
/// asciinema server URL
|
||||
#[arg(long, global = true)]
|
||||
server_url: Option<String>,
|
||||
|
||||
/// Quiet mode, i.e. suppress diagnostic messages
|
||||
#[clap(short, long, global = true)]
|
||||
quiet: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Commands {
|
||||
/// Record a terminal session
|
||||
Rec(cmd::rec::Cli),
|
||||
|
||||
/// Replay a terminal session
|
||||
Play(cmd::play::Cli),
|
||||
|
||||
/// Stream a terminal session
|
||||
Stream(cmd::stream::Cli),
|
||||
|
||||
/// Concatenate multiple recordings
|
||||
Cat(cmd::cat::Cli),
|
||||
|
||||
/// Convert a recording into another format
|
||||
Convert(cmd::convert::Cli),
|
||||
|
||||
/// Upload a recording to an asciinema server
|
||||
Upload(cmd::upload::Cli),
|
||||
|
||||
/// Authenticate this CLI with an asciinema server account
|
||||
Auth(cmd::auth::Cli),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let config = Config::new(cli.server_url.clone())?;
|
||||
|
||||
@@ -69,8 +31,8 @@ fn main() -> Result<()> {
|
||||
Commands::Rec(record) => record.run(&config),
|
||||
Commands::Play(play) => play.run(&config),
|
||||
Commands::Stream(stream) => stream.run(&config),
|
||||
Commands::Cat(cat) => cat.run(),
|
||||
Commands::Convert(convert) => convert.run(),
|
||||
Commands::Cat(cat) => cat.run(&config),
|
||||
Commands::Convert(convert) => convert.run(&config),
|
||||
Commands::Upload(upload) => upload.run(&config),
|
||||
Commands::Auth(auth) => auth.run(&config),
|
||||
}
|
||||
|
||||
74
src/pty.rs
74
src/pty.rs
@@ -15,9 +15,9 @@ use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::os::fd::{AsFd, RawFd};
|
||||
use std::os::fd::{BorrowedFd, OwnedFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::str::FromStr;
|
||||
use std::{env, fs};
|
||||
|
||||
type TtySizeOverride = (Option<u16>, Option<u16>);
|
||||
type ExtraEnv = HashMap<String, String>;
|
||||
|
||||
pub trait Recorder {
|
||||
@@ -31,10 +31,10 @@ pub fn exec<S: AsRef<str>, T: Tty + ?Sized, R: Recorder>(
|
||||
command: &[S],
|
||||
extra_env: &ExtraEnv,
|
||||
tty: &mut T,
|
||||
winsize_override: Option<WinsizeOverride>,
|
||||
tty_size_override: Option<TtySizeOverride>,
|
||||
recorder: &mut R,
|
||||
) -> Result<i32> {
|
||||
let winsize = get_winsize(&*tty, winsize_override.as_ref());
|
||||
let winsize = get_winsize(&*tty, &tty_size_override);
|
||||
recorder.start(winsize.into())?;
|
||||
let result = unsafe { pty::forkpty(Some(&winsize), None) }?;
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn exec<S: AsRef<str>, T: Tty + ?Sized, R: Recorder>(
|
||||
result.master.as_raw_fd(),
|
||||
child,
|
||||
tty,
|
||||
winsize_override,
|
||||
tty_size_override,
|
||||
recorder,
|
||||
),
|
||||
|
||||
@@ -58,10 +58,10 @@ fn handle_parent<T: Tty + ?Sized, R: Recorder>(
|
||||
master_fd: RawFd,
|
||||
child: unistd::Pid,
|
||||
tty: &mut T,
|
||||
winsize_override: Option<WinsizeOverride>,
|
||||
tty_size_override: Option<TtySizeOverride>,
|
||||
recorder: &mut R,
|
||||
) -> Result<i32> {
|
||||
let wait_result = match copy(master_fd, child, tty, winsize_override, recorder) {
|
||||
let wait_result = match copy(master_fd, child, tty, tty_size_override, recorder) {
|
||||
Ok(Some(status)) => Ok(status),
|
||||
Ok(None) => wait::waitpid(child, None),
|
||||
|
||||
@@ -85,7 +85,7 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
|
||||
master_raw_fd: RawFd,
|
||||
child: unistd::Pid,
|
||||
tty: &mut T,
|
||||
winsize_override: Option<WinsizeOverride>,
|
||||
tty_size_override: Option<TtySizeOverride>,
|
||||
recorder: &mut R,
|
||||
) -> Result<Option<WaitStatus>> {
|
||||
let mut master = unsafe { fs::File::from_raw_fd(master_raw_fd) };
|
||||
@@ -223,7 +223,7 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
|
||||
|
||||
if sigwinch_read {
|
||||
sigwinch_fd.flush();
|
||||
let winsize = get_winsize(&*tty, winsize_override.as_ref());
|
||||
let winsize = get_winsize(&*tty, &tty_size_override);
|
||||
set_pty_size(master_raw_fd, &winsize);
|
||||
recorder.resize(winsize.into());
|
||||
}
|
||||
@@ -287,61 +287,25 @@ fn handle_child<S: AsRef<str>>(command: &[S], extra_env: &ExtraEnv) -> Result<()
|
||||
unsafe { libc::_exit(1) }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WinsizeOverride {
|
||||
Full(u16, u16),
|
||||
Cols(u16),
|
||||
Rows(u16),
|
||||
}
|
||||
|
||||
impl FromStr for WinsizeOverride {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once('x') {
|
||||
Some((cols, "")) => {
|
||||
let cols: u16 = cols.parse()?;
|
||||
|
||||
Ok(WinsizeOverride::Cols(cols))
|
||||
}
|
||||
|
||||
Some(("", rows)) => {
|
||||
let rows: u16 = rows.parse()?;
|
||||
|
||||
Ok(WinsizeOverride::Rows(rows))
|
||||
}
|
||||
|
||||
Some((cols, rows)) => {
|
||||
let cols: u16 = cols.parse()?;
|
||||
let rows: u16 = rows.parse()?;
|
||||
|
||||
Ok(WinsizeOverride::Full(cols, rows))
|
||||
}
|
||||
|
||||
None => {
|
||||
bail!("{s}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_winsize<T: Tty + ?Sized>(
|
||||
tty: &T,
|
||||
winsize_override: Option<&WinsizeOverride>,
|
||||
tty_size_override: &Option<TtySizeOverride>,
|
||||
) -> pty::Winsize {
|
||||
let mut winsize = tty.get_size();
|
||||
|
||||
match winsize_override {
|
||||
Some(WinsizeOverride::Full(cols, rows)) => {
|
||||
match tty_size_override {
|
||||
Some((None, None)) => (),
|
||||
|
||||
Some((Some(cols), None)) => {
|
||||
winsize.ws_col = *cols;
|
||||
}
|
||||
|
||||
Some((None, Some(rows))) => {
|
||||
winsize.ws_row = *rows;
|
||||
}
|
||||
|
||||
Some(WinsizeOverride::Cols(cols)) => {
|
||||
Some((Some(cols), Some(rows))) => {
|
||||
winsize.ws_col = *cols;
|
||||
}
|
||||
|
||||
Some(WinsizeOverride::Rows(rows)) => {
|
||||
winsize.ws_row = *rows;
|
||||
}
|
||||
|
||||
@@ -438,7 +402,7 @@ impl Drop for SignalFd {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Recorder;
|
||||
use crate::pty::{ExtraEnv, WinsizeOverride};
|
||||
use crate::pty::ExtraEnv;
|
||||
use crate::tty::{NullTty, TtySize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -558,7 +522,7 @@ sys.stdout.write('bar');
|
||||
&["true"],
|
||||
&ExtraEnv::new(),
|
||||
&mut NullTty::open().unwrap(),
|
||||
Some(WinsizeOverride::Full(100, 50)),
|
||||
Some((Some(100), Some(50))),
|
||||
&mut recorder,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user