Skip to main content

consortium/
range_set.rs

1//! Numeric range sets with folding and autostep support.
2//!
3//! This module is the Rust implementation of `ClusterShell.RangeSet`.
4//!
5//! A [`RangeSet`] manages a set of non-negative integers as a union of
6//! string-represented values, with optional step detection (autostep) for
7//! compact string representation (e.g. `"0-8/2"`).
8//!
9//! Internally, like Python's ClusterShell, the set stores string representations
10//! of integers, preserving zero-padding per element (e.g. "001", "02", "3").
11
12use std::collections::BTreeSet;
13use std::fmt;
14use thiserror::Error;
15
16/// Special constant used to disable autostep feature.
17pub const AUTOSTEP_DISABLED: f64 = 1e100;
18
19// ---------------------------------------------------------------------------
20// Errors
21// ---------------------------------------------------------------------------
22
23#[derive(Debug, Error)]
24pub enum RangeSetError {
25    #[error("parse error: {msg} (in \"{part}\")")]
26    ParseError { part: String, msg: String },
27
28    #[error("padding mismatch: {msg} (in \"{part}\")")]
29    PaddingError { part: String, msg: String },
30}
31
32pub type Result<T> = std::result::Result<T, RangeSetError>;
33
34// ---------------------------------------------------------------------------
35// Sorting key
36// ---------------------------------------------------------------------------
37
38/// Sort key matching Python's RangeSet._sorted():
39/// - Negative numbers (starting with '-'): sort by (-len, int_value)
40/// - Non-negative: sort by (len, string_value)
41fn sort_key(s: &str) -> (i64, i64, String) {
42    if s.starts_with('-') {
43        let ival: i64 = s.parse().unwrap_or(0);
44        (-(s.len() as i64), ival, String::new())
45    } else {
46        (s.len() as i64, 0, s.to_string())
47    }
48}
49
50fn sorted_elements(elements: &BTreeSet<String>) -> Vec<String> {
51    let mut v: Vec<String> = elements.iter().cloned().collect();
52    v.sort_by(|a, b| sort_key(a).cmp(&sort_key(b)));
53    v
54}
55
56// ---------------------------------------------------------------------------
57// Slice representation (used internally for display)
58// ---------------------------------------------------------------------------
59
60#[derive(Debug, Clone)]
61struct RangeSlice {
62    start: i64,
63    stop: i64, // exclusive
64    step: i64,
65    pad: usize,
66}
67
68// ---------------------------------------------------------------------------
69// RangeSet
70// ---------------------------------------------------------------------------
71
72/// An ordered set of integer string representations, supporting cluster
73/// range notation like `"1-10,15,20-30/2"`.
74#[derive(Debug, Clone)]
75pub struct RangeSet {
76    /// Internal storage: string representations of integers, preserving padding.
77    elements: BTreeSet<String>,
78    /// Autostep threshold (internal representation = user_value - 1).
79    /// When >= AUTOSTEP_DISABLED, stepping is off.
80    _autostep: f64,
81}
82
83impl RangeSet {
84    /// Create an empty `RangeSet`.
85    pub fn new() -> Self {
86        Self {
87            elements: BTreeSet::new(),
88            _autostep: AUTOSTEP_DISABLED,
89        }
90    }
91
92    /// Parse a range set from a string pattern like `"1-4,6,8-12/2"`.
93    ///
94    /// `autostep` is the user-facing autostep threshold (minimum element count
95    /// to trigger step folding). Pass `None` to disable.
96    pub fn parse(pattern: &str, autostep: Option<u32>) -> Result<Self> {
97        let mut rs = Self::new();
98        rs.set_autostep(autostep);
99        rs._parse(pattern)?;
100        Ok(rs)
101    }
102
103    /// Internal parse: comma-separated subranges.
104    fn _parse(&mut self, pattern: &str) -> Result<()> {
105        for subrange in pattern.split(',') {
106            let subrange = subrange.trim();
107
108            let (baserange, step) = if subrange.contains('/') {
109                let parts: Vec<&str> = subrange.splitn(2, '/').collect();
110                let step_str = parts[1];
111                let step: i64 = step_str.parse().map_err(|_| RangeSetError::ParseError {
112                    part: subrange.to_string(),
113                    msg: "cannot convert string to integer".to_string(),
114                })?;
115                (parts[0], step)
116            } else {
117                (subrange, 1i64)
118            };
119
120            // Parse begin and end
121            let (begin_str, end_str, _begin_sign, _end_sign) =
122                self.parse_range_part(baserange, subrange, step)?;
123
124            // Compute padding
125            let (start, stop, pad) = self.compute_padding(&begin_str, &end_str, subrange)?;
126
127            // Validate
128            if stop > 1e100 as i64 || start > stop || step < 1 {
129                return Err(RangeSetError::ParseError {
130                    part: subrange.to_string(),
131                    msg: "invalid values in range".to_string(),
132                });
133            }
134
135            self.add_range(start, stop + 1, step, pad as u32);
136        }
137        Ok(())
138    }
139
140    /// Parse the begin-end part of a range, handling negative numbers.
141    fn parse_range_part<'a>(
142        &self,
143        baserange: &'a str,
144        subrange: &str,
145        step: i64,
146    ) -> Result<(String, String, i64, i64)> {
147        if !baserange.contains('-') {
148            // Single value
149            if step != 1 {
150                return Err(RangeSetError::ParseError {
151                    part: subrange.to_string(),
152                    msg: "invalid step usage".to_string(),
153                });
154            }
155            return Ok((baserange.to_string(), baserange.to_string(), 1, 1));
156        }
157
158        // Has a dash — could be a range or a negative number
159        let parts: Vec<&str> = baserange.split('-').collect();
160        match parts.len() {
161            2 => {
162                // "begin-end"
163                let begin = parts[0].trim().to_string();
164                let end = parts[1].trim().to_string();
165                if begin.is_empty() {
166                    // "-5" → single negative number
167                    if end.is_empty() {
168                        return Err(RangeSetError::ParseError {
169                            part: subrange.to_string(),
170                            msg: "cannot convert string to integer".to_string(),
171                        });
172                    }
173                    return Ok((end.clone(), end, -1, -1));
174                }
175                Ok((begin, end, 1, 1))
176            }
177            3 => {
178                // "-begin-end" (negative start)
179                let begin = parts[1].trim().to_string();
180                let end = parts[2].trim().to_string();
181                Ok((begin, end, -1, 1))
182            }
183            4 => {
184                // "-begin--end" (both negative)
185                let begin = parts[1].trim().to_string();
186                let end = parts[3].trim().to_string();
187                Ok((begin, end, -1, -1))
188            }
189            _ => Err(RangeSetError::ParseError {
190                part: subrange.to_string(),
191                msg: "cannot convert string to integer".to_string(),
192            }),
193        }
194    }
195
196    /// Compute padding and numeric start/stop from begin/end strings.
197    fn compute_padding(&self, begin: &str, end: &str, subrange: &str) -> Result<(i64, i64, usize)> {
198        let mut pad: usize = 0;
199
200        // Parse begin
201        let begin_int: i64 = begin.parse().map_err(|_| {
202            if subrange.is_empty() {
203                RangeSetError::ParseError {
204                    part: subrange.to_string(),
205                    msg: "empty range".to_string(),
206                }
207            } else {
208                RangeSetError::ParseError {
209                    part: subrange.to_string(),
210                    msg: "cannot convert string to integer".to_string(),
211                }
212            }
213        })?;
214
215        if begin_int != 0 {
216            let begins = begin.trim_start_matches('0');
217            if begin.len() - begins.len() > 0 {
218                pad = begin.len();
219            }
220        } else if begin.len() > 1 {
221            pad = begin.len();
222        }
223
224        // Parse end
225        let end_trimmed = if end.parse::<i64>().unwrap_or(1) != 0 {
226            end.trim_start_matches('0')
227        } else {
228            end
229        };
230
231        let mut endpad: usize = 0;
232        if end.len() - end_trimmed.len() > 0 {
233            endpad = end.len();
234        }
235
236        // Check padding consistency
237        if (pad > 0 || endpad > 0) && begin.len() != end.len() {
238            return Err(RangeSetError::ParseError {
239                part: subrange.to_string(),
240                msg: "padding length mismatch".to_string(),
241            });
242        }
243
244        let end_int: i64 = end_trimmed.parse().map_err(|_| RangeSetError::ParseError {
245            part: subrange.to_string(),
246            msg: "cannot convert string to integer".to_string(),
247        })?;
248
249        Ok((begin_int, end_int, pad))
250    }
251
252    /// Add a range of integers [start, stop) with given step and padding.
253    pub fn add_range(&mut self, start: i64, stop: i64, step: i64, pad: u32) {
254        assert!(start < stop, "please provide ordered node index ranges");
255        assert!(step > 0);
256        assert!(stop - start < 1_000_000_000, "range too large");
257
258        let mut i = start;
259        while i < stop {
260            if pad == 0 {
261                self.elements.insert(format!("{}", i));
262            } else {
263                self.elements
264                    .insert(format!("{:0>width$}", i, width = pad as usize));
265            }
266            i += step;
267        }
268    }
269
270    /// Number of individual values in the set.
271    pub fn len(&self) -> usize {
272        self.elements.len()
273    }
274
275    /// Whether the set is empty.
276    pub fn is_empty(&self) -> bool {
277        self.elements.is_empty()
278    }
279
280    /// Add a string element directly.
281    pub fn add_str(&mut self, s: &str) {
282        self.elements.insert(s.to_string());
283    }
284
285    /// Add an integer element with optional zero-padding.
286    pub fn add_int(&mut self, value: i64, pad: u32) {
287        if pad == 0 {
288            self.elements.insert(format!("{}", value));
289        } else {
290            self.elements
291                .insert(format!("{:0>width$}", value, width = pad as usize));
292        }
293    }
294
295    /// Remove an element by string. Panics if not present.
296    pub fn remove_str(&mut self, s: &str) {
297        if !self.elements.remove(s) {
298            panic!("element '{}' not found in RangeSet", s);
299        }
300    }
301
302    /// Remove an element by string if present (no error).
303    pub fn discard_str(&mut self, s: &str) {
304        self.elements.remove(s);
305    }
306
307    /// Check if a string element is in the set.
308    pub fn contains_str(&self, s: &str) -> bool {
309        self.elements.contains(s)
310    }
311
312    /// Check if an integer (unpadded) is in the set.
313    pub fn contains_int(&self, value: i64) -> bool {
314        self.elements.contains(&format!("{}", value))
315    }
316
317    /// Get sorted elements as owned strings.
318    pub fn sorted(&self) -> Vec<String> {
319        sorted_elements(&self.elements)
320    }
321
322    /// Iterate as strings (owned).
323    pub fn striter(&self) -> impl Iterator<Item = String> {
324        sorted_elements(&self.elements).into_iter()
325    }
326
327    /// Iterate as integer values.
328    pub fn intiter(&self) -> impl Iterator<Item = i64> {
329        sorted_elements(&self.elements)
330            .into_iter()
331            .map(|s| s.parse::<i64>().unwrap_or(0))
332    }
333
334    // -----------------------------------------------------------------------
335    // Set operations
336    // -----------------------------------------------------------------------
337
338    /// Return the union of two range sets.
339    pub fn union(&self, other: &RangeSet) -> RangeSet {
340        let mut result = self.clone();
341        result.update(other);
342        result
343    }
344
345    /// Return the intersection of two range sets.
346    pub fn intersection(&self, other: &RangeSet) -> RangeSet {
347        let mut result = self.clone();
348        result.intersection_update(other);
349        result
350    }
351
352    /// Return the difference (self - other).
353    pub fn difference(&self, other: &RangeSet) -> RangeSet {
354        let mut result = self.clone();
355        result.difference_update(other);
356        result
357    }
358
359    /// Return the symmetric difference.
360    pub fn symmetric_difference(&self, other: &RangeSet) -> RangeSet {
361        let mut result = self.clone();
362        result.symmetric_difference_update(other);
363        result
364    }
365
366    /// Update self to be the union with other.
367    pub fn update(&mut self, other: &RangeSet) {
368        for elem in &other.elements {
369            self.elements.insert(elem.clone());
370        }
371    }
372
373    /// Update self to be the intersection with other.
374    pub fn intersection_update(&mut self, other: &RangeSet) {
375        self.elements = self
376            .elements
377            .intersection(&other.elements)
378            .cloned()
379            .collect();
380    }
381
382    /// Update self to be self - other.
383    pub fn difference_update(&mut self, other: &RangeSet) {
384        self.elements = self.elements.difference(&other.elements).cloned().collect();
385    }
386
387    /// Update self to be the symmetric difference with other.
388    pub fn symmetric_difference_update(&mut self, other: &RangeSet) {
389        self.elements = self
390            .elements
391            .symmetric_difference(&other.elements)
392            .cloned()
393            .collect();
394    }
395
396    // -----------------------------------------------------------------------
397    // Autostep
398    // -----------------------------------------------------------------------
399
400    /// Get autostep value (user-facing: minimum node count, or None if disabled).
401    pub fn autostep(&self) -> Option<u32> {
402        if self._autostep >= AUTOSTEP_DISABLED {
403            None
404        } else {
405            Some((self._autostep as u32) + 1)
406        }
407    }
408
409    /// Set autostep value (user-facing: minimum node count, or None to disable).
410    pub fn set_autostep(&mut self, val: Option<u32>) {
411        match val {
412            None => self._autostep = AUTOSTEP_DISABLED,
413            Some(v) => self._autostep = (v as f64) - 1.0,
414        }
415    }
416
417    // -----------------------------------------------------------------------
418    // Display internals: _slices_padding, _strslices
419    // -----------------------------------------------------------------------
420
421    /// Iterator over (RangeSlice, padding) pairs, implementing Python's
422    /// `_slices_padding(autostep)`.
423    fn slices_padding(&self, autostep: f64) -> Vec<RangeSlice> {
424        let sorted = sorted_elements(&self.elements);
425        if sorted.is_empty() {
426            return Vec::new();
427        }
428
429        let mut result: Vec<RangeSlice> = Vec::new();
430
431        let mut cur_pad: usize = 0;
432        let mut cur_padded: bool = false;
433        let mut cur_start: Option<i64> = None;
434        let mut cur_step: Option<i64> = None;
435        let mut last_idx: i64 = 0;
436
437        for si in &sorted {
438            let idx: i64 = si.parse().unwrap_or(0);
439            let digitlen = si.len();
440            let padded = digitlen > 1 && si.starts_with('0');
441
442            if let Some(cs) = cur_start {
443                // Check for padding mismatch
444                let padding_mismatch = if cur_padded {
445                    digitlen != cur_pad
446                } else {
447                    padded
448                };
449
450                // Check for step mismatch
451                let step_mismatch = if let Some(cstep) = cur_step {
452                    cstep != idx - last_idx
453                } else {
454                    false
455                };
456
457                if padding_mismatch || step_mismatch {
458                    let (stepped, step) = if let Some(cstep) = cur_step {
459                        let stepped =
460                            (cstep == 1) || ((last_idx - cs) as f64 >= autostep * cstep as f64);
461                        (stepped, cstep)
462                    } else {
463                        (true, 1i64)
464                    };
465
466                    if stepped {
467                        result.push(RangeSlice {
468                            start: cs,
469                            stop: last_idx + 1,
470                            step,
471                            pad: if cur_padded { cur_pad } else { 0 },
472                        });
473                        cur_start = Some(idx);
474                        cur_padded = padded;
475                        cur_pad = digitlen;
476                    } else {
477                        if padding_mismatch {
478                            let stop = last_idx + 1;
479                            let mut j = cs;
480                            while j < stop {
481                                result.push(RangeSlice {
482                                    start: j,
483                                    stop: j + 1,
484                                    step: 1,
485                                    pad: if cur_padded { cur_pad } else { 0 },
486                                });
487                                j += step;
488                            }
489                            cur_start = Some(idx);
490                            cur_padded = padded;
491                            cur_pad = digitlen;
492                        } else {
493                            let stop = last_idx - step + 1;
494                            let mut j = cs;
495                            while j < stop {
496                                result.push(RangeSlice {
497                                    start: j,
498                                    stop: j + 1,
499                                    step: 1,
500                                    pad: if cur_padded { cur_pad } else { 0 },
501                                });
502                                j += step;
503                            }
504                            cur_start = Some(last_idx);
505                        }
506                    }
507
508                    cur_step = if step_mismatch {
509                        Some(idx - last_idx)
510                    } else {
511                        None
512                    };
513                    last_idx = idx;
514                    continue;
515                }
516            } else {
517                // First element
518                cur_padded = padded;
519                cur_pad = digitlen;
520                cur_start = Some(idx);
521                cur_step = None;
522                last_idx = idx;
523                continue;
524            }
525
526            cur_step = Some(idx - last_idx);
527            last_idx = idx;
528        }
529
530        // Flush remaining
531        if let Some(cs) = cur_start {
532            if let Some(cstep) = cur_step {
533                let stepped = (last_idx - cs) as f64 >= self._autostep * cstep as f64;
534                if stepped || cstep == 1 {
535                    result.push(RangeSlice {
536                        start: cs,
537                        stop: last_idx + 1,
538                        step: cstep,
539                        pad: if cur_padded { cur_pad } else { 0 },
540                    });
541                } else {
542                    let mut j = cs;
543                    while j <= last_idx {
544                        result.push(RangeSlice {
545                            start: j,
546                            stop: j + 1,
547                            step: 1,
548                            pad: if cur_padded { cur_pad } else { 0 },
549                        });
550                        j += cstep;
551                    }
552                }
553            } else {
554                // Single element
555                result.push(RangeSlice {
556                    start: cs,
557                    stop: last_idx + 1,
558                    step: 1,
559                    pad: if cur_padded { cur_pad } else { 0 },
560                });
561            }
562        }
563
564        result
565    }
566
567    /// Folded slices using the instance's autostep setting.
568    fn folded_slices(&self) -> Vec<RangeSlice> {
569        self.slices_padding(self._autostep)
570    }
571
572    /// Format slices as strings.
573    fn strslices(&self) -> Vec<String> {
574        self.folded_slices()
575            .iter()
576            .map(|sli| {
577                if sli.start + 1 == sli.stop {
578                    // Single element
579                    if sli.pad > 0 {
580                        format!("{:0>width$}", sli.start, width = sli.pad)
581                    } else {
582                        format!("{}", sli.start)
583                    }
584                } else if sli.step == 1 {
585                    // Contiguous range
586                    if sli.pad > 0 {
587                        format!(
588                            "{:0>width$}-{:0>width$}",
589                            sli.start,
590                            sli.stop - 1,
591                            width = sli.pad
592                        )
593                    } else {
594                        format!("{}-{}", sli.start, sli.stop - 1)
595                    }
596                } else {
597                    // Stepped range
598                    if sli.pad > 0 {
599                        format!(
600                            "{:0>width$}-{:0>width$}/{}",
601                            sli.start,
602                            sli.stop - 1,
603                            sli.step,
604                            width = sli.pad
605                        )
606                    } else {
607                        format!("{}-{}/{}", sli.start, sli.stop - 1, sli.step)
608                    }
609                }
610            })
611            .collect()
612    }
613
614    /// Get the maximum padding value in the set.
615    pub fn padding(&self) -> Option<usize> {
616        let mut result = None;
617        for si in sorted_elements(&self.elements) {
618            let digitlen = si.len();
619            if digitlen > 1 && si.starts_with('0') {
620                result = Some(digitlen);
621            }
622        }
623        result
624    }
625}
626
627impl Default for RangeSet {
628    fn default() -> Self {
629        Self::new()
630    }
631}
632
633impl fmt::Display for RangeSet {
634    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
635        write!(f, "{}", self.strslices().join(","))
636    }
637}
638
639// ---------------------------------------------------------------------------
640// RangeSetND (N-dimensional) — stub
641// ---------------------------------------------------------------------------
642
643/// N-dimensional range set, used internally by NodeSet for multi-range patterns.
644#[derive(Debug, Clone)]
645pub struct RangeSetND {
646    pub vecrangesets: Vec<Vec<RangeSet>>,
647    _autostep: f64,
648}
649
650impl RangeSetND {
651    pub fn new(autostep: Option<u32>) -> Self {
652        Self {
653            vecrangesets: Vec::new(),
654            _autostep: autostep.map(|v| v as f64).unwrap_or(AUTOSTEP_DISABLED),
655        }
656    }
657}
658
659// ---------------------------------------------------------------------------
660// Tests
661// ---------------------------------------------------------------------------
662
663#[cfg(test)]
664mod tests {
665    use super::*;
666
667    fn test_rs(input: &str, expected: &str, expected_len: usize) {
668        let rs = RangeSet::parse(input, Some(3)).unwrap();
669        assert_eq!(
670            rs.to_string(),
671            expected,
672            "parse '{}' display mismatch",
673            input
674        );
675        assert_eq!(rs.len(), expected_len, "parse '{}' len mismatch", input);
676    }
677
678    #[test]
679    fn test_simple() {
680        test_rs("0", "0", 1);
681        test_rs("1", "1", 1);
682        test_rs("0-2", "0-2", 3);
683        test_rs("1-3", "1-3", 3);
684        test_rs("1-3,4-6", "1-6", 6);
685        test_rs("1-3,4-6,7-10", "1-10", 10);
686    }
687
688    #[test]
689    fn test_step_simple() {
690        test_rs("0-4/2", "0-4/2", 3);
691        test_rs("1-4/2", "1,3", 2);
692        test_rs("1-4/3", "1,4", 2);
693        test_rs("1-4/4", "1", 1);
694    }
695
696    #[test]
697    fn test_step_advanced() {
698        test_rs("1-4/4,2-6/2", "1-2,4,6", 4);
699        test_rs("6-24/6,9-21/6", "6-24/3", 7);
700        test_rs("0-24/2,9-21/2", "0-8/2,9-22,24", 20);
701        test_rs("0-24/2,9-21/2,100", "0-8/2,9-22,24,100", 21);
702        test_rs("3-21/9,6-24/9,9-27/9", "3-27/3", 9);
703        test_rs("1-17/2,2-18/2", "1-18", 18);
704        test_rs("1-17/01", "1-17", 17);
705        test_rs("1-17/02", "1-17/2", 9);
706        test_rs("1-17/03", "1-16/3", 6);
707    }
708
709    #[test]
710    fn test_step_advanced_more() {
711        test_rs("0-24/2,9-21/2,100-101", "0-8/2,9-22,24,100-101", 22);
712        test_rs("101-121/4,1-225/112", "1,101-121/4,225", 8);
713        test_rs("1-32/3,13-28/9", "1-31/3", 11);
714        test_rs("1-32/3,13-22/9", "1-31/3", 11);
715        test_rs("1-32/3,13-31/9", "1-31/3", 11);
716        test_rs("1-32/3,13-40/9", "1-31/3,40", 12);
717    }
718
719    #[test]
720    fn test_step_more_complex() {
721        test_rs("1-16/3,13-28/6", "1-19/3,25", 8);
722        test_rs("1-16/3,1-16/6", "1-16/3", 6);
723        test_rs("1-16/6,1-16/3", "1-16/3", 6);
724        test_rs("1-17/2,33-41/2,2-18/2", "1-18,33-41/2", 23);
725        test_rs("1-17/2,33-41/2,2-20/2", "1-18,20,33-41/2", 24);
726        test_rs("1-17/2,33-41/2,2-19/2", "1-18,33-41/2", 23);
727    }
728
729    #[test]
730    fn test_bad_syntax() {
731        assert!(RangeSet::parse("", None).is_err());
732        assert!(RangeSet::parse("-", None).is_err());
733        assert!(RangeSet::parse("A", None).is_err());
734        assert!(RangeSet::parse("2-5/a", None).is_err());
735        assert!(RangeSet::parse("3/2", None).is_err());
736        assert!(RangeSet::parse("3-/2", None).is_err());
737        assert!(RangeSet::parse("-/2", None).is_err());
738        assert!(RangeSet::parse("4-a/2", None).is_err());
739        assert!(RangeSet::parse("4-3/2", None).is_err());
740        assert!(RangeSet::parse("4-5/-2", None).is_err());
741        assert!(RangeSet::parse("004-002", None).is_err());
742    }
743
744    #[test]
745    fn test_intersection() {
746        let mut r1 = RangeSet::parse("4-34", None).unwrap();
747        let r2 = RangeSet::parse("27-42", None).unwrap();
748        r1.intersection_update(&r2);
749        assert_eq!(r1.to_string(), "27-34");
750        assert_eq!(r1.len(), 8);
751    }
752
753    #[test]
754    fn test_intersection_complex() {
755        let mut r1 = RangeSet::parse("2-450,654-700,800", None).unwrap();
756        let r2 = RangeSet::parse("500-502,690-820,830-840,900", None).unwrap();
757        r1.intersection_update(&r2);
758        assert_eq!(r1.to_string(), "690-700,800");
759        assert_eq!(r1.len(), 12);
760    }
761
762    #[test]
763    fn test_intersection_step() {
764        let mut r1 = RangeSet::parse("4-34/2", None).unwrap();
765        let r2 = RangeSet::parse("28-42/2", None).unwrap();
766        r1.intersection_update(&r2);
767        assert_eq!(r1.to_string(), "28,30,32,34");
768        assert_eq!(r1.len(), 4);
769
770        let mut r1 = RangeSet::parse("4-34/2", None).unwrap();
771        let r2 = RangeSet::parse("27-42/2", None).unwrap();
772        r1.intersection_update(&r2);
773        assert_eq!(r1.to_string(), "");
774        assert_eq!(r1.len(), 0);
775
776        let mut r1 = RangeSet::parse("2-60/3", Some(3)).unwrap();
777        let r2 = RangeSet::parse("3-50/2", Some(3)).unwrap();
778        r1.intersection_update(&r2);
779        assert_eq!(r1.to_string(), "5-47/6");
780        assert_eq!(r1.len(), 8);
781    }
782
783    #[test]
784    fn test_difference() {
785        let mut r1 = RangeSet::parse("4,7-33", None).unwrap();
786        let r2 = RangeSet::parse("8-33", None).unwrap();
787        r1.difference_update(&r2);
788        assert_eq!(r1.to_string(), "4,7");
789        assert_eq!(r1.len(), 2);
790    }
791
792    #[test]
793    fn test_difference_bounds() {
794        let mut r1 = RangeSet::parse("1-10,39-41,50-60", None).unwrap();
795        let r2 = RangeSet::parse("1-10,38-39,50-60", None).unwrap();
796        r1.difference_update(&r2);
797        assert_eq!(r1.len(), 2);
798        assert_eq!(r1.to_string(), "40-41");
799    }
800
801    #[test]
802    fn test_symmetric_difference() {
803        let mut r1 = RangeSet::parse("4,7-33", None).unwrap();
804        let r2 = RangeSet::parse("8-34", None).unwrap();
805        r1.symmetric_difference_update(&r2);
806        assert_eq!(r1.to_string(), "4,7,34");
807        assert_eq!(r1.len(), 3);
808    }
809
810    #[test]
811    fn test_symmetric_difference_complex() {
812        let mut r1 = RangeSet::parse("5,7,10-12,33-50", None).unwrap();
813        let r2 = RangeSet::parse("8-34", None).unwrap();
814        r1.symmetric_difference_update(&r2);
815        assert_eq!(r1.to_string(), "5,7-9,13-32,35-50");
816        assert_eq!(r1.len(), 40);
817
818        let mut r1 = RangeSet::parse("8-30", None).unwrap();
819        let r2 = RangeSet::parse("31-40", None).unwrap();
820        r1.symmetric_difference_update(&r2);
821        assert_eq!(r1.to_string(), "8-40");
822        assert_eq!(r1.len(), 33);
823
824        let mut r1 = RangeSet::parse("8-30", None).unwrap();
825        let r2 = RangeSet::parse("8-30", None).unwrap();
826        r1.symmetric_difference_update(&r2);
827        assert_eq!(r1.to_string(), "");
828        assert_eq!(r1.len(), 0);
829    }
830
831    #[test]
832    fn test_union() {
833        let r1 = RangeSet::parse("1-10", None).unwrap();
834        let r2 = RangeSet::parse("8-20", None).unwrap();
835        let r3 = r1.union(&r2);
836        assert_eq!(r3.to_string(), "1-20");
837        assert_eq!(r3.len(), 20);
838    }
839
840    #[test]
841    fn test_contains() {
842        let r1 = RangeSet::parse("1-100,102,105-242,800", None).unwrap();
843        assert_eq!(r1.len(), 240);
844        assert!(r1.contains_str("99"));
845        assert!(!r1.contains_str("099"));
846        assert!(!r1.contains_str("101"));
847    }
848
849    #[test]
850    fn test_padding() {
851        let r1 = RangeSet::parse("001-010", None).unwrap();
852        assert_eq!(r1.len(), 10);
853        assert!(r1.contains_str("001"));
854        assert!(r1.contains_str("010"));
855        assert!(!r1.contains_str("1"));
856        assert!(!r1.contains_str("10"));
857
858        let r2 = RangeSet::parse("00-02", None).unwrap();
859        assert!(r2.contains_str("00"));
860        assert!(!r2.contains_str("0"));
861        assert!(r2.contains_str("01"));
862        assert!(!r2.contains_str("1"));
863
864        let r3 = RangeSet::parse("0003-0143,0360-1000", None).unwrap();
865        assert!(!r3.contains_str("360"));
866        assert!(r3.contains_str("0360"));
867    }
868
869    #[test]
870    fn test_padding_display() {
871        test_rs("001-010", "001-010", 10);
872        test_rs("00-02", "00-02", 3);
873    }
874
875    #[test]
876    fn test_empty() {
877        let rs = RangeSet::new();
878        assert!(rs.is_empty());
879        assert_eq!(rs.len(), 0);
880        assert_eq!(rs.to_string(), "");
881    }
882
883    #[test]
884    fn test_add_remove() {
885        let mut rs = RangeSet::new();
886        rs.add_int(5, 0);
887        rs.add_int(3, 0);
888        rs.add_int(1, 0);
889        assert_eq!(rs.len(), 3);
890        assert_eq!(rs.to_string(), "1,3,5");
891        rs.discard_str("3");
892        assert_eq!(rs.len(), 2);
893        assert_eq!(rs.to_string(), "1,5");
894    }
895
896    #[test]
897    fn test_intiter() {
898        let rs = RangeSet::parse("3,1,5,10", None).unwrap();
899        let vals: Vec<i64> = rs.intiter().collect();
900        assert_eq!(vals, vec![1, 3, 5, 10]);
901    }
902
903    #[test]
904    fn test_large_range() {
905        let rs = RangeSet::parse("1-1000", None).unwrap();
906        assert_eq!(rs.len(), 1000);
907        assert_eq!(rs.to_string(), "1-1000");
908    }
909
910    #[test]
911    fn test_diff_step() {
912        let mut r1 = RangeSet::parse("4-34/2", Some(3)).unwrap();
913        let r2 = RangeSet::parse("3-33/2", Some(3)).unwrap();
914        r1.difference_update(&r2);
915        assert_eq!(r1.to_string(), "4-34/2");
916        assert_eq!(r1.len(), 16);
917
918        let mut r1 = RangeSet::parse("4-34/2", Some(3)).unwrap();
919        let r2 = RangeSet::parse("2-14/2", Some(3)).unwrap();
920        r1.difference_update(&r2);
921        assert_eq!(r1.to_string(), "16-34/2");
922        assert_eq!(r1.len(), 10);
923
924        let mut r1 = RangeSet::parse("4-34/2", Some(3)).unwrap();
925        let r2 = RangeSet::parse("28-52/2", Some(3)).unwrap();
926        r1.difference_update(&r2);
927        assert_eq!(r1.to_string(), "4-26/2");
928        assert_eq!(r1.len(), 12);
929
930        let mut r1 = RangeSet::parse("4-34/2", Some(3)).unwrap();
931        let r2 = RangeSet::parse("12-18/2", Some(3)).unwrap();
932        r1.difference_update(&r2);
933        assert_eq!(r1.to_string(), "4-10/2,20-34/2");
934        assert_eq!(r1.len(), 12);
935    }
936
937    #[test]
938    fn test_diff_step_complex() {
939        let mut r1 = RangeSet::parse("1-100", Some(3)).unwrap();
940        let r2 = RangeSet::parse("2-98/2", Some(3)).unwrap();
941        r1.difference_update(&r2);
942        assert_eq!(r1.to_string(), "1-99/2,100");
943        assert_eq!(r1.len(), 51);
944
945        let mut r1 = RangeSet::parse("1-1000", Some(3)).unwrap();
946        let r2 = RangeSet::parse("2-999/2", Some(3)).unwrap();
947        r1.difference_update(&r2);
948        assert_eq!(r1.to_string(), "1-999/2,1000");
949        assert_eq!(r1.len(), 501);
950    }
951
952    #[test]
953    fn test_folding() {
954        // Python: testFolding
955        let r1 = RangeSet::parse(
956            "112,114-117,119,121,130,132,134,136,138,139-141,144,147-148",
957            Some(6),
958        )
959        .unwrap();
960        assert_eq!(
961            r1.to_string(),
962            "112,114-117,119,121,130,132,134,136,138-141,144,147-148"
963        );
964
965        let r1 = RangeSet::parse(
966            "112,114-117,119,121,130,132,134,136,138,139-141,144,147-148",
967            Some(5),
968        )
969        .unwrap();
970        assert_eq!(
971            r1.to_string(),
972            "112,114-117,119,121,130-138/2,139-141,144,147-148"
973        );
974
975        let r1 = RangeSet::parse("1,3-4,6,8", None).unwrap();
976        assert_eq!(r1.to_string(), "1,3-4,6,8");
977
978        let r1 = RangeSet::parse("1,3-4,6,8", Some(4)).unwrap();
979        assert_eq!(r1.to_string(), "1,3-4,6,8");
980
981        let r1 = RangeSet::parse("1,3-4,6,8", Some(2)).unwrap();
982        assert_eq!(r1.to_string(), "1-3/2,4,6-8/2");
983
984        let r1 = RangeSet::parse("1,3-4,6,8", Some(3)).unwrap();
985        assert_eq!(r1.to_string(), "1,3-4,6,8");
986    }
987
988    #[test]
989    fn test_mixed_padding() {
990        // Python: test_mixed_padding
991        let r0 = RangeSet::parse("030-031,032-100/2,101-489", Some(3)).unwrap();
992        assert_eq!(r0.to_string(), "030-032,034-100/2,101-489");
993
994        let r1 = RangeSet::parse("030-032,033-100/3,102", Some(3)).unwrap();
995        assert_eq!(r1.to_string(), "030-033,036-102/3");
996
997        let r2 = RangeSet::parse("030-032,033-100/3,101", Some(3)).unwrap();
998        assert_eq!(r2.to_string(), "030-033,036-099/3,101");
999
1000        let r3 = RangeSet::parse("030-032,033-100/3,100", Some(3)).unwrap();
1001        assert_eq!(r3.to_string(), "030-033,036-099/3,100");
1002    }
1003
1004    #[test]
1005    fn test_mixed_padding_with_different_widths() {
1006        // Python: test_mixed_padding r5 case
1007        let r5 = RangeSet::parse("030-032,033-100/3,99-105/3,0001", Some(3)).unwrap();
1008        assert_eq!(r5.to_string(), "99,030-033,036-105/3,0001");
1009    }
1010
1011    #[test]
1012    fn test_padding_mismatch_errors() {
1013        // Python: test_mixed_padding_mismatch
1014        assert!(RangeSet::parse("1-044", None).is_err());
1015        assert!(RangeSet::parse("01-044", None).is_err());
1016        assert!(RangeSet::parse("001-44", None).is_err());
1017        assert!(RangeSet::parse("0-9,1-044", None).is_err());
1018        assert!(RangeSet::parse("0-9,01-044", None).is_err());
1019        assert!(RangeSet::parse("0-9,001-44", None).is_err());
1020        assert!(RangeSet::parse("030-032,033-99/3,100", None).is_err());
1021    }
1022
1023    #[test]
1024    fn test_update_complex() {
1025        // Python: testUpdate
1026        let mut r1 = RangeSet::parse("1-100,102,105-242,800", None).unwrap();
1027        assert_eq!(r1.len(), 240);
1028        let r2 = RangeSet::parse("243-799,1924-1984", None).unwrap();
1029        assert_eq!(r2.len(), 618);
1030        r1.update(&r2);
1031        assert_eq!(r1.len(), 240 + 618);
1032        assert_eq!(r1.to_string(), "1-100,102,105-800,1924-1984");
1033    }
1034
1035    #[test]
1036    fn test_union_complex() {
1037        // Python: testUnion
1038        let r1 = RangeSet::parse("1-100,102,105-242,800", None).unwrap();
1039        let r2 = RangeSet::parse("243-799,1924-1984", None).unwrap();
1040        let r3 = r1.union(&r2);
1041        assert_eq!(r3.len(), 858);
1042        assert_eq!(r3.to_string(), "1-100,102,105-800,1924-1984");
1043
1044        // with overlap
1045        let r2 = RangeSet::parse("200-799", None).unwrap();
1046        let r3 = r1.union(&r2);
1047        assert_eq!(r3.len(), 797);
1048        assert_eq!(r3.to_string(), "1-100,102,105-800");
1049    }
1050
1051    #[test]
1052    fn test_intersection_length() {
1053        // Python: testIntersectionLength
1054        let r1 = RangeSet::parse("115-117,130,166-170,4780-4999", None).unwrap();
1055        assert_eq!(r1.len(), 229);
1056        let r2 = RangeSet::parse("116-117,130,4781-4999", None).unwrap();
1057        assert_eq!(r2.len(), 222);
1058        let res = r1.intersection(&r2);
1059        assert_eq!(res.len(), 222);
1060
1061        let r1 = RangeSet::parse("115-200", None).unwrap();
1062        assert_eq!(r1.len(), 86);
1063        let r2 = RangeSet::parse("116-117,119,123-131,133,149,199", None).unwrap();
1064        assert_eq!(r2.len(), 15);
1065        let res = r1.intersection(&r2);
1066        assert_eq!(res.len(), 15);
1067
1068        // StopIteration test
1069        let r1 = RangeSet::parse("115-117,130,166-170,4780-4999,5003", None).unwrap();
1070        assert_eq!(r1.len(), 230);
1071        let r2 = RangeSet::parse("116-117,130,4781-4999", None).unwrap();
1072        assert_eq!(r2.len(), 222);
1073        let res = r1.intersection(&r2);
1074        assert_eq!(res.len(), 222);
1075
1076        // StopIteration test2
1077        let r1 = RangeSet::parse("130,166-170,4780-4999", None).unwrap();
1078        assert_eq!(r1.len(), 226);
1079        let r2 = RangeSet::parse("116-117", None).unwrap();
1080        assert_eq!(r2.len(), 2);
1081        let res = r1.intersection(&r2);
1082        assert_eq!(res.len(), 0);
1083    }
1084
1085    #[test]
1086    fn test_ior() {
1087        // Python: test_ior
1088        let mut r1 = RangeSet::parse("1,3-9,14-21,30-39,42", None).unwrap();
1089        let r2 = RangeSet::parse("2-5,10-32,35,40-41", None).unwrap();
1090        r1.update(&r2);
1091        assert_eq!(r1.len(), 42);
1092        assert_eq!(r1.to_string(), "1-42");
1093    }
1094
1095    #[test]
1096    fn test_iand() {
1097        // Python: test_iand
1098        let mut r1 = RangeSet::parse("1,3-9,14-21,30-39,42", None).unwrap();
1099        let r2 = RangeSet::parse("2-5,10-32,35,40-41", None).unwrap();
1100        r1.intersection_update(&r2);
1101        assert_eq!(r1.len(), 15);
1102        assert_eq!(r1.to_string(), "3-5,14-21,30-32,35");
1103    }
1104
1105    #[test]
1106    fn test_ixor() {
1107        // Python: test_ixor
1108        let mut r1 = RangeSet::parse("1,3-9,14-21,30-39,42", None).unwrap();
1109        let r2 = RangeSet::parse("2-5,10-32,35,40-41", None).unwrap();
1110        r1.symmetric_difference_update(&r2);
1111        assert_eq!(r1.len(), 27);
1112        assert_eq!(r1.to_string(), "1-2,6-13,22-29,33-34,36-42");
1113    }
1114
1115    #[test]
1116    fn test_isub() {
1117        // Python: test_isub
1118        let mut r1 = RangeSet::parse("1,3-9,14-21,30-39,42", None).unwrap();
1119        let r2 = RangeSet::parse("2-5,10-32,35,40-41", None).unwrap();
1120        r1.difference_update(&r2);
1121        assert_eq!(r1.len(), 12);
1122        assert_eq!(r1.to_string(), "1,6-9,33-34,36-39,42");
1123    }
1124
1125    #[test]
1126    fn test_add_range_api() {
1127        // Python: testAddRange
1128        let mut r1 = RangeSet::new();
1129        r1.add_range(1, 100, 1, 0);
1130        assert_eq!(r1.len(), 99);
1131        assert_eq!(r1.to_string(), "1-99");
1132        r1.add_range(40, 101, 1, 0);
1133        assert_eq!(r1.len(), 100);
1134        assert_eq!(r1.to_string(), "1-100");
1135        r1.add_range(399, 423, 2, 0);
1136        assert_eq!(r1.len(), 112);
1137        assert_eq!(
1138            r1.to_string(),
1139            "1-100,399,401,403,405,407,409,411,413,415,417,419,421"
1140        );
1141
1142        // With autostep
1143        let mut r1 = RangeSet::new();
1144        r1.set_autostep(Some(3));
1145        r1.add_range(1, 100, 1, 0);
1146        assert_eq!(r1.autostep(), Some(3));
1147        assert_eq!(r1.len(), 99);
1148        assert_eq!(r1.to_string(), "1-99");
1149        r1.add_range(40, 101, 1, 0);
1150        assert_eq!(r1.len(), 100);
1151        assert_eq!(r1.to_string(), "1-100");
1152        r1.add_range(399, 423, 2, 0);
1153        assert_eq!(r1.len(), 112);
1154        assert_eq!(r1.to_string(), "1-100,399-421/2");
1155    }
1156
1157    #[test]
1158    fn test_add_range_bounds() {
1159        let mut r1 = RangeSet::parse("1-30", Some(2)).unwrap();
1160        assert_eq!(r1.len(), 30);
1161        assert_eq!(r1.to_string(), "1-30");
1162        r1.add_range(32, 35, 1, 0);
1163        assert_eq!(r1.len(), 33);
1164        assert_eq!(r1.to_string(), "1-30,32-34");
1165        r1.add_range(31, 32, 1, 0);
1166        assert_eq!(r1.len(), 34);
1167        assert_eq!(r1.to_string(), "1-34");
1168
1169        let mut r1 = RangeSet::parse("1-30", Some(3)).unwrap();
1170        r1.add_range(40, 65, 10, 0);
1171        assert_eq!(r1.autostep(), Some(3));
1172        assert_eq!(r1.len(), 33);
1173        assert_eq!(r1.to_string(), "1-30,40-60/10");
1174
1175        // One
1176        r1.add_range(103, 104, 1, 0);
1177        assert_eq!(r1.len(), 34);
1178        assert_eq!(r1.to_string(), "1-30,40-60/10,103");
1179    }
1180
1181    #[test]
1182    fn test_autostep_property() {
1183        let mut rs = RangeSet::new();
1184        assert_eq!(rs.autostep(), None);
1185        rs.set_autostep(Some(3));
1186        assert_eq!(rs.autostep(), Some(3));
1187        rs.set_autostep(None);
1188        assert_eq!(rs.autostep(), None);
1189    }
1190
1191    #[test]
1192    fn test_padding_property() {
1193        let r1 = RangeSet::parse("001-010", None).unwrap();
1194        assert_eq!(r1.padding(), Some(3));
1195
1196        let r2 = RangeSet::parse("1-10", None).unwrap();
1197        assert_eq!(r2.padding(), None);
1198
1199        let r3 = RangeSet::parse("0003-0143,0360-1000", None).unwrap();
1200        assert_eq!(r3.padding(), Some(4));
1201    }
1202
1203    #[test]
1204    fn test_copy() {
1205        // Python: testCopy
1206        let rangeset = RangeSet::parse("115-117,130,166-170,4780-4999", None).unwrap();
1207        assert_eq!(rangeset.len(), 229);
1208        assert_eq!(rangeset.to_string(), "115-117,130,166-170,4780-4999");
1209        let mut r1 = rangeset.clone();
1210        let mut r2 = rangeset.clone();
1211        r1.remove_str("166");
1212        assert_eq!(rangeset.len(), r1.len() + 1);
1213        assert_eq!(rangeset.to_string(), "115-117,130,166-170,4780-4999");
1214        assert_eq!(r1.to_string(), "115-117,130,167-170,4780-4999");
1215        r2.update(&RangeSet::parse("118", None).unwrap());
1216        assert_eq!(rangeset.len() + 1, r2.len());
1217        assert_eq!(r2.to_string(), "115-118,130,166-170,4780-4999");
1218    }
1219
1220    #[test]
1221    fn test_remove_and_discard() {
1222        // Python: testRemove / testDiscard
1223        let mut r1 = RangeSet::parse("1-100,102,105-242,800", None).unwrap();
1224        assert_eq!(r1.len(), 240);
1225        r1.remove_str("100");
1226        assert_eq!(r1.len(), 239);
1227        assert_eq!(r1.to_string(), "1-99,102,105-242,800");
1228
1229        r1.discard_str("101"); // should not panic
1230        r1.discard_str("105");
1231        assert_eq!(r1.len(), 238);
1232        assert_eq!(r1.to_string(), "1-99,102,106-242,800");
1233    }
1234
1235    #[test]
1236    #[should_panic(expected = "not found")]
1237    fn test_remove_missing_panics() {
1238        let mut r1 = RangeSet::parse("1-100", None).unwrap();
1239        r1.remove_str("101");
1240    }
1241}