Skip to main content

clap_builder/builder/
range.rs

1/// Values per occurrence for an argument
2#[derive(Copy, Clone, PartialEq, Eq, Hash)]
3pub struct ValueRange {
4    start_inclusive: usize,
5    end_inclusive: usize,
6}
7
8impl ValueRange {
9    /// Nor argument values, or a flag
10    pub const EMPTY: Self = Self {
11        start_inclusive: 0,
12        end_inclusive: 0,
13    };
14
15    /// A single argument value, the most common case for options
16    pub const SINGLE: Self = Self {
17        start_inclusive: 1,
18        end_inclusive: 1,
19    };
20
21    #[cfg(debug_assertions)]
22    pub(crate) const OPTIONAL: Self = Self {
23        start_inclusive: 0,
24        end_inclusive: 1,
25    };
26
27    pub(crate) const FULL: Self = Self {
28        start_inclusive: 0,
29        end_inclusive: usize::MAX,
30    };
31
32    /// Create a range
33    ///
34    /// # Panics
35    ///
36    /// In debug builds, if the range accepts no values. For an exclusive range
37    /// (`start..end`) that means `end` must be greater than `start`, so both
38    /// reversed ranges like `10..5` and empty ranges like `5..5` panic.
39    ///
40    /// # Examples
41    ///
42    /// ```rust
43    /// # use clap_builder as clap;
44    /// # use clap::builder::ValueRange;
45    /// let range = ValueRange::new(5);
46    /// let range = ValueRange::new(5..10);
47    /// let range = ValueRange::new(5..=10);
48    /// let range = ValueRange::new(5..);
49    /// let range = ValueRange::new(..10);
50    /// let range = ValueRange::new(..=10);
51    /// ```
52    ///
53    /// While these will panic:
54    /// ```should_panic
55    /// # use clap_builder as clap;
56    /// # use clap::builder::ValueRange;
57    /// let range = ValueRange::new(10..5);  // Panics! (reversed)
58    /// ```
59    /// ```should_panic
60    /// # use clap_builder as clap;
61    /// # use clap::builder::ValueRange;
62    /// let range = ValueRange::new(5..5);  // Panics! (empty)
63    /// ```
64    pub fn new(range: impl Into<Self>) -> Self {
65        range.into()
66    }
67
68    pub(crate) fn raw(start_inclusive: usize, end_inclusive: usize) -> Self {
69        debug_assert!(start_inclusive <= end_inclusive);
70        Self {
71            start_inclusive,
72            end_inclusive,
73        }
74    }
75
76    /// Fewest number of values the argument accepts
77    pub fn min_values(&self) -> usize {
78        self.start_inclusive
79    }
80
81    /// Most number of values the argument accepts
82    pub fn max_values(&self) -> usize {
83        self.end_inclusive
84    }
85
86    /// Report whether the argument takes any values (ie is a flag)
87    ///
88    /// # Examples
89    ///
90    /// ```rust
91    /// # use clap_builder as clap;
92    /// # use clap::builder::ValueRange;
93    /// let range = ValueRange::new(5);
94    /// assert!(range.takes_values());
95    ///
96    /// let range = ValueRange::new(0);
97    /// assert!(!range.takes_values());
98    /// ```
99    pub fn takes_values(&self) -> bool {
100        self.end_inclusive != 0
101    }
102
103    pub(crate) fn is_unbounded(&self) -> bool {
104        self.end_inclusive == usize::MAX
105    }
106
107    pub(crate) fn is_fixed(&self) -> bool {
108        self.start_inclusive == self.end_inclusive
109    }
110
111    pub(crate) fn is_multiple(&self) -> bool {
112        self.start_inclusive != self.end_inclusive || 1 < self.start_inclusive
113    }
114
115    pub(crate) fn num_values(&self) -> Option<usize> {
116        self.is_fixed().then_some(self.start_inclusive)
117    }
118
119    pub(crate) fn accepts_more(&self, current: usize) -> bool {
120        current < self.end_inclusive
121    }
122}
123
124impl std::ops::RangeBounds<usize> for ValueRange {
125    fn start_bound(&self) -> std::ops::Bound<&usize> {
126        std::ops::Bound::Included(&self.start_inclusive)
127    }
128
129    fn end_bound(&self) -> std::ops::Bound<&usize> {
130        std::ops::Bound::Included(&self.end_inclusive)
131    }
132}
133
134impl Default for ValueRange {
135    fn default() -> Self {
136        Self::SINGLE
137    }
138}
139
140impl From<usize> for ValueRange {
141    fn from(fixed: usize) -> Self {
142        (fixed..=fixed).into()
143    }
144}
145
146impl From<std::ops::Range<usize>> for ValueRange {
147    fn from(range: std::ops::Range<usize>) -> Self {
148        let start_inclusive = range.start;
149        let end_inclusive = range.end.saturating_sub(1);
150        Self::raw(start_inclusive, end_inclusive)
151    }
152}
153
154impl From<std::ops::RangeFull> for ValueRange {
155    fn from(_: std::ops::RangeFull) -> Self {
156        Self::FULL
157    }
158}
159
160impl From<std::ops::RangeFrom<usize>> for ValueRange {
161    fn from(range: std::ops::RangeFrom<usize>) -> Self {
162        let start_inclusive = range.start;
163        let end_inclusive = usize::MAX;
164        Self::raw(start_inclusive, end_inclusive)
165    }
166}
167
168impl From<std::ops::RangeTo<usize>> for ValueRange {
169    fn from(range: std::ops::RangeTo<usize>) -> Self {
170        let start_inclusive = 0;
171        let end_inclusive = range.end.saturating_sub(1);
172        Self::raw(start_inclusive, end_inclusive)
173    }
174}
175
176impl From<std::ops::RangeInclusive<usize>> for ValueRange {
177    fn from(range: std::ops::RangeInclusive<usize>) -> Self {
178        let start_inclusive = *range.start();
179        let end_inclusive = *range.end();
180        Self::raw(start_inclusive, end_inclusive)
181    }
182}
183
184impl From<std::ops::RangeToInclusive<usize>> for ValueRange {
185    fn from(range: std::ops::RangeToInclusive<usize>) -> Self {
186        let start_inclusive = 0;
187        let end_inclusive = range.end;
188        Self::raw(start_inclusive, end_inclusive)
189    }
190}
191
192impl std::fmt::Display for ValueRange {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        ok!(self.start_inclusive.fmt(f));
195        if self.is_fixed() {
196        } else if self.end_inclusive == usize::MAX {
197            ok!("..".fmt(f));
198        } else {
199            ok!("..=".fmt(f));
200            ok!(self.end_inclusive.fmt(f));
201        }
202        Ok(())
203    }
204}
205
206impl std::fmt::Debug for ValueRange {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        write!(f, "{self}")
209    }
210}
211
212#[cfg(test)]
213mod test {
214    use super::*;
215
216    use std::ops::RangeBounds;
217
218    #[test]
219    fn from_fixed() {
220        let range: ValueRange = 5.into();
221        assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
222        assert_eq!(range.end_bound(), std::ops::Bound::Included(&5));
223        assert!(range.is_fixed());
224        assert!(range.is_multiple());
225        assert_eq!(range.num_values(), Some(5));
226        assert!(range.takes_values());
227    }
228
229    #[test]
230    fn from_fixed_empty() {
231        let range: ValueRange = 0.into();
232        assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
233        assert_eq!(range.end_bound(), std::ops::Bound::Included(&0));
234        assert!(range.is_fixed());
235        assert!(!range.is_multiple());
236        assert_eq!(range.num_values(), Some(0));
237        assert!(!range.takes_values());
238    }
239
240    #[test]
241    fn from_range() {
242        let range: ValueRange = (5..10).into();
243        assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
244        assert_eq!(range.end_bound(), std::ops::Bound::Included(&9));
245        assert!(!range.is_fixed());
246        assert!(range.is_multiple());
247        assert_eq!(range.num_values(), None);
248        assert!(range.takes_values());
249    }
250
251    #[test]
252    fn from_range_inclusive() {
253        let range: ValueRange = (5..=10).into();
254        assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
255        assert_eq!(range.end_bound(), std::ops::Bound::Included(&10));
256        assert!(!range.is_fixed());
257        assert!(range.is_multiple());
258        assert_eq!(range.num_values(), None);
259        assert!(range.takes_values());
260    }
261
262    #[test]
263    fn from_range_full() {
264        let range: ValueRange = (..).into();
265        assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
266        assert_eq!(range.end_bound(), std::ops::Bound::Included(&usize::MAX));
267        assert!(!range.is_fixed());
268        assert!(range.is_multiple());
269        assert_eq!(range.num_values(), None);
270        assert!(range.takes_values());
271    }
272
273    #[test]
274    fn from_range_from() {
275        let range: ValueRange = (5..).into();
276        assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
277        assert_eq!(range.end_bound(), std::ops::Bound::Included(&usize::MAX));
278        assert!(!range.is_fixed());
279        assert!(range.is_multiple());
280        assert_eq!(range.num_values(), None);
281        assert!(range.takes_values());
282    }
283
284    #[test]
285    fn from_range_to() {
286        let range: ValueRange = (..10).into();
287        assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
288        assert_eq!(range.end_bound(), std::ops::Bound::Included(&9));
289        assert!(!range.is_fixed());
290        assert!(range.is_multiple());
291        assert_eq!(range.num_values(), None);
292        assert!(range.takes_values());
293    }
294
295    #[test]
296    fn from_range_to_inclusive() {
297        let range: ValueRange = (..=10).into();
298        assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
299        assert_eq!(range.end_bound(), std::ops::Bound::Included(&10));
300        assert!(!range.is_fixed());
301        assert!(range.is_multiple());
302        assert_eq!(range.num_values(), None);
303        assert!(range.takes_values());
304    }
305}