dia_semver/
range.rs

1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4Dia-Semver
5
6Copyright (C) 2018-2022  Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2018-2022".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27//! # Range
28
29mod impls;
30
31use {
32    alloc::borrow::Cow,
33    core::{
34        fmt::{self, Display, Formatter},
35        ops::{Bound, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
36    },
37    crate::Semver,
38};
39
40const INCLUSIVE_OPEN: char = '[';
41const INCLUSIVE_CLOSE: char = ']';
42const EXCLUSIVE_OPEN: char = '(';
43const EXCLUSIVE_CLOSE: char = ')';
44
45/// # Range
46///
47/// A range can be respresented in string, following these rules:
48///
49/// - Start and end are placed inside one of `[]`, `[)`..., separated by a comma.
50/// - `[` and `]` are inclusive.
51/// - `(` and `)` are exclusive.
52/// - White spaces can be included. They will be ignored by parser.
53/// - For unbounded ranges, start and/or end indexes must be inclusive.
54///
55/// - For protection against flood attack, max length of the string is one of:
56///
57///     + `255` bytes (on 8-bit machines)
58///     + `4096` bytes (on larger machines)
59///
60/// ## Examples
61///
62/// ```
63/// use core::ops::RangeBounds;
64/// use core::str::FromStr;
65/// use dia_semver::{Range, Semver};
66///
67/// // An empty range
68/// let range = Range::from(Semver::new(0, 1, 2)..Semver::new(0, 0, 0));
69/// assert!(range.is_empty());
70///
71/// // Only one single semver
72/// let range = Range::from(Semver::new(0, 1, 2));
73/// assert!(range.contains(&Semver::new(0, 1, 2)));
74/// assert!(range.contains(&Semver::new(0, 1, 3)) == false);
75///
76/// // Inclusive range
77/// let range = Range::from_str("[0.1.2, 0.2.0-beta]")?;
78/// assert!(range.contains(&Semver::new(0, 1, 3)));
79/// assert!(range.contains(&Semver::from_str("0.2.0-alpha")?));
80/// assert!(range.contains(&Semver::new(0, 2, 0)) == false);
81///
82/// // Exclusive range
83/// let range = Range::from(Semver::new(0, 1, 2)..Semver::new(0, 2, 0));
84/// assert!(range.contains(&Semver::new(0, 2, 0)) == false);
85///
86/// // Unbounded ranges
87/// assert!(Range::from(..).contains(&Semver::new(1, 2, 0)));
88/// assert!(Range::from_str("[ , 1]")?.contains(&Semver::from(1_u8)));
89/// assert!(Range::from_str("[ , 1)")?.contains(&Semver::from(1_u8)) == false);
90///
91/// # dia_semver::Result::Ok(())
92/// ```
93#[derive(Debug, Eq, PartialEq, Hash, Clone)]
94pub struct Range {
95    start: Bound<Semver>,
96    end: Bound<Semver>,
97}
98
99impl Range {
100
101    /// # Checks if this range is empty
102    pub fn is_empty(&self) -> bool {
103        match (&self.start, &self.end) {
104            (Bound::Included(start), Bound::Included(end)) => start > end,
105            (Bound::Included(start), Bound::Excluded(end)) => start >= end,
106            (Bound::Included(_), Bound::Unbounded) => false,
107
108            (Bound::Excluded(start), Bound::Included(end)) => start >= end,
109            (Bound::Excluded(start), Bound::Excluded(end)) => start >= end,
110            (Bound::Excluded(_), Bound::Unbounded) => false,
111
112            (Bound::Unbounded, _) => false,
113        }
114    }
115
116}
117
118impl From<&Semver> for Range {
119
120    fn from(semver: &Semver) -> Self {
121        Self::from(semver.clone())
122    }
123
124}
125
126impl From<Semver> for Range {
127
128    fn from(semver: Semver) -> Self {
129        Self {
130            start: Bound::Included(semver.clone()),
131            end: Bound::Included(semver),
132        }
133    }
134
135}
136
137impl From<core::ops::Range<Semver>> for Range {
138
139    fn from(range: core::ops::Range<Semver>) -> Self {
140        Self {
141            start: Bound::Included(range.start),
142            end: Bound::Excluded(range.end),
143        }
144    }
145
146}
147
148impl From<RangeInclusive<Semver>> for Range {
149
150    fn from(range: RangeInclusive<Semver>) -> Self {
151        let (start, end) = range.into_inner();
152        Self {
153            start: Bound::Included(start),
154            end: Bound::Included(end),
155        }
156    }
157
158}
159
160impl From<RangeFrom<Semver>> for Range {
161
162    fn from(range: RangeFrom<Semver>) -> Self {
163        Self {
164            start: Bound::Included(range.start),
165            end: Bound::Unbounded,
166        }
167    }
168
169}
170
171impl From<RangeTo<Semver>> for Range {
172
173    fn from(range: RangeTo<Semver>) -> Self {
174        Self {
175            start: Bound::Unbounded,
176            end: Bound::Excluded(range.end),
177        }
178    }
179
180}
181
182impl From<RangeToInclusive<Semver>> for Range {
183
184    fn from(range: RangeToInclusive<Semver>) -> Self {
185        Self {
186            start: Bound::Unbounded,
187            end: Bound::Included(range.end),
188        }
189    }
190
191}
192
193impl From<RangeFull> for Range {
194
195    fn from(_: RangeFull) -> Self {
196        Self {
197            start: Bound::Unbounded,
198            end: Bound::Unbounded,
199        }
200    }
201
202}
203
204impl Display for Range {
205
206    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
207        let (open, start) = match &self.start {
208            Bound::Included(start) => (INCLUSIVE_OPEN, Cow::Owned(start.to_short_format())),
209            Bound::Excluded(start) => (EXCLUSIVE_OPEN, Cow::Owned(start.to_short_format())),
210            Bound::Unbounded => (INCLUSIVE_OPEN, Cow::Borrowed(concat!())),
211        };
212        let (close, end) = match &self.end {
213            Bound::Included(end) => (INCLUSIVE_CLOSE, Cow::Owned(end.to_short_format())),
214            Bound::Excluded(end) => (EXCLUSIVE_CLOSE, Cow::Owned(end.to_short_format())),
215            Bound::Unbounded => (INCLUSIVE_CLOSE, Cow::Borrowed(concat!())),
216        };
217        write!(
218            f,
219            concat!("{open}", "{start}", ',', ' ', "{end}", "{close}"),
220            open=open, start=start, end=end, close=close,
221        )
222    }
223
224}
225
226impl RangeBounds<Semver> for Range {
227
228    fn start_bound(&self) -> Bound<&Semver> {
229        // TODO: use Bound::as_ref() when it is stabilized
230        match &self.start {
231            Bound::Included(start) => Bound::Included(start),
232            Bound::Excluded(start) => Bound::Excluded(start),
233            Bound::Unbounded => Bound::Unbounded,
234        }
235    }
236
237    fn end_bound(&self) -> Bound<&Semver> {
238        // TODO: use Bound::as_ref() when it is stabilized
239        match &self.end {
240            Bound::Included(end) => Bound::Included(end),
241            Bound::Excluded(end) => Bound::Excluded(end),
242            Bound::Unbounded => Bound::Unbounded,
243        }
244    }
245
246}