diff --git a/Cargo.lock b/Cargo.lock index 173d37d032e..f59169c1c87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3388,6 +3388,7 @@ dependencies = [ "assert_cmd", "async-recursion", "async-trait", + "atty", "clap 4.3.2", "clap_complete", "clap_mangen", diff --git a/implementations/rust/ockam/ockam_command/Cargo.toml b/implementations/rust/ockam/ockam_command/Cargo.toml index 1c056572e6e..2a2360da77f 100644 --- a/implementations/rust/ockam/ockam_command/Cargo.toml +++ b/implementations/rust/ockam/ockam_command/Cargo.toml @@ -55,6 +55,7 @@ path = "src/bin/ockam.rs" anyhow = "1" async-recursion = { version = "1.0.0" } async-trait = "0.1" +atty = "0.2.14" clap = { version = "4.3.2", features = ["derive", "cargo", "wrap_help"] } clap_complete = "4.3.1" clap_mangen = "0.2.12" diff --git a/implementations/rust/ockam/ockam_command/src/lib.rs b/implementations/rust/ockam/ockam_command/src/lib.rs index 3b1d8a59476..178f208b6fe 100644 --- a/implementations/rust/ockam/ockam_command/src/lib.rs +++ b/implementations/rust/ockam/ockam_command/src/lib.rs @@ -80,8 +80,7 @@ use secure_channel::{listener::SecureChannelListenerCommand, SecureChannelComman use service::ServiceCommand; use space::SpaceCommand; use status::StatusCommand; -use std::path::PathBuf; -use std::sync::Mutex; +use std::{ffi::OsString, path::Path, path::PathBuf, process, process::Stdio, sync::Mutex}; use tcp::{ connection::TcpConnectionCommand, inlet::TcpInletCommand, listener::TcpListenerCommand, outlet::TcpOutletCommand, @@ -112,7 +111,7 @@ after_long_help = docs::after_help(AFTER_LONG_HELP), version, long_version = Version::long(), next_help_heading = "Global Options", -disable_help_flag = true +disable_help_flag = true, )] pub struct OckamCommand { #[command(subcommand)] @@ -315,13 +314,16 @@ pub fn run() { .map(replace_hyphen_with_stdin) .collect::>(); - let command = OckamCommand::parse_from(input); - - if !command.global_args.test_argument_parser { - check_if_an_upgrade_is_available(); - } + match OckamCommand::try_parse_from(input) { + Ok(command) => { + if !command.global_args.test_argument_parser { + check_if_an_upgrade_is_available(); + } - command.run(); + command.run(); + } + Err(help) => show_help(help), + }; } impl OckamCommand { @@ -469,3 +471,78 @@ pub(crate) fn replace_hyphen_with_stdin(s: String) -> String { s } } + +const ENV_FORCE_COLOR: &str = "ockam_force_color"; + +fn show_help(help: clap::Error) { + use std::env; + let mut try_fallback = false; + let preferred_pager = env::var_os("PAGER").unwrap_or_else(|| { + try_fallback = true; + OsString::from("less") + }); + + if preferred_pager == "false" { + use clap::{ColorChoice::*, CommandFactory}; + let possibly_forced = if env::var_os(ENV_FORCE_COLOR).is_some() { + Always + } else { + Auto + }; + + help.with_cmd(&OckamCommand::command().color(possibly_forced)) + .exit(); + } + + if let Ok(()) = paginate_with(preferred_pager, &help) { + return; + } + if try_fallback { + if let Ok(()) = paginate_with(OsString::from("more"), &help) { + return; + } + } + paginate_with(OsString::from("false"), &help) + .expect("displaying help without pagination should always work"); +} + +fn paginate_with(pager: OsString, help: &clap::Error) -> Result<()> { + let mut pager_invocation = process::Command::new(&pager); + if Path::new(&pager) + .file_name() + .map_or("", |s| s.to_str().unwrap_or("")) + == "less" + { + pager_invocation.env("LESS", "FRX"); + // - F: no pagination if the text fits entirely into the window + // - R: allow ANSI escapes output formatting + // - X: prevents clearing the screen on exit + // - using env var in case a lesser `less` poses as `less` + } + let mut pager_process = pager_invocation.stdin(Stdio::piped()).spawn()?; + let pipe = Stdio::from(pager_process.stdin.take().expect("stdin open?")); + + let exit_status = { + let mut my_args = std::env::args_os(); + let my_exe_name = my_args.next().unwrap_or("ockam".into()); + + let mut rerun = process::Command::new(my_exe_name); + rerun.args(my_args).env("PAGER", "false"); + use atty::Stream::*; + let output_stream = if help.use_stderr() { + rerun.stderr(pipe); + Stderr + } else { + rerun.stdout(pipe); + Stdout + }; + if atty::is(output_stream) { + rerun.env(ENV_FORCE_COLOR, "_"); + } + rerun.status()?.code().unwrap_or(exitcode::SOFTWARE) + // dropping owned pipe hands over pager control to the user + }; + + pager_process.wait()?; + process::exit(exit_status); +}