/// Values per occurrence for an argument #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ValueRange { start_inclusive: usize, end_inclusive: usize, } impl ValueRange { /// Nor argument values, or a flag pub const EMPTY: Self = Self { start_inclusive: 0, end_inclusive: 0, }; /// A single argument value, the most common case for options pub const SINGLE: Self = Self { start_inclusive: 1, end_inclusive: 1, }; /// Create a range /// /// # Panics /// /// If the end is less than the start /// /// # Examples /// /// ``` /// # use clap::builder::ValueRange; /// let range = ValueRange::new(5); /// let range = ValueRange::new(5..10); /// let range = ValueRange::new(5..=10); /// let range = ValueRange::new(5..); /// let range = ValueRange::new(..10); /// let range = ValueRange::new(..=10); /// ``` /// /// While this will panic: /// ```should_panic /// # use clap::builder::ValueRange; /// let range = ValueRange::new(10..5); // Panics! /// ``` pub fn new(range: impl Into) -> Self { range.into() } pub(crate) fn raw(start_inclusive: usize, end_inclusive: usize) -> Self { debug_assert!(start_inclusive <= end_inclusive); Self { start_inclusive, end_inclusive, } } /// Fewest number of values the argument accepts pub fn min_values(&self) -> usize { self.start_inclusive } /// Most number of values the argument accepts pub fn max_values(&self) -> usize { self.end_inclusive } /// Report whether the argument takes any values (ie is a flag) /// /// # Examples /// /// ``` /// # use clap::builder::ValueRange; /// let range = ValueRange::new(5); /// assert!(range.takes_values()); /// /// let range = ValueRange::new(0); /// assert!(!range.takes_values()); /// ``` pub fn takes_values(&self) -> bool { self.end_inclusive != 0 } pub(crate) fn is_unbounded(&self) -> bool { self.end_inclusive == usize::MAX } pub(crate) fn is_fixed(&self) -> bool { self.start_inclusive == self.end_inclusive } pub(crate) fn is_multiple(&self) -> bool { self.start_inclusive != self.end_inclusive || 1 < self.start_inclusive } pub(crate) fn num_values(&self) -> Option { self.is_fixed().then_some(self.start_inclusive) } pub(crate) fn accepts_more(&self, current: usize) -> bool { current < self.end_inclusive } } impl std::ops::RangeBounds for ValueRange { fn start_bound(&self) -> std::ops::Bound<&usize> { std::ops::Bound::Included(&self.start_inclusive) } fn end_bound(&self) -> std::ops::Bound<&usize> { std::ops::Bound::Included(&self.end_inclusive) } } impl Default for ValueRange { fn default() -> Self { Self::SINGLE } } impl From for ValueRange { fn from(fixed: usize) -> Self { (fixed..=fixed).into() } } impl From> for ValueRange { fn from(range: std::ops::Range) -> Self { let start_inclusive = range.start; let end_inclusive = range.end.saturating_sub(1); Self::raw(start_inclusive, end_inclusive) } } impl From for ValueRange { fn from(_: std::ops::RangeFull) -> Self { let start_inclusive = 0; let end_inclusive = usize::MAX; Self::raw(start_inclusive, end_inclusive) } } impl From> for ValueRange { fn from(range: std::ops::RangeFrom) -> Self { let start_inclusive = range.start; let end_inclusive = usize::MAX; Self::raw(start_inclusive, end_inclusive) } } impl From> for ValueRange { fn from(range: std::ops::RangeTo) -> Self { let start_inclusive = 0; let end_inclusive = range.end.saturating_sub(1); Self::raw(start_inclusive, end_inclusive) } } impl From> for ValueRange { fn from(range: std::ops::RangeInclusive) -> Self { let start_inclusive = *range.start(); let end_inclusive = *range.end(); Self::raw(start_inclusive, end_inclusive) } } impl From> for ValueRange { fn from(range: std::ops::RangeToInclusive) -> Self { let start_inclusive = 0; let end_inclusive = range.end; Self::raw(start_inclusive, end_inclusive) } } impl std::fmt::Display for ValueRange { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { ok!(self.start_inclusive.fmt(f)); if !self.is_fixed() { ok!("..=".fmt(f)); ok!(self.end_inclusive.fmt(f)); } Ok(()) } } impl std::fmt::Debug for ValueRange { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self) } } #[cfg(test)] mod test { use super::*; use std::ops::RangeBounds; #[test] fn from_fixed() { let range: ValueRange = 5.into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&5)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&5)); assert!(range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), Some(5)); assert!(range.takes_values()); } #[test] fn from_fixed_empty() { let range: ValueRange = 0.into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&0)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&0)); assert!(range.is_fixed()); assert!(!range.is_multiple()); assert_eq!(range.num_values(), Some(0)); assert!(!range.takes_values()); } #[test] fn from_range() { let range: ValueRange = (5..10).into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&5)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&9)); assert!(!range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), None); assert!(range.takes_values()); } #[test] fn from_range_inclusive() { let range: ValueRange = (5..=10).into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&5)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&10)); assert!(!range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), None); assert!(range.takes_values()); } #[test] fn from_range_full() { let range: ValueRange = (..).into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&0)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&usize::MAX)); assert!(!range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), None); assert!(range.takes_values()); } #[test] fn from_range_from() { let range: ValueRange = (5..).into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&5)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&usize::MAX)); assert!(!range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), None); assert!(range.takes_values()); } #[test] fn from_range_to() { let range: ValueRange = (..10).into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&0)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&9)); assert!(!range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), None); assert!(range.takes_values()); } #[test] fn from_range_to_inclusive() { let range: ValueRange = (..=10).into(); assert_eq!(range.start_bound(), std::ops::Bound::Included(&0)); assert_eq!(range.end_bound(), std::ops::Bound::Included(&10)); assert!(!range.is_fixed()); assert!(range.is_multiple()); assert_eq!(range.num_values(), None); assert!(range.takes_values()); } }