aboutsummaryrefslogtreecommitdiff
path: root/mingling/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling/src')
-rw-r--r--mingling/src/example_docs.rs1
-rw-r--r--mingling/src/res.rs15
-rw-r--r--mingling/src/res/confirmer.rs268
-rw-r--r--mingling/src/res/osc94.rs264
-rw-r--r--mingling/src/res/osc94/state.rs74
-rw-r--r--mingling/src/setups.rs34
-rw-r--r--mingling/src/setups/confirmer.rs60
-rw-r--r--mingling/src/setups/osc94.rs89
-rw-r--r--mingling/src/setups/stdin_args.rs82
9 files changed, 868 insertions, 19 deletions
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index 55aabdf..c292598 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -2970,6 +2970,7 @@ pub mod example_setup {}
/// path = "../../mingling"
/// features = [
/// "structural_renderer",
+/// "yaml_serde_fmt",
/// "parser",
/// ]
///
diff --git a/mingling/src/res.rs b/mingling/src/res.rs
index a35559c..82c4e00 100644
--- a/mingling/src/res.rs
+++ b/mingling/src/res.rs
@@ -1,9 +1,14 @@
-// Doc Not Optimize
-mod exit_code;
-pub use exit_code::*;
+#[allow(unused_imports)]
+pub use mingling_core::core_res::*;
mod dirs;
pub use dirs::*;
-#[allow(unused_imports)]
-pub use mingling_core::core_res::*;
+mod exit_code;
+pub use exit_code::*;
+
+mod confirmer;
+pub use confirmer::*;
+
+mod osc94;
+pub use osc94::*;
diff --git a/mingling/src/res/confirmer.rs b/mingling/src/res/confirmer.rs
new file mode 100644
index 0000000..900562b
--- /dev/null
+++ b/mingling/src/res/confirmer.rs
@@ -0,0 +1,268 @@
+use std::io::{BufRead, Write};
+
+/// A confirmer for interactive confirmation.
+///
+/// This structure caches the confirmed state to avoid repeated prompts.
+///
+/// Typically, `Confirmer` is registered via [`ConfirmerSetup`], and then injected into functions
+/// through Mingling's resource injection system.
+///
+/// # Registration
+///
+/// Before use, the [`ConfirmerSetup`] must be registered with the program:
+///
+/// ```
+/// # use mingling::MockProgramCollect as ThisProgram;
+/// use mingling::setup::ConfirmerSetup;
+/// use mingling::Program;
+///
+/// let mut program = Program::<ThisProgram>::new();
+/// program.with_setup(ConfirmerSetup);
+/// ```
+///
+/// # Examples
+///
+/// ```
+/// use mingling::res::{Confirmer, YesConfirm};
+///
+/// // In actual use, obtain the registered confirmer through the resource injection system
+/// let confirmer = Confirmer::new_confirmed();
+/// assert!(confirmer.ask::<YesConfirm>("Continue? [y/n] "));
+/// ```
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Confirmer {
+ pub(crate) confirmed: bool,
+}
+
+impl Confirmer {
+ /// Creates a new `Confirmer` instance.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling::res::Confirmer;
+ ///
+ /// let confirmer = Confirmer::new();
+ /// ```
+ #[must_use]
+ pub const fn new() -> Self {
+ Self { confirmed: false }
+ }
+
+ /// Creates a `Confirmer` instance in the confirmed state.
+ ///
+ /// The returned `Confirmer` will directly return `true` when calling [`ask`](Confirmer::ask) or
+ /// [`try_ask`](Confirmer::try_ask), without prompting the user.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling::res::{Confirmer, YesConfirm};
+ ///
+ /// let confirmer = Confirmer::new_confirmed();
+ /// assert!(confirmer.ask::<YesConfirm>("Continue? [y/n] "));
+ /// ```
+ #[must_use]
+ pub const fn new_confirmed() -> Self {
+ Self { confirmed: true }
+ }
+
+ /// Marks the confirmer as confirmed.
+ ///
+ /// After calling this method, subsequent calls to [`ask`](Confirmer::ask) or
+ /// [`try_ask`](Confirmer::try_ask) on this confirmer will directly return `true`
+ /// without prompting the user.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling::res::{Confirmer, YesConfirm};
+ ///
+ /// let mut confirmer = Confirmer::new();
+ /// confirmer.set_confirmed();
+ /// assert!(confirmer.ask::<YesConfirm>("Continue? [y/n] "));
+ /// ```
+ pub const fn set_confirmed(&mut self) {
+ self.confirmed = true;
+ }
+
+ /// Asks the user a confirmation question, with at most one attempt.
+ ///
+ /// Returns `false` if the user provides an unrecognizable answer.
+ /// Returns `true` directly if already confirmed previously.
+ ///
+ /// # Parameters
+ ///
+ /// * `ask` - The prompt text to display to the user.
+ ///
+ /// # Returns
+ ///
+ /// Returns a boolean indicating whether the user confirmed. Returns `false` if the user's input
+ /// could not be parsed or the maximum number of attempts was reached.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling::res::{Confirmer, YesConfirm};
+ ///
+ /// let confirmer = Confirmer::new_confirmed();
+ /// let confirmed = confirmer.ask::<YesConfirm>("Delete this file? [y/n] ");
+ /// ```
+ pub fn ask<P: ConfirmerPredicate>(&self, ask: impl AsRef<str>) -> bool {
+ self.try_ask::<P>(ask, ConfirmerCount::Max(1))
+ .unwrap_or(false)
+ }
+
+ /// Asks the user a confirmation question, allowing a specified maximum number of attempts.
+ ///
+ /// # Parameters
+ ///
+ /// * `ask` - The prompt text to display to the user.
+ /// * `count` - The maximum number of attempts. Passing `0` means unlimited attempts (loop
+ /// indefinitely), passing a positive integer means at most that many attempts.
+ ///
+ /// # Returns
+ ///
+ /// Returns `Some(true)` for confirmation, `Some(false)` for rejection.
+ /// Returns `None` if the maximum number of attempts is reached without being able to parse
+ /// the user's input.
+ ///
+ /// # Panics
+ ///
+ /// This function panics when the standard error output (`stderr`) cannot be flushed or when
+ /// reading from standard input fails.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling::res::{Confirmer, YesConfirm};
+ ///
+ /// let confirmer = Confirmer::new_confirmed();
+ /// let confirmed = confirmer.try_ask::<YesConfirm>("Confirm execution? [y/n] ", 3);
+ /// ```
+ pub fn try_ask<P: ConfirmerPredicate>(
+ &self,
+ ask: impl AsRef<str>,
+ count: impl Into<ConfirmerCount>,
+ ) -> Option<bool> {
+ if self.confirmed {
+ return Some(true);
+ }
+
+ let count = count.into();
+ let mut attempts = 0usize;
+
+ loop {
+ eprint!("{}", ask.as_ref());
+ std::io::stderr().flush().unwrap();
+
+ let stdin = std::io::stdin();
+ let mut input = String::new();
+ stdin.lock().read_line(&mut input).unwrap();
+ if let Some(result) = P::is_yes(&input) {
+ return Some(result);
+ }
+
+ attempts += 1;
+ match count {
+ ConfirmerCount::Loop => {}
+ ConfirmerCount::Max(max) => {
+ if attempts >= max {
+ return None;
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Specifies the maximum number of attempts for a confirmation prompt.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ConfirmerCount {
+ /// Loop indefinitely until the user gives a parseable answer.
+ Loop,
+ /// Ask at most the specified number of times.
+ Max(usize),
+}
+
+macro_rules! impl_from_for_confirmer_count {
+ ($($t:ty),*) => {
+ $(
+ impl From<$t> for ConfirmerCount {
+ fn from(n: $t) -> Self {
+ if n == 0 {
+ ConfirmerCount::Loop
+ } else {
+ match usize::try_from(n) {
+ Ok(max) => ConfirmerCount::Max(max),
+ Err(_) => ConfirmerCount::Max(usize::MAX),
+ }
+ }
+ }
+ }
+ )*
+ };
+}
+
+impl_from_for_confirmer_count!(
+ i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
+);
+
+/// Defines how to parse user confirmation input.
+///
+/// A type implementing this trait determines which user input strings are treated as "yes" or "no".
+pub trait ConfirmerPredicate {
+ /// Parses the user's input string, returning whether it is "yes".
+ ///
+ /// Returns `Some(true)` for yes, `Some(false)` for no,
+ /// and `None` if the input cannot be parsed (requiring re-entry).
+ fn is_yes(str: &str) -> Option<bool>;
+}
+
+/// A `ConfirmerPredicate` implementation that accepts "y"/"yes" as yes and "n"/"no" as no.
+///
+/// Input comparison is case-insensitive and automatically trims leading/trailing whitespace.
+///
+/// # Examples
+///
+/// ```
+/// use mingling::res::{Confirmer, YesConfirm};
+///
+/// let confirmer = Confirmer::default();
+/// let confirmed = confirmer.ask::<YesConfirm>("Continue? [y/n] ");
+/// ```
+pub struct YesConfirm;
+
+/// A `ConfirmerPredicate` implementation that accepts "true"/"t" as yes and "false"/"f" as no.
+///
+/// Input comparison is case-insensitive and automatically trims leading/trailing whitespace.
+///
+/// # Examples
+///
+/// ```
+/// use mingling::res::{Confirmer, TrueConfirm};
+///
+/// let confirmer = Confirmer::default();
+/// let confirmed = confirmer.ask::<TrueConfirm>("Enable this feature? [true/false] ");
+/// ```
+pub struct TrueConfirm;
+
+impl ConfirmerPredicate for YesConfirm {
+ fn is_yes(str: &str) -> Option<bool> {
+ match str.trim().to_lowercase().as_str() {
+ "y" | "yes" => Some(true),
+ "n" | "no" => Some(false),
+ _ => None,
+ }
+ }
+}
+
+impl ConfirmerPredicate for TrueConfirm {
+ fn is_yes(str: &str) -> Option<bool> {
+ match str.trim().to_lowercase().as_str() {
+ "true" | "t" => Some(true),
+ "false" | "f" => Some(false),
+ _ => None,
+ }
+ }
+}
diff --git a/mingling/src/res/osc94.rs b/mingling/src/res/osc94.rs
new file mode 100644
index 0000000..8f873aa
--- /dev/null
+++ b/mingling/src/res/osc94.rs
@@ -0,0 +1,264 @@
+mod state;
+pub use state::*;
+
+/// Process `OSC 9;4` status.
+///
+/// Provides support for the `OSC 9;4` protocol. You can inject it into the execution flow
+/// through Mingling's resource injection system, and use it to control your process state.
+///
+/// Typically, `OSC94` is registered via [`OSC94Setup`], and then injected into functions
+/// through Mingling's resource injection system.
+///
+/// # Registration
+///
+/// Before use, the [`OSC94Setup`] must be registered with the program:
+///
+/// ```
+/// # use mingling::MockProgramCollect as ThisProgram;
+/// use mingling::setup::OSC94Setup;
+/// use mingling::Program;
+///
+/// let mut program = Program::<ThisProgram>::new();
+/// program.with_setup(OSC94Setup);
+/// ```
+///
+/// # Example
+///
+/// ```
+/// use mingling::res::{OSC94, OSC94State};
+///
+/// let osc94 = OSC94::default();
+/// let mut guard = osc94.get_mut();
+///
+/// guard.set_progress(0.5);
+/// assert_eq!(guard.state(), OSC94State::Normal(0.5));
+/// ```
+#[derive(Debug, Default, Clone, Copy)]
+pub struct OSC94 {
+ pub(crate) is_support: bool,
+}
+
+impl OSC94 {
+ /// Get a guard for modifying progress.
+ ///
+ /// The returned [`OSC94Guard`] allows you to set the process state and progress.
+ /// If the current environment supports the `OSC 9;4` protocol, state changes will
+ /// be sent to the terminal in real time.
+ ///
+ /// # Returns
+ ///
+ /// Returns an [`OSC94Guard`] with an initial state of [`OSC94State::Clean`].
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::{OSC94, OSC94State};
+ ///
+ /// let osc94 = OSC94::default();
+ /// let guard = osc94.get_mut();
+ /// assert_eq!(guard.state(), OSC94State::Clean);
+ /// ```
+ #[must_use]
+ pub const fn get_mut(&self) -> OSC94Guard {
+ OSC94Guard {
+ is_support: self.is_support,
+ msg: OSC94State::Clean,
+ }
+ }
+}
+
+/// A guard for modifying process state.
+///
+/// Obtained via [`OSC94::get_mut`]. When the guard is dropped, the process state is
+/// automatically restored to [`OS94State::Clean`], so no manual cleanup is needed.
+///
+/// # Example
+///
+/// Create a guard via [`OSC94`], and the state is automatically restored to Clean
+/// when the guard is dropped:
+///
+/// ```
+/// use mingling::res::OSC94;
+///
+/// let osc94 = OSC94::default();
+/// {
+/// let mut guard = osc94.get_mut();
+/// guard.set_progress(0.5);
+/// // When leaving this scope, the guard is dropped and the process state is automatically restored to Clean
+/// }
+/// ```
+pub struct OSC94Guard {
+ pub(crate) is_support: bool,
+ msg: OSC94State,
+}
+
+impl OSC94Guard {
+ /// Set the process state to Clean.
+ ///
+ /// Indicates that the process has finished or is in a normal, problem-free state.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::{OSC94, OSC94State};
+ ///
+ /// let osc94 = OSC94::default();
+ /// let mut guard = osc94.get_mut();
+ /// guard.set_progress(0.5);
+ /// guard.set_clean_state();
+ /// assert_eq!(guard.state(), OSC94State::Clean);
+ /// ```
+ pub fn set_clean_state(&mut self) {
+ self.msg = OSC94State::Clean;
+ if self.is_support {
+ self.msg.send();
+ }
+ }
+
+ /// Set the process state to Error.
+ ///
+ /// Indicates that an error occurred during process execution.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::{OSC94, OSC94State};
+ ///
+ /// let osc94 = OSC94::default();
+ /// let mut guard = osc94.get_mut();
+ /// guard.set_error_state();
+ /// assert_eq!(guard.state(), OSC94State::Error);
+ /// ```
+ pub fn set_error_state(&mut self) {
+ self.msg = OSC94State::Error;
+ if self.is_support {
+ self.msg.send();
+ }
+ }
+
+ /// Set the process state to Warn.
+ ///
+ /// Indicates that a warning occurred during process execution, but it has not
+ /// reached the level of an error.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::{OSC94, OSC94State};
+ ///
+ /// let osc94 = OSC94::default();
+ /// let mut guard = osc94.get_mut();
+ /// guard.set_warn_state();
+ /// assert_eq!(guard.state(), OSC94State::Warn);
+ /// ```
+ pub fn set_warn_state(&mut self) {
+ self.msg = OSC94State::Warn;
+ if self.is_support {
+ self.msg.send();
+ }
+ }
+
+ /// Set the process state to Unknown.
+ ///
+ /// Indicates that the process state cannot be determined or has not been defined.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::{OSC94, OSC94State};
+ ///
+ /// let osc94 = OSC94::default();
+ /// let mut guard = osc94.get_mut();
+ /// guard.set_unknown_state();
+ /// assert_eq!(guard.state(), OSC94State::Unknown);
+ /// ```
+ pub fn set_unknown_state(&mut self) {
+ self.msg = OSC94State::Unknown;
+ if self.is_support {
+ self.msg.send();
+ }
+ }
+
+ /// Set the progress of the process.
+ ///
+ /// The `progress` parameter should be between `0.0` and `1.0`. `0.0` indicates
+ /// the start of the task, and `1.0` indicates the completion of the task.
+ /// Values outside this range are not clamped, but it is recommended to keep them
+ /// within this range.
+ ///
+ /// # Parameters
+ ///
+ /// * `progress` - The progress value, ranging from `0.0` to `1.0`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::OSC94;
+ ///
+ /// let osc94 = OSC94::default();
+ /// let mut guard = osc94.get_mut();
+ /// guard.set_progress(0.5);
+ /// assert_eq!(guard.progress(), 0.5);
+ /// ```
+ pub fn set_progress(&mut self, progress: f32) {
+ self.msg = OSC94State::Normal(progress);
+ if self.is_support {
+ self.msg.send();
+ }
+ }
+
+ /// Get the current process state.
+ ///
+ /// # Returns
+ ///
+ /// Returns the current [`OSC94State`] value, representing the state of the process.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::{OSC94, OSC94State};
+ ///
+ /// let osc94 = OSC94::default();
+ /// let guard = osc94.get_mut();
+ /// assert_eq!(guard.state(), OSC94State::Clean);
+ /// ```
+ #[must_use]
+ pub const fn state(&self) -> OSC94State {
+ self.msg
+ }
+
+ /// Get the current progress value.
+ ///
+ /// Returns the actual progress value only when the state is [`OSC94State::Normal`];
+ /// otherwise returns `0.0`.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `f32` progress value, ranging from `0.0` to `1.0`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use mingling::res::OSC94;
+ ///
+ /// let osc94 = OSC94::default();
+ /// let mut guard = osc94.get_mut();
+ /// guard.set_progress(0.25);
+ /// assert_eq!(guard.progress(), 0.25);
+ /// ```
+ #[must_use]
+ pub const fn progress(&self) -> f32 {
+ match self.msg {
+ OSC94State::Normal(progress) => progress,
+ _ => 0.0,
+ }
+ }
+}
+
+impl Drop for OSC94Guard {
+ fn drop(&mut self) {
+ if self.is_support {
+ OSC94State::Clean.send();
+ }
+ }
+}
diff --git a/mingling/src/res/osc94/state.rs b/mingling/src/res/osc94/state.rs
new file mode 100644
index 0000000..c3d2bdf
--- /dev/null
+++ b/mingling/src/res/osc94/state.rs
@@ -0,0 +1,74 @@
+/// `OSC 9;4` 协议消息
+///
+/// 用于通过 ANSI 转义序列向终端发送任务进度通知消息
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum OSC94State {
+ /// 清除/隐藏进度(任务完成时使用),对应状态码 `0`
+ Clean,
+ /// 正常状态,对应状态码 `1`,需要配合进度值(0-100)
+ Normal(f32),
+ /// 错误状态,对应状态码 `2`(通常显示为红色)
+ Error,
+ /// 不确定状态,对应状态码 `3`(显示为无限循环的动画,用于进度未知的任务)
+ Unknown,
+ /// 警告状态,对应状态码 `4`(通常显示为黄色)
+ Warn,
+}
+
+impl OSC94State {
+ /// Returns the state code for the `OSC 9;4` protocol.
+ #[must_use]
+ pub const fn state_code(&self) -> u8 {
+ match self {
+ Self::Clean => 0,
+ Self::Normal(_) => 1,
+ Self::Error => 2,
+ Self::Unknown => 3,
+ Self::Warn => 4,
+ }
+ }
+
+ /// Returns the progress value (0-100) for the `Normal` state, clamped to the valid range.
+ #[must_use]
+ pub const fn progress(&self) -> f32 {
+ match self {
+ Self::Normal(progress) => (progress.clamp(0.0, 1.0) * 100.0).round(),
+ _ => 0.0,
+ }
+ }
+
+ /// Converts the message into the corresponding `OSC 9;4` escape sequence string.
+ #[must_use]
+ pub fn to_escape_sequence(&self) -> String {
+ format!("\x1b]9;4;{};{}\x07", self.state_code(), self.progress())
+ }
+
+ /// Sends the OSC 9;4 message to the terminal via stdout.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the stdout stream cannot be flushed.
+ pub fn send(&self) {
+ use std::io::Write;
+ print!("{}", self.to_escape_sequence());
+ std::io::stdout().flush().unwrap();
+ }
+}
+
+impl std::fmt::Display for OSC94State {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.to_escape_sequence())
+ }
+}
+
+impl From<OSC94State> for String {
+ fn from(msg: OSC94State) -> Self {
+ msg.to_escape_sequence()
+ }
+}
+
+impl From<&OSC94State> for String {
+ fn from(msg: &OSC94State) -> Self {
+ msg.to_escape_sequence()
+ }
+}
diff --git a/mingling/src/setups.rs b/mingling/src/setups.rs
index 3a3f13d..7a523cc 100644
--- a/mingling/src/setups.rs
+++ b/mingling/src/setups.rs
@@ -1,28 +1,34 @@
-// Doc Not Optimize
+/// Picker's `ProgramSetup` variant.
+///
+/// Internally does not use its own argument parsing,
+/// but relies on `arg_picker`'s argument parsing capability.
+#[cfg(feature = "picker")]
+pub mod picker;
+
mod basic;
pub use basic::*;
+mod confirmer;
+pub use confirmer::*;
+
mod dirs;
pub use dirs::*;
mod exit_code;
pub use exit_code::*;
-/// Picker's `ProgramSetup` variant.
-///
-/// Internally does not use its own argument parsing,
-/// but relies on `arg_picker`'s argument parsing capability.
-#[cfg(feature = "picker")]
-pub mod picker;
-
-#[cfg(feature = "structural_renderer")]
-mod structural_renderer;
-
-#[cfg(feature = "structural_renderer")]
-pub use structural_renderer::*;
+mod osc94;
+pub use osc94::*;
#[cfg(feature = "repl")]
mod repl_basic;
-
#[cfg(feature = "repl")]
pub use repl_basic::*;
+
+mod stdin_args;
+pub use stdin_args::*;
+
+#[cfg(feature = "structural_renderer")]
+mod structural_renderer;
+#[cfg(feature = "structural_renderer")]
+pub use structural_renderer::*;
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,
+ }
+}