dia-semver 11.0.1

For handling Semantic Versions 2.0.0
Documentation
/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Dia-Semver

Copyright (C) 2018-2022  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2018-2022".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # Range

mod impls;

use {
    alloc::borrow::Cow,
    core::{
        fmt::{self, Display, Formatter},
        ops::{Bound, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
    },
    crate::Semver,
};

const INCLUSIVE_OPEN: char = '[';
const INCLUSIVE_CLOSE: char = ']';
const EXCLUSIVE_OPEN: char = '(';
const EXCLUSIVE_CLOSE: char = ')';

/// # Range
///
/// A range can be respresented in string, following these rules:
///
/// - Start and end are placed inside one of `[]`, `[)`..., separated by a comma.
/// - `[` and `]` are inclusive.
/// - `(` and `)` are exclusive.
/// - White spaces can be included. They will be ignored by parser.
/// - For unbounded ranges, start and/or end indexes must be inclusive.
///
/// - For protection against flood attack, max length of the string is one of:
///
///     + `255` bytes (on 8-bit machines)
///     + `4096` bytes (on larger machines)
///
/// ## Examples
///
/// ```
/// use core::ops::RangeBounds;
/// use core::str::FromStr;
/// use dia_semver::{Range, Semver};
///
/// // An empty range
/// let range = Range::from(Semver::new(0, 1, 2)..Semver::new(0, 0, 0));
/// assert!(range.is_empty());
///
/// // Only one single semver
/// let range = Range::from(Semver::new(0, 1, 2));
/// assert!(range.contains(&Semver::new(0, 1, 2)));
/// assert!(range.contains(&Semver::new(0, 1, 3)) == false);
///
/// // Inclusive range
/// let range = Range::from_str("[0.1.2, 0.2.0-beta]")?;
/// assert!(range.contains(&Semver::new(0, 1, 3)));
/// assert!(range.contains(&Semver::from_str("0.2.0-alpha")?));
/// assert!(range.contains(&Semver::new(0, 2, 0)) == false);
///
/// // Exclusive range
/// let range = Range::from(Semver::new(0, 1, 2)..Semver::new(0, 2, 0));
/// assert!(range.contains(&Semver::new(0, 2, 0)) == false);
///
/// // Unbounded ranges
/// assert!(Range::from(..).contains(&Semver::new(1, 2, 0)));
/// assert!(Range::from_str("[ , 1]")?.contains(&Semver::from(1_u8)));
/// assert!(Range::from_str("[ , 1)")?.contains(&Semver::from(1_u8)) == false);
///
/// # dia_semver::Result::Ok(())
/// ```
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct Range {
    start: Bound<Semver>,
    end: Bound<Semver>,
}

impl Range {

    /// # Checks if this range is empty
    pub fn is_empty(&self) -> bool {
        match (&self.start, &self.end) {
            (Bound::Included(start), Bound::Included(end)) => start > end,
            (Bound::Included(start), Bound::Excluded(end)) => start >= end,
            (Bound::Included(_), Bound::Unbounded) => false,

            (Bound::Excluded(start), Bound::Included(end)) => start >= end,
            (Bound::Excluded(start), Bound::Excluded(end)) => start >= end,
            (Bound::Excluded(_), Bound::Unbounded) => false,

            (Bound::Unbounded, _) => false,
        }
    }

}

impl From<&Semver> for Range {

    fn from(semver: &Semver) -> Self {
        Self::from(semver.clone())
    }

}

impl From<Semver> for Range {

    fn from(semver: Semver) -> Self {
        Self {
            start: Bound::Included(semver.clone()),
            end: Bound::Included(semver),
        }
    }

}

impl From<core::ops::Range<Semver>> for Range {

    fn from(range: core::ops::Range<Semver>) -> Self {
        Self {
            start: Bound::Included(range.start),
            end: Bound::Excluded(range.end),
        }
    }

}

impl From<RangeInclusive<Semver>> for Range {

    fn from(range: RangeInclusive<Semver>) -> Self {
        let (start, end) = range.into_inner();
        Self {
            start: Bound::Included(start),
            end: Bound::Included(end),
        }
    }

}

impl From<RangeFrom<Semver>> for Range {

    fn from(range: RangeFrom<Semver>) -> Self {
        Self {
            start: Bound::Included(range.start),
            end: Bound::Unbounded,
        }
    }

}

impl From<RangeTo<Semver>> for Range {

    fn from(range: RangeTo<Semver>) -> Self {
        Self {
            start: Bound::Unbounded,
            end: Bound::Excluded(range.end),
        }
    }

}

impl From<RangeToInclusive<Semver>> for Range {

    fn from(range: RangeToInclusive<Semver>) -> Self {
        Self {
            start: Bound::Unbounded,
            end: Bound::Included(range.end),
        }
    }

}

impl From<RangeFull> for Range {

    fn from(_: RangeFull) -> Self {
        Self {
            start: Bound::Unbounded,
            end: Bound::Unbounded,
        }
    }

}

impl Display for Range {

    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        let (open, start) = match &self.start {
            Bound::Included(start) => (INCLUSIVE_OPEN, Cow::Owned(start.to_short_format())),
            Bound::Excluded(start) => (EXCLUSIVE_OPEN, Cow::Owned(start.to_short_format())),
            Bound::Unbounded => (INCLUSIVE_OPEN, Cow::Borrowed(concat!())),
        };
        let (close, end) = match &self.end {
            Bound::Included(end) => (INCLUSIVE_CLOSE, Cow::Owned(end.to_short_format())),
            Bound::Excluded(end) => (EXCLUSIVE_CLOSE, Cow::Owned(end.to_short_format())),
            Bound::Unbounded => (INCLUSIVE_CLOSE, Cow::Borrowed(concat!())),
        };
        write!(
            f,
            concat!("{open}", "{start}", ',', ' ', "{end}", "{close}"),
            open=open, start=start, end=end, close=close,
        )
    }

}

impl RangeBounds<Semver> for Range {

    fn start_bound(&self) -> Bound<&Semver> {
        // TODO: use Bound::as_ref() when it is stabilized
        match &self.start {
            Bound::Included(start) => Bound::Included(start),
            Bound::Excluded(start) => Bound::Excluded(start),
            Bound::Unbounded => Bound::Unbounded,
        }
    }

    fn end_bound(&self) -> Bound<&Semver> {
        // TODO: use Bound::as_ref() when it is stabilized
        match &self.end {
            Bound::Included(end) => Bound::Included(end),
            Bound::Excluded(end) => Bound::Excluded(end),
            Bound::Unbounded => Bound::Unbounded,
        }
    }

}