diff options
Diffstat (limited to 'mingling/src/confirm')
| -rw-r--r-- | mingling/src/confirm/count.rs | 75 | ||||
| -rw-r--r-- | mingling/src/confirm/predicate.rs | 60 |
2 files changed, 135 insertions, 0 deletions
diff --git a/mingling/src/confirm/count.rs b/mingling/src/confirm/count.rs new file mode 100644 index 0000000..c9f9db7 --- /dev/null +++ b/mingling/src/confirm/count.rs @@ -0,0 +1,75 @@ +/// Specifies the maximum number of attempts for a confirmation prompt. +/// +/// # Default Implementations +/// +/// `ConfirmCount` implements the following traits by default: +/// +/// - [`Debug`] — for formatted output and debugging. +/// - [`Clone`] — to create a copy of the value. +/// - [`Copy`] — since the enum holds no heap-allocated data, it can be trivially copied. +/// - [`PartialEq`] — allows comparing two `ConfirmCount` values for equality. +/// - [`Eq`] — provides full equality semantics (as opposed to just partial). +/// - [`From<T>`] for all primitive integer types (`i8`–`i128`, `isize`, `u8`–`u128`, `usize`), +/// allowing convenient conversion from a raw number. +/// +/// # What the Numbers Mean +/// +/// The numeric value passed to a `From` conversion represents the **maximum number of times** +/// the confirmation prompt will be shown to the user. For example: +/// +/// - `ConfirmCount::from(3)` → asks at most **3** times before giving up. +/// - `ConfirmCount::from(1)` → asks exactly **1** time. +/// - `ConfirmCount::from(0)` → interpreted as [`ConfirmCount::Loop`], meaning it will keep asking +/// indefinitely until a valid answer is parsed. +/// +/// # Examples +/// +/// ``` +/// use mingling::confirm::ConfirmCount; +/// +/// // Convert from a numeric value +/// let count: ConfirmCount = 3.into(); +/// assert_eq!(count, ConfirmCount::Max(3)); +/// +/// // Zero means loop forever +/// let loop_count: ConfirmCount = 0.into(); +/// assert_eq!(loop_count, ConfirmCount::Loop); +/// +/// // Large values are capped at usize::MAX +/// let big: ConfirmCount = i128::MAX.into(); +/// assert_eq!(big, ConfirmCount::Max(usize::MAX)); +/// +/// // From a usize directly +/// let from_usize = ConfirmCount::from(5usize); +/// assert_eq!(from_usize, ConfirmCount::Max(5)); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConfirmCount { + /// 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_Confirm_count { + ($($t:ty),*) => { + $( + impl From<$t> for ConfirmCount { + fn from(n: $t) -> Self { + if n == 0 { + ConfirmCount::Loop + } else { + match usize::try_from(n) { + Ok(max) => ConfirmCount::Max(max), + Err(_) => ConfirmCount::Max(usize::MAX), + } + } + } + } + )* + }; +} + +impl_from_for_Confirm_count!( + i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize +); diff --git a/mingling/src/confirm/predicate.rs b/mingling/src/confirm/predicate.rs new file mode 100644 index 0000000..786a459 --- /dev/null +++ b/mingling/src/confirm/predicate.rs @@ -0,0 +1,60 @@ +/// 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 ConfirmPredicate { + /// 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 `ConfirmPredicate` 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::ResConfirm; +/// use mingling::confirm::YesConfirm; +/// +/// let confirm = ResConfirm::default(); +/// let confirmed = confirm.ask::<YesConfirm>("Continue? [y/n] "); +/// ``` +pub struct YesConfirm; + +/// A `ConfirmPredicate` 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::ResConfirm; +/// use mingling::confirm::TrueConfirm; +/// +/// let confirm = ResConfirm::default(); +/// let confirmed = confirm.ask::<TrueConfirm>("Enable this feature? [true/false] "); +/// ``` +pub struct TrueConfirm; + +impl ConfirmPredicate 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 ConfirmPredicate 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, + } + } +} |
