diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-15 20:21:45 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-15 20:21:45 +0800 |
| commit | 20dd9f182ec842a267fc48c72578c6169c121ccb (patch) | |
| tree | 57ff1690078f295581f3c54c3b0093048b1b5c42 /mingling/src/res/confirm.rs | |
| parent | 046ac064b6f4790f9053ad1e6109843bb38f89dd (diff) | |
refactor: rename Confirmer to ResConfirm and OSC94 to ResOSC94
Rename `Confirmer` resource to `ResConfirm`, `ConfirmerCount` to
`ConfirmCount`, and `ConfirmerPredicate` to `ConfirmPredicate`. Update
related setups, guards, and documentation references.
Diffstat (limited to 'mingling/src/res/confirm.rs')
| -rw-r--r-- | mingling/src/res/confirm.rs | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/mingling/src/res/confirm.rs b/mingling/src/res/confirm.rs new file mode 100644 index 0000000..baf8caf --- /dev/null +++ b/mingling/src/res/confirm.rs @@ -0,0 +1,184 @@ +use std::io::{BufRead, Write}; + +use crate::confirm::{ConfirmCount, ConfirmPredicate}; + +/// A confirm for interactive confirmation. +/// +/// This structure caches the confirmed state to avoid repeated prompts. +/// +/// Typically, `ResConfirm` is registered via `ConfirmSetup`, and then injected into functions +/// through Mingling's resource injection system. +/// +/// # Registration +/// +/// Before use, the `ConfirmSetup` must be registered with the program: +/// +/// ``` +/// # use mingling::MockProgramCollect as ThisProgram; +/// use mingling::setup::ConfirmSetup; +/// use mingling::Program; +/// +/// let mut program = Program::<ThisProgram>::new(); +/// program.with_setup(ConfirmSetup); +/// ``` +/// +/// # Examples +/// +/// ``` +/// use mingling::res::ResConfirm; +/// use mingling::confirm::YesConfirm; +/// +/// // In actual use, obtain the registered Confirm through the resource injection system +/// let confirm = ResConfirm::new_confirmed(); +/// assert!(confirm.ask::<YesConfirm>("Continue? [y/n] ")); +/// ``` +#[derive(Debug, Default, Clone, Copy)] +pub struct ResConfirm { + pub(crate) confirmed: bool, +} + +impl ResConfirm { + /// Creates a new `ResConfirm` instance. + /// + /// # Examples + /// + /// ``` + /// use mingling::res::ResConfirm; + /// + /// let confirm = ResConfirm::new(); + /// ``` + #[must_use] + pub const fn new() -> Self { + Self { confirmed: false } + } + + /// Creates a `Confirm` instance in the confirmed state. + /// + /// The returned `Confirm` will directly return `true` when calling [`ask`](Confirm::ask) or + /// [`try_ask`](Confirm::try_ask), without prompting the user. + /// + /// # Examples + /// + /// ``` + /// use mingling::res::ResConfirm; + /// use mingling::confirm::YesConfirm; + /// + /// let confirm = ResConfirm::new_confirmed(); + /// assert!(confirm.ask::<YesConfirm>("Continue? [y/n] ")); + /// ``` + #[must_use] + pub const fn new_confirmed() -> Self { + Self { confirmed: true } + } + + /// Marks the Confirm as confirmed. + /// + /// After calling this method, subsequent calls to [`ask`](Confirm::ask) or + /// [`try_ask`](Confirm::try_ask) on this Confirm will directly return `true` + /// without prompting the user. + /// + /// # Examples + /// + /// ``` + /// use mingling::res::ResConfirm; + /// use mingling::confirm::YesConfirm; + /// + /// let mut confirm = ResConfirm::new(); + /// confirm.set_confirmed(); + /// assert!(confirm.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::ResConfirm; + /// use mingling::confirm::YesConfirm; + /// + /// let confirm = ResConfirm::new_confirmed(); + /// let confirmed = confirm.ask::<YesConfirm>("Delete this file? [y/n] "); + /// ``` + pub fn ask<P: ConfirmPredicate>(&self, ask: impl AsRef<str>) -> bool { + self.try_ask::<P>(ask, ConfirmCount::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::ResConfirm; + /// use mingling::confirm::YesConfirm; + /// + /// let confirm = ResConfirm::new_confirmed(); + /// let confirmed = confirm.try_ask::<YesConfirm>("Confirm execution? [y/n] ", 3); + /// ``` + pub fn try_ask<P: ConfirmPredicate>( + &self, + ask: impl AsRef<str>, + count: impl Into<ConfirmCount>, + ) -> 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 { + ConfirmCount::Loop => {} + ConfirmCount::Max(max) => { + if attempts >= max { + return None; + } + } + } + } + } +} |
