bl-range-set 0.1.0

A Rust library for working with range sets
Documentation

use crate::discrete_range_set::{DiscreteRange, DiscreteRangeSet};

pub type Error = Box<dyn std::error::Error>;
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CharRange(char, char);

impl Into<DiscreteRange<u32>> for CharRange {
    fn into(self) -> DiscreteRange<u32> {
        DiscreteRange::new(self.0 as u32, self.1 as u32).unwrap()
    }
}

impl CharRange {
    pub fn new(start: char, end: char) -> Result<Self> {
        if start > end {
            return Err("Invalid char range (negative size)".into());
        }
        Ok(CharRange(start, end))
    }

    pub fn len(&self) -> u32 {
        self.1 as u32 - self.0 as u32 + 1
    }

    pub fn contains(&self, value: char) -> bool {
        value >= self.0 && value <= self.1
    }

    pub fn try_merge(&self, other: &Self) -> Result<Self> {
        if self.1 as u32 + 1 < other.0 as u32 || other.1 as u32 + 1 < self.0 as u32 {
            return Err("Disjoint char ranges cannot be merged".into());
        }

        Ok(CharRange(self.0.min(other.0), self.1.max(other.1))) 
    }

    pub fn char(c: char) -> Self {
        CharRange(c, c)
    }

    pub fn lowercase() -> Self {
        CharRange('a', 'z')
    }

    pub fn uppercase() -> Self {
        CharRange('A', 'Z')
    }

    pub fn digits() -> Self {
        CharRange('0', '9')
    }

    pub fn all() -> Self {
        CharRange('\u{0000}', '\u{10FFFF}')
    }
}

impl From<(char, char)> for CharRange {
    fn from(tuple: (char, char)) -> Self {
        CharRange::new(tuple.0, tuple.1).unwrap()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CharRangeSet(DiscreteRangeSet<u32>);

impl CharRangeSet {
    pub fn new() -> Self {
        CharRangeSet(DiscreteRangeSet::new())
    }

    pub fn add_range<R: Into<CharRange>>(&mut self, range: R) {
        self.0.add_range(range.into());
    }

    pub fn contains(&self, value: char) -> bool {
        self.0.contains(value as u32)
    }

    pub fn contains_any<I: IntoIterator<Item = char>>(&self, values: I) -> bool {
        for v in values {
            if self.contains(v) {
                return true;
            }
        }
        false
    }

    pub fn contains_all<I: IntoIterator<Item = char>>(&self, values: I) -> bool {
        for v in values {
            if !self.contains(v) {
                return false;
            }
        }
        true
    }

    pub fn with_range<R: Into<CharRange>>(mut self, range: R) -> Self {
        self.add_range(range);
        self
    }

    pub fn with_lowercase(mut self) -> Self {
        self.add_range(CharRange::lowercase());
        self
    }

    pub fn with_uppercase(mut self) -> Self {
        self.add_range(CharRange::uppercase());
        self
    }

    pub fn with_digits(mut self) -> Self {
        self.add_range(CharRange::digits());
        self
    }

    pub fn with_all(mut self) -> Self {
        self.add_range(CharRange::all());
        self
    }

    pub fn with_alpha(mut self) -> Self {
        self.add_range(CharRange::lowercase());
        self.add_range(CharRange::uppercase());
        self
    }

    pub fn with_alphanumeric(mut self) -> Self {
        self.add_range(CharRange::lowercase());
        self.add_range(CharRange::uppercase());
        self.add_range(CharRange::digits());
        self
    }

    pub fn with_chars<I: IntoIterator<Item = char>>(mut self, chars: I) -> Self {
        for c in chars {
            self.add_range(CharRange::char(c));
        }
        self
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_char_range() {
        let range = CharRange::new('a', 'd').unwrap();
        assert_eq!(range.len(), 4);
        assert!(range.contains('b'));
        assert!(!range.contains('e'));

        let range2 = CharRange::new('c', 'f').unwrap();
        let merged = range.try_merge(&range2).unwrap();
        assert_eq!(merged, CharRange::new('a', 'f').unwrap());
    }

    #[test]
    fn test_char_range_set() {
        let mut set = CharRangeSet::new();
        set.add_range(CharRange::new('a', 'd').unwrap());
        set.add_range(CharRange::new('f', 'h').unwrap());
        set.add_range(CharRange::new('c', 'g').unwrap());

        assert!(set.contains('b'));
        assert!(set.contains('e'));
        assert!(!set.contains('i'));
    }

    #[test]
    fn test_char_range_set_builder() {
        let set = CharRangeSet::new()
            .with_lowercase()
            .with_uppercase()
            .with_digits()
            .with_chars(vec!['_', '-']);

        assert!(set.contains('a'));
        assert!(set.contains('Z'));
        assert!(set.contains('5'));
        assert!(set.contains('_'));
        assert!(!set.contains('@'));
        assert!(!set.contains(' '));
    }
}