From aaaddd66ec9d5b3ca007a5b40003f6d9c33dbb4a Mon Sep 17 00:00:00 2001 From: Avril Date: Thu, 27 Mar 2025 15:51:04 +0000 Subject: [PATCH] Added fully-functional (default) feature `progress-reactive`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fortune for leanify-many's current commit: Blessing − 吉 --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/arg.rs | 26 ++++++++++++++++++++++++++ src/ext.rs | 17 +++++++++++++++++ src/main.rs | 1 + src/work.rs | 49 +++++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 88 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 818588a..083020b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,7 +178,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "leanify-many" -version = "1.2.1+1" +version = "1.2.1+2" dependencies = [ "cfg-if", "futures", diff --git a/Cargo.toml b/Cargo.toml index bf3f1db..3e69fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leanify-many" -version = "1.2.1+1" +version = "1.2.1+2" description = "spawn leanify subprocesses" authors = ["Avril "] edition = "2018" @@ -48,7 +48,7 @@ collect_err = [] # Without this feature enabled, `leanify` subprocesses will receive terminating `SIGINT`s as normal. shutdown = ["libc"] -# TODO: Implement this to: Capture `SIGWINCH` events and re-size + re-render the progress bar to the new terminal width. (XXX: Use a background thread (outside the thread-pool, as it's blocking) listening on `signal_hooks::Signals.forever()` for this that sends events through a shared Tokio `CondVar` notify_all() call.) +# Capture `SIGWINCH` events and re-size + re-render the progress bar to the new terminal width when appropriate. progress-reactive = ["progress", "tokio/signal", "signal-hook", "terminal_size"] [dependencies] diff --git a/src/arg.rs b/src/arg.rs index 9091459..a67586b 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -28,6 +28,7 @@ mod extra { #[inline] fn extra_args(#[allow(unused_variables)] output: &mut W) -> fmt::Result { #[cfg(feature="progress")] writeln!(output, " --no-progress Do not display progress bar")?; + #[cfg(feature="progress-reactive")] writeln!(output, " --static-progress Do not dynamically resize the progress bar")?; #[cfg(feature="colour")] writeln!(output, " --no-colour Do not display terminal colours")?; #[cfg(feature="colour")] writeln!(output, " --colour Always display terminal colour, even if env flags tell us not to")?; #[cfg(feature="shutdown")] writeln!(output, " -n, --no-cancel Do not capture `SIGINT`s for graceful shutdown.")?; @@ -163,6 +164,9 @@ fn comp_flags() check!(on "splash", "Show splash-screen"); check!(on "colour", "Enable coloured output"); check!(on "progress", "Enable progress bar"); + if cfg!(feature="progress") { + check!(on "progress-reactive", "Enable progress bar to reactively-resize to terminal width when changed."); + } check!(on "collect_err", "Collect the output of children's stderr instead of printing immediately"); check!(off "threads", "Enable threaded scheduler (usually not needed)"); check!(off "checked_pass", "Check the arguments passed with `--passthrough` to leanify. By default they are passed as is"); @@ -258,6 +262,8 @@ pub struct Flags { /// Display the progress bar #[cfg(feature="progress")] pub progress: bool, + /// Allow the dynamic reactive resizing of the progress bar + #[cfg(feature="progress-reactive")] pub progress_reactive: bool, /// Force use of colour #[cfg(feature="colour")] pub coloured: Option, /// Limit max children to this number @@ -268,6 +274,20 @@ pub struct Flags #[cfg(feature="shutdown")] pub graceful_shutdown: bool, } +impl Flags +{ + /// Should we watch for `SIGWINCH` to dynamically resize the progress bar? + /// + /// This will return false if there is either: not an output window that can be resized, no rendered progress bar (specified by user,) no dynamic-resize of progress bar (specified by user,) or the `progress-reactive` feature was not enabled at build-time. + #[inline] + pub fn watch_sigwinch(&self) -> bool + { + #![allow(unreachable_code)] + #[cfg(feature="progress-reactive")] return self.progress && self.progress_reactive && (util::is_terminal(&std::io::stdout()) || util::is_terminal(&std::io::stderr())); + false + } +} + impl Default for Flags { #[inline] @@ -279,6 +299,7 @@ impl Default for Flags hard_limit: None, leanify_flags: Default::default(), #[cfg(feature="shutdown")] graceful_shutdown: true, + #[cfg(feature="progress-reactive")] progress_reactive: true, } } } @@ -364,6 +385,11 @@ where I: IntoIterator, continue; } }, + #[cfg(feature="progress-reactive")] + "--static-progress" => { + cfg.flags.progress_reactive = false; + continue; + }, #[cfg(feature="progress")] "--no-progress" => { cfg.flags.progress = false; diff --git a/src/ext.rs b/src/ext.rs index 78bc335..9188cb6 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -24,3 +24,20 @@ where I: Iterator, output } } + +/// Explicit utilities +pub mod util { + use crate::*; + use std::os::fd::*; + + /// Check if `stream` is open to a tty. + pub fn is_terminal(stream: &T) -> bool + { + use std::ffi::c_int; + unsafe extern "C" { + safe fn isatty(fd: c_int) -> c_int; + } + + isatty(stream.as_fd().as_raw_fd()) == 1 + } +} diff --git a/src/main.rs b/src/main.rs index 093afe8..5b994f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use cfg_if::cfg_if; mod defer; mod ext; pub use ext::JoinStrsExt as _; +pub use ext::util; #[cfg(feature="splash")] mod splash; diff --git a/src/work.rs b/src/work.rs index 45726a2..9633944 100644 --- a/src/work.rs +++ b/src/work.rs @@ -144,12 +144,43 @@ where I: IntoIterator, } }; - // To close the backing handle and un-hook the signals: `close_resize_handle().await.expect("Failed to close reactive handle")` + /// Coerce a potentially-`None` thunk's return type into an `Option`, so that `Option T>` becomes `Fn() -> Option`. + /// + /// # `Option` mapping + /// Example usage would be `maybe_func!(if flag { Some(some_thunk_expr) } else { None })` will map the return type to `Option`, creating a thunk with the same signature as `some_thunk_expr` but an `Option`-wrapped return type. + /// + /// ## Internal type-coercion + /// The return-type can also be coerced itself via `maybe_func!(U: option_thunk_expression)` (where `U: From>`,) creating `for>> Option T> -> Fn() -> Option`. + macro_rules! maybe_func { + ($type:ty: $func:expr) => {{ + let func = $func; + move || -> $type { + if let Some(func) = func { + Some(func()).into() + } else { + None.into() + } + } + }}; + ($func:expr) => {{ + let func = $func; + move || { + if let Some(func) = func { + Some(func()) + } else { + None + } + } + }} + } + + // To close the backing handle and un-hook the signals: `close_resize_handle().await[.expect("Failed to close reactive handle")]` // NOTE: This must be called *before* shutting down the progress-bar. + // The return type is coerced to `Option>` (`None` is for if `flags.sigwinch()` is false.) #[cfg(feature="progress-reactive")] - let close_resize_handle = { + let close_resize_handle = maybe_func!(futures::future::OptionFuture<_>: if flags.watch_sigwinch() { let mut progress = progress.clone(); - + let (rx, handle) = progress::reactive::spawn_signal_watcher([signal_hook::consts::signal::SIGWINCH])?; let raw_handle = handle.signal_handle().clone(); @@ -157,7 +188,7 @@ where I: IntoIterator, use futures::stream::StreamExt; let _on_exit = defer::Defer::new(move || raw_handle.close()); - + let rx = rx.into_stream(); futures::pin_mut!(rx); @@ -177,14 +208,16 @@ where I: IntoIterator, } }); use std::io; - async move || -> io::Result<()> { + Some(async move || -> io::Result<()> { if handle.close()? == false { return Err(io::Error::new(io::ErrorKind::BrokenPipe, "The `resize_handle` task has already exited before it was requested to (BUG: This is not a problem, but means the resize-handle has not been closed in the right order in the bringdown code.)")); } rx.await?; Ok(()) - } - }; + }) + } else { + None + }); let display = { #[cfg(feature="progress")] let mut progress = progress.clone(); @@ -336,7 +369,7 @@ where I: IntoIterator, #[cfg(not(feature="progress"))] eprintln!("[{}] Child panic {:?}", colour::style(colour!(Color::BrightRed), "e"), failed); } #[cfg(feature="progress-reactive")] { - if let Err(e) = close_resize_handle().await { + if let Some(Err(e)) = close_resize_handle().await { let _ = progress.eprintln(format!("[{}] Warning! Failed to close progress-reactive resize handle: {:?}", colour::style(colour!(Color::Yellow),"!"), e)).await; } };