diff options
Diffstat (limited to 'mingling/src/setups')
| -rw-r--r-- | mingling/src/setups/confirmer.rs | 60 | ||||
| -rw-r--r-- | mingling/src/setups/osc94.rs | 89 | ||||
| -rw-r--r-- | mingling/src/setups/stdin_args.rs | 82 |
3 files changed, 231 insertions, 0 deletions
diff --git a/mingling/src/setups/confirmer.rs b/mingling/src/setups/confirmer.rs new file mode 100644 index 0000000..1824745 --- /dev/null +++ b/mingling/src/setups/confirmer.rs @@ -0,0 +1,60 @@ +use mingling_core::{ + Program, ProgramCollect, config, hook::ProgramHook, setup::ProgramSetup, this, +}; + +use crate::res::Confirmer; + +/// Confirmer setup for managing confirmation state +/// +/// This Setup manages the confirmation flag within the program's resource +/// store. It registers a [`Confirmer`] resource and sets up a hook that +/// checks the user's confirmation mode during program execution. +/// +/// # Usage +/// +/// This Setup can be registered using the +/// [`Program`](https://docs.rs/mingling/latest/mingling/struct.Program.html) +/// `with_setup` method, for example: +/// +/// ```rust +/// # use mingling::MockProgramCollect as ThisProgram; +/// use mingling::Program; +/// use mingling::setup::ConfirmerSetup; +/// +/// let mut program = Program::<ThisProgram>::new(); +/// program.with_setup(ConfirmerSetup); +/// ``` +/// +/// # Behavior +/// +/// - Registers a [`Confirmer`] resource that tracks confirmation state. +/// - At the beginning of command execution, checks whether the user's +/// confirmation mode is set to `Skip`. +/// - If confirmation is skipped, the [`Confirmer`] resource is updated +/// to record the confirmed state. +/// +/// # Notes +/// +/// - This Setup applies uniformly to all subcommands of the entire program. +/// - The confirmation state is determined by the global `config` setting; +/// it does not support per-command overrides. +pub struct ConfirmerSetup; + +impl<C> ProgramSetup<C> for ConfirmerSetup +where + C: ProgramCollect<Enum = C> + 'static, +{ + fn setup(self, program: &mut Program<C>) { + program.with_resource(Confirmer::new()); + + program.with_hook(ProgramHook::empty().on_pre_dispatch::<_, ()>(|_| { + let p = this::<C>(); + let confirmed = p.user_context.confirmation == config::ConfirmationMode::Skip; + if confirmed { + p.modify_res(|c: &mut Confirmer| { + c.set_confirmed(); + }); + } + })); + } +} diff --git a/mingling/src/setups/osc94.rs b/mingling/src/setups/osc94.rs new file mode 100644 index 0000000..2f9a319 --- /dev/null +++ b/mingling/src/setups/osc94.rs @@ -0,0 +1,89 @@ +use mingling_core::{Program, ProgramCollect, setup::ProgramSetup}; + +use crate::res::OSC94; + +/// `OSC 9;4` Setup for managing terminal progress notification state +/// +/// This Setup manages the terminal's `OSC 9;4` protocol support state within the +/// program's resource store. It registers an [`OSC94`] resource that tracks whether +/// the current terminal supports the protocol, and provides a helper resource that +/// can be used to send progress notification messages. +/// +/// # Usage +/// +/// This Setup can be registered using the +/// [`Program`](https://docs.rs/mingling/latest/mingling/struct.Program.html) +/// `with_setup` method, for example: +/// +/// ```rust +/// # use mingling::MockProgramCollect as ThisProgram; +/// use mingling::Program; +/// use mingling::setup::OSC94Setup; +/// +/// let mut program = Program::<ThisProgram>::new(); +/// program.with_setup(OSC94Setup); +/// ``` +/// +/// # Behavior +/// +/// - Registers an [`OSC94`] resource that tracks whether the current terminal +/// supports the `OSC 9;4` protocol. +/// - The support check inspects various environment variables such as `TERM_PROGRAM`, +/// `WT_SESSION`, `VTE_VERSION`, and `TERM`. +/// +/// # Notes +/// +/// - The support state is determined at setup time and stored in the resource store. +/// - Use [`OSC94Message`] to construct and send progress notification messages. +pub struct OSC94Setup; + +impl<C> ProgramSetup<C> for OSC94Setup +where + C: ProgramCollect<Enum = C> + 'static, +{ + fn setup(self, program: &mut Program<C>) { + program.with_resource(OSC94 { + is_support: is_support_osc94(), + }); + } +} + +/// Check whether the current terminal environment supports the `OSC 9;4` protocol +/// +/// This function inspects various environment variables to determine whether the +/// current terminal supports Microsoft's +/// [OSC 9;4 protocol](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences), +/// which allows sending task progress notifications via ANSI escape sequences. +/// +/// Supported terminal environments include: +/// - **`TERM_PROGRAM`**: `ghostty`, `WezTerm`, `iTerm.app` +/// - **`WT_SESSION`**: Windows Terminal +/// - **`VTE_VERSION`**: VTE-based terminals (such as GNOME Terminal, Konsole, etc.) +/// - **`TERM`**: terminal emulators containing `xterm` +/// +/// Returns `true` if the current terminal supports the `OSC 9;4` protocol, so that +/// progress notification escape sequences can be safely sent. +fn is_support_osc94() -> bool { + if let Ok(program) = std::env::var("TERM_PROGRAM") { + match program.as_str() { + "ghostty" | "WezTerm" | "iTerm.app" => return true, + _ => {} + } + } + + if std::env::var("WT_SESSION").is_ok() { + return true; + } + + if std::env::var("VTE_VERSION").is_ok() { + return true; + } + + if let Ok(term) = std::env::var("TERM") + && term.contains("xterm") + { + return true; + } + + false +} diff --git a/mingling/src/setups/stdin_args.rs b/mingling/src/setups/stdin_args.rs new file mode 100644 index 0000000..ca55ca7 --- /dev/null +++ b/mingling/src/setups/stdin_args.rs @@ -0,0 +1,82 @@ +use std::io::{IsTerminal, Read}; + +use mingling_core::{ + Program, ProgramCollect, hook::ProgramHook, setup::ProgramSetup, utils::ArgumentSplitter, +}; + +/// Uses the standard input as arguments for the program +/// +/// This Setup can take standard input supplied via a pipe or redirect, +/// split it according to whitespace and quoting rules, and append +/// the resulting arguments to the end of the command argument list. +/// +/// # Usage +/// +/// This Setup can be registered using the +/// [`Program`](https://docs.rs/mingling/latest/mingling/struct.Program.html) +/// `with_setup` method, for example: +/// +/// ```rust +/// # use mingling::MockProgramCollect as ThisProgram; +/// use mingling::Program; +/// use mingling::setup::StandardInputArgsSetup; +/// +/// let mut program = Program::<ThisProgram>::new(); +/// program.with_setup(StandardInputArgsSetup); +/// ``` +/// +/// # Behavior +/// +/// - Standard input is only read when it is not a terminal (i.e., when +/// there is piped or redirected input). +/// - The read content is split into multiple arguments according to +/// whitespace and quoting rules. +/// - If the standard input content is empty, no arguments are produced. +/// - All input is converted to UTF-8 encoding (lossy conversion is used +/// when strict parsing is not possible). +/// +/// # Notes +/// +/// - This Setup applies uniformly to all subcommands of the entire program +/// and does not provide fine-grained control. If you need different +/// standard input behavior across different subcommands (e.g., some +/// subcommands read stdin while others ignore it), **do not use this Setup**. +/// - This Setup does **not** provide any validation rules. Content provided +/// via standard input is treated as trusted arguments and appended directly. +/// As a result, the input source can also inject arbitrary arguments into +/// the command, so you should be careful when processing untrusted input. +pub struct StandardInputArgsSetup; + +impl<C> ProgramSetup<C> for StandardInputArgsSetup +where + C: ProgramCollect<Enum = C>, +{ + fn setup(self, program: &mut Program<C>) { + program.with_hook(ProgramHook::empty().on_pre_dispatch(|ctx| { + let pipe_input = read_stdin(); + if let Some(pipe_input) = pipe_input { + ctx.arguments.append(&mut pipe_input.trim().split_args()); + } + })); + } +} + +fn read_stdin() -> Option<String> { + // Check if stdin is a terminal (no piped input) or has data available + if std::io::stdin().is_terminal() { + return None; + } + + let mut bytes = Vec::new(); + match std::io::stdin().read_to_end(&mut bytes) { + Ok(_) => { + if bytes.is_empty() { + return None; + } + // Handle encoding differences, ensure output is always UTF-8. + // First try strict UTF-8 parsing; fall back to lossy conversion + Some(String::from_utf8_lossy(&bytes).into_owned()) + } + Err(_) => None, + } +} |
