diff --git a/crates/irtt-cli/src/applet.rs b/crates/irtt-cli/src/applet.rs index 29998b5..9e3d573 100644 --- a/crates/irtt-cli/src/applet.rs +++ b/crates/irtt-cli/src/applet.rs @@ -1,4 +1,6 @@ -use std::path::Path; +use std::{ffi::OsString, path::Path}; + +use clap::Command; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RequestedApplet { @@ -7,20 +9,17 @@ pub enum RequestedApplet { Server, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AppletAvailability { - Client, - Tui, - Server, - None, +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AppletDispatch { + Run { + applet: RequestedApplet, + argv: Vec, + }, + Help(String), } pub fn detect_applet_from_argv0(argv0: &str) -> Option { - match Path::new(argv0) - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or(argv0) - { + match applet_basename(argv0) { "irtt-cli" => Some(RequestedApplet::Client), "irtt-tui" => Some(RequestedApplet::Tui), "irttd" | "irtt-server" => Some(RequestedApplet::Server), @@ -29,20 +28,73 @@ pub fn detect_applet_from_argv0(argv0: &str) -> Option { } } -pub const fn default_applet_for_features( - client_enabled: bool, - tui_enabled: bool, - server_enabled: bool, -) -> AppletAvailability { - if client_enabled { - AppletAvailability::Client - } else if tui_enabled { - AppletAvailability::Tui - } else if server_enabled { - AppletAvailability::Server - } else { - AppletAvailability::None +pub fn dispatch_from_argv(argv: Vec) -> Result { + let argv0 = argv + .first() + .and_then(|arg| arg.to_str()) + .unwrap_or("irtt-rs"); + + if let Some(applet) = detect_applet_from_argv0(argv0) { + return Ok(AppletDispatch::Run { applet, argv }); } + + let basename = applet_basename(argv0); + if basename.starts_with("irtt-") && basename != "irtt-rs" { + return Err(format!( + "unknown applet name '{basename}'. Known applet names: irtt-rs, irtt-cli, irtt-tui, irtt-server" + )); + } + + let Some(command) = argv.get(1).and_then(|arg| arg.to_str()) else { + return Err(format!( + "choose an applet: client, tui, or server\n\n{}", + dispatcher_help() + )); + }; + + match command { + "-h" | "--help" => Ok(AppletDispatch::Help(dispatcher_help())), + "client" => Ok(AppletDispatch::Run { + applet: RequestedApplet::Client, + argv: applet_argv("irtt-cli", &argv[2..]), + }), + "tui" => Ok(AppletDispatch::Run { + applet: RequestedApplet::Tui, + argv: applet_argv("irtt-tui", &argv[2..]), + }), + "server" => Ok(AppletDispatch::Run { + applet: RequestedApplet::Server, + argv: applet_argv("irtt-server", &argv[2..]), + }), + _ => Err(format!( + "unknown applet '{command}'. Choose one of: client, tui, server\n\n{}", + dispatcher_help() + )), + } +} + +pub fn dispatcher_help() -> String { + let mut command = Command::new("irtt-rs") + .about("IRTT-compatible multi-applet dispatcher") + .subcommand(Command::new("client").about("Run the stream client applet")) + .subcommand(Command::new("tui").about("Run the terminal UI applet")) + .subcommand(Command::new("server").about("Run the server applet")) + .after_help("Applet binary names: irtt-cli, irtt-tui, irtt-server"); + command.render_help().to_string() +} + +fn applet_basename(argv0: &str) -> &str { + Path::new(argv0) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(argv0) +} + +fn applet_argv(applet_name: &str, args: &[OsString]) -> Vec { + let mut argv = Vec::with_capacity(args.len() + 1); + argv.push(OsString::from(applet_name)); + argv.extend(args.iter().cloned()); + argv } #[cfg(test)] diff --git a/crates/irtt-cli/src/main.rs b/crates/irtt-cli/src/main.rs index b506481..5f85089 100644 --- a/crates/irtt-cli/src/main.rs +++ b/crates/irtt-cli/src/main.rs @@ -6,9 +6,7 @@ use std::{ }; use irtt_cli::{ - applet::{ - default_applet_for_features, detect_applet_from_argv0, AppletAvailability, RequestedApplet, - }, + applet::{dispatch_from_argv, AppletDispatch, RequestedApplet}, signal::install_signal_handler, }; @@ -44,15 +42,6 @@ fn run_from_env() -> Result<(), Box> { } } -fn default_requested_applet() -> Result> { - match default_applet_for_features(cfg!(feature = "client"), cfg!(feature = "tui"), false) { - AppletAvailability::Client => Ok(RequestedApplet::Client), - AppletAvailability::Tui => Ok(RequestedApplet::Tui), - AppletAvailability::Server => Ok(RequestedApplet::Server), - AppletAvailability::None => Err("no runnable applet is available in this build".into()), - } -} - #[cfg(feature = "client")] fn run_client_applet( argv: Vec,