number_range/
lib.rs

1//! # Introduction
2//! To convert from human readable number range into an
3//! iterator. It makes it easy to parse the command line arguments in
4//! the form of `num`, `num1:num2`, `num1:step:num2` or comma
5//! separated list of them.
6//!
7//! # Features
8//! - Parse from human redable format into an iterator ([`NumberRange<T>`])
9//!   for any generic number format
10//! - Configuration options for list and range separators ([`NumberRangeOptions`]).
11//!   You can also use it to provide options to parse numbers in different
12//!   localization, like grouping or different decimal separator.
13//!
14//! # Limitations
15//! - Step size needs to be the same type as the number type, which
16//!   means you can't use negative numbers for unsigned numbers.
17//! - Automatic step size can only be one, not negative one as the code
18//!   is generic for unsigned too, so if you want negative step for
19//!   signed numbers you need to specify that.
20//! - Although it works with floats as well, not just integers, the
21//!   float step size might not be accurate.
22
23use anyhow::{Context, Result};
24use itertools::Itertools;
25use std::collections::VecDeque;
26
27#[derive(Debug)]
28pub struct NumberRangeError;
29
30impl std::error::Error for NumberRangeError {}
31
32impl std::fmt::Display for NumberRangeError {
33    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
34        write!(f, "Invalid number range")
35    }
36}
37
38/// Number type for simple interger numbers or number range. The
39/// [`NumberRange<T>`] is made up of these, so you can use it to build
40/// the [`NumberRange<T>`] manually.
41///
42/// ```rust
43/// # use std::error::Error;
44/// # use number_range::{NumberRange,Number};
45/// #
46/// # fn main() -> Result<(), Box<dyn Error>> {
47/// let mut rng = NumberRange::<i64>::default();
48/// rng.numbers.push_back(Number::Single(1));
49/// rng.numbers.push_back(Number::Range(3,2,6));
50/// rng.numbers.push_back(Number::Range(-4,1,-2));
51/// assert_eq!(format!("{}", rng), "1,3:2:6,-4:-2");
52/// assert_eq!(rng.collect::<Vec<i64>>(), vec![1, 3, 5, -4, -3, -2]);
53/// #     Ok(())
54/// # }
55/// ```
56#[derive(Debug)]
57pub enum Number<T> {
58    Single(T),
59    Range(T, T, T),
60}
61
62impl<T: num::Zero + std::cmp::PartialOrd + Copy> Number<T> {
63    /// Checks the validity of the number/range
64    ///
65    /// ```rust
66    /// # use std::error::Error;
67    /// # use number_range::Number;
68    /// #
69    /// # fn main() -> Result<(), Box<dyn Error>> {
70    /// assert!(Number::Single(1).is_valid());
71    /// assert!(Number::Range(3,2,6).is_valid());
72    /// assert!(Number::Range(-4,1,-2).is_valid());
73    /// #     Ok(())
74    /// # }
75    /// ```
76    pub fn is_valid(&self) -> bool {
77        match self {
78            Number::Single(_) => true,
79            Number::Range(start, step, end) => {
80                ((start <= end) && (step > &num::Zero::zero()))
81                    || ((start >= end) && (step < &num::Zero::zero()))
82            }
83        }
84    }
85    /// Opposite of is_valid
86    ///
87    /// ```rust
88    /// # use std::error::Error;
89    /// # use number_range::Number;
90    /// #
91    /// # fn main() -> Result<(), Box<dyn Error>> {
92    /// assert!(Number::Range(3,-2,6).is_invalid());
93    /// assert!(Number::Range(4,1,2).is_invalid());
94    /// #     Ok(())
95    /// # }
96    /// ```
97    pub fn is_invalid(&self) -> bool {
98        !self.is_valid()
99    }
100}
101
102/// Options for the NumberRange, includes different separator
103/// character customization.
104///
105/// For example, if you're dealing with unsigned numbers then you can
106/// use `-` as a range separator to parse ranges from many sources.
107///
108/// ```rust
109/// # use std::error::Error;
110/// # use number_range::NumberRangeOptions;
111/// #
112/// # fn main() -> Result<(), Box<dyn Error>> {
113///     let rng: Vec<usize> = NumberRangeOptions::default()
114///         .with_range_sep('-')
115///         .parse("1,4,6-10,14")
116///         .unwrap()
117///         .collect();
118///     println!("{:?}", rng);
119/// #   Ok(())
120/// # }
121/// ```
122///
123/// Since it is made using generics to be able to pass as many types
124/// of numeric types as possible, you might have to give the types in
125/// between when rust cannot infer it.
126///
127/// It can be inferred if you have intermediate variables with type.
128/// ```rust
129/// # use std::error::Error;
130/// # use number_range::{NumberRange,NumberRangeOptions};
131/// #
132/// # fn main() -> Result<(), Box<dyn Error>> {
133///     let rng: NumberRange<usize> = NumberRangeOptions::default()
134///         .with_range_sep('-')
135///         .parse("1,4,6-10,14")
136///         .unwrap();
137///     println!("{:?}", rng.collect::<Vec<usize>>());
138/// #   Ok(())
139/// # }
140/// ```
141///
142/// ```rust
143/// # use std::error::Error;
144/// # use number_range::NumberRangeOptions;
145/// #
146/// # fn main() -> Result<(), Box<dyn Error>> {
147/// NumberRangeOptions::<usize>::new()
148///              .with_list_sep(',')
149///              .with_range_sep('-')
150///              .parse("1,3-10,14")?;
151/// #     Ok(())
152/// # }
153/// ```
154///
155/// Or with default start and end:
156/// ```rust
157/// # use std::error::Error;
158/// # use number_range::NumberRangeOptions;
159/// #
160/// # fn main() -> Result<(), Box<dyn Error>> {
161/// assert_eq!(NumberRangeOptions::<usize>::new()
162///              .with_list_sep(',')
163///              .with_range_sep(':')
164///              .with_default_start(1)
165///              .parse(":4,14")?.collect::<Vec<usize>>(), vec![1,2,3,4,14]);
166/// #     Ok(())
167/// # }
168/// ```
169///
170/// All the numbers in the string must be of the same type that you
171/// want to parse into, due to that restriction even the step needs to
172/// be unsigned for unsigned number (meaning `"4:-1:1"` would fail
173/// even if the final output should be unsigned).
174///
175/// Another function is to parse the numbers in different localization
176/// like different decimal separators or grouping of numbers.
177/// ```rust
178/// # use std::error::Error;
179/// # use number_range::{NumberRange, NumberRangeOptions};
180/// #
181/// # fn main() -> Result<(), Box<dyn Error>> {
182/// let rng: Vec<usize> = NumberRangeOptions::new()
183///              .with_list_sep(';')
184///              .with_range_sep('-')
185///              .with_group_sep(',')
186///              .with_whitespace(true)
187///              .parse("1,200; 1, 400, 230")?.collect();
188/// assert_eq!(rng, vec![1200, 1400230]);
189/// #     Ok(())
190/// # }
191/// ```
192#[derive(Debug)]
193pub struct NumberRangeOptions<T> {
194    /// Character used to group numbers [default: `_`]. Group
195    /// separator is the first one to be removed from the string, if
196    /// any other characters are same as the group separators then
197    /// they'll be useless.
198    pub group_sep: char,
199    /// Remove spaces between the numbers. While spaces are removed
200    /// after the group separator. If any other separator characters
201    /// are whitespace they'll be useless.
202    pub whitespace: bool,
203    /// Decimal separator [default: `.`]. Decimal separator is
204    /// replaced by `.` for rust to parse the float properly. The
205    /// replacement occurs after the whitespace removal.
206    pub decimal_sep: char,
207    /// Separator for different numbers or numbers range [default:
208    /// `,`]. List separator is used to split first.
209    pub list_sep: char,
210    /// Separator for range start, step, and end [default: `:`]. This
211    /// one is used at the end, so if it is using the same character
212    /// as other separators, it'll be useless, or have different
213    /// meaning.
214    pub range_sep: char,
215    /// Default start value, if the start value is ommited in a range,
216    /// it'll be used
217    pub default_start: Option<T>,
218    /// Default end value, if the end value is ommited in a range,
219    /// it'll be used
220    pub default_end: Option<T>,
221}
222
223/// Representation of Number Ranges, once you've parsed the string you
224/// can iterate though it.
225///
226/// ```rust
227/// # use std::error::Error;
228/// # use number_range::NumberRange;
229/// #
230/// # fn main() -> Result<(), Box<dyn Error>> {
231/// NumberRange::<i64>::default()
232///     .parse_str("-10,3:10,14:2:20")?;
233/// #     Ok(())
234/// # }
235/// ```
236///
237/// You can also build it from vector/list in rust, and get the string
238/// representation for human readability
239///
240/// ```rust
241/// # use std::error::Error;
242/// # use number_range::{NumberRange,NumberRangeOptions};
243/// #
244/// # fn main() -> Result<(), Box<dyn Error>> {
245///   assert_eq!(
246///       format!("{}", NumberRange::default()
247///              .from_vec([1,3,4,5,6,7,8,9,10,14], None)),
248///                        "1,3:10,14");
249///   assert_eq!(
250///       format!("{}", NumberRange::from_options(
251///              NumberRangeOptions::new().with_range_sep('-')
252///              ).from_vec(vec![1,3,4,5,6,7,8,9,10,14], Some(1))),
253///                        "1,3-10,14");
254///   assert_eq!(
255///       format!("{}", NumberRange::default()
256///              .from_vec([1,3,5,7,9,10,14], Some(2))),
257///                        "1:2:9,10,14");
258/// #     Ok(())
259/// # }
260/// ```
261///
262#[derive(Debug)]
263pub struct NumberRange<'a, T> {
264    pub numbers: VecDeque<Number<T>>,
265    original_repr: Option<&'a str>,
266    pub options: NumberRangeOptions<T>,
267}
268
269impl<'a, T: std::fmt::Display + num::One + std::cmp::PartialEq> std::fmt::Display
270    for NumberRange<'a, T>
271{
272    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
273        let repr = self
274            .numbers
275            .iter()
276            .map(|n| match n {
277                Number::Single(v) => format!("{}", v),
278                Number::Range(s, i, e) => {
279                    if i.is_one() {
280                        format!("{}{}{}", s, self.options.range_sep, e)
281                    } else {
282                        format!("{}{}{}{1}{}", s, self.options.range_sep, i, e)
283                    }
284                }
285            })
286            .join(&self.options.list_sep.to_string());
287        write!(f, "{}", repr)
288    }
289}
290
291impl<'a, T: Copy + std::ops::Add<Output = T> + std::cmp::PartialOrd + num::Zero> Iterator
292    for NumberRange<'a, T>
293{
294    type Item = T;
295
296    fn next(&mut self) -> Option<T> {
297        if self.numbers.is_empty() {
298            return None;
299        }
300        match self.numbers[0] {
301            Number::Single(v) => {
302                self.numbers.pop_front();
303                Some(v)
304            }
305            Number::Range(start, step, end) => {
306                // checking this one coz people can insert their invalid ranges or parse invalid ones
307                if self.numbers[0].is_valid() {
308                    let next_step = Number::Range(start + step, step, end);
309                    // checking here to always have valid steps
310                    if next_step.is_valid() {
311                        self.numbers[0] = next_step;
312                    } else {
313                        self.numbers.pop_front();
314                    }
315                    Some(start)
316                } else {
317                    self.numbers.pop_front();
318                    self.next()
319                }
320            }
321        }
322    }
323}
324
325impl<
326        T: std::str::FromStr
327            + num::One
328            + Copy
329            + std::str::FromStr
330            + std::cmp::PartialOrd
331            + std::ops::Add<Output = T>,
332    > Default for NumberRangeOptions<T>
333{
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339impl<T: std::str::FromStr + num::One + Copy + std::cmp::PartialOrd + std::ops::Add<Output = T>>
340    NumberRangeOptions<T>
341{
342    /// New struct with default options
343    pub fn new() -> Self {
344        Self {
345            list_sep: ',',
346            range_sep: ':',
347            decimal_sep: '.',
348            group_sep: '_',
349            whitespace: false,
350            default_start: None,
351            default_end: None,
352        }
353    }
354
355    /// Change the group separator character
356    pub fn with_group_sep(mut self, sep: char) -> Self {
357        self.group_sep = sep;
358        self
359    }
360
361    /// Change the group separator character
362    pub fn with_whitespace(mut self, flag: bool) -> Self {
363        self.whitespace = flag;
364        self
365    }
366
367    /// Change the decimal separator character
368    pub fn with_decimal_sep(mut self, sep: char) -> Self {
369        self.decimal_sep = sep;
370        self
371    }
372
373    /// Change the list separator character
374    pub fn with_list_sep(mut self, sep: char) -> Self {
375        self.list_sep = sep;
376        self
377    }
378
379    /// Change the range separator character
380    pub fn with_range_sep(mut self, sep: char) -> Self {
381        self.range_sep = sep;
382        self
383    }
384
385    /// Include a default start value
386    pub fn with_default_start(mut self, def: T) -> Self {
387        self.default_start = Some(def);
388        self
389    }
390
391    /// Include a default end value
392    pub fn with_default_end(mut self, def: T) -> Self {
393        self.default_end = Some(def);
394        self
395    }
396
397    /// Same as [`NumberRange::parse_str()`], Makes a
398    /// [`NumberRange<T>`] and parses the string.
399    pub fn parse<'a>(self, numstr: &'a str) -> Result<NumberRange<T>>
400    where
401        <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
402    {
403        let nr = NumberRange::from_options(self);
404        nr.parse_str(numstr)
405    }
406}
407
408impl<
409        'a,
410        T: num::One
411            + std::str::FromStr
412            + num::One
413            + Copy
414            + std::cmp::PartialOrd
415            + std::ops::Add<Output = T>,
416    > Default for NumberRange<'a, T>
417{
418    /// It builds a NumberRange struct with
419    /// [`NumberRangeOptions::new()`] options.
420    fn default() -> Self {
421        Self {
422            numbers: VecDeque::new(),
423            original_repr: None,
424            options: NumberRangeOptions::default(),
425        }
426    }
427}
428
429impl<
430        'a,
431        T: std::str::FromStr + num::One + Copy + std::cmp::PartialOrd + std::ops::Add<Output = T>,
432    > NumberRange<'a, T>
433where
434    <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
435{
436    /// New NumberRange struct from NumberRangeOptions
437    pub fn from_options(options: NumberRangeOptions<T>) -> Self {
438        Self {
439            numbers: VecDeque::new(),
440            original_repr: None,
441            options,
442        }
443    }
444
445    /// Get the Original String that was used to parse the iterator
446    pub fn original(&self) -> &str {
447        self.original_repr.unwrap_or("")
448    }
449
450    /// Parse the human readable string (`numstr`).
451    ///
452    /// Once parsed the NumberRange struct can be used as an
453    /// Iterator. Use `.collect::<T>()` to convert it into a vector.
454    ///
455    /// ```rust
456    /// # use std::error::Error;
457    /// # use number_range::NumberRange;
458    /// #
459    /// # fn main() -> Result<(), Box<dyn Error>> {
460    /// NumberRange::<i64>::default().parse_str("1,3,5:10")?;
461    /// #     Ok(())
462    /// # }
463    /// ```
464    pub fn parse_str(mut self, numstr: &'a str) -> Result<Self> {
465        self.original_repr = Some(numstr);
466        self.parse()
467    }
468
469    pub fn from_vec<V>(self, nums: V, increment: Option<T>) -> Self
470    where
471        T: std::cmp::Ord,
472        V: IntoIterator<Item = T>,
473    {
474        let mut nums: Vec<T> = nums.into_iter().collect();
475        nums.sort();
476        self.from_vec_nosort(&nums, increment)
477    }
478
479    pub fn from_vec_nosort(mut self, nums: &[T], increment: Option<T>) -> Self
480    where
481        T: std::cmp::Ord,
482    {
483        self.original_repr = None;
484        let inc = increment.unwrap_or(num::one());
485        self.numbers.clear();
486        if nums.len() > 0 {
487            let mut first = &nums[0];
488            let mut prev = &nums[0];
489            let mut rng = false;
490            for current in &nums[1..] {
491                if current == prev {
492                    continue;
493                }
494                if *current == (*prev + inc) {
495                    if !rng {
496                        rng = true;
497                        first = prev;
498                    }
499                } else {
500                    if rng {
501                        self.numbers.push_back(Number::Range(*first, inc, *prev));
502                    } else {
503                        self.numbers.push_back(Number::Single(*prev));
504                    }
505                    rng = false;
506                }
507                prev = current;
508            }
509            if rng {
510                self.numbers.push_back(Number::Range(*first, inc, *prev));
511            } else {
512                self.numbers.push_back(Number::Single(*prev));
513            }
514        }
515        self
516    }
517
518    fn sanitize_number(&self, num: &str) -> String {
519        let num = num.trim().replace(self.options.group_sep, "");
520        let num = if self.options.whitespace {
521            num.split_whitespace().join("")
522        } else {
523            num
524        };
525        num.replace(self.options.decimal_sep, ".")
526    }
527
528    fn parse_number(&self, num: &str, def: &Option<T>) -> Result<T> {
529        let s = self.sanitize_number(num);
530        if def.is_some() && s == "" {
531            return Ok(def.unwrap());
532        } else {
533            s.parse::<T>()
534                .with_context(|| format!("{} Not a Number", num))
535        }
536    }
537
538    pub fn parse(mut self) -> Result<Self> {
539        if let Some(numstr) = self.original_repr {
540            if self.sanitize_number(numstr) == "" {
541                self.numbers.clear();
542                return Ok(self);
543            }
544            let numbers: VecDeque<Number<T>> = numstr
545                .split(self.options.list_sep)
546                .map(|seq_str| -> Result<Number<T>> {
547                    match seq_str.matches(self.options.range_sep).count() {
548                        0 => self.parse_number(seq_str, &None).map(|v| Number::Single(v)),
549                        1 => match seq_str.split_once(self.options.range_sep) {
550                            Some((start, end)) => {
551                                let start =
552                                    self.parse_number(start, &self.options.default_start)?;
553                                let end = self.parse_number(end, &self.options.default_end)?;
554                                Ok(Number::Range(start, num::One::one(), end))
555                            }
556                            None => panic!(
557                                "Checked there is single range_separator, yet split to 2 failed."
558                            ),
559                        },
560                        2 => {
561                            let nums: Vec<T> = seq_str
562                                .splitn(3, self.options.range_sep)
563                                .enumerate()
564                                .map(|(i, s)| -> Result<T> {
565                                    self.parse_number(
566                                        s,
567                                        [
568                                            &self.options.default_start,
569                                            &Some(num::One::one()),
570                                            &self.options.default_end,
571                                        ][i],
572                                    )
573                                })
574                                .collect::<Result<Vec<T>>>()?;
575                            Ok(Number::Range(nums[0], nums[1], nums[2]))
576                        }
577                        _ => Err::<Number<_>, anyhow::Error>(NumberRangeError {}.into())
578                            .with_context(|| {
579                                format!(
580                                    "Too many range separators ({}) on {}",
581                                    self.options.range_sep, seq_str
582                                )
583                            }),
584                    }
585                })
586                .collect::<Result<VecDeque<Number<T>>>>()?;
587            self.numbers = numbers;
588            Ok(self)
589        } else {
590            Err::<NumberRange<'_, _>, anyhow::Error>(NumberRangeError {}.into())
591                .with_context(|| "Nothing to Parse".to_string())
592        }
593    }
594}
595
596/// Macro rule for generating number range. The [`NumberRange<T>`] is
597/// made with default options, then parsed.
598///
599/// ```rust
600/// # use std::error::Error;
601/// # use number_range::numrng;
602/// #
603/// # fn main() -> Result<(), Box<dyn Error>> {
604/// let mut rng = numrng!(1,3:2:6,-4:-2);
605/// assert_eq!(format!("{}", rng), "1,3:2:6,-4:-2");
606/// assert_eq!(rng.collect::<Vec<i64>>(), vec![1, 3, 5, -4, -3, -2]);
607/// assert_eq!(numrng!().collect::<Vec<i64>>(), vec![]);
608/// #     Ok(())
609/// # }
610/// ```
611#[macro_export]
612macro_rules! numrng {
613    ($($l:tt)*) => {
614	// stringify seems to introduce extra spaces between all characters
615        $crate::NumberRangeOptions::new().with_whitespace(true).parse(stringify!($($l)*)).unwrap()
616    };
617}
618
619/// Macro rule for generating number range into vec. The
620/// [`NumberRange<T>`] is made with default options, then parsed and
621/// collected into a Vec<T>.
622///
623/// ```rust
624/// # use std::error::Error;
625/// # use number_range::numvec;
626/// #
627/// # fn main() -> Result<(), Box<dyn Error>> {
628/// let mut rng: Vec<i64> = numvec!(1,3:2:6,-4:-2);
629/// assert_eq!(rng, vec![1, 3, 5, -4, -3, -2]);
630/// #     Ok(())
631/// # }
632/// ```
633#[macro_export]
634macro_rules! numvec {
635    ($($l:tt)*) => {
636        $crate::numrng!($($l)*)
637            .collect()
638    };
639}
640
641#[cfg(test)]
642use rstest::rstest;
643
644#[cfg(test)]
645mod tests {
646    use super::*;
647
648    #[rstest]
649    fn manual_build() {
650        let mut rng = NumberRange::<i64>::default();
651        rng.numbers.push_back(Number::Single(1));
652        rng.numbers.push_back(Number::Range(3, 2, 6));
653        rng.numbers.push_back(Number::Range(-4, 1, -2));
654        assert_eq!(format!("{}", rng), "1,3:2:6,-4:-2");
655        assert_eq!(rng.collect::<Vec<i64>>(), vec![1, 3, 5, -4, -3, -2]);
656    }
657
658    #[rstest]
659    fn options_build() {
660        let rng: NumberRange<usize> = NumberRangeOptions::<usize>::default()
661            .with_list_sep('*')
662            .with_range_sep('/')
663            .parse("1*3/5*9/2/15")
664            .expect("Parsing should be succesful");
665        assert_eq!(rng.collect::<Vec<usize>>(), vec![1, 3, 4, 5, 9, 11, 13, 15]);
666    }
667
668    #[rstest]
669    fn manual_build_then_modify() {
670        let mut rng = NumberRange::<i64>::default();
671        rng.numbers.push_back(Number::Single(1));
672        rng.numbers.push_back(Number::Range(3, 2, 6));
673        rng.numbers.push_back(Number::Range(-4, 1, -2));
674        let values = vec![1, 3, 5, -4, -3, -2];
675        assert_eq!(format!("{}", rng), "1,3:2:6,-4:-2");
676        for i in 0..4 {
677            assert_eq!(rng.next().expect("Should have next"), values[i]);
678        }
679        assert_eq!(format!("{}", rng), "-3:-2");
680        rng.numbers.push_back(Number::Single(1));
681        assert_eq!(format!("{}", rng), "-3:-2,1");
682        assert_eq!(rng.collect::<Vec<i64>>(), vec![-3, -2, 1]);
683    }
684
685    #[rstest]
686    fn options_build_then_modify() {
687        let mut rng: NumberRange<usize> = NumberRangeOptions::<usize>::default()
688            .with_list_sep(':')
689            .with_range_sep('-')
690            .parse("1:3-5:9")
691            .expect("Parsing should be succesful");
692        let values = vec![1, 3, 4, 5, 9];
693        for i in 0..4 {
694            assert_eq!(rng.next().expect("Should have next"), values[i]);
695        }
696        assert_eq!(format!("{}", rng), "9");
697        rng.numbers.push_back(Number::Range(11, 2, 15));
698        assert_eq!(format!("{}", rng), "9:11-2-15");
699        assert_eq!(rng.collect::<Vec<usize>>(), vec![9, 11, 13, 15]);
700    }
701
702    #[rstest]
703    #[case("200", vec![200])]
704    #[case("-200", vec![-200])]
705    #[case("1,4", vec![1, 4])]
706    #[case("1, -4", vec![1, -4])]
707    #[should_panic]
708    #[case("1.0, 2", vec![])]
709    #[case("1: 3", vec![1, 2, 3])]
710    #[case(" -1:10", vec![-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])]
711    #[case("3 , 5:10", vec![3, 5, 6, 7, 8, 9, 10])]
712    fn comma_default_int(#[case] numstr: &str, #[case] numvec: Vec<i64>) {
713        assert_eq!(
714            NumberRange::<i64>::default()
715                .parse_str(numstr)
716                .unwrap()
717                .collect::<Vec<i64>>(),
718            numvec
719        );
720    }
721
722    #[rstest]
723    fn limits_test() {
724        let nr1: Vec<usize> = NumberRangeOptions::<usize>::new()
725            .with_range_sep('-')
726            .with_default_start(0)
727            .parse("-2")
728            .unwrap()
729            .collect();
730        assert_eq!(nr1, vec![0, 1, 2]);
731        let nr1: Vec<usize> = NumberRangeOptions::<usize>::new()
732            .with_range_sep('-')
733            .with_default_end(5)
734            .parse("2-")
735            .unwrap()
736            .collect();
737        assert_eq!(nr1, vec![2, 3, 4, 5]);
738        let nr1: Vec<usize> = NumberRangeOptions::<usize>::new()
739            .with_range_sep('-')
740            .with_default_start(0)
741            .with_default_end(5)
742            .parse("-")
743            .unwrap()
744            .collect();
745        assert_eq!(nr1, vec![0, 1, 2, 3, 4, 5]);
746    }
747
748    #[rstest]
749    #[case("200", vec![200])]
750    #[case("1,4", vec![1, 4])]
751    #[should_panic]
752    #[case("1,-4", vec![])]
753    #[should_panic]
754    #[case(",4", vec![])]
755    #[should_panic]
756    #[case("1,,4", vec![])]
757    #[should_panic]
758    #[case("1,4,", vec![])]
759    #[should_panic]
760    #[case("1,4.0", vec![])]
761    #[case("1:3", vec![1, 2, 3])]
762    #[case("1:10", vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])]
763    fn default_usize(#[case] numstr: &str, #[case] numvec: Vec<usize>) {
764        assert_eq!(
765            NumberRange::<usize>::default()
766                .parse_str(numstr)
767                .unwrap()
768                .collect::<Vec<usize>>(),
769            numvec
770        );
771    }
772
773    #[rstest]
774    #[case("200", vec!["200"])]
775    #[case("1,4", vec!["1,4", "4"])]
776    #[case("1:4", vec!["1:4", "2:4", "3:4", "4:4"])]
777    #[case("10:-4:4", vec!["10:-4:4", "6:-4:4"])]
778    #[case("4:-1:1", vec!["4:-1:1", "3:-1:1", "2:-1:1", "1:-1:1"])]
779    #[case("1:4:10", vec!["1:4:10", "5:4:10", "9:4:10"])]
780    fn format_test_loop(#[case] numstr: &str, #[case] numvec: Vec<&str>) {
781        let mut rng: NumberRange<i64> = NumberRange::default().parse_str(numstr).unwrap();
782        for fmt_str in numvec {
783            assert_eq!(fmt_str, format!("{}", &rng));
784            rng.next();
785        }
786        assert!(rng.next().is_none());
787    }
788
789    #[rstest]
790    #[case("200", ',',vec![200])]
791    #[case("1,4", ',', vec![1, 4])]
792    #[case("1:4", ':', vec![1, 4])]
793    #[case("1:4:4", ':', vec![1, 4, 4])]
794    #[case("1/4", '/', vec![1, 4])]
795    #[should_panic]
796    #[case("1--4", '-', vec![])]
797    #[should_panic]
798    #[case("1,-4", ':', vec![])]
799    fn comma_test_sep_usize(#[case] numstr: &str, #[case] sep: char, #[case] numvec: Vec<usize>) {
800        assert_eq!(
801            NumberRangeOptions::<usize>::new()
802                .with_list_sep(sep)
803                .parse(numstr)
804                .unwrap()
805                .collect::<Vec<usize>>(),
806            numvec
807        );
808    }
809
810    #[rstest]
811    #[case("200", '-',vec![200])]
812    #[case("1-4", '-', vec![1,2,3,4])]
813    #[case("1:3:4", ':', vec![1, 4])]
814    #[should_panic]
815    #[case("4:-3:1", ':', vec![])]
816    #[should_panic]
817    #[case("1--4", '-', vec![])]
818    fn comma_test_range_usize(#[case] numstr: &str, #[case] sep: char, #[case] numvec: Vec<usize>) {
819        assert_eq!(
820            NumberRangeOptions::<usize>::new()
821                .with_range_sep(sep)
822                .parse(numstr)
823                .unwrap()
824                .collect::<Vec<usize>>(),
825            numvec
826        );
827    }
828
829    #[rstest]
830    #[case("200", '-',vec![200])]
831    #[case("1-4", '-', vec![1,2,3,4])]
832    #[case("1:3:4", ':', vec![1, 4])]
833    #[case("4:-3:1", ':', vec![4, 1])]
834    #[case("-4:1", ':', vec![-4, -3, -2, -1, 0, 1])]
835    #[case("1:-4", ':', vec![])]
836    fn comma_test_range_i64(#[case] numstr: &str, #[case] sep: char, #[case] numvec: Vec<i64>) {
837        assert_eq!(
838            NumberRangeOptions::<i64>::new()
839                .with_range_sep(sep)
840                .parse(numstr)
841                .unwrap()
842                .collect::<Vec<i64>>(),
843            numvec
844        );
845    }
846
847    #[rstest]
848    #[case("200", '-',vec![200.0])]
849    #[case("1-4", '-', vec![1.0,2.0,3.0,4.0])]
850    #[case("1:3:4", ':', vec![1.0, 4.0])]
851    #[case("1:.5:4", ':', vec![1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0])]
852    #[case("4:-3:1", ':', vec![4.0, 1.0])]
853    #[case("-4:1", ':', vec![-4.0, -3.0, -2.0, -1.0, 0.0, 1.0])]
854    #[case("1:-4", ':', vec![])]
855    fn comma_test_range_f64(#[case] numstr: &str, #[case] sep: char, #[case] numvec: Vec<f64>) {
856        assert_eq!(
857            NumberRangeOptions::<f64>::new()
858                .with_range_sep(sep)
859                .parse(numstr)
860                .unwrap()
861                .collect::<Vec<f64>>(),
862            numvec
863        );
864    }
865
866    #[rstest]
867    fn comma_test_empty_range() {
868        assert_eq!(
869            NumberRange::default()
870                .parse_str("")
871                .unwrap()
872                .collect::<Vec<f64>>(),
873            vec![]
874        );
875        // testing to make sure it removes the old values from iterators
876        let rng = NumberRange::default().parse_str("1:10").unwrap();
877        assert_eq!(rng.parse_str("").unwrap().collect::<Vec<f64>>(), vec![]);
878    }
879
880    #[rstest]
881    #[case([1,2,3], None, "1:3")]
882    #[case(vec![1,2,3], None, "1:3")]
883    #[case([1,3,5,7], None, "1,3,5,7")]
884    #[case([1,3,5,7,10], Some(2), "1:2:7,10")]
885    fn rng_from_vec(
886        #[case] inp: impl IntoIterator<Item = i64>,
887        #[case] inc: Option<i64>,
888        #[case] s: &str,
889    ) {
890        assert_eq!(format!("{}", NumberRange::default().from_vec(inp, inc)), s);
891    }
892}